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

[インデックス 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系リンカ(例: ldgcc が内部で呼び出すリンカ)は、コマンドライン引数を左から右へと順に処理します。この際、ライブラリ(-lオプションで指定されるもの)は、そのライブラリが提供するシンボルを必要とするオブジェクトファイルや他のライブラリよりも後に指定される必要があります。

もしライブラリが先に指定され、その後にそのライブラリのシンボルを必要とするオブジェクトファイルが来た場合、リンカは既にそのライブラリの情報を「忘れて」しまっているか、まだそのシンボルが必要であることを認識していないため、未解決のシンボルエラー("undefined reference")が発生する可能性があります。

Cgoを使用する場合、GoのコードからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりするために、Cのライブラリをリンクする必要があります。これらのライブラリは CgoLDFLAGS を通じてリンカに渡されます。このコミット以前は、これらのフラグがリンカコマンドラインの適切な位置に配置されていなかったため、特定の環境やライブラリの組み合わせでリンクエラーが発生する可能性がありました。

このコミットは、CgoLDFLAGS を含むすべてのライブラリ関連のリンカ引数が、リンカコマンドラインの最後に確実に配置されるようにすることで、この問題を解決し、Cgoを利用するGoプログラムのビルドの堅牢性を高めることを目的としています。

前提知識の解説

リンカの動作とコマンドライン引数

リンカ(Linker)は、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含むファイル)や、事前にコンパイルされたライブラリファイルを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。

Unix系システムにおけるリンカの重要な特性の一つは、その「ワンパス」または「順序依存」の動作です。リンカはコマンドライン引数を左から右へ順に処理し、シンボル解決を行います。

  • オブジェクトファイル: リンカはオブジェクトファイルからシンボル定義(関数や変数の実体)を読み込み、未解決のシンボル参照(他のファイルで定義されている関数や変数への呼び出し)を記録します。
  • ライブラリファイル: ライブラリファイル(例: libm.alibpthread.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 buildgo rungo test などのコマンドを通じてGoプログラムをコンパイル・リンクする役割を担っています。これらのコマンドは、内部的にGoコンパイラ(gc)、アセンブラ(go tool asm)、リンカ(go tool link)、そしてCgoを使用する場合はCコンパイラ(gccなど)を呼び出します。

src/cmd/go/build.go は、このビルドシステムの中心的な部分であり、各種ツールの呼び出しロジックや、ビルドプロセスの各ステップ(コンパイル、アセンブル、リンクなど)を管理しています。このファイル内の builder 構造体は、ビルドのコンテキストと状態を保持し、run メソッドを通じて外部コマンドを実行します。

技術的詳細

このコミットの主要な技術的変更点は、コマンド実行のための引数処理を汎用化し、特にリンカ引数の順序問題を解決することにあります。

  1. stringList ヘルパー関数の導入: src/cmd/go/main.gostringList という新しいヘルパー関数が追加されました。この関数は、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 に変換する役割を担います。

  2. 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 に変換します。

  3. 各ビルドツール呼び出し箇所の修正: 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 がリンカコマンドラインの最後に渡されることが保証されます。

  4. builder.gccCmd の変更: src/cmd/go/build.go 内の builder.gccCmd 関数も変更されました。この関数は以前、GCCコマンドラインのプレフィックスとサフィックス(引数)を結合して完全なコマンドラインを返していましたが、変更後はプレフィックスのみを返すようになりました。これにより、gccgccld の呼び出し元で、stringList を使ってフラグやオブジェクトファイルをより柔軟に、かつ適切な順序で追加できるようになりました。

これらの変更により、Goのビルドシステムが外部コマンド(特にリンカ)を呼び出す際の引数処理がより柔軟になり、CgoLDFLAGS のような重要なリンカフラグが、Unix系リンカの期待するコマンドラインの最後に確実に配置されるようになりました。これは、Cgoを使用するGoプログラムのクロスプラットフォームでのビルドの信頼性を高める上で非常に重要です。

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

src/cmd/go/main.gostringList 関数の追加

// 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.gorun 関数の変更

--- 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.gobuilder.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.gobuilder.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 関数の引数処理の変更、そして各ビルドツール呼び出し箇所での引数渡し方の統一です。

  1. stringList 関数の役割: この関数は、Goの可変長引数(...interface{})の柔軟性を活用し、string 型の個々の引数と []string 型のスライス引数をシームレスに結合して、単一の []string スライスを生成します。これにより、呼び出し側は引数を渡す際に、個々の文字列と文字列スライスを混在させて指定できるようになります。これは、特にリンカコマンドのように、固定のオプションと可変のファイルリストやフラグリストが混在するケースで非常に便利です。

  2. run 関数の汎用化: builder.run メソッドとグローバルな run 関数は、外部コマンドを実行するための主要なインターフェースです。以前は ...string を受け取っていましたが、...interface{} に変更し、内部で stringList を呼び出すことで、より多様な引数形式に対応できるようになりました。これにより、各ビルドツール(gc, ld, gccld など)は、引数をより自然な形で run 関数に渡せるようになります。

  3. リンカ引数順序の解決 (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 (文字列) の前に来ても、stringListimportArgs の内容を展開し、その後に mainpkg を配置するため、結果的に mainpkg がリンカコマンドラインの最後に位置することになります。 Unix系リンカの特性上、ライブラリ引数(importArgs に含まれる可能性のある -L-l フラグ)は、それらが解決するシンボルを必要とするオブジェクトファイル(mainpkg が最終的に参照するオブジェクトファイル)の後に来る必要があります。この変更により、importArgsmainpkg の直前に展開されることで、この要件が満たされ、リンカがシンボルを正しく解決できるようになります。

この一連の変更は、Goのビルドシステムが外部ツールを呼び出す際の引数処理をより堅牢かつ柔軟にし、特にCgoのような複雑なビルドシナリオにおけるリンカの挙動に関する問題を根本的に解決しています。

関連リンク

参考にした情報源リンク