[インデックス 18838] ファイルの概要
このコミットは、Go言語のビルドツール (cmd/go
) において、CGO_CFLAGS および CGO_CXXFLAGS といったシステム環境変数を適切に尊重するように変更を加えるものです。これにより、Cgo を利用するGoプログラムのビルド時に、システムレベルで設定されたC/C++コンパイラのフラグが正しく適用されるようになります。
コミット
commit b87c7729ba41972ec06387f3461b604f35484a79
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Wed Mar 12 07:16:22 2014 +0100
cmd/go: respect system CGO_CFLAGS and CGO_CXXFLAGS
Fixes #6882
LGTM=iant
R=rsc, iant
CC=golang-codereviews
https://golang.org/cl/72080043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b87c7729ba41972ec06387f3461b604f35484a79
元コミット内容
cmd/go: respect system CGO_CFLAGS and CGO_CXXFLAGS
このコミットは、GoのビルドシステムがCgoを使用する際に、システム環境変数 CGO_CFLAGS
および CGO_CXXFLAGS
を尊重するように修正します。これにより、Cgoを介してC/C++コードをコンパイルする際に、ユーザーが環境変数で指定したコンパイラフラグが適用されるようになります。
変更の背景
Go言語は、Cgoというメカニズムを通じてC言語のコードと連携することができます。これにより、既存のCライブラリをGoプログラムから呼び出したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。Cgoを使用する際、Goのビルドツールは内部的にC/C++コンパイラ(通常はGCCやClang)を呼び出してC/C++コードをコンパイルします。
しかし、このコミット以前は、GoのビルドツールがC/C++コンパイラを呼び出す際に、システム環境変数として設定されている CGO_CFLAGS
や CGO_CXXFLAGS
を適切に利用していませんでした。これらの環境変数は、C/C++コンパイラに渡す追加のコンパイルフラグ(例: インクルードパス、最適化レベル、警告設定など)を指定するために一般的に使用されます。
この問題は、GoのIssue #6882として報告されていました(ただし、現在のGoのIssueトラッカーではこの番号のIssueは見つかりませんでした。これは古いIssueであるか、別のトラッカーで管理されていた可能性があります)。このコミットは、この問題を解決し、Cgoビルドプロセスにおいてシステム環境変数のコンパイラフラグが正しく適用されるようにすることで、より柔軟なビルド設定と、既存のC/C++プロジェクトとの統合を容易にすることを目的としています。
前提知識の解説
- Cgo: Go言語がC言語のコードと相互運用するためのメカニズムです。Goのソースコード内に
import "C"
を記述することで、Cの関数を呼び出したり、Cのデータ構造を利用したりできます。Cgoを使用すると、Goのビルドプロセス中にC/C++コンパイラが呼び出され、C/C++コードがコンパイルされてGoの実行可能ファイルにリンクされます。 - コンパイラフラグ: C/C++コンパイラ(例: GCC, Clang)に渡されるコマンドライン引数で、コンパイルの挙動を制御します。例えば、
-I
はインクルードファイルの検索パスを指定し、-D
はマクロを定義し、-O2
は最適化レベルを指定します。 CGO_CFLAGS
: CgoがCコードをコンパイルする際に使用する追加のCコンパイラフラグを指定するための環境変数です。CGO_CXXFLAGS
: CgoがC++コードをコンパイルする際に使用する追加のC++コンパイラフラグを指定するための環境変数です。CGO_LDFLAGS
: CgoがC/C++コードをリンクする際に使用する追加のリンカフラグを指定するための環境変数です。cmd/go
: Go言語の公式ビルドツールであり、go build
,go run
,go test
などのコマンドを提供します。このツールがCgoのビルドプロセスを管理しています。os.Getenv(key)
: Goの標準ライブラリos
パッケージの関数で、指定された環境変数の値を取得します。strings.Fields(s)
: Goの標準ライブラリstrings
パッケージの関数で、文字列を空白文字で分割し、文字列のスライスを返します。
技術的詳細
このコミットの主要な変更点は、src/cmd/go/build.go
ファイル内のC/C++コンパイラおよびリンカの呼び出しロジックにあります。具体的には、以下の点が変更されています。
-
envList
関数の変更:- 変更前:
func envList(key string) []string { return strings.Fields(os.Getenv(key)) }
- この関数は、指定された環境変数の値を読み取り、空白で分割して文字列のスライスを返していました。しかし、環境変数が空の場合にデフォルト値を適用するロジックがありませんでした。
- 変更後:
func envList(key, def string) []string { v := os.Getenv(key); if v == "" { v = def }; return strings.Fields(v) }
def
引数が追加され、環境変数が空の場合にそのデフォルト値を使用するようになりました。これにより、環境変数が設定されていない場合でも適切なデフォルトフラグが適用されるようになります。
- 変更前:
-
ccompilerPath
関数の削除とcflags
関数の導入:- 変更前は
ccompilerPath
関数がC/C++コンパイラのパスとデフォルトフラグを処理していましたが、このコミットで削除されました。 - 新たに
cflags
関数が導入されました。func (b *builder) cflags(p *Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { var defaults string if def { defaults = "-g -O2" // デフォルトの最適化・デバッグフラグ } cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) return }
- この
cflags
関数は、CGO_CPPFLAGS
,CGO_CFLAGS
,CGO_CXXFLAGS
,CGO_LDFLAGS
の各環境変数を読み取り、それらにパッケージ固有のCgoフラグ (p.CgoCPPFLAGS
など) を結合して返します。 def
引数によって、デフォルトの-g -O2
フラグを含めるかどうかが制御されます。これにより、Cgoツール自体がコンパイラを呼び出す際にはデフォルトフラグを含め、SWIGなど他のツールがコンパイラを呼び出す際には含めないといった柔軟な制御が可能になります。
- この
- 変更前は
-
CgoおよびSWIGビルドロジックの更新:
builder.cgo
関数内で、以前は個別にos.Getenv
を呼び出してフラグを取得していた箇所が、新しく導入されたb.cflags(p, true)
の呼び出しに置き換えられました。これにより、システム環境変数とパッケージ固有のフラグの結合が統一的に処理されるようになります。- SWIG (Simplified Wrapper and Interface Generator) を使用するビルドパス (
builder.swig
,builder.swigOne
) においても、C/C++コンパイラの呼び出し時にb.cflags
から取得したフラグが渡されるように修正されました。特に、swigOne
関数では、C++コンパイル時に-fPIC
(Position-Independent Code) フラグが追加されるロジックも含まれています。
これらの変更により、Goのビルドツールは、Cgoを介してC/C++コードをコンパイルする際に、ユーザーが設定した CGO_CFLAGS
や CGO_CXXFLAGS
環境変数を適切に解釈し、C/C++コンパイラに渡すことができるようになりました。
コアとなるコードの変更箇所
src/cmd/go/build.go
ファイルが変更されています。
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1748,9 +1748,9 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,\
if !extld {
var compiler []string
if cxx {
- compiler = ccompilerPath("CXX", defaultCXX)
+ compiler = envList("CXX", defaultCXX)
} else {
- compiler = ccompilerPath("CC", defaultCC)
+ compiler = envList("CC", defaultCC)
}
ldflags = append(ldflags, "-extld="+compiler[0])
if len(compiler) > 1 {
@@ -2011,8 +2011,8 @@ func (b *builder) ccompilerCmd(envvar, defcmd, objdir string) []string {\
// NOTE: env.go's mkEnv knows that the first three
// strings returned are "gcc", "-I", objdir (and cuts them off).
- compiler := ccompilerPath(envvar, defcmd)
- a := []string{compiler[0], "-I", objdir, "-g", "-O2"}
+ compiler := envList(envvar, defcmd)
+ a := []string{compiler[0], "-I", objdir}
a = append(a, compiler[1:]...)
// Definitely want -fPIC but on Windows gcc complains
@@ -2065,18 +2065,28 @@ func (b *builder) gccArchArgs() []string {\
return nil
}
-func envList(key string) []string {
- return strings.Fields(os.Getenv(key))
+// envList returns the value of the given environment variable broken
+// into fields, using the default value when the variable is empty.
+func envList(key, def string) []string {
+ v := os.Getenv(key)
+ if v == "" {
+ v = def
+ }
+ return strings.Fields(v)
}
-// ccompilerCmd returns the compilerpath for the given environment
-// variable and using the default command when the variable is empty.
-func ccompilerPath(envvar, defcmd string) []string {
- compiler := envList(envvar)
- if len(compiler) == 0 {
- compiler = strings.Fields(defcmd)
- }
- return compiler
+// Return the flags to use when invoking the C or C++ compilers, or cgo.
+func (b *builder) cflags(p *Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
+ var defaults string
+ if def {
+ defaults = "-g -O2"
+ }
+
+ cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
+ cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
+ cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
+ ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
+ return
}
var cgoRe = regexp.MustCompile(`[/\\\\:]`)\
@@ -2088,10 +2098,8 @@ var (\
)
func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles, gxxfiles, mfiles []string) (outGo, outObj []string, err error) {\
-\tcgoCPPFLAGS := stringList(envList("CGO_CPPFLAGS"), p.CgoCPPFLAGS)\
-\tcgoCFLAGS := stringList(envList("CGO_CFLAGS"), p.CgoCFLAGS)\
-\tcgoCXXFLAGS := stringList(envList("CGO_CXXFLAGS"), p.CgoCXXFLAGS)\
-\tcgoLDFLAGS := stringList(envList("CGO_LDFLAGS"), p.CgoLDFLAGS)\
+\tcgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := b.cflags(p, true)
+\t_, cgoexeCFLAGS, _, _ := b.cflags(p, false)
// If we are compiling Objective-C code, then we need to link against libobjc
if len(mfiles) > 0 {
@@ -2162,7 +2170,7 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles, gxxfiles, mfiles\
objExt = "o"
}
-\tif err := b.run(p.Dir, p.ImportPath, cgoenv, cgoExe, "-objdir", obj, cgoflags, "--", cgoCPPFLAGS, cgoCFLAGS, p.CgoFiles); err != nil {\
+\tif err := b.run(p.Dir, p.ImportPath, cgoenv, cgoExe, "-objdir", obj, cgoflags, "--", cgoCPPFLAGS, cgoexeCFLAGS, p.CgoFiles); err != nil {\
return nil, nil, err
}
outGo = append(outGo, gofiles...)\
@@ -2317,11 +2325,14 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles, gxxfiles, mfiles\
// TODO: Don't build a shared library, once SWIG emits the necessary
// pragmas for external linking.\
func (b *builder) swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []string) (outGo, outObj []string, err error) {\
+\tcgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, _ := b.cflags(p, true)
+\tcflags := stringList(cgoCPPFLAGS, cgoCFLAGS)
+\tcxxflags := stringList(cgoCPPFLAGS, cgoCXXFLAGS)
var extraObj []string
for _, file := range gccfiles {\
ofile := obj + cgoRe.ReplaceAllString(file[:len(file)-1], "_") + "o"\
-\t\tif err := b.gcc(p, ofile, nil, file); err != nil {\
+\t\tif err := b.gcc(p, ofile, cflags, file); err != nil {\
return nil, nil, err
}
extraObj = append(extraObj, ofile)\
@@ -2330,7 +2341,7 @@ func (b *builder) swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []stri\
for _, file := range gxxfiles {\
// Append .o to the file, just in case the pkg has file.c and file.cpp\
ofile := obj + cgoRe.ReplaceAllString(file, "_") + ".o"\
-\t\tif err := b.gxx(p, ofile, nil, file); err != nil {\
+\t\tif err := b.gxx(p, ofile, cxxflags, file); err != nil {\
return nil, nil, err
}
extraObj = append(extraObj, ofile)\
@@ -2339,7 +2350,7 @@ func (b *builder) swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []stri\
for _, file := range mfiles {\
// Append .o to the file, just in case the pkg has file.c and file.cpp\
ofile := obj + cgoRe.ReplaceAllString(file, "_") + ".o"\
-\t\tif err := b.gcc(p, ofile, nil, file); err != nil {\
+\t\tif err := b.gcc(p, ofile, cflags, file); err != nil {\
return nil, nil, err
}
extraObj = append(extraObj, ofile)\
@@ -2405,6 +2416,14 @@ func (b *builder) swigIntSize(obj string) (intsize string, err error) {\
// Run SWIG on one SWIG input file.\
func (b *builder) swigOne(p *Package, file, obj string, cxx bool, intgosize string, extraObj []string) (outGo, outObj string, err error) {\
+\tcgoCPPFLAGS, cgoCFLAGS, cgoCXXFLAGS, cgoLDFLAGS := b.cflags(p, true)
+\tvar cflags []string
+\tif cxx {\
+\t\tcflags = stringList(cgoCPPFLAGS, cgoCXXFLAGS, "-fPIC")
+\t} else {\
+\t\tcflags = stringList(cgoCPPFLAGS, cgoCFLAGS, "-fPIC")
+\t}\
+\
\tn := 5 // length of ".swig"\
\tif cxx {\
\t\tn = 8 // length of ".swigcxx"\
@@ -2459,8 +2478,14 @@ func (b *builder) swigOne(p *Package, file, obj string, cxx bool, intgosize stri\
// gcc\
\tgccObj := obj + gccBase + "o"\
-\tif err := b.gcc(p, gccObj, []string{"-g", "-fPIC", "-O2"}, obj+gccBase+gccExt); err != nil {\
-\t\treturn "", "", err\
+\tif !cxx {\
+\t\tif err := b.gcc(p, gccObj, cflags, obj+gccBase+gccExt); err != nil {\
+\t\t\treturn "", "", err\
+\t\t}\
+\t} else {\
+\t\tif err := b.gxx(p, gccObj, cflags, obj+gccBase+gccExt); err != nil {\
+\t\t\treturn "", "", err\
+\t\t}\
\t}\
// create shared library\
@@ -2474,7 +2499,7 @@ func (b *builder) swigOne(p *Package, file, obj string, cxx bool, intgosize stri\
\tif cxx {\
\t\tcxxlib = []string{"-lstdc++"}\
\t}\
-\tldflags := stringList(osldflags[goos], cxxlib)\
+\tldflags := stringList(osldflags[goos], cflags, cgoLDFLAGS, cxxlib)\
\ttarget := filepath.Join(obj, soname)\
\tb.run(p.Dir, p.ImportPath, nil, b.gccCmd(p.Dir), "-o", target, gccObj, extraObj, ldflags)\
コアとなるコードの解説
envList
関数の変更:- この変更は、環境変数が設定されていない場合にデフォルト値を適用する汎用的なロジックを導入します。これにより、
CGO_CFLAGS
やCGO_CXXFLAGS
が設定されていない場合でも、Goのビルドツールが適切なデフォルトフラグ(例:-g -O2
)を使用してC/C++コードをコンパイルできるようになります。
- この変更は、環境変数が設定されていない場合にデフォルト値を適用する汎用的なロジックを導入します。これにより、
ccompilerPath
の削除とcflags
の導入:ccompilerPath
は、コンパイラのパスとデフォルトフラグを一緒に処理する役割を持っていましたが、cflags
関数はCgo関連のすべてのコンパイラフラグ(CGO_CPPFLAGS, CGO_CFLAGS, CGO_CXXFLAGS, CGO_LDFLAGS)を統一的に処理する責任を負います。cflags
関数は、環境変数から取得したフラグと、Goパッケージの定義 (p.CgoCPPFLAGS
など) から取得したフラグをstringList
関数で結合します。これにより、ユーザーが環境変数で指定したフラグと、Goパッケージ自体が要求するフラグの両方がC/C++コンパイラに渡されるようになります。def
引数により、-g -O2
のようなデフォルトの最適化・デバッグフラグを含めるかどうかを制御できるようになり、Cgoツール内部でのコンパイラ呼び出しと、SWIGのような外部ツールからの呼び出しで異なるフラグセットを適用する柔軟性が向上しました。
builder.cgo
およびbuilder.swig
内のフラグ取得ロジックの統一:- 以前は
builder.cgo
内でCGO_CPPFLAGS
などを個別にenvList
で取得していましたが、cflags
関数を呼び出すことで、これらのフラグの取得と結合がより簡潔かつ統一的に行われるようになりました。 - SWIG関連の関数 (
builder.swig
,builder.swigOne
) でも同様にcflags
関数が利用されるようになり、SWIGが生成するC/C++コードのコンパイル時にもシステム環境変数のフラグが適用されるようになりました。特にswigOne
では、C++コンパイル時に-fPIC
が追加されることで、共有ライブラリのビルドにおける互換性が向上します。
- 以前は
- リンカフラグの変更:
swigOne
関数内のリンカ呼び出しにおいて、ldflags
の生成にcflags
とcgoLDFLAGS
が追加されました。これにより、SWIGで生成された共有ライブラリをリンクする際にも、Cgoのリンカフラグが適切に適用されるようになります。
これらの変更は、GoのビルドシステムがCgoを介してC/C++コードをコンパイルする際の柔軟性と互換性を大幅に向上させます。開発者は、システム環境変数を通じてC/C++コンパイラの挙動をより細かく制御できるようになり、特定のビルド環境やライブラリの要件に合わせてGoプログラムをビルドすることが容易になります。
関連リンク
- Go言語のCgoに関する公式ドキュメント: https://pkg.go.dev/cmd/cgo
- Go言語のビルドコマンドに関する公式ドキュメント: https://pkg.go.dev/cmd/go
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/72080043 (コミットメッセージに記載されているリンク)
- Go言語のIssueトラッカー (過去のIssueは現在のトラッカーでは見つからない場合があります): https://github.com/golang/go/issues