[インデックス 16160] ファイルの概要
このコミットは、Go言語のcgo
ツールとgo
コマンドにおける#cgo
ディレクティブの処理方法に関する重要な変更を導入しています。具体的には、#cgo
ディレクティブの解析とフラグの処理の責任をcmd/cgo
からcmd/go
へ移行することで、ビルドシステムの柔軟性と堅牢性を向上させています。
コミット
commit d06be395cc8094b3cb132be238d745b668dbfa04
Author: Andrew Wilkins <axwalk@gmail.com>
Date: Wed Apr 10 21:41:54 2013 -0700
cmd/cgo, cmd/go: remove #cgo directive parsing from cmd/cgo
This change removes processing of #cgo directives from cmd/cgo,
pushing the onus back on cmd/go to pass all necessary flags.
Fixes #5224. See comments for rationale.
R=golang-dev, iant, r
CC=golang-dev
https://golang.org/cl/8610044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d06be395cc8094b3cb132be238d745b668dbfa04
元コミット内容
cmd/cgo, cmd/go: remove #cgo directive parsing from cmd/cgo
この変更は、#cgo
ディレクティブの処理をcmd/cgo
から削除し、必要なすべてのフラグを渡す責任をcmd/go
に戻します。
Issue #5224 を修正します。理由についてはコメントを参照してください。
変更の背景
Go言語のcgo
は、GoのコードからC言語のコードを呼び出すためのメカニズムを提供します。#cgo
ディレクティブは、Cコンパイラやリンカに渡すフラグ(例: CFLAGS
, LDFLAGS
)を指定するために使用されます。
このコミット以前は、cmd/cgo
ツールがGoソースファイル内の#cgo
ディレクティブを解析し、それらのフラグをCコンパイラ(通常はGCC)に渡していました。しかし、このアプローチにはいくつかの問題がありました。
- 責任の分散:
cgo
ディレクティブの解析とフラグの管理がcmd/cgo
とcmd/go
の間で分散しており、どちらが最終的なフラグを決定するのかが不明確になる可能性がありました。 - 複雑性:
cmd/cgo
がpkg-config
のような外部ツールを呼び出してフラグを解決するロジックを持っており、これがcmd/cgo
のコードベースを複雑にしていました。 - ビルドプロセスの制御:
cmd/go
(Goのビルドコマンド)がビルドプロセス全体を統括する立場にあるにもかかわらず、cgo
関連のフラグの最終的な制御がcmd/cgo
に委ねられていることで、ビルドの柔軟性やデバッグの難易度が増していました。
これらの問題を解決し、ビルドプロセスの制御を一元化するために、#cgo
ディレクティブの解析とフラグの処理の責任をcmd/cgo
からcmd/go
へ完全に移行するという決定がなされました。これにより、cmd/go
がすべてのビルドフラグを把握し、必要に応じてcmd/cgo
に渡すという、よりクリーンで管理しやすいアーキテクチャが実現されます。
前提知識の解説
- cgo: Go言語の機能の一つで、GoのコードからC言語の関数を呼び出したり、C言語のコードからGoの関数を呼び出したりすることを可能にします。Goのソースファイル内で
import "C"
と記述することで有効になります。 #cgo
ディレクティブ:cgo
を使用するGoソースファイルのimport "C"
ブロックの直前にあるCコメント内に記述される特殊なディレクティブです。Cコンパイラやリンカに渡すオプション(例:CFLAGS
,LDFLAGS
)を指定するために使われます。#cgo CFLAGS: -I/path/to/include
: Cコンパイラに渡す追加のコンパイルフラグを指定します。#cgo LDFLAGS: -L/path/to/lib -lfoo
: リンカに渡す追加のリンカフラグを指定します。#cgo pkg-config: foo bar
:pkg-config
ツールを使用して、foo
とbar
ライブラリのCフラグとLフラグを自動的に取得します。
cmd/cgo
: Goのツールチェーンの一部で、cgo
を使用するGoソースファイルをC言語のソースファイルとGo言語のソースファイルに変換する役割を担います。このツールは、Goのビルドプロセス中にcmd/go
によって呼び出されます。cmd/go
: Go言語のビルドコマンド(go build
,go install
など)を実行する主要なツールです。ソースファイルのコンパイル、リンク、パッケージ管理など、Goプロジェクトのビルドプロセス全体を管理します。- 環境変数 (Environment Variables): プロセスに渡されるキーと値のペアのリストです。プログラムの動作に影響を与える設定やパスなどを指定するために使用されます。このコミットでは、
CGO_CFLAGS
やCGO_LDFLAGS
といった環境変数が重要な役割を果たします。 pkg-config
: ライブラリのコンパイル時およびリンク時に必要なフラグ(インクルードパス、ライブラリパス、ライブラリ名など)を自動的に取得するためのコマンドラインツールです。
技術的詳細
このコミットの核心は、#cgo
ディレクティブの処理ロジックをcmd/cgo
からcmd/go
へ移動することです。
cmd/cgo
側の変更点:
src/cmd/cgo/gcc.go
:Package.ParseFlags
関数が削除されました。この関数は、Goソースファイルのプリアンブルから#cgo CFLAGS
やLDFLAGS
などのディレクティブを解析し、Package
構造体のCgoFlags
に格納する役割を担っていました。pkgConfig
関数も削除されました。この関数はpkg-config
コマンドを実行し、その出力を解析してCフラグとLフラグを取得していました。- 代わりに、
File.DiscardCgoDirectives
関数が導入されました。この関数は、import "C"
プリアンブル内の#cgo
ディレクティブを単に破棄し、_cgo_export.h
などの生成ファイルにそれらが含まれないようにします。これは、cmd/cgo
がもはやこれらのディレクティブを解釈する必要がないためです。
src/cmd/cgo/main.go
:main
関数内で、各Goソースファイルを処理する際にp.ParseFlags(f, input)
の呼び出しがf.DiscardCgoDirectives()
に置き換えられました。newPackage
関数から、gccOptions
をコピーするロジックが削除されました。代わりに、p.addToFlag("CFLAGS", args)
が直接呼び出され、cmd/go
から渡された初期のCフラグがPackage
構造体に追加されるようになりました。
cmd/go
側の変更点:
src/cmd/go/build.go
:builder.run
およびbuilder.runOut
関数が変更され、新たにenv []string
引数を受け取るようになりました。これにより、実行するコマンドに特定の環境変数を渡すことができるようになります。builder.cgo
関数が大幅に変更されました。pkg-config
の呼び出しロジックがcmd/go
に移されました。pkg-config --cflags
とpkg-config --libs
を実行し、その結果をcgoCFLAGS
とcgoLDFLAGS
に格納します。CGO_LDFLAGS
環境変数を構築するロジックが追加されました。p.CgoLDFLAGS
(#cgo LDFLAGS
やpkg-config
から収集されたもの)を引用符で囲み、CGO_LDFLAGS
環境変数として設定します。cgoExe
(cmd/cgo
の実行ファイル)を呼び出す際に、この新しく構築されたcgoenv
(CGO_LDFLAGS
を含む)がb.run
関数に渡されるようになりました。これにより、cmd/cgo
は環境変数を通じて必要なリンカフラグを受け取ります。
src/cmd/go/main.go
:mergeEnvLists
という新しいヘルパー関数が追加されました。この関数は、2つの環境変数リストをマージし、同じ名前の変数がin
リストでout
リストのものを上書きするようにします。envForDir
関数がmergeEnvLists
を使用するように変更されました。これにより、PWD
環境変数を設定する際に、既存の環境変数リストと適切にマージされるようになります。
これらの変更により、cmd/go
は#cgo
ディレクティブの解析、pkg-config
の実行、そして最終的なC/Lフラグの決定と、それらをcmd/cgo
に環境変数として渡すという、一元的な制御を担うことになります。cmd/cgo
は、GoとCの間の橋渡しに特化し、フラグの解析や外部ツールの呼び出しといった複雑なロジックから解放されます。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルに集中しています。
src/cmd/cgo/gcc.go
:ParseFlags
関数の削除。pkgConfig
関数の削除。File.DiscardCgoDirectives
関数の追加。
src/cmd/cgo/main.go
:main
関数内でp.ParseFlags
の呼び出しをf.DiscardCgoDirectives
に置き換え。newPackage
関数でのgccOptions
の扱い変更。
src/cmd/go/build.go
:builder.run
およびbuilder.runOut
関数のシグネチャ変更(env []string
引数の追加)。builder.cgo
関数内でのpkg-config
の呼び出しとCGO_LDFLAGS
環境変数の構築ロジックの追加。
src/cmd/go/main.go
:mergeEnvLists
関数の追加。envForDir
関数でのmergeEnvLists
の使用。
コアとなるコードの解説
src/cmd/cgo/gcc.go
(変更前)
// ParseFlags extracts #cgo CFLAGS and LDFLAGS options from the file
// preamble. Multiple occurrences are concatenated with a separating space,
// even across files.
func (p *Package) ParseFlags(f *File, srcfile string) {
// ... #cgo ディレクティブを解析し、p.addToFlagを呼び出すロジック ...
}
// pkgConfig runs pkg-config and extracts --libs and --cflags information
// for packages.
func pkgConfig(packages []string) (cflags, ldflags []string, err error) {
// ... pkg-config コマンドを実行し、出力を解析するロジック ...
}
src/cmd/cgo/gcc.go
(変更後)
// DiscardCgoDirectives processes the import C preamble, and discards
// all #cgo CFLAGS and LDFLAGS directives, so they don't make their
// way into _cgo_export.h.
func (f *File) DiscardCgoDirectives() {
linesIn := strings.Split(f.Preamble, "\n")
linesOut := make([]string, 0, len(linesIn))
for _, line := range linesIn {
l := strings.TrimSpace(line)
// #cgo ディレクティブでなければそのまま残す
if len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(rune(l[4])) {
linesOut = append(linesOut, line)
}
// #cgo ディレクティブであれば破棄(linesOutに追加しない)
}
f.Preamble = strings.Join(linesOut, "\n")
}
変更後、ParseFlags
とpkgConfig
は完全に削除され、DiscardCgoDirectives
が導入されました。これは、cmd/cgo
が#cgo
ディレクティブの解析やpkg-config
の実行に関与しなくなったことを明確に示しています。
src/cmd/cgo/main.go
(変更前)
func main() {
// ...
for i, input := range goFiles {
// Parse flags for all files before translating due to CFLAGS.
f := new(File)
f.ReadGo(input)
p.ParseFlags(f, input) // ここでParseFlagsが呼び出されていた
fs[i] = f
}
// ...
}
func newPackage(args []string) *Package {
// Copy the gcc options to a new slice so the list
// can grow without overwriting the slice that args is in.
gccOptions := make([]string, len(args))
copy(gccOptions, args)
// ...
p := &Package{
// ...
GccOptions: gccOptions, // ここでgccOptionsが設定されていた
// ...
}
return p
}
src/cmd/cgo/main.go
(変更後)
func main() {
// ...
for i, input := range goFiles {
f := new(File)
f.ReadGo(input)
f.DiscardCgoDirectives() // DiscardCgoDirectivesが呼び出される
fs[i] = f
}
// ...
}
func newPackage(args []string) *Package {
// gccOptionsのコピーロジックが削除された
// ...
p := &Package{
// ...
// GccOptionsフィールドは削除されたか、別の方法で初期化される
// ...
}
p.addToFlag("CFLAGS", args) // CFLAGSが直接追加される
return p
}
main
関数では、p.ParseFlags
の代わりにf.DiscardCgoDirectives
が呼び出され、#cgo
ディレクティブの解析がcmd/cgo
から完全に排除されました。newPackage
では、cmd/go
から渡された初期のargs
が直接CFLAGS
として追加されるようになりました。
src/cmd/go/build.go
(変更前)
func (b *builder) runOut(dir string, desc string, cmdargs ...interface{}) ([]byte, error) {
// ...
cmd.Env = envForDir(cmd.Dir) // 環境変数はenvForDirで設定
// ...
}
func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outC []string, err error) {
// ...
if pkgs := p.CgoPkgConfig; len(pkgs) > 0 {
out, err := b.runOut(p.Dir, p.ImportPath, "pkg-config", "--cflags", pkgs) // pkg-configをcmd/goから呼び出し
// ...
out, err = b.runOut(p.Dir, p.ImportPath, "pkg-config", "--libs", pkgs) // pkg-configをcmd/goから呼び出し
// ...
}
// ...
if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, cgoflags, "--", cgoCFLAGS, p.CgoFiles); err != nil {
return nil, nil, err
}
// ...
}
src/cmd/go/build.go
(変更後)
func (b *builder) runOut(dir string, desc string, env []string, cmdargs ...interface{}) ([]byte, error) {
// ...
cmd.Env = mergeEnvLists(env, envForDir(cmd.Dir)) // 新たにenv引数を受け取り、マージする
// ...
}
func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outC []string, err error) {
// ...
if pkgs := p.CgoPkgConfig; len(pkgs) > 0 {
out, err := b.runOut(p.Dir, p.ImportPath, nil, "pkg-config", "--cflags", pkgs) // env引数にnilを渡す
// ...
out, err = b.runOut(p.Dir, p.ImportPath, nil, "pkg-config", "--libs", pkgs) // env引数にnilを渡す
// ...
}
// Update $CGO_LDFLAGS with p.CgoLDFLAGS.
var cgoenv []string
if len(cgoLDFLAGS) > 0 {
flags := make([]string, len(cgoLDFLAGS))
for i, f := range cgoLDFLAGS {
flags[i] = strconv.Quote(f)
}
cgoenv = []string{"CGO_LDFLAGS=" + strings.Join(flags, " ")} // CGO_LDFLAGS環境変数を構築
}
if err := b.run(p.Dir, p.ImportPath, cgoenv, cgoExe, "-objdir", obj, cgoflags, "--", cgoCFLAGS, p.CgoFiles); err != nil {
return nil, nil, err
}
// ...
}
runOut
関数はenv
引数を受け取るようになり、cmd.Env
を設定する際にこの引数と既存の環境変数をマージするようになりました。cgo
関数では、pkg-config
の呼び出しは引き続きcmd/go
が行いますが、その結果得られたリンカフラグはCGO_LDFLAGS
環境変数として構築され、cmd/cgo
を呼び出す際にb.run
を通じて渡されるようになりました。
src/cmd/go/main.go
(変更前)
func envForDir(dir string) []string {
env := os.Environ()
for i, kv := range env {
if strings.HasPrefix(kv, "PWD=") {
env[i] = "PWD=" + dir
return env
}
}
env = append(env, "PWD="+dir)
return env
}
src/cmd/go/main.go
(変更後)
// mergeEnvLists merges the two environment lists such that
// variables with the same name in "in" replace those in "out".
func mergeEnvLists(in, out []string) []string {
NextVar:
for _, inkv := range in {
k := strings.SplitAfterN(inkv, "=", 2)[0]
for i, outkv := range out {
if strings.HasPrefix(outkv, k) {
out[i] = inkv
continue NextVar
}
}
out = append(out, inkv)
}
return out
}
func envForDir(dir string) []string {
// Internally we only use rooted paths, so dir is rooted.
// Even if dir is not rooted, no harm done.
return mergeEnvLists([]string{"PWD=" + dir}, os.Environ())
}
mergeEnvLists
関数が追加され、環境変数リストを効率的にマージできるようになりました。envForDir
はこの新しい関数を利用して、PWD
環境変数を設定する際に既存の環境変数と適切に結合します。
これらの変更により、#cgo
ディレクティブの解析とフラグの処理に関する責任がcmd/go
に集約され、cmd/cgo
はよりシンプルな役割を担うようになりました。
関連リンク
- Go言語の
cgo
に関する公式ドキュメント: https://pkg.go.dev/cmd/cgo - Go言語のビルドコマンドに関する公式ドキュメント: https://pkg.go.dev/cmd/go
pkg-config
のmanページ (一般的な情報):man pkg-config
(Linux/macOSのターミナルで実行)
参考にした情報源リンク
- コミットハッシュ:
d06be395cc8094b3cb132be238d745b668dbfa04
- GitHub上のコミットページ: https://github.com/golang/go/commit/d06be395cc8094b3cb132be238d745b668dbfa04
- Go CL (Change List) 8610044: https://golang.org/cl/8610044 (これは古いGoのコードレビューシステムへのリンクであり、現在はアクセスできない可能性がありますが、コミットメッセージに記載されているため含めました。)
- Go Issue 5224: コミットメッセージに記載されていますが、公開されているGoのIssueトラッカーでは見つかりませんでした。これは内部的な追跡番号であるか、非常に古いIssueである可能性があります。