Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19333] ファイルの概要

このコミットでは、Goコマンド(cmd/go)におけるSWIG生成オブジェクトのリンク方法が変更されました。具体的には、共有ライブラリを介するのではなく、SWIGが生成したオブジェクトファイルを直接バイナリにリンクするようになりました。この変更に伴い、SWIGのバージョン3.0以降が必要となります。

変更されたファイルは以下の通りです。

  • doc/go1.3.html: Go 1.3のリリースノートに、SWIG 3.0の要件と変更されたリンク方法に関する記述が追加されました。
  • src/cmd/go/build.go: Goのビルドプロセスを司る主要なファイルで、SWIGオブジェクトの共有ライブラリ生成およびリンクに関するロジックが削除され、直接リンクのロジックが調整されました。
  • src/cmd/go/clean.go: go cleanコマンドに関連するファイルで、SWIG共有ライブラリのクリーンアップロジックが削除されました。
  • src/cmd/go/doc.go: goコマンドのドキュメントの一部で、SWIG共有ライブラリ(*.so)に関する記述が削除されました。
  • src/cmd/go/pkg.go: パッケージ情報の管理に関連するファイルで、SWIG共有ライブラリの命名規則やディレクトリ構造を定義していた関数(swigSonameswigDir)が削除されました。

コミット

cmd/go: link SWIG objects directly rather than using a shared library

This change requires using SWIG version 3.0 or later. Earlier
versions of SWIG do not generate the pragmas required to use
the external linker.

Fixes #7155.
Fixes #7156.

LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/97120046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/02cc45aded62e23f6bb6142174ab5b12f7d5b486

元コミット内容

cmd/go: link SWIG objects directly rather than using a shared library

This change requires using SWIG version 3.0 or later. Earlier
versions of SWIG do not generate the pragmas required to use
the external linker.

Fixes #7155.
Fixes #7156.

LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/97120046

変更の背景

このコミットの主な背景は、GoプログラムがSWIG(Simplified Wrapper and Interface Generator)を利用する際に発生していた、共有ライブラリ(.soファイルなど)の使用に伴う問題の解決です。

以前のGoのビルドシステムでは、SWIGによって生成されたC/C++コードは、一度共有ライブラリとしてコンパイルされ、その後Goのバイナリにリンクされていました。このアプローチにはいくつかの課題がありました。

  1. デプロイメントの複雑さ: 共有ライブラリは、実行時にシステム上に存在する必要があります。これにより、Goバイナリを配布する際に、依存する共有ライブラリも一緒に配布し、適切なパスに配置する必要が生じ、デプロイメントが複雑になる可能性がありました。特にクロスコンパイル環境や異なるOS環境でのデプロイでは、共有ライブラリの互換性問題が発生しやすくなります。
  2. リンカーの複雑性: 共有ライブラリを扱うことは、ビルドシステムにとって追加の複雑性をもたらします。共有ライブラリの検索パス、バージョン管理、シンボル解決など、リンカーが考慮すべき事項が増えます。
  3. SWIGの進化: SWIG自体も進化しており、特にバージョン3.0以降では、外部リンカーが直接オブジェクトファイルを扱うために必要な「プラグマ(pragmas)」を生成するようになりました。これにより、共有ライブラリを介さずに直接リンクする道が開かれました。

コミットメッセージにある Fixes #7155 および Fixes #7156 は、これらの共有ライブラリに関連する具体的な問題やバグを指していると考えられます。これらの問題は、共有ライブラリの生成、リンク、または実行時の挙動に関連していた可能性が高いです。共有ライブラリの使用を廃止し、直接リンクに切り替えることで、これらの根本的な問題を解決し、GoとSWIGの連携をより堅牢でシンプルなものにすることが目的でした。

前提知識の解説

SWIG (Simplified Wrapper and Interface Generator)

SWIGは、C/C++で書かれたコードを、Python, Java, Ruby, Go, PHP, C#など、様々なスクリプト言語や高水準言語から呼び出せるようにするためのオープンソースツールです。SWIGは、C/C++のヘッダーファイルやインターフェース定義ファイル(.iファイル)を読み込み、ターゲット言語のAPIとC/C++の関数やデータ構造を橋渡しする「ラッパーコード」を自動生成します。

Go言語の場合、SWIGはCgoと連携して動作します。SWIGがC/C++コードからGoのラッパーコードとC/C++のオブジェクトファイルを生成し、CgoがそのオブジェクトファイルをGoのバイナリにリンクする役割を担います。

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Goのソースファイル内にimport "C"という特殊なインポート文を記述することで、C言語のコードを直接埋め込むことができます。Cgoは、Goのビルドプロセスの一部として、Cコードをコンパパイルし、Goのコードとリンクするための橋渡しを行います。

共有ライブラリ (Shared Library) と静的リンク (Static Linking)

プログラムをコンパイルし、実行可能ファイルを生成する際には、通常、他のコード(ライブラリ)と結合する必要があります。この結合方法には大きく分けて「共有ライブラリ」と「静的リンク」の2種類があります。

  • 共有ライブラリ (Shared Library):

    • WindowsではDLL (Dynamic Link Library)、Linux/Unix系では.so (Shared Object)、macOSでは.dylib (Dynamic Library) と呼ばれます。
    • プログラムの実行時にメモリにロードされるライブラリです。
    • 複数のプログラムが同じ共有ライブラリを使用する場合、メモリ上にはそのライブラリのコピーが1つだけ存在するため、メモリ効率が良いとされます。
    • ライブラリの更新が容易で、ライブラリを更新するだけで、それを使用するすべてのプログラムがその恩恵を受けられます(ただし、APIの互換性が保たれている場合)。
    • 欠点としては、プログラムの実行時にライブラリが見つからないとエラーになる(「DLL Hell」や「Shared Library Hell」と呼ばれる問題)ことや、デプロイメント時にライブラリも一緒に配布する必要がある点が挙げられます。
  • 静的リンク (Static Linking):

    • ライブラリのコードが、コンパイル時に直接実行可能ファイルに組み込まれる方式です。
    • 生成される実行可能ファイルは、外部ライブラリに依存しないため、単一のファイルとして配布・実行が可能です。デプロイメントが非常にシンプルになります。
    • 複数のプログラムが同じライブラリを使用する場合でも、それぞれの実行可能ファイルにライブラリのコピーが含まれるため、ディスク容量やメモリ使用量が増える可能性があります。
    • ライブラリの更新があった場合、それを使用するすべてのプログラムを再コンパイルする必要があります。
    • Go言語は、デフォルトで静的リンクを強く推奨しており、生成されるバイナリは通常、外部の共有ライブラリにほとんど依存しません(Cgoを使用しない場合)。

このコミットは、SWIGが生成するC/C++オブジェクトを、従来の共有ライブラリを介したリンクから、Goのビルドシステムが直接静的にリンクする方式へと変更するものです。

技術的詳細

このコミットの技術的な核心は、Goのビルドツールであるcmd/goが、SWIGによって生成されたC/C++のオブジェクトファイルを、中間的な共有ライブラリを介さずに、直接最終的なGoの実行可能バイナリにリンクするように変更された点です。

変更前のアプローチ (共有ライブラリ経由のリンク)

変更前は、GoプログラムがSWIGを使用する場合、以下のようなプロセスが踏まれていました。

  1. SWIGが、Goのラッパーコード(.goファイル)と、C/C++のソースコード(.c.cxxファイル)を生成します。
  2. 生成されたC/C++ソースコードは、Goのビルドシステムによってコンパイルされ、オブジェクトファイル(.oファイル)になります。
  3. これらのオブジェクトファイルは、さらに共有ライブラリ(例: _obj/_cgo_swig_go.so のような名前)としてビルドされます。この共有ライブラリには、SWIGが生成したC/C++の関数やデータが含まれます。
  4. 最終的に、Goのリンカーが、この共有ライブラリをGoの実行可能バイナリにリンクします。この際、共有ライブラリは実行時にロードされる形式で参照されます。

このアプローチでは、src/cmd/go/build.go内のgcToolchain.ld関数やtools gccgoToolchain.ld関数が、SWIG関連の共有ライブラリのパスをリンカーに渡すためのロジックを含んでいました。また、src/cmd/go/pkg.goには、共有ライブラリの命名規則を決定するswigSoname関数や、共有ライブラリを配置するディレクトリを決定するswigDir関数が存在していました。

変更後のアプローチ (直接リンク)

このコミットにより、プロセスは以下のように簡素化されました。

  1. SWIGが、GoのラッパーコードとC/C++のソースコードを生成します。
  2. 生成されたC/C++ソースコードは、Goのビルドシステムによってコンパイルされ、オブジェクトファイル(.oファイル)になります。
  3. 重要な変更点: これらのオブジェクトファイルは、共有ライブラリとしてビルドされることなく、Goのリンカーによって直接最終的なGoの実行可能バイナリに静的にリンクされます。

この変更の鍵となるのは、SWIGのバージョン3.0以降が生成する「プラグマ」です。コミットメッセージにある「Earlier versions of SWIG do not generate the pragmas required to use the external linker.」という記述がこれを明確に示しています。SWIG 3.0以降は、外部リンカーが直接オブジェクトファイルを扱うために必要なメタデータや指示を生成するようになり、これによりGoのビルドシステムが共有ライブラリを介さずに直接リンクすることが可能になりました。

技術的なメリット

  • デプロイメントの簡素化: 最終的なGoバイナリが単一の実行可能ファイルとなり、外部のSWIG関連共有ライブラリに依存しなくなるため、配布とデプロイが大幅に簡素化されます。
  • ビルドシステムの簡素化: 共有ライブラリの生成、配置、リンクパスの管理といった複雑なロジックがcmd/goから削除され、ビルドシステム自体の保守性が向上します。
  • 堅牢性の向上: 共有ライブラリのロード失敗やバージョン不一致といった実行時エラーのリスクが低減されます。
  • パフォーマンスの可能性: 共有ライブラリの動的ロードに伴うオーバーヘッドがなくなるため、起動時間や実行時のパフォーマンスがわずかに向上する可能性があります。

この変更は、Goの「単一バイナリ」という哲学により合致するものであり、GoとSWIGの連携をよりシームレスでGoらしいものにしました。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、主にsrc/cmd/go/build.gosrc/cmd/go/pkg.goに集中しています。

src/cmd/go/build.go

  • func (b *builder) install(a *action) (err error):
    • SWIG共有ライブラリをインストールディレクトリにコピーするロジックが完全に削除されました。
      -	if a.p.usesSwig() {
      -		for _, f := range stringList(a.p.SwigFiles, a.p.SwigCXXFiles) {
      -			dir = a.p.swigDir(&buildContext)
      -			if err := b.mkdir(dir); err != nil {
      -				return err
      -			}
      -			soname := a.p.swigSoname(f)
      -			source := filepath.Join(a.objdir, soname)
      -			target := filepath.Join(dir, soname)
      -			if err = b.copyFile(a, target, source, perm); err != nil {
      -				return err
      -			}
      -		}
      -	}
      
  • func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error:
    • Goコンパイラツールチェーンのリンカー関数から、SWIG共有ライブラリのディレクトリをリンカー引数に追加するロジックが削除されました。
      -	swigDirs := make(map[string]bool)
      -	swigArg := []string{}
      // ...
      -	if a.p != nil && a.p.usesSwig() {
      -		sd := a.p.swigDir(&buildContext)
      -		if len(swigArg) == 0 {
      -			swigArg = []string{"-r", sd}
      -		} else if !swigDirs[sd] {
      -			swigArg[1] += ":"
      -			swigArg[1] += sd
      -		}
      -		swigDirs[sd] = true
      -		if a.objdir != "" && !swigDirs[a.objdir] {
      -			swigArg[1] += ":"
      -			swigArg[1] += a.objdir
      -			swigDirs[a.objdir] = true
      -		}
      -	}
      // ...
      -	return b.run(".", p.ImportPath, nil, tool(archChar+"l"), "-o", out, importArgs, swigArg, ldflags, mainpkg)
      +	return b.run(".", p.ImportPath, nil, tool(archChar+"l"), "-o", out, importArgs, ldflags, mainpkg)
      
  • func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error:
    • GCCGoツールチェーンのリンカー関数から、SWIG共有ライブラリのオブジェクトファイル(sfiles)をリンカー引数に追加するロジックが削除されました。
      -	sfiles := []string{}
      // ...
      -		if a.p.usesSwig() {
      -			sd := a.p.swigDir(&buildContext)
      -			if a.objdir != "" {
      -				sd = a.objdir
      -			}
      -			for _, f := range stringList(a.p.SwigFiles, a.p.SwigCXXFiles) {
      -				soname := a.p.swigSoname(f)
      -				sfiles = append(sfiles, filepath.Join(sd, soname))
      -			}
      -			usesCgo = true
      -		}
      // ...
      -	ldflags = append(ldflags, sfiles...)
      
  • func (b *builder) swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []string) (outGo, outObj []string, err error):
    • SWIGによって生成されたC/C++/Objective-CのオブジェクトファイルをextraObjとして収集するロジックが削除され、直接outObjに追加されるようになりました。
    • b.swigOneの呼び出しからextraObj引数が削除されました。
      -	var extraObj []string
      // ...
      -		extraObj = append(extraObj, ofile)
      +		outObj = append(outObj, ofile)
      // ...
      -	goFile, objFile, err := b.swigOne(p, f, obj, false, intgosize, extraObj)
      +	goFile, objFile, gccObjFile, err := b.swigOne(p, f, obj, false, intgosize)
      // ...
      -	goFile, objFile, err := b.swigOne(p, f, obj, true, intgosize, extraObj)
      +	goFile, objFile, gccObjFile, err := b.swigOne(p, f, obj, true, intgosize)
      
  • func (b *builder) swigOne(p *Package, file, obj string, cxx bool, intgosize string, extraObj []string) (outGo, outObj string, err error) から func (b *builder) swigOne(p *Package, file, obj string, cxx bool, intgosize string) (outGo, outObj, objGccObj string, err error):
    • 関数のシグネチャが変更され、extraObj引数が削除され、代わりにobjGccObjという新しい戻り値が追加されました。これは、SWIGが生成するGoラッパーファイル、CgoがコンパイルするCオブジェクトファイル、そしてSWIGが生成するGCC/G++オブジェクトファイルをそれぞれ返すことを意味します。
    • SWIGコマンドの呼び出しから-soname引数が削除されました。
    • 最も重要な変更として、SWIGが生成したC/C++オブジェクトファイルから共有ライブラリを作成するロジックが完全に削除されました。
      -	// create shared library
      -	osldflags := map[string][]string{
      -		"darwin":  {"-dynamiclib", "-Wl,-undefined,dynamic_lookup"},
      -		"freebsd": {"-shared", "-lpthread", "-lm"},
      -		"linux":   {"-shared", "-lpthread", "-lm"},
      -		"windows": {"-shared", "-lm", "-mthreads"},
      -	}
      -	var cxxlib []string
      -	if cxx {
      -		cxxlib = []string{"-lstdc++"}
      -	}
      -	ldflags := stringList(osldflags[goos], cflags, cgoLDFLAGS, cxxlib)
      -	target := filepath.Join(obj, soname)
      -	if err := b.run(p.Dir, p.ImportPath, nil, b.gccCmd(p.Dir), "-o", target, gccObj, extraObj, ldflags); err != nil {
      -		return "", "", err
      -	}
      -
      -	return obj + goFile, cObj, nil
      +	return obj + goFile, cObj, gccObj, nil
      
    • SWIGのバージョンチェックが2.0.9から3.0に変更されました。

src/cmd/go/clean.go

  • func clean(p *Package):
    • go cleanコマンドがSWIG共有ライブラリを削除するロジックが削除されました。
      -	if cleanI && p.usesSwig() {
      -		for _, f := range stringList(p.SwigFiles, p.SwigCXXFiles) {
      -			dir := p.swigDir(&buildContext)
      -			soname := p.swigSoname(f)
      -			target := filepath.Join(dir, soname)
      -			if buildN || buildX {
      -				b.showcmd("", "rm -f %s", target)
      -			}
      -			if !buildN {
      -				removeFile(target)
      -			}
      -		}
      -	}
      

src/cmd/go/doc.go

  • goコマンドのドキュメントから、SWIGによって生成される*.soファイルに関する記述が削除されました。
    -	*.so             from SWIG
    

src/cmd/go/pkg.go

  • SWIG共有ライブラリの命名規則を返すswigSoname関数と、SWIG共有ライブラリのディレクトリを返すswigDir関数が完全に削除されました。
    -func (p *Package) swigSoname(file string) string {
    -	return strings.Replace(p.ImportPath, "/", "-", -1) + "-" + strings.Replace(file, ".", "-", -1) + ".so"
    -}
    -
    -// swigDir returns the name of the shared SWIG directory for a
    -// package.
    -func (p *Package) swigDir(ctxt *build.Context) string {
    -	dir := p.build.PkgRoot
    -	if ctxt.Compiler == "gccgo" {
    -		dir = filepath.Join(dir, "gccgo_"+ctxt.GOOS+"_"+ctxt.GOARCH)
    -	} else {
    -		dir = filepath.Join(dir, ctxt.GOOS+"_"+ctxt.GOARCH)
    -	}
    -	return filepath.Join(dir, "swig")
    -}
    

コアとなるコードの解説

このコミットの核心は、GoのビルドシステムがSWIGによって生成されたC/C++コードを扱う方法を根本的に変更した点にあります。以前は、SWIGが生成したC/C++オブジェクトは、一度共有ライブラリとしてパッケージ化され、その後Goのバイナリにリンクされていました。この変更により、共有ライブラリのステップが完全に排除され、SWIGが生成したオブジェクトファイルが直接Goの最終バイナリに静的にリンクされるようになりました。

具体的には、以下の変更が重要です。

  1. 共有ライブラリ生成ロジックの削除:

    • src/cmd/go/build.goswigOne関数内で、SWIGが生成したC/C++オブジェクトファイル(gccObj)から共有ライブラリ(target)を作成していた大規模なコードブロックが削除されました。これは、osldflagscxxlibldflagsといった共有ライブラリのビルドに必要な変数の定義と、b.run(..., b.gccCmd(p.Dir), "-o", target, gccObj, extraObj, ldflags)によるリンカー呼び出しを含んでいました。この削除により、GoのビルドプロセスからSWIG関連の共有ライブラリ生成が完全に消滅しました。
    • これに伴い、install関数やclean関数からも、共有ライブラリのコピーや削除に関するロジックが削除されています。
  2. リンカーへのSWIGオブジェクトの直接渡し:

    • gcToolchain.ldおよびgccgoToolchain.ld関数から、SWIG共有ライブラリのパスをリンカーに渡すためのswigArgsfilesといった変数の使用が削除されました。これは、共有ライブラリがもはや存在しないため、それらをリンカーに渡す必要がなくなったことを意味します。
    • 代わりに、swigOne関数が返すgccObj(SWIGが生成したC/C++オブジェクトファイル)が、直接Goのリンカーに渡されるようになりました。これにより、SWIGのC/C++コードがGoのバイナリに静的に組み込まれます。
  3. SWIG 3.0の要件:

    • swigOne関数内のSWIGバージョンチェックが、errors.New("must have SWIG version >= 2.0.9\\n")からerrors.New("must have SWIG version >= 3.0\\n")に変更されました。これは、SWIG 3.0以降が、外部リンカーが直接オブジェクトファイルを扱うために必要な「プラグマ」を生成するようになったためです。このプラグマがなければ、GoのビルドシステムはSWIGオブジェクトを正しく静的リンクできません。
  4. 関連ユーティリティ関数の削除:

    • src/cmd/go/pkg.goから、SWIG共有ライブラリのファイル名を生成するswigSoname関数と、共有ライブラリを配置するディレクトリを決定するswigDir関数が削除されました。これらの関数は共有ライブラリの概念に密接に関連していたため、共有ライブラリが廃止されたことで不要になりました。

これらの変更により、GoとSWIGの連携はよりシンプルで堅牢になり、Goの「単一バイナリ」という哲学にさらに近づきました。ユーザーはSWIG 3.0以降を使用するだけで、SWIGによって生成されたC/C++コードがGoのバイナリに自動的に静的リンクされる恩恵を受けられるようになりました。

関連リンク

参考にした情報源リンク