[インデックス 18416] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、Cgoを有効にした状態でのクロスコンパイルを改善するための重要な変更を導入しています。具体的には、ターゲットアーキテクチャ向けのC/C++コンパイラを指定するための新しい環境変数CC_FOR_TARGET
とCXX_FOR_TARGET
を導入し、クロスコンパイル時にはCGO_ENABLED
がデフォルトで無効になるように挙動を変更しています。これにより、より柔軟かつ安全なクロスコンパイル環境が提供されます。
コミット
commit 2dc759d7c69cbb0800de53d6ca391c703ad42d9c
Author: Elias Naur <elias.naur@gmail.com>
Date: Thu Feb 6 09:11:00 2014 -0800
cmd/go, cmd/cgo, make.bash: cross compiling with cgo enabled
Introduce two new environment variables, CC_FOR_TARGET and CXX_FOR_TARGET.
CC_FOR_TARGET defaults to CC and is used when compiling for GOARCH, while
CC remains for compiling for GOHOSTARCH.
CXX_FOR_TARGET defaults to CXX and is used when compiling C++ code for
GOARCH.
CGO_ENABLED defaults to disabled when cross compiling and has to be
explicitly enabled.
Update #4714
LGTM=minux.ma, iant
R=golang-codereviews, minux.ma, iant, rsc, dominik.honnef
CC=golang-codereviews
https://golang.org/cl/57100043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2dc759d7c69cbb0800de53d6ca391c703ad42d9c
元コミット内容
このコミットは、Goのビルドシステム、特にcmd/go
、cmd/cgo
、make.bash
に関連するもので、Cgoを有効にした状態でのクロスコンパイルのサポートを改善します。主な変更点は以下の通りです。
- 新しい環境変数の導入:
CC_FOR_TARGET
: ターゲットアーキテクチャ (GOARCH
) 向けのCコードをコンパイルする際に使用されるコンパイラを指定します。デフォルトはCC
です。CXX_FOR_TARGET
: ターゲットアーキテクチャ (GOARCH
) 向けのC++コードをコンパイルする際に使用されるコンパイラを指定します。デフォルトはCXX
、または設定されていない場合はg++
やclang++
です。- 既存の
CC
はホストアーキテクチャ (GOHOSTARCH
) 向けのコンパイルに引き続き使用されます。
CGO_ENABLED
のデフォルト挙動の変更:- クロスコンパイル時には、
CGO_ENABLED
がデフォルトで無効になります。Cgoを有効にするには、明示的にCGO_ENABLED=1
を設定する必要があります。
- クロスコンパイル時には、
- Issue #4714の更新: この変更は、GoのIssueトラッカーで報告されていたクロスコンパイル時のCgoに関する問題を解決します。
変更の背景
Go言語は、その設計思想の一つとして、静的リンクされたバイナリを生成し、デプロイの容易さを追求しています。しかし、Cgo(GoとC言語の相互運用機能)を使用する場合、GoのコードがC/C++のコードとリンクされるため、外部のC/C++コンパイラやリンカが必要になります。
従来のGoのビルドシステムでは、クロスコンパイル(例えば、LinuxマシンでWindows向けのバイナリをビルドする)を行う際に、Cgoが絡むと問題が発生することがありました。具体的には、Goのビルドツールが、ホストシステム(ビルドを実行しているマシン)のC/C++コンパイラを、ターゲットシステム(生成されるバイナリが実行されるマシン)のC/C++コードのコンパイルにも使用しようとする傾向がありました。これは、異なるアーキテクチャやOS向けのC/C++コードをコンパイルする際に、適切なクロスコンパイラが使用されないという問題を引き起こしました。
このコミットは、この問題を解決するために、ターゲットアーキテクチャ向けのC/C++コンパイラを明示的に指定できるメカニズムを提供します。これにより、Goのビルドシステムが、ホストとターゲットで異なるC/C++コンパイラを適切に使い分けられるようになり、Cgoを有効にした状態でのクロスコンパイルがより堅牢になります。また、クロスコンパイル時にCGO_ENABLED
をデフォルトで無効にすることで、意図しないCgoの利用を防ぎ、ビルドの失敗や予期せぬ挙動を避けるための安全策を講じています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドに関する概念と環境変数について理解しておく必要があります。
- Cgo: Go言語の機能の一つで、GoのコードからC言語の関数を呼び出したり、C言語のコードをGoのプログラムに組み込んだりすることを可能にします。Cgoを使用すると、Goのプログラムは外部のC/C++ライブラリに依存するようになり、その結果、ビルドプロセスにC/C++コンパイラとリンカが必要になります。
- クロスコンパイル (Cross-compilation): あるプラットフォーム(ホスト)で、別のプラットフォーム(ターゲット)向けの実行可能ファイルをビルドするプロセスです。例えば、macOS上でLinux向けのGoバイナリをビルドする場合などがこれに当たります。
GOARCH
: Goのプログラムが実行されるターゲットのCPUアーキテクチャ(例:amd64
,arm
,arm64
)を指定する環境変数です。GOHOSTARCH
: ビルドを実行しているホストのCPUアーキテクチャを指定する環境変数です。通常はGOARCH
と同じですが、クロスコンパイル時には異なります。GOOS
: Goのプログラムが実行されるターゲットのオペレーティングシステム(例:linux
,windows
,darwin
)を指定する環境変数です。GOHOSTOS
: ビルドを実行しているホストのオペレーティングシステムを指定する環境変数です。通常はGOOS
と同じですが、クロスコンパイル時には異なります。CC
: C言語のコンパイラを指定する環境変数です。通常、ホストシステム上でCコードをコンパイルする際に使用されます。CXX
: C++言語のコンパイラを指定する環境変数です。通常、ホストシステム上でC++コードをコンパイルする際に使用されます。CGO_ENABLED
: Cgoを有効にするかどうかを制御する環境変数です。1
に設定するとCgoが有効になり、0
に設定すると無効になります。ldflags
: Goのリンカに渡されるフラグです。外部リンカ (extld
) の指定など、リンク時の挙動を制御するために使用されます。extld
(External Linker): Goのビルドプロセスが、GoのコードとCgoでコンパイルされたC/C++コードを最終的にリンクするために使用する外部リンカ(通常はC/C++コンパイラ自身)を指します。
技術的詳細
このコミットの技術的な核心は、Goのビルドツールが、ホストとターゲットのC/C++コンパイラを適切に区別し、使用できるようにすることです。
-
CC_FOR_TARGET
とCXX_FOR_TARGET
の導入:src/cmd/dist/a.h
とsrc/cmd/dist/build.c
で、新しいグローバル変数defaultcctarget
とdefaultcxxtarget
が導入されました。これらは、それぞれCC_FOR_TARGET
とCXX_FOR_TARGET
環境変数の値を保持します。src/cmd/dist/build.c
のinit()
関数内で、これらの新しい環境変数が読み込まれます。もしCC_FOR_TARGET
が設定されていなければCC
の値が、CXX_FOR_TARGET
が設定されていなければCXX
の値(またはデフォルトのg++
/clang++
)がフォールバックとして使用されます。- これにより、Goのビルドシステムは、ホスト向けのコンパイルには
CC
/CXX
を、ターゲット向けのコンパイルにはCC_FOR_TARGET
/CXX_FOR_TARGET
を使い分けることが可能になります。
-
cmd/go/build.go
におけるリンカの選択ロジックの変更:gcToolchain.ld
関数(Goのリンカを呼び出す部分)において、外部リンカ (-extld
) の選択ロジックが変更されました。- 以前は、C++コードが含まれる場合にのみ
CXX
環境変数(またはg++
)を外部リンカとして使用していました。 - 変更後、
cxx
(C++コードが含まれるか)の有無にかかわらず、ccompilerPath
ヘルパー関数を使用して適切なコンパイラパスを取得するようになりました。 ccompilerPath
関数は、CXX
とdefaultCXX
(C++の場合)、またはCC
とdefaultCC
(Cの場合)を引数に取り、CC_FOR_TARGET
やCXX_FOR_TARGET
の値を考慮して、ターゲット向けの適切なコンパイラを決定します。これにより、クロスコンパイル時にターゲットアーキテクチャ用のC/C++コンパイラが正しく選択されるようになります。
-
CGO_ENABLED
のクロスコンパイル時のデフォルト無効化:src/pkg/go/build/build.go
のdefaultContext()
関数内で、CGO_ENABLED
の初期化ロジックが変更されました。- 以前は、
CGO_ENABLED
が明示的に設定されていない場合、クロスコンパイル時でもcgoEnabled
マップに基づいてCgoが有効になる可能性がありました。 - 変更後、
runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS
(つまり、ホストとターゲットが同じ場合)でない限り、CGO_ENABLED
はデフォルトで無効 (false
) に設定されるようになりました。これにより、クロスコンパイル時にCgoを有効にするには、ユーザーが明示的にCGO_ENABLED=1
を設定する必要があるという、より安全な挙動が強制されます。
-
src/make.bash
の更新:- Goのビルドスクリプトである
make.bash
が更新され、新しい環境変数CC_FOR_TARGET
とCXX_FOR_TARGET
に関する説明が追加されました。 - また、ホスト向けのビルドとターゲット向けのビルドで、
CC
とCC_FOR_TARGET
を適切に使い分けるように変更されました。特に、ターゲット向けのビルドではCC=$CC_FOR_TARGET
が設定されるようになりました。
- Goのビルドスクリプトである
これらの変更により、Goのビルドシステムは、Cgoを使用するクロスコンパイルのシナリオにおいて、より予測可能で堅牢な動作をするようになりました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと、その変更の概要は以下の通りです。
src/cmd/dist/a.h
:defaultcxxtarget
とdefaultcctarget
という新しい外部変数が宣言されました。これらは、ターゲット向けのC/C++コンパイラを指します。
src/cmd/dist/build.c
:defaultcxxtarget
とdefaultcctarget
の定義が追加され、CC_FOR_TARGET
とCXX_FOR_TARGET
環境変数を読み込むロジックがinit()
関数に追加されました。cmdenv()
関数で、CC_FOR_TARGET
が環境変数として出力されるようになりました。
src/cmd/dist/buildgo.c
:mkzdefaultcc
関数内で、defaultcc
とdefaultcxx
の代わりにdefaultcctarget
とdefaultcxxtarget
が使用されるようになりました。これは、Goのビルドツールが生成する内部的なコンパイラ設定に影響します。
src/cmd/go/build.go
:gcToolchain.ld
関数内の外部リンカ (-extld
) の選択ロジックが大幅に変更されました。C++コードの有無にかかわらず、ccompilerPath
ヘルパー関数を使用して適切なコンパイラパスを取得するようになりました。ccompilerPath
という新しいヘルパー関数が追加されました。この関数は、指定された環境変数とデフォルトコマンドに基づいて、コンパイラのパスを決定します。
src/make.bash
:CC_FOR_TARGET
とCXX_FOR_TARGET
に関する説明がコメントとして追加されました。- ホスト向けのビルドとターゲット向けのビルドで、
CC
とCC_FOR_TARGET
を適切に設定するロジックが変更されました。
src/pkg/go/build/build.go
:defaultContext()
関数内で、クロスコンパイル時にCGO_ENABLED
がデフォルトで無効になるようにロジックが変更されました。
コアとなるコードの解説
src/cmd/dist/build.c
の変更点
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -166,14 +167,23 @@ init(void)
}
defaultcc = btake(&b);
- xgetenv(&b, "CXX");
+ xgetenv(&b, "CC_FOR_TARGET");
if(b.len == 0) {
- if(defaultclang)
- bprintf(&b, "clang++");
- else
- bprintf(&b, "g++");
+ bprintf(&b, defaultcc);
}
- defaultcxx = btake(&b);
+ defaultcctarget = btake(&b);
+
+ xgetenv(&b, "CXX_FOR_TARGET");
+ if(b.len == 0) {
+ xgetenv(&b, "CXX");
+ if(b.len == 0) {
+ if(defaultclang)
+ bprintf(&b, "clang++");
+ else
+ bprintf(&b, "g++");
+ }
+ }
+ defaultcxxtarget = btake(&b);
xsetenv("GOROOT", goroot);
xsetenv("GOARCH", goarch);
@@ -1537,6 +1547,7 @@ cmdenv(int argc, char **argv)\n usage();\n
xprintf(format, "CC", defaultcc);\n
+ xprintf(format, "CC_FOR_TARGET", defaultcctarget);\n
xprintf(format, "GOROOT", goroot);\n
xprintf(format, "GOBIN", gobin);\n
xprintf(format, "GOARCH", goarch);\n
このスニペットは、src/cmd/dist/build.c
のinit()
関数における変更を示しています。
- 以前は
CXX
環境変数のみをチェックしてdefaultcxx
を設定していましたが、変更後はCC_FOR_TARGET
とCXX_FOR_TARGET
を優先的にチェックするようになりました。 CC_FOR_TARGET
が設定されていない場合、defaultcc
(ホスト向けのCコンパイラ)がdefaultcctarget
のデフォルト値として使用されます。これは、ターゲット向けのCコンパイラが明示的に指定されていない場合、ホストのCコンパイラがフォールバックとして使われることを意味します。CXX_FOR_TARGET
が設定されていない場合、まずCXX
がチェックされ、それも設定されていなければclang++
またはg++
がデフォルトとして使用されます。cmdenv()
関数では、CC_FOR_TARGET
も環境変数として出力されるようになり、Goのビルドプロセス全体でこの新しい変数が利用可能になります。
src/cmd/go/build.go
の変更点
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1708,42 +1708,42 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,\n if buildContext.InstallSuffix != "" {\n ldflags = append(ldflags, "-installsuffix", buildContext.InstallSuffix)\n }\n- if cxx {\n- // The program includes C++ code. If the user has not\n- // specified the -extld option, then default to\n- // linking with the compiler named by the CXX\n- // environment variable, or g++ if CXX is not set.\n- extld := false\n- for _, f := range ldflags {\n- if f == "-extld" || strings.HasPrefix(f, "-extld=") {\n- extld = true\n- break\n- }\n- }\n- if !extld {\n- compiler := strings.Fields(os.Getenv("CXX"))\n- if len(compiler) == 0 {\n- compiler = []string{"g++"}\n- }\n- ldflags = append(ldflags, "-extld="+compiler[0])\n- if len(compiler) > 1 {\n- extldflags := false\n- add := strings.Join(compiler[1:], " ")\n- for i, f := range ldflags {\n- if f == "-extldflags" && i+1 < len(ldflags) {\n- ldflags[i+1] = add + " " + ldflags[i+1]\n- extldflags = true\n- break\n- } else if strings.HasPrefix(f, "-extldflags=") {\n- ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]\n- extldflags = true\n- break\n- }\n- }\n- if !extldflags {\n- ldflags = append(ldflags, "-extldflags="+add)\n- }\n- }\n- }\n- }\n+ // If the user has not specified the -extld option, then specify the\n+ // appropriate linker. In case of C++ code, use the compiler named\n+ // by the CXX environment variable or defaultCXX if CXX is not set.\n+ // Else, use the CC environment variable and defaultCC as fallback.\n+ extld := false\n+ for _, f := range ldflags {\n+ if f == "-extld" || strings.HasPrefix(f, "-extld=") {\n+ extld = true\n+ break\n+ }\n+ }\n+ if !extld {\n+ var compiler []string\n+ if cxx {\n+ compiler = ccompilerPath("CXX", defaultCXX)\n+ } else {\n+ compiler = ccompilerPath("CC", defaultCC)\n+ }\n+ ldflags = append(ldflags, "-extld="+compiler[0])\n+ if len(compiler) > 1 {\n+ extldflags := false\n+ add := strings.Join(compiler[1:], " ")\n+ for i, f := range ldflags {\n+ if f == "-extldflags" && i+1 < len(ldflags) {\n+ ldflags[i+1] = add + " " + ldflags[i+1]\n+ extldflags = true\n+ break\n+ } else if strings.HasPrefix(f, "-extldflags=") {\n+ ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]\n+ extldflags = true\n+ break\n+ }\n+ }\n+ if !extldflags {\n+ ldflags = append(ldflags, "-extldflags="+add)\n+ }\n+ }\n+ }\n return b.run(".", p.ImportPath, nil, tool(archChar+"l"), "-o", out, importArgs, swigArg, ldflags, mainpkg)\n@@ -2039,6 +2036,16 @@ func envList(key string) []string {\n return strings.Fields(os.Getenv(key))\n }\n \n+// ccompilerCmd returns the compilerpath for the given environment\n+// variable and using the default command when the variable is empty.\n+func ccompilerPath(envvar, defcmd string) []string {\n+ compiler := envList(envvar)\n+ if len(compiler) == 0 {\n+ compiler = strings.Fields(defcmd)\n+ }\n+ return compiler\n+}\n+\n var cgoRe = regexp.MustCompile(`[/\\\\:]`)\n \n var (\n```
このスニペットは、`src/cmd/go/build.go`の`gcToolchain.ld`関数における外部リンカの選択ロジックの変更と、新しく追加された`ccompilerPath`ヘルパー関数を示しています。
* 以前はC++コードが含まれる場合にのみ`CXX`を考慮していましたが、変更後はCまたはC++のどちらのコードが含まれるかに応じて、`ccompilerPath`関数を使って適切なコンパイラ(`CC_FOR_TARGET`または`CXX_FOR_TARGET`を考慮)を選択するようになりました。
* `ccompilerPath`関数は、指定された環境変数(例: "CC_FOR_TARGET")からコンパイラパスを取得し、もし環境変数が空であればデフォルトのコマンド(例: `defaultcctarget`)を使用します。これにより、クロスコンパイル時にターゲットアーキテクチャ向けのC/C++コンパイラが正しくリンカとして使用されるようになります。
### `src/pkg/go/build/build.go` の変更点
```diff
--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -303,8 +303,7 @@ func defaultContext() Context {\n case "0":\n c.CgoEnabled = false\n default:\n- // golang.org/issue/5141\n- // cgo should be disabled for cross compilation builds\n+ // cgo must be explicitly enabled for cross compilation builds\n if runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS {\n c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH]\n break\n```
このスニペットは、`src/pkg/go/build/build.go`の`defaultContext()`関数における`CgoEnabled`のデフォルト挙動の変更を示しています。
* 以前のコメントは「cgoはクロスコンパイルビルドでは無効にすべき」と示唆していましたが、コードは必ずしもそれを強制していませんでした。
* 変更後、`runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS`(つまり、ホストとターゲットが同じ場合)でない限り、`CgoEnabled`はデフォルトで`false`になります。これにより、クロスコンパイル時にCgoを有効にするには、ユーザーが明示的に`CGO_ENABLED=1`を設定する必要があるという、より厳格なポリシーが適用されます。
## 関連リンク
* Go CL 57100043: [https://golang.org/cl/57100043](https://golang.org/cl/57100043)
* Go Issue 4714: [https://golang.org/issue/4714](https://golang.org/issue/4714)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (Cgo, Cross-compilationに関する情報)
* Go言語のソースコード (上記コミットの変更点)
* Go言語のIssueトラッカー (Issue 4714)
* 一般的なクロスコンパイルとC/C++コンパイラの概念に関する知識