[インデックス 12333] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
におけるビルドプロセスの一部を修正するものです。具体的には、ビルド時にコンパイラに渡されるインクルードディレクトリのパスが重複して追加されるのを防ぐための変更が行われています。これにより、ビルドの効率性が向上し、潜在的なビルドエラーや警告が回避されます。
コミット
commit 1feecdd633c1943e5c4a0ced3a14788ee00f343c
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Mar 2 11:31:13 2012 -0500
cmd/go: avoid repeated include dirs.
Fixes #3171.
R=golang-dev, rsc
CC=golang-dev, remy
https://golang.org/cl/5724045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1feecdd633c1943e5c4a0ced3a14788ee00f343c
元コミット内容
このコミットは、src/cmd/go/build.go
ファイルに対して1行の追加と1行の削除を行っています。変更の目的は、go build
コマンドがコンパイラに渡すインクルードディレクトリのリストに、同じディレクトリが複数回含まれることを避けることです。これは、Issue #3171 を修正するものです。
変更の背景
Go言語のビルドシステム、特に go build
コマンドは、ソースコードをコンパイルし、実行可能ファイルやライブラリを生成する際に、必要なパッケージや依存関係を解決します。この過程で、コンパイラは特定のディレクトリ(インクルードディレクトリ)を検索して、ヘッダーファイルやその他の必要なリソースを見つけます。
このコミットが行われる前は、cmd/go
のビルドロジックにおいて、インクルードディレクトリのパスを管理する際に、特定の条件下で同じパスが複数回リストに追加されてしまうバグが存在していました。これは、incMap
というマップ(Go言語におけるハッシュマップ)を使用して重複を避ける試みがなされていたにもかかわらず発生していました。
具体的には、incMap[dir] = true
という行が、dir
変数が filepath.Join
によって変更される可能性のある処理の後に配置されていたため、元の dir
の値が incMap
に正しく記録されず、結果として重複したパスが inc
スライスに追加される可能性がありました。このような重複は、ビルド時間のわずかな増加や、一部のコンパイラやツールチェーンで予期せぬ警告やエラーを引き起こす可能性がありました。
Issue #3171 はこの問題点を指摘しており、このコミットはその修正を目的としています。
前提知識の解説
cmd/go
: Go言語の公式ツールチェーンの主要なコマンドラインインターフェースです。go build
,go run
,go test
,go get
など、Go開発者が日常的に使用する多くのサブコマンドを提供します。- インクルードディレクトリ (Include Directories): コンパイラがソースコードをコンパイルする際に、
#include
(C/C++) やimport
(Go) などのディレクティブで指定された外部ファイル(ヘッダーファイル、パッケージなど)を探すためのディレクトリのリストです。コンパイラはこれらのディレクトリを順番に検索し、必要なファイルを見つけます。 go build
: Goのソースコードをコンパイルして実行可能バイナリを生成するコマンドです。このプロセスには、依存関係の解決、コンパイル、リンクが含まれます。filepath.Join
: Go言語の標準ライブラリpath/filepath
パッケージに含まれる関数で、複数のパス要素を結合して単一のパスを生成します。OS固有のパス区切り文字(Windowsでは\
、Unix系では/
)を適切に処理します。map
(マップ): Go言語におけるキーと値のペアを格納するデータ構造で、他の言語のハッシュマップや辞書に相当します。キーの一意性を保証するために使用され、このコミットではインクルードディレクトリのパスの重複を検出・防止するためにincMap
として利用されています。slice
(スライス): Go言語における可変長配列です。このコミットでは、最終的にコンパイラに渡されるインクルードディレクトリのパスを格納するinc
というスライスが使用されています。gccgoToolchain
: Go言語のコンパイラには、公式のgc
コンパイラと、GCCをバックエンドとするgccgo
コンパイラがあります。このコードは、どちらのツールチェーンが使用されているかに応じて、インクルードパスの構造を調整しています。
技術的詳細
src/cmd/go/build.go
内の builder.includeArgs
関数は、ビルドプロセス中にコンパイラに渡すインクルードディレクトリの引数を生成する役割を担っています。この関数は、all
という *action
のスライスを受け取り、それぞれの action
に関連付けられたパッケージディレクトリ (a1.pkgdir
) を処理します。
この関数は、incMap
という map[string]bool
型のマップを使用して、既に追加されたディレクトリのパスを追跡し、重複を防ぐことを意図していました。また、inc
という []string
型のスライスに、最終的なインクルードパスの引数を flag
と dir
のペアとして追加します。
問題のコードブロックは以下の通りでした(変更前):
if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(dir, "gccgo")
} else {
dir = filepath.Join(dir, goos+"_"+goarch)
}
incMap[dir] = true // ここが問題の箇所
inc = append(inc, flag, dir)
}
このコードでは、incMap[dir] = true
の行が、dir
変数が filepath.Join
によって変更された後に実行されていました。
例えば、a1.pkgdir
が /path/to/pkg
であったとします。
if dir := a1.pkgdir; ...
でdir
は/path/to/pkg
になります。!incMap[dir]
のチェックが行われます。もし/path/to/pkg
がまだincMap
になければ、条件は真となり、ブロックに入ります。dir = filepath.Join(dir, "gccgo")
のような行で、dir
の値が/path/to/pkg/gccgo
に変更されます。- 問題点:
incMap[dir] = true
が実行されるとき、dir
は既に/path/to/pkg/gccgo
になっています。したがって、incMap
には/path/to/pkg/gccgo
が追加されますが、元の/path/to/pkg
は追跡されません。 - もし別の
action
で、同じa1.pkgdir
(/path/to/pkg
) が処理され、かつfilepath.Join
の結果が異なる場合(例えば、goos+"_"+goarch
のパスが異なる場合)、またはa1.pkgdir
がincMap
に追加される前に別のパスとして処理された場合、元のa1.pkgdir
が重複して処理される可能性がありました。
このバグは、incMap
が a1.pkgdir
の元の値を正しく追跡できていなかったために発生しました。
コアとなるコードの変更箇所
変更は src/cmd/go/build.go
ファイルの builder.includeArgs
関数内で行われました。
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -790,12 +790,12 @@ func (b *builder) includeArgs(flag string, all []*action) []string {
// Finally, look in the installed package directories for each action.
for _, a1 := range all {
if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
- if _, ok := buildToolchain.(gccgoToolchain); ok {
- dir = filepath.Join(dir, "gccgo")
- } else {
- dir = filepath.Join(dir, goos+"_"+goarch)
- }
- incMap[dir] = true
+ incMap[dir] = true // この行が移動した
+ if _, ok := buildToolchain.(gccgoToolchain); ok {
+ dir = filepath.Join(dir, "gccgo")
+ } else {
+ dir = filepath.Join(dir, goos+"_"+goarch)
+ }
inc = append(inc, flag, dir)
}
}
コアとなるコードの解説
変更は非常にシンプルですが、その影響は重要です。
incMap[dir] = true
の行が、if dir := a1.pkgdir; ...
の条件が真であった直後、かつ dir
変数が filepath.Join
によって変更される前に移動されました。
変更後:
if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
incMap[dir] = true // ここに移動
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(dir, "gccgo")
} else {
dir = filepath.Join(dir, goos+"_"+goarch)
}
inc = append(inc, flag, dir)
}
この変更により、以下のようになります。
if dir := a1.pkgdir; ...
の条件が真となり、ブロックに入った直後、dir
はまだa1.pkgdir
の元の値(例:/path/to/pkg
)を保持しています。- この時点で
incMap[dir] = true
が実行されるため、incMap
にはa1.pkgdir
の元の値が正しく「既に追加済み」として記録されます。 - その後、
dir
はfilepath.Join
によってgccgo
やgoos_goarch
のサブディレクトリを含むパス(例:/path/to/pkg/gccgo
)に変更されます。 - 最終的に、この変更された
dir
の値がinc
スライスに追加されます。
これにより、incMap
は a1.pkgdir
の元の値を正確に追跡できるようになり、同じ a1.pkgdir
から派生するインクルードパスが複数回処理されることを効果的に防ぎます。結果として、コンパイラに渡されるインクルードディレクトリのリストから重複が排除され、ビルドプロセスの堅牢性と効率性が向上します。
関連リンク
- Go Issue #3171: https://github.com/golang/go/issues/3171
- Go CL 5724045: https://golang.org/cl/5724045
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ
- Go言語のIssueトラッカー
path/filepath
パッケージのドキュメント- Go言語のマップとスライスに関する基本的な情報