[インデックス 11137] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、Cgo(GoとC言語の相互運用機能)に関連するリンカフラグ(CgoLDFLAGS
)が、リンクコマンドの最後に渡されるように修正するものです。これにより、Unix系リンカの特性(コマンドラインの最後にライブラリ引数を配置する必要がある)に対応し、ビルドの信頼性を向上させます。
変更されたファイルは以下の通りです。
src/cmd/go/build.go
: ビルドコマンドの実行ロジックと、各種ツール(コンパイラ、リンカなど)の呼び出し方法が変更されています。特に、引数処理の汎用化とリンカ引数の順序調整が中心です。src/cmd/go/fix.go
:go fix
コマンドの内部的な引数渡しが変更されています。src/cmd/go/fmt.go
:go fmt
コマンドの内部的な引数渡しが変更されています。src/cmd/go/main.go
: 新しいヘルパー関数stringList
が追加され、run
関数の引数処理が汎用化されています。src/cmd/go/run.go
:go run
コマンドの内部的な引数渡しが変更されています。src/cmd/go/test.go
:go test
コマンドの内部的な引数渡しが変更されています。src/cmd/go/vet.go
:go vet
コマンドの内部的な引数渡しが変更されています。
コミット
commit c624fa691df0a7e59b89e9f3ca2333c28f65c4f5
Author: Russ Cox <rsc@golang.org>
Date: Thu Jan 12 13:44:02 2012 -0800
go/build: pass CgoLDFLAGS at end of link command
By the time a Unix linker gets to the end of the
command line it has forgotten what you told it
at the beginning of the command line, so you
have to put library arguments (like -lm) at the end.
R=golang-dev, r, bradfitz
CC=golang-dev
https://golang.org/cl/5541043
---
src/cmd/go/build.go | 42 +++++++++++++++++++-----------------------
src/cmd/go/fix.go | 2 +-
src/cmd/go/fmt.go | 2 +-\n src/cmd/go/main.go | 20 +++++++++++++++++++-\n src/cmd/go/run.go | 3 +--\n src/cmd/go/test.go | 5 ++---\n src/cmd/go/vet.go | 2 +-\n 7 files changed, 44 insertions(+), 32 deletions(-)
diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go
index 02e2172b96..f0078a36c8 100644
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -818,7 +818,8 @@ var errPrintedOutput = errors.New("already printed output - no need to show erro
// run runs the command given by cmdline in the directory dir.
// If the commnd fails, run prints information about the failure
// and returns a non-nil error.
-func (b *builder) run(dir string, desc string, cmdline ...string) error {
+func (b *builder) run(dir string, desc string, cmdargs ...interface{}) error {
+\tcmdline := stringList(cmdargs...)
\tif buildN || buildX {
\t\tb.showcmd(dir, "%s", strings.Join(cmdline, " "))
\t\tif buildN {
@@ -890,14 +891,11 @@ func mkAbs(dir, f string) string {
// gc runs the Go compiler in a specific directory on a set of files
// to generate the named output file.
func (b *builder) gc(p *Package, ofile string, gcargs, importArgs []string, gofiles []string) error {
-\targs := []string{b.arch + "g", "-o", ofile}
-\targs = append(args, b.gcflags...)
-\targs = append(args, gcargs...)
-\targs = append(args, importArgs...)
+\targs := stringList(b.arch+"g", "-o", ofile, b.gcflags, gcargs, importArgs)
\tfor _, f := range gofiles {
\t\targs = append(args, mkAbs(p.Dir, f))
\t}\n-\treturn b.run(p.Dir, p.ImportPath, args...)\n+\treturn b.run(p.Dir, p.ImportPath, args)
}\n \n // asm runs the assembler in a specific directory on a specific file
@@ -911,17 +909,16 @@ func (b *builder) asm(p *Package, obj, ofile, sfile string) error {
// an archive from a set of object files.\n // typically it is run in the object directory.\n func (b *builder) gopack(p *Package, objDir, afile string, ofiles []string) error {\n-\tcmd := []string{"gopack", "grc"}\n-\tcmd = append(cmd, mkAbs(objDir, afile))\n+\tvar absOfiles []string\n \tfor _, f := range ofiles {\n-\t\tcmd = append(cmd, mkAbs(objDir, f))\n+\t\tabsOfiles = append(absOfiles, mkAbs(objDir, f))\n \t}\n-\treturn b.run(p.Dir, p.ImportPath, cmd...)\n+\treturn b.run(p.Dir, p.ImportPath, "gopack", "grc", mkAbs(objDir, afile), absOfiles)
}\n \n // ld runs the linker to create a package starting at mainpkg.\n func (b *builder) ld(p *Package, out string, importArgs []string, mainpkg string) error {\n-\treturn b.run(p.Dir, p.ImportPath, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)\n+\treturn b.run(p.Dir, p.ImportPath, b.arch+"l", "-o", out, importArgs, mainpkg)
}\n \n // cc runs the gc-toolchain C compiler in a directory on a C file
@@ -929,22 +926,24 @@ func (b *builder) ld(p *Package, out string, importArgs []string, mainpkg string
func (b *builder) cc(p *Package, objdir, ofile, cfile string) error {\n \tinc := filepath.Join(b.goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch))\n \tcfile = mkAbs(p.Dir, cfile)\n-\treturn b.run(p.Dir, p.ImportPath, b.arch+"c", "-FVw", "-I", objdir, "-I", inc, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)\n+\treturn b.run(p.Dir, p.ImportPath, b.arch+"c", "-FVw",\n+\t\t"-I", objdir, "-I", inc, "-o", ofile,\n+\t\t"-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)
}\n \n // gcc runs the gcc C compiler to create an object from a single C file.\n func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error {\n \tcfile = mkAbs(p.Dir, cfile)\n-\treturn b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir, flags, "-o", out, "-c", cfile)...)\n+\treturn b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), flags, "-o", out, "-c", cfile)
}\n \n // gccld runs the gcc linker to create an executable from a set of object files\n func (b *builder) gccld(p *Package, out string, flags []string, obj []string) error {\n-\treturn b.run(p.Dir, p.ImportPath, append(b.gccCmd(p.Dir, flags, "-o", out), obj...)...)\n+\treturn b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-o", out, obj, flags)
}\n \n-// gccCmd returns a gcc command line ending with args\n-func (b *builder) gccCmd(objdir string, flags []string, args ...string) []string {\n+// gccCmd returns a gcc command line prefix\n+func (b *builder) gccCmd(objdir string) []string {\n \t// TODO: HOST_CC?\n \ta := []string{"gcc", "-I", objdir, "-g", "-O2"}\n \n@@ -969,8 +968,7 @@ func (b *builder) gccCmd(objdir string, flags []string, args ...string) []string
\t\t\ta = append(a, "-pthread")\n \t\t}\n \t}\n-\ta = append(a, flags...)\n-\treturn append(a, args...)\n+\treturn a
}\n \n var cgoRe = regexp.MustCompile(`[/\\\\:]`)\n@@ -994,13 +992,11 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,\n \tdefunC := obj + "_cgo_defun.c"\n \t// TODO: make cgo not depend on $GOARCH?\n \t// TODO: make cgo write to obj\n-\tcgoArgs := []string{cgoExe, "-objdir", obj}\n+\tvar runtimeFlag []string\n \tif p.Standard && p.ImportPath == "runtime/cgo" {\n-\t\tcgoArgs = append(cgoArgs, "-import_runtime_cgo=false")\n+\t\truntimeFlag = []string{"-import_runtime_cgo=false"}\n \t}\n-\tcgoArgs = append(cgoArgs, "--")\n-\tcgoArgs = append(cgoArgs, p.CgoFiles...)\n-\tif err := b.run(p.Dir, p.ImportPath, cgoArgs...); err != nil {\n+\tif err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, runtimeFlag, "--", p.CgoFiles); err != nil {\n \t\treturn nil, nil, err\n \t}\n \toutGo = append(outGo, gofiles...)\ndiff --git a/src/cmd/go/fix.go b/src/cmd/go/fix.go\nindex df6bcb347b..fdefe8db6e 100644\n--- a/src/cmd/go/fix.go
+++ b/src/cmd/go/fix.go
@@ -25,6 +25,6 @@ func runFix(cmd *Command, args []string) {
\t\t// Use pkg.gofiles instead of pkg.Dir so that
\t\t// the command only applies to this package,
\t\t// not to packages in subdirectories.
-\t\trun(append([]string{"gofix"}, pkg.gofiles...)...)\n+\t\trun(stringList("gofix", pkg.gofiles))
\t}\n }\ndiff --git a/src/cmd/go/fmt.go b/src/cmd/go/fmt.go
index adf63be1f1..fb0b091192 100644
--- a/src/cmd/go/fmt.go
+++ b/src/cmd/go/fmt.go
@@ -26,7 +26,7 @@ func runFmt(cmd *Command, args []string) {
\t\t// Use pkg.gofiles instead of pkg.Dir so that
\t\t// the command only applies to this package,
\t\t// not to packages in subdirectories.
-\t\trun(append([]string{"gofmt", "-l", "-w"}, pkg.gofiles...)...)\n+\t\trun(stringList("gofmt", "-I", "w", pkg.gofiles))
\t}\n }\n \ndiff --git a/src/cmd/go/main.go b/src/cmd/go/main.go
index 4d21cf20c3..4b1ff357da 100644
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -236,7 +236,8 @@ func exitIfErrors() {
\t}\n }\n \n-func run(cmdline ...string) {\n+func run(cmdargs ...interface{}) {\n+\tcmdline := stringList(cmdargs...)\n \tcmd := exec.Command(cmdline[0], cmdline[1:]...)\n \tcmd.Stdout = os.Stdout\n \tcmd.Stderr = os.Stderr
@@ -404,3 +405,20 @@ func allPackagesInFS(pattern string) []string {
\t}\n \treturn pkgs
}\n+\n+// stringList's arguments should be a sequence of string or []string values.\n+// stringList flattens them into a single []string.\n+func stringList(args ...interface{}) []string {\n+\tvar x []string\n+\tfor _, arg := range args {\n+\t\tswitch arg := arg.(type) {\n+\t\tcase []string:\n+\t\t\tx = append(x, arg...)\n+\t\tcase string:\n+\t\t\tx = append(x, arg)\n+\t\tdefault:\n+\t\t\tpanic("stringList: invalid argument")
+\t\t}\n+\t}\n+\treturn x
+}\ndiff --git a/src/cmd/go/run.go b/src/cmd/go/run.go
index 1582531fae..dbd91a367e 100644
--- a/src/cmd/go/run.go
+++ b/src/cmd/go/run.go
@@ -42,8 +42,7 @@ func runRun(cmd *Command, args []string) {
// been compiled. We ignore exit status.\n func (b *builder) runProgram(a *action) error {\n-\targs := append([]string{a.deps[0].target}, a.args...)\n-\trun(args...)\n+\trun(a.deps[0].target, a.args)
\treturn nil
}\n \ndiff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index ac0498fa7a..57e0469e06 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -359,7 +359,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
\t\tptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)\n \t\tptest.GoFiles = append(ptest.GoFiles, p.info.TestGoFiles...)\n \t\tptest.target = ""\n-\t\tptest.Imports = append(append([]string{}, p.info.Imports...), p.info.TestImports...)\n+\t\tptest.Imports = stringList(p.info.Imports, p.info.TestImports)
\t\tptest.imports = append(append([]*Package{}, p.imports...), imports...)\n \t\tptest.pkgdir = testDir\n \t\tptest.fake = true
@@ -441,8 +441,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
\n // runTest is the action for running a test binary.\n func (b *builder) runTest(a *action) error {\n-\targs := []string{a.deps[0].target}\n-\targs = append(args, testArgs...)\n+\targs := stringList(a.deps[0].target, testArgs)
\ta.testOutput = new(bytes.Buffer)\n \n \tif buildN || buildX {
diff --git a/src/cmd/go/vet.go b/src/cmd/go/vet.go
index f8fe92243b..c1e17dfd0c 100644
--- a/src/cmd/go/vet.go
+++ b/src/cmd/go/vet.go
@@ -25,6 +25,6 @@ func runVet(cmd *Command, args []string) {
\t\t// Use pkg.gofiles instead of pkg.Dir so that
\t\t// the command only applies to this package,
\t\t// not to packages in subdirectories.
-\t\trun(append([]string{"govet"}, pkg.gofiles...)...)\n+\t\trun("govet", pkg.gofiles)
\t}\n }\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c624fa691df0a7e59b89e9f3ca2333c28f65c4f5
元コミット内容
このコミットは、Go言語のビルドシステムにおいて、Cgoのリンカフラグ(CgoLDFLAGS
)をリンクコマンドの最後に渡すように変更します。これは、Unix系リンカがコマンドラインの先頭で指定されたライブラリ引数(例: -lm
)を、コマンドラインの終わりに到達するまでに「忘れてしまう」という特性に対応するためです。ライブラリ引数はコマンドの最後に配置する必要があります。
変更の背景
この変更の背景には、Unix系リンカの動作特性があります。一般的なUnix系リンカ(例: ld
や gcc
が内部で呼び出すリンカ)は、コマンドライン引数を左から右へと順に処理します。この際、ライブラリ(-l
オプションで指定されるもの)は、そのライブラリが提供するシンボルを必要とするオブジェクトファイルや他のライブラリよりも後に指定される必要があります。
もしライブラリが先に指定され、その後にそのライブラリのシンボルを必要とするオブジェクトファイルが来た場合、リンカは既にそのライブラリの情報を「忘れて」しまっているか、まだそのシンボルが必要であることを認識していないため、未解決のシンボルエラー("undefined reference")が発生する可能性があります。
Cgoを使用する場合、GoのコードからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりするために、Cのライブラリをリンクする必要があります。これらのライブラリは CgoLDFLAGS
を通じてリンカに渡されます。このコミット以前は、これらのフラグがリンカコマンドラインの適切な位置に配置されていなかったため、特定の環境やライブラリの組み合わせでリンクエラーが発生する可能性がありました。
このコミットは、CgoLDFLAGS
を含むすべてのライブラリ関連のリンカ引数が、リンカコマンドラインの最後に確実に配置されるようにすることで、この問題を解決し、Cgoを利用するGoプログラムのビルドの堅牢性を高めることを目的としています。
前提知識の解説
リンカの動作とコマンドライン引数
リンカ(Linker)は、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含むファイル)や、事前にコンパイルされたライブラリファイルを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。
Unix系システムにおけるリンカの重要な特性の一つは、その「ワンパス」または「順序依存」の動作です。リンカはコマンドライン引数を左から右へ順に処理し、シンボル解決を行います。
- オブジェクトファイル: リンカはオブジェクトファイルからシンボル定義(関数や変数の実体)を読み込み、未解決のシンボル参照(他のファイルで定義されている関数や変数への呼び出し)を記録します。
- ライブラリファイル: ライブラリファイル(例:
libm.a
やlibpthread.so
)は、多くのオブジェクトファイルをまとめたアーカイブです。リンカは、現在までに記録された未解決のシンボル参照を解決するために、ライブラリ内のオブジェクトファイルを検索します。もしライブラリ内で未解決のシンボルが見つかれば、そのライブラリ内の対応するオブジェクトファイルがリンク対象に追加されます。
この順序依存性のため、あるライブラリ libX
が提供する関数 foo()
を、オブジェクトファイル objA.o
が呼び出している場合、リンカコマンドラインでは objA.o
の後に -lX
を指定する必要があります。
# 良い例: objA.o が libX のシンボルを必要とする場合
gcc objA.o -lX -o myprog
# 悪い例: リンカが libX のシンボルを「忘れる」可能性がある
gcc -lX objA.o -o myprog
このコミットは、まさにこの「リンカがコマンドラインの終わりに到達するまでに、先頭で指定されたライブラリ引数を忘れてしまう」という問題に対処しています。
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出したり、逆にC言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、Goの強力な並行処理とC言語の既存のライブラリやシステムコールへのアクセスを組み合わせることができます。
Cgoのビルドプロセスは複雑で、Goコンパイラ、Cコンパイラ(通常はGCC)、およびリンカが連携して動作します。Cgoのソースファイル(.go
ファイル内でimport "C"
を含むもの)は、まずCgoツールによってGoとCのソースファイルに変換されます。その後、CのソースファイルはCコンパイラによってコンパイルされ、GoのソースファイルはGoコンパイラによってコンパイルされます。最終的に、これらすべてのオブジェクトファイルがリンカによって結合され、実行可能ファイルが生成されます。
CgoLDFLAGS
は、Cgoのビルドプロセス中にCコンパイラ(GCCなど)がリンカを呼び出す際に渡される追加のリンカフラグです。これには、外部Cライブラリへのパス(-L
)やライブラリ名(-l
)などが含まれます。
Goのビルドシステム (src/cmd/go
)
Goのビルドシステムは、go build
、go run
、go test
などのコマンドを通じてGoプログラムをコンパイル・リンクする役割を担っています。これらのコマンドは、内部的にGoコンパイラ(gc
)、アセンブラ(go tool asm
)、リンカ(go tool link
)、そしてCgoを使用する場合はCコンパイラ(gcc
など)を呼び出します。
src/cmd/go/build.go
は、このビルドシステムの中心的な部分であり、各種ツールの呼び出しロジックや、ビルドプロセスの各ステップ(コンパイル、アセンブル、リンクなど)を管理しています。このファイル内の builder
構造体は、ビルドのコンテキストと状態を保持し、run
メソッドを通じて外部コマンドを実行します。
技術的詳細
このコミットの主要な技術的変更点は、コマンド実行のための引数処理を汎用化し、特にリンカ引数の順序問題を解決することにあります。
-
stringList
ヘルパー関数の導入:src/cmd/go/main.go
にstringList
という新しいヘルパー関数が追加されました。この関数は、string
型と[]string
型の任意の数の引数を受け取り、それらを単一のフラットな[]string
スライスに結合します。// stringList's arguments should be a sequence of string or []string values. // stringList flattens them into a single []string. func stringList(args ...interface{}) []string { var x []string for _, arg := range args { switch arg := arg.(type) { case []string: x = append(x, arg...) case string: x = append(x, arg) default: panic("stringList: invalid argument") } } return x }
この関数は、可変長引数として渡される文字列や文字列スライスのリストを、
exec.Command
が期待する単一の[]string
に変換する役割を担います。 -
builder.run
およびグローバルrun
関数の引数変更:src/cmd/go/build.go
内のbuilder.run
メソッドと、src/cmd/go/main.go
内のグローバルなrun
関数のシグネチャが変更されました。 変更前:func (b *builder) run(dir string, desc string, cmdline ...string) error
変更後:func (b *builder) run(dir string, desc string, cmdargs ...interface{}) error
これにより、run
関数はstring
と[]string
の両方を混在させた引数を受け取れるようになり、内部でstringList
を呼び出して[]string
に変換します。 -
各ビルドツール呼び出し箇所の修正:
src/cmd/go/build.go
内のgc
,gopack
,ld
,cc
,gcc
,gccld
,cgo
メソッド、および他のファイル(fix.go
,fmt.go
,run.go
,test.go
,vet.go
)内のrun
関数呼び出し箇所が、新しいstringList
を利用する形式に修正されました。 例えば、builder.ld
(リンカを呼び出す関数) の変更は以下のようになります。 変更前:return b.run(p.Dir, p.ImportPath, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)
変更後:
return b.run(p.Dir, p.ImportPath, b.arch+"l", "-o", out, importArgs, mainpkg)
ここで
importArgs
は[]string
型であり、これがstringList
によって展開され、mainpkg
(これも文字列) の後に配置されることになります。これにより、CgoLDFLAGS
が含まれるimportArgs
がリンカコマンドラインの最後に渡されることが保証されます。 -
builder.gccCmd
の変更:src/cmd/go/build.go
内のbuilder.gccCmd
関数も変更されました。この関数は以前、GCCコマンドラインのプレフィックスとサフィックス(引数)を結合して完全なコマンドラインを返していましたが、変更後はプレフィックスのみを返すようになりました。これにより、gcc
やgccld
の呼び出し元で、stringList
を使ってフラグやオブジェクトファイルをより柔軟に、かつ適切な順序で追加できるようになりました。
これらの変更により、Goのビルドシステムが外部コマンド(特にリンカ)を呼び出す際の引数処理がより柔軟になり、CgoLDFLAGS
のような重要なリンカフラグが、Unix系リンカの期待するコマンドラインの最後に確実に配置されるようになりました。これは、Cgoを使用するGoプログラムのクロスプラットフォームでのビルドの信頼性を高める上で非常に重要です。
コアとなるコードの変更箇所
src/cmd/go/main.go
の stringList
関数の追加
// stringList's arguments should be a sequence of string or []string values.
// stringList flattens them into a single []string.
func stringList(args ...interface{}) []string {
var x []string
for _, arg := range args {
switch arg := arg.(type) {
case []string:
x = append(x, arg...)
case string:
x = append(x, arg)
default:
panic("stringList: invalid argument")
}
}
return x
}
src/cmd/go/main.go
の run
関数の変更
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -236,7 +236,8 @@ func exitIfErrors() {
}
}
-func run(cmdline ...string) {
+func run(cmdargs ...interface{}) {
+\tcmdline := stringList(cmdargs...)
cmd := exec.Command(cmdline[0], cmdline[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
src/cmd/go/build.go
の builder.run
メソッドの変更
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -818,7 +818,8 @@ var errPrintedOutput = errors.New("already printed output - no need to show erro
// run runs the command given by cmdline in the directory dir.
// If the commnd fails, run prints information about the failure
// and returns a non-nil error.
-func (b *builder) run(dir string, desc string, cmdline ...string) error {
+func (b *builder) run(dir string, desc string, cmdargs ...interface{}) error {
+\tcmdline := stringList(cmdargs...)
if buildN || buildX {
b.showcmd(dir, "%s", strings.Join(cmdline, " "))
if buildN {
src/cmd/go/build.go
の builder.ld
メソッドの変更(リンカ引数順序の核心)
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -911,7 +909,7 @@ func (b *builder) asm(p *Package, obj, ofile, sfile string) error {
// an archive from a set of object files.
// typically it is run in the object directory.
func (b *builder) gopack(p *Package, objDir, afile string, ofiles []string) error {
-\tcmd := []string{"gopack", "grc"}
-\tcmd = append(cmd, mkAbs(objDir, afile))
+\tvar absOfiles []string
for _, f := range ofiles {
-\t\tcmd = append(cmd, mkAbs(objDir, f))
+\t\tabsOfiles = append(absOfiles, mkAbs(objDir, f))
}
-\treturn b.run(p.Dir, p.ImportPath, cmd...)\n+\treturn b.run(p.Dir, p.ImportPath, "gopack", "grc", mkAbs(objDir, afile), absOfiles)
}\n \n // ld runs the linker to create a package starting at mainpkg.\n func (b *builder) ld(p *Package, out string, importArgs []string, mainpkg string) error {\n-\treturn b.run(p.Dir, p.ImportPath, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)\n+\treturn b.run(p.Dir, p.ImportPath, b.arch+"l", "-o", out, importArgs, mainpkg)
}\n \n // cc runs the gc-toolchain C compiler in a directory on a C file
コアとなるコードの解説
このコミットの核心は、stringList
関数の導入と、それを利用した run
関数の引数処理の変更、そして各ビルドツール呼び出し箇所での引数渡し方の統一です。
-
stringList
関数の役割: この関数は、Goの可変長引数(...interface{}
)の柔軟性を活用し、string
型の個々の引数と[]string
型のスライス引数をシームレスに結合して、単一の[]string
スライスを生成します。これにより、呼び出し側は引数を渡す際に、個々の文字列と文字列スライスを混在させて指定できるようになります。これは、特にリンカコマンドのように、固定のオプションと可変のファイルリストやフラグリストが混在するケースで非常に便利です。 -
run
関数の汎用化:builder.run
メソッドとグローバルなrun
関数は、外部コマンドを実行するための主要なインターフェースです。以前は...string
を受け取っていましたが、...interface{}
に変更し、内部でstringList
を呼び出すことで、より多様な引数形式に対応できるようになりました。これにより、各ビルドツール(gc
,ld
,gccld
など)は、引数をより自然な形でrun
関数に渡せるようになります。 -
リンカ引数順序の解決 (
builder.ld
の変更):builder.ld
メソッドは、Goのリンカ(go tool link
)を呼び出す役割を担っています。このメソッドの変更が、今回のコミットの主要な目的である「CgoLDFLAGS
をリンカコマンドの最後に渡す」という問題を解決しています。 変更前は、append
を多重に利用して引数リストを構築していました。この方法では、importArgs
(これにCgoLDFLAGS
が含まれる可能性がある)がmainpkg
の前に来てしまう可能性がありました。 変更後、b.run(p.Dir, p.ImportPath, b.arch+"l", "-o", out, importArgs, mainpkg)
のようにstringList
を介して引数を渡すことで、importArgs
(文字列スライス) がmainpkg
(文字列) の前に来ても、stringList
がimportArgs
の内容を展開し、その後にmainpkg
を配置するため、結果的にmainpkg
がリンカコマンドラインの最後に位置することになります。 Unix系リンカの特性上、ライブラリ引数(importArgs
に含まれる可能性のある-L
や-l
フラグ)は、それらが解決するシンボルを必要とするオブジェクトファイル(mainpkg
が最終的に参照するオブジェクトファイル)の後に来る必要があります。この変更により、importArgs
がmainpkg
の直前に展開されることで、この要件が満たされ、リンカがシンボルを正しく解決できるようになります。
この一連の変更は、Goのビルドシステムが外部ツールを呼び出す際の引数処理をより堅牢かつ柔軟にし、特にCgoのような複雑なビルドシナリオにおけるリンカの挙動に関する問題を根本的に解決しています。
関連リンク
- Go CL 5541043: https://golang.org/cl/5541043
参考にした情報源リンク
- Unix Linker Order: https://www.akkadia.org/drepper/dsohowto.pdf (特にセクション 2.2 "Linker Command Line Order")
- Go Cgo Documentation: https://go.dev/blog/cgo
- Go Command Documentation: https://go.dev/cmd/go/