[インデックス 11065] ファイルの概要
このコミットは、Goコマンドラインツール(cmd/go
)におけるコンパイル時のインポートディレクトリリストの処理に関するバグ修正です。具体的には、go build
コマンドが、新しくビルドされたパッケージではなく、古いインストール済みのパッケージを誤って参照してしまうという、長らくユーザーを悩ませてきた問題に対処しています。
コミット
commit 0ad241dd550d224f65c634f767f5e73a025fc2bc
Author: Russ Cox <rsc@golang.org>
Date: Mon Jan 9 15:38:07 2012 -0800
cmd/go: fix import directory list for compilation
This fixes the most annoying bug in the go command,
that 'go build' sometimes ignored packages it had just
rebuilt in favor of stale installed ones.
This part of the code needs more thought, but this small
change is an important improvement.
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5531053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ad241dd550d224f65c634f767f5e73a025fc2bc
元コミット内容
cmd/go: fix import directory list for compilation
これはGoコマンドにおける最も厄介なバグを修正するものです。go build
が、新しく再ビルドしたばかりのパッケージではなく、古いインストール済みのパッケージを優先してしまうことがありました。
このコード部分はさらなる検討が必要ですが、この小さな変更は重要な改善です。
変更の背景
Goのビルドシステムでは、依存関係にあるパッケージをコンパイルする際に、そのパッケージのコンパイル済みアーカイブファイル(.a
ファイル)がどこにあるかを知る必要があります。このパスは、コンパイラに渡される-I
フラグによって指定されます。
問題は、go build
がパッケージを再ビルドした際に、新しく生成された一時的なビルドディレクトリ内のパッケージではなく、GOPATH/pkg
やGOROOT/pkg
といった永続的な場所にインストールされている古い(staleな)パッケージを誤って参照してしまうことがあった点です。これにより、開発者がコードを変更してビルドしても、その変更が反映されないという混乱が生じていました。
このコミットは、コンパイラに渡すインポートディレクトリのリスト(-I
フラグのリスト)を構築するロジックを修正し、常に最新のビルド済みパッケージが参照されるようにすることを目的としています。
前提知識の解説
- Go Modules / GOPATH: Goプロジェクトは、依存関係の管理にGo Modulesを使用するか、あるいは古いGOPATHモデルを使用します。どちらのモデルでも、コンパイル済みのパッケージアーカイブ(
.a
ファイル)は通常、GOPATH/pkg
またはGOROOT/pkg
といった特定のディレクトリにインストールされます。 go build
コマンド: Goのソースコードをコンパイルし、実行可能ファイルやパッケージアーカイブを生成するコマンドです。go install
コマンド:go build
と同様にコンパイルを行いますが、コンパイルされたパッケージアーカイブをGOPATH/pkg
やGOROOT/pkg
などの標準的な場所にインストールします。- コンパイラのインクルードパス (
-I
フラグ): Goコンパイラ(gc
)は、インポートされたパッケージの定義を見つけるために、-I
フラグで指定されたディレクトリを検索します。複数の-I
フラグが指定された場合、コンパイラは通常、指定された順序でディレクトリを検索し、最初に見つかったパッケージを使用します。ただし、コンパイラの実装によっては、特定のパス(例: インストール済みパス)に優先順位を付ける場合があります。 - Staleパッケージ: ソースコードが更新されたにもかかわらず、コンパイル済みのパッケージアーカイブ(
.a
ファイル)が古い状態のままであることを指します。
技術的詳細
この修正は、src/cmd/go/build.go
ファイル内のbuilder
構造体のbuild
メソッドにあります。このメソッドは、Goパッケージをビルドする際の主要なロジックを含んでおり、特に依存関係のパッケージをコンパイラが参照できるように、適切なインクルードパス(-I
フラグ)を構築する部分が重要です。
build
メソッド内では、inc
という文字列スライスがコンパイラに渡す-I
フラグのリストを構築するために使用されます。また、incMap
というマップは、同じディレクトリが複数回inc
に追加されるのを防ぐために使用されます。
問題の核心は、依存関係のパッケージに対して、一時的なビルドディレクトリ(a1.pkgdir
)と、永続的なインストールディレクトリ(a1.p.t.PkgDir()
)の両方が存在する場合に、どちらのパスをinc
リストに含めるか、そしてその順序をどうするかという点にありました。
元のコードでは、以下の2つのループでインクルードパスを追加していました。
- 一時的なビルドディレクトリの追加: 依存関係のパッケージが一時的にビルドされたディレクトリ(
a1.pkgdir
)をinc
に追加。 - インストール済みディレクトリの追加: 依存関係のパッケージがインストールされているディレクトリ(
a1.p.t.PkgDir()
)をinc
に追加。
このロジックでは、新しくビルドされたパッケージが一時ディレクトリに存在しても、その後に古いインストール済みパッケージのディレクトリがinc
に追加されることで、コンパイラが古い方を参照してしまう可能性がありました。
コアとなるコードの変更箇所
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -509,9 +509,9 @@ func (b *builder) build(a *action) error {
incMap[build.Path[0].PkgDir()] = true // goroot
incMap[""] = true // ignore empty strings
- // build package directories of dependencies
+ // temporary build package directories of dependencies.
for _, a1 := range a.deps {
- if pkgdir := a1.pkgdir; !incMap[pkgdir] {
+ if pkgdir := a1.pkgdir; pkgdir != a1.p.t.PkgDir() && !incMap[pkgdir] {
incMap[pkgdir] = true
inc = append(inc, "-I", pkgdir)
}
@@ -522,7 +522,7 @@ func (b *builder) build(a *action) error {
// then installed package directories of dependencies
for _, a1 := range a.deps {
- if pkgdir := a1.p.t.PkgDir(); !incMap[pkgdir] {
+ if pkgdir := a1.p.t.PkgDir(); pkgdir == a1.pkgdir && !incMap[pkgdir] {
incMap[pkgdir] = true
inc = append(inc, "-I", pkgdir)
}
コアとなるコードの解説
変更は主に2つのfor
ループ内の条件式にあります。
-
最初のループ(一時的なビルドディレクトリの追加):
- 変更前:
if pkgdir := a1.pkgdir; !incMap[pkgdir]
- これは、依存パッケージ
a1
の一時的なビルドディレクトリa1.pkgdir
がまだinc
リストに追加されていなければ、追加するというシンプルなロジックでした。
- これは、依存パッケージ
- 変更後:
if pkgdir := a1.pkgdir; pkgdir != a1.p.t.PkgDir() && !incMap[pkgdir]
- 新しい条件
pkgdir != a1.p.t.PkgDir()
が追加されました。これは、「一時的なビルドディレクトリa1.pkgdir
が、そのパッケージの永続的なインストールディレクトリa1.p.t.PkgDir()
と異なる場合のみ、その一時ディレクトリをinc
リストに追加する」という意味です。 - この変更により、もしパッケージが一時ディレクトリではなく、直接その最終的なインストール先にビルドされている場合(つまり
a1.pkgdir
とa1.p.t.PkgDir()
が同じ場合)、この最初のループではそのパスは追加されなくなります。これにより、一時的なビルドパスとインストールパスが重複して追加されるのを防ぎ、特に一時パスがインストールパスと異なる場合に、一時パスが優先されるようにします。
- 新しい条件
- 変更前:
-
2番目のループ(インストール済みディレクトリの追加):
- 変更前:
if pkgdir := a1.p.t.PkgDir(); !incMap[pkgdir]
- これは、依存パッケージ
a1
のインストール済みディレクトリa1.p.t.PkgDir()
がまだinc
リストに追加されていなければ、追加するというロジックでした。
- これは、依存パッケージ
- 変更後:
if pkgdir := a1.p.t.PkgDir(); pkgdir == a1.pkgdir && !incMap[pkgdir]
- 新しい条件
pkgdir == a1.pkgdir
が追加されました。これは、「インストール済みディレクトリa1.p.t.PkgDir()
が、そのパッケージの一時的なビルドディレクトリa1.pkgdir
と同じ場合のみ、そのインストール済みディレクトリをinc
リストに追加する」という意味です。 - この変更は、最初のループの変更と連携しています。もしパッケージが一時ディレクトリにビルドされず、直接インストール先にビルドされた場合(
a1.pkgdir
とa1.p.t.PkgDir()
が同じ場合)、そのパスは最初のループではスキップされ、この2番目のループで初めてinc
リストに追加されます。これにより、一時的なビルドパスとインストールパスが異なる場合に、古いインストールパスが誤って追加されるのを防ぎます。
- 新しい条件
- 変更前:
これらの変更により、go build
は以下のロジックでインクルードパスを構築するようになります。
- もし依存パッケージが、その永続的なインストール場所とは異なる一時ディレクトリに新しくビルドされた場合、その一時ディレクトリのパスのみが最初のループで
inc
に追加されます。これにより、コンパイラは最新のビルド結果を確実に参照します。 - もし依存パッケージが、一時ディレクトリではなく、その永続的なインストール場所に直接ビルドされた場合(または
a1.pkgdir
が既にインストール場所を指している場合)、そのパスは最初のループではスキップされ、2番目のループでinc
に追加されます。
この修正は、go build
が常に最新のコンパイル済みパッケージを参照するように、インクルードパスの優先順位を正しく設定することを保証します。これにより、「新しく再ビルドしたばかりのパッケージではなく、古いインストール済みのパッケージを優先してしまう」というバグが解消されます。
関連リンク
- Go Gerrit Change-ID: https://golang.org/cl/5531053
参考にした情報源リンク
- コミット情報:
./commit_data/11065.txt
- GitHubコミットページ: https://github.com/golang/go/commit/0ad241dd550d224f65c634f767f5e73a025fc2bc
- Go言語のビルドシステムに関する一般的な知識