[インデックス 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共有ライブラリの命名規則やディレクトリ構造を定義していた関数(swigSoname
、swigDir
)が削除されました。
コミット
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のバイナリにリンクされていました。このアプローチにはいくつかの課題がありました。
- デプロイメントの複雑さ: 共有ライブラリは、実行時にシステム上に存在する必要があります。これにより、Goバイナリを配布する際に、依存する共有ライブラリも一緒に配布し、適切なパスに配置する必要が生じ、デプロイメントが複雑になる可能性がありました。特にクロスコンパイル環境や異なるOS環境でのデプロイでは、共有ライブラリの互換性問題が発生しやすくなります。
- リンカーの複雑性: 共有ライブラリを扱うことは、ビルドシステムにとって追加の複雑性をもたらします。共有ライブラリの検索パス、バージョン管理、シンボル解決など、リンカーが考慮すべき事項が増えます。
- 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」と呼ばれる問題)ことや、デプロイメント時にライブラリも一緒に配布する必要がある点が挙げられます。
- WindowsではDLL (Dynamic Link Library)、Linux/Unix系では
-
静的リンク (Static Linking):
- ライブラリのコードが、コンパイル時に直接実行可能ファイルに組み込まれる方式です。
- 生成される実行可能ファイルは、外部ライブラリに依存しないため、単一のファイルとして配布・実行が可能です。デプロイメントが非常にシンプルになります。
- 複数のプログラムが同じライブラリを使用する場合でも、それぞれの実行可能ファイルにライブラリのコピーが含まれるため、ディスク容量やメモリ使用量が増える可能性があります。
- ライブラリの更新があった場合、それを使用するすべてのプログラムを再コンパイルする必要があります。
- Go言語は、デフォルトで静的リンクを強く推奨しており、生成されるバイナリは通常、外部の共有ライブラリにほとんど依存しません(Cgoを使用しない場合)。
このコミットは、SWIGが生成するC/C++オブジェクトを、従来の共有ライブラリを介したリンクから、Goのビルドシステムが直接静的にリンクする方式へと変更するものです。
技術的詳細
このコミットの技術的な核心は、Goのビルドツールであるcmd/go
が、SWIGによって生成されたC/C++のオブジェクトファイルを、中間的な共有ライブラリを介さずに、直接最終的なGoの実行可能バイナリにリンクするように変更された点です。
変更前のアプローチ (共有ライブラリ経由のリンク)
変更前は、GoプログラムがSWIGを使用する場合、以下のようなプロセスが踏まれていました。
- SWIGが、Goのラッパーコード(
.go
ファイル)と、C/C++のソースコード(.c
や.cxx
ファイル)を生成します。 - 生成されたC/C++ソースコードは、Goのビルドシステムによってコンパイルされ、オブジェクトファイル(
.o
ファイル)になります。 - これらのオブジェクトファイルは、さらに共有ライブラリ(例:
_obj/_cgo_swig_go.so
のような名前)としてビルドされます。この共有ライブラリには、SWIGが生成したC/C++の関数やデータが含まれます。 - 最終的に、Goのリンカーが、この共有ライブラリをGoの実行可能バイナリにリンクします。この際、共有ライブラリは実行時にロードされる形式で参照されます。
このアプローチでは、src/cmd/go/build.go
内のgcToolchain.ld
関数やtools gccgoToolchain.ld
関数が、SWIG関連の共有ライブラリのパスをリンカーに渡すためのロジックを含んでいました。また、src/cmd/go/pkg.go
には、共有ライブラリの命名規則を決定するswigSoname
関数や、共有ライブラリを配置するディレクトリを決定するswigDir
関数が存在していました。
変更後のアプローチ (直接リンク)
このコミットにより、プロセスは以下のように簡素化されました。
- SWIGが、GoのラッパーコードとC/C++のソースコードを生成します。
- 生成されたC/C++ソースコードは、Goのビルドシステムによってコンパイルされ、オブジェクトファイル(
.o
ファイル)になります。 - 重要な変更点: これらのオブジェクトファイルは、共有ライブラリとしてビルドされることなく、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.go
とsrc/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 - } - } - }
- SWIG共有ライブラリをインストールディレクトリにコピーするロジックが完全に削除されました。
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)
- Goコンパイラツールチェーンのリンカー関数から、SWIG共有ライブラリのディレクトリをリンカー引数に追加するロジックが削除されました。
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...)
- GCCGoツールチェーンのリンカー関数から、SWIG共有ライブラリのオブジェクトファイル(
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)
- SWIGによって生成されたC/C++/Objective-Cのオブジェクトファイルを
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の最終バイナリに静的にリンクされるようになりました。
具体的には、以下の変更が重要です。
-
共有ライブラリ生成ロジックの削除:
src/cmd/go/build.go
のswigOne
関数内で、SWIGが生成したC/C++オブジェクトファイル(gccObj
)から共有ライブラリ(target
)を作成していた大規模なコードブロックが削除されました。これは、osldflags
、cxxlib
、ldflags
といった共有ライブラリのビルドに必要な変数の定義と、b.run(..., b.gccCmd(p.Dir), "-o", target, gccObj, extraObj, ldflags)
によるリンカー呼び出しを含んでいました。この削除により、GoのビルドプロセスからSWIG関連の共有ライブラリ生成が完全に消滅しました。- これに伴い、
install
関数やclean
関数からも、共有ライブラリのコピーや削除に関するロジックが削除されています。
-
リンカーへのSWIGオブジェクトの直接渡し:
gcToolchain.ld
およびgccgoToolchain.ld
関数から、SWIG共有ライブラリのパスをリンカーに渡すためのswigArg
やsfiles
といった変数の使用が削除されました。これは、共有ライブラリがもはや存在しないため、それらをリンカーに渡す必要がなくなったことを意味します。- 代わりに、
swigOne
関数が返すgccObj
(SWIGが生成したC/C++オブジェクトファイル)が、直接Goのリンカーに渡されるようになりました。これにより、SWIGのC/C++コードがGoのバイナリに静的に組み込まれます。
-
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オブジェクトを正しく静的リンクできません。
-
関連ユーティリティ関数の削除:
src/cmd/go/pkg.go
から、SWIG共有ライブラリのファイル名を生成するswigSoname
関数と、共有ライブラリを配置するディレクトリを決定するswigDir
関数が削除されました。これらの関数は共有ライブラリの概念に密接に関連していたため、共有ライブラリが廃止されたことで不要になりました。
これらの変更により、GoとSWIGの連携はよりシンプルで堅牢になり、Goの「単一バイナリ」という哲学にさらに近づきました。ユーザーはSWIG 3.0以降を使用するだけで、SWIGによって生成されたC/C++コードがGoのバイナリに自動的に静的リンクされる恩恵を受けられるようになりました。
関連リンク
- Go Code Review: https://golang.org/cl/97120046
- Go Issue #7155 (詳細不明、共有ライブラリ関連の問題と推測される): https://go.dev/issue/7155
- Go Issue #7156 (詳細不明、共有ライブラリ関連の問題と推測される): https://go.dev/issue/7156
参考にした情報源リンク
- SWIG公式ウェブサイト: http://www.swig.org/
- Go言語公式ドキュメント (Cgo): https://go.dev/blog/c-go-is-not-c
- 共有ライブラリと静的ライブラリに関する一般的な情報源 (例: Wikipedia, 各種プログラミングブログ)
- Go言語のビルドプロセスに関する一般的な情報源 (例: Goの公式ドキュメント、Goのソースコード解説記事)
- GoのIssue Tracker (過去のIssue検索): https://go.dev/issue/
- Goのコミット履歴 (GitHub): https://github.com/golang/go/commits/master