Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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/pkgGOROOT/pkgといった永続的な場所にインストールされている古い(staleな)パッケージを誤って参照してしまうことがあった点です。これにより、開発者がコードを変更してビルドしても、その変更が反映されないという混乱が生じていました。

このコミットは、コンパイラに渡すインポートディレクトリのリスト(-Iフラグのリスト)を構築するロジックを修正し、常に最新のビルド済みパッケージが参照されるようにすることを目的としています。

前提知識の解説

  • Go Modules / GOPATH: Goプロジェクトは、依存関係の管理にGo Modulesを使用するか、あるいは古いGOPATHモデルを使用します。どちらのモデルでも、コンパイル済みのパッケージアーカイブ(.aファイル)は通常、GOPATH/pkgまたはGOROOT/pkgといった特定のディレクトリにインストールされます。
  • go buildコマンド: Goのソースコードをコンパイルし、実行可能ファイルやパッケージアーカイブを生成するコマンドです。
  • go installコマンド: go buildと同様にコンパイルを行いますが、コンパイルされたパッケージアーカイブをGOPATH/pkgGOROOT/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つのループでインクルードパスを追加していました。

  1. 一時的なビルドディレクトリの追加: 依存関係のパッケージが一時的にビルドされたディレクトリ(a1.pkgdir)をincに追加。
  2. インストール済みディレクトリの追加: 依存関係のパッケージがインストールされているディレクトリ(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ループ内の条件式にあります。

  1. 最初のループ(一時的なビルドディレクトリの追加):

    • 変更前: 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.pkgdira1.p.t.PkgDir()が同じ場合)、この最初のループではそのパスは追加されなくなります。これにより、一時的なビルドパスとインストールパスが重複して追加されるのを防ぎ、特に一時パスがインストールパスと異なる場合に、一時パスが優先されるようにします。
  2. 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.pkgdira1.p.t.PkgDir()が同じ場合)、そのパスは最初のループではスキップされ、この2番目のループで初めてincリストに追加されます。これにより、一時的なビルドパスとインストールパスが異なる場合に、古いインストールパスが誤って追加されるのを防ぎます。

これらの変更により、go buildは以下のロジックでインクルードパスを構築するようになります。

  • もし依存パッケージが、その永続的なインストール場所とは異なる一時ディレクトリに新しくビルドされた場合、その一時ディレクトリのパスのみが最初のループでincに追加されます。これにより、コンパイラは最新のビルド結果を確実に参照します。
  • もし依存パッケージが、一時ディレクトリではなく、その永続的なインストール場所に直接ビルドされた場合(またはa1.pkgdirが既にインストール場所を指している場合)、そのパスは最初のループではスキップされ、2番目のループでincに追加されます。

この修正は、go buildが常に最新のコンパイル済みパッケージを参照するように、インクルードパスの優先順位を正しく設定することを保証します。これにより、「新しく再ビルドしたばかりのパッケージではなく、古いインストール済みのパッケージを優先してしまう」というバグが解消されます。

関連リンク

参考にした情報源リンク