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

[インデックス 12555] ファイルの概要

このコミットは、Go言語のビルドツールである cmd/go におけるローカルインポートの挙動に関する重要な修正を導入しています。具体的には、ローカルパッケージのインポートパスの解決方法を改善し、非ローカルパッケージからのローカルインポートを禁止することで、ビルド時の問題(特にバイナリ内のパッケージ重複)を解消することを目的としています。

コミット

commit cd7ae05d52e4434453be9a4af2888aedc75330aa
Author: Russ Cox <rsc@golang.org>
Date:   Sun Mar 11 15:53:42 2012 -0400

    cmd/go: local import fixes
    
    1) The -D argument should always be a pseudo-import path,
    like _/Users/rsc/foo/bar, never a standard import path,
    because we want local imports to always resolve to pseudo-paths.
    
    2) Disallow local imports in non-local packages.  Otherwise
    everything works but you get two copies of a package
    (the real one and the "local" one) in your binary.
    
    R=golang-dev, bradfitz, yiyu.jgl
    CC=golang-dev
    https://golang.org/cl/5787055

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/cd7ae05d52e4434453be9a4af2888aedc75330aa

元コミット内容

cmd/go: local import fixes

  1. -D 引数は常に _/Users/rsc/foo/bar のような擬似インポートパスであるべきで、標準のインポートパスであってはならない。これは、ローカルインポートが常に擬似パスに解決されるようにするためである。
  2. 非ローカルパッケージでのローカルインポートを禁止する。そうしないと、すべてが機能するように見えても、バイナリ内にパッケージのコピーが2つ(実際のパッケージと「ローカル」なパッケージ)できてしまう。

変更の背景

Go言語の初期のビルドシステムでは、ローカルディレクトリにあるパッケージ(例えば go build .go run main.go でビルドされるようなケース)の扱いに関して、いくつかの曖昧さや問題がありました。特に、以下の2つの主要な問題が認識されていました。

  1. ローカルインポートパスの不適切な解決: go コマンドが内部的に使用する -D 引数(これはビルドパスやディレクトリを示すものと推測されます)が、ローカルなソースディレクトリを指しているにもかかわらず、標準のインポートパスとして扱われてしまう可能性がありました。これにより、ローカルなパッケージがグローバルなパッケージと混同され、意図しない挙動を引き起こす原因となっていました。ローカルインポートは、その性質上、常にファイルシステム上の物理的なパスに紐づく「擬似インポートパス」として扱われるべきでした。
  2. バイナリ内のパッケージ重複: 最も深刻な問題は、非ローカルパッケージ(例えば、github.com/user/repo/mypackage のようなリモートリポジトリからインポートされるパッケージ)が、ローカルな相対パス(例: ./util)を使って別のパッケージをインポートしようとした場合に発生していました。この状況では、Goツールチェインは同じパッケージを2つの異なる方法で認識してしまい、結果として最終的な実行可能バイナリ内にそのパッケージのコードが二重にコンパイルされて含まれてしまうことがありました。これは、バイナリサイズの肥大化だけでなく、ランタイムにおける予期せぬ動作(例えば、パッケージレベルのグローバル変数が異なるインスタンスで存在し、状態が共有されないなど)を引き起こす可能性がありました。

これらの問題を解決し、Goのビルドシステムの一貫性と信頼性を向上させるために、本コミットの変更が導入されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびビルドシステムに関する概念を理解しておく必要があります。

  • Goパッケージとインポートパス: Goのコードはパッケージに組織化されており、他のパッケージのコードを利用するには import ステートメントを使用します。インポートパスは、パッケージを一意に識別するための文字列です。
    • 標準インポートパス: fmt, net/http のようにGoの標準ライブラリや、github.com/user/repo/mypackage のようにリモートリポジトリから取得されるパッケージのパス。これらは通常、GOPATH やモジュールキャッシュに基づいて解決されます。
    • ローカルインポートパス(相対インポートパス): ./util../common のように、現在のソースファイルからの相対パスで指定されるインポートパス。これらは通常、同じプロジェクト内のローカルなパッケージを指します。
  • 擬似インポートパス (Pseudo-import paths): Goツールチェインが内部的に使用する特別なインポートパスの形式です。これは、ファイルシステム上の絶対パスを基にして生成され、特にローカルなソースディレクトリからビルドされるパッケージ(例: go build . でビルドされるパッケージ)を、標準のインポートパスを持つパッケージと区別するために用いられます。例えば、/home/user/project/src/main.go からビルドされるパッケージは、内部的に _/home/user/project/src のような擬似インポートパスを持つことがあります。これにより、同じ名前のパッケージが異なる場所にある場合に衝突を避けることができます。
  • cmd/go: Go言語の公式コマンドラインツールであり、Goプログラムのビルド、テスト、依存関係管理など、Go開発のあらゆる側面を管理する中心的なツールです。このツールは、Goのソースコードをコンパイルし、リンクして実行可能バイナリを生成する複雑なプロセスを内部的に実行します。
  • リンカ (ld): コンパイルされたオブジェクトファイル(.o ファイルなど)を結合し、必要なライブラリとリンクして、最終的な実行可能バイナリを生成するツールです。Goのビルドプロセスにおいて重要な役割を担います。
  • Package 構造体: cmd/go 内部で、Goのパッケージに関するメタデータ(インポートパス、ディレクトリ、ソースファイルリスト、依存関係など)を保持するために使用されるデータ構造です。この構造体には、パッケージがローカルであるかどうかを示すフラグ(local)や、相対インポートを解決するためのプレフィックス(localPrefix)などのフィールドが含まれます。

技術的詳細

このコミットは、主に src/cmd/go/build.gosrc/cmd/go/pkg.go の2つのファイルにわたる変更を通じて、前述の問題に対処しています。

  1. ローカルパッケージの明示的なマーク付け (pkg.local = true):

    • src/cmd/go/build.gogoFilesPackage 関数内で、pkg.local = true が追加されました。この関数は、コマンドラインで直接指定されたGoファイル(例: go run main.go)からパッケージを構築する際に呼び出されます。これにより、これらのパッケージが「ローカル」であるという情報が Package 構造体に明示的に記録されるようになりました。この local フラグは、後述のローカルインポートの検証ロジックで利用されます。
  2. リンカの作業ディレクトリの変更:

    • src/cmd/go/build.go 内の gcToolchain.ld および gccgcToolchain.ld 関数において、リンカ (tool(archChar+"l")gccgo) を実行する際の作業ディレクトリが p.Dir から . (カレントディレクトリ) に変更されました。
    • この変更は、コミットメッセージの「-D 引数は常に擬似インポートパスであるべき」という点に関連しています。リンカがカレントディレクトリを基準に動作することで、内部的なパス解決がより一貫して擬似パスとして行われるようになり、ローカルなビルド環境におけるパスの曖昧さが解消されます。これにより、ローカルインポートが標準インポートパスとして誤って解釈されることを防ぎます。
  3. localPrefix の解決ロジックの変更:

    • src/cmd/go/pkg.goPackage.load 関数内で、p.localPrefix の設定方法が p.ImportPath から dirToImportPath(p.Dir) に変更されました。
    • localPrefix は、相対インポートパス(例: ./foo)を解決する際の基準となるパスです。以前はパッケージの ImportPath を使用していましたが、これはローカルパッケージの場合に擬似パスではない可能性がありました。dirToImportPath(p.Dir) を使用することで、localPrefix が常にパッケージの物理的なディレクトリから導出された擬似パスとなることが保証されます。これにより、相対インポートの解決が常にローカルなファイルシステム上のパスに正確にマッピングされるようになります。
  4. 非ローカルパッケージからのローカルインポートの禁止:

    • src/cmd/go/pkg.goPackage.load 関数内のインポート解決ロジックに、新しい検証が追加されました。
    • loadImport 関数でインポートされたパッケージ p1p1.local (ローカルパッケージ) であると判断された場合、さらに現在のパッケージ p!p.local (非ローカルパッケージ) であるかどうかをチェックします。
    • もし非ローカルパッケージがローカルパッケージをインポートしようとしている場合、PackageError が生成され、"local import %q in non-local package" というエラーメッセージが設定されます。
    • この変更は、バイナリ内のパッケージ重複問題に対する直接的な解決策です。非ローカルパッケージがローカルインポートを使用することを明示的に禁止することで、同じパッケージが異なるインポートパス(標準パスと擬似パス)で二重に認識され、コンパイルされるシナリオを防ぎます。これにより、ビルドの健全性が保たれ、予期せぬランタイムの問題が回避されます。

コアとなるコードの変更箇所

src/cmd/go/build.go

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -383,6 +383,7 @@ func goFilesPackage(gofiles []string) *Package {
 
 	bp, err := ctxt.ImportDir(dir, 0)
 	pkg := new(Package)
+	pkg.local = true // ここでローカルパッケージとしてマーク
 	pkg.load(&stk, bp, err)
 	pkg.localPrefix = dirToImportPath(dir)
 	pkg.ImportPath = "command-line-arguments"
@@ -1202,7 +1203,7 @@ func (gcToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s
 
 func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
 	importArgs := b.includeArgs("-L", allactions)
-	return b.run(p.Dir, p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg)
+	return b.run(".", p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg) // リンカの作業ディレクトリを '.' に変更
 }
 
 func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
@@ -1284,7 +1285,7 @@ func (tools gccgcToolchain) ld(b *builder, p *Package, out string, allactions []
 		ldflags = append(ldflags, afile)
 	}
 	ldflags = append(ldflags, cgoldflags...)
-	return b.run(p.Dir, p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(\", ldflags, \"-Wl,-)\")
+	return b.run(".", p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(\", ldflags, \"-Wl,-)\") // リンカの作業ディレクトリを '.' に変更
 }
 
 func (gccgcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
@@ -1308,6 +1309,9 @@ func (b *builder) gccld(p *Package, out string, flags []string, obj []string) er
 
 // gccCmd returns a gcc command line prefix
 func (b *builder) gccCmd(objdir string) []string {
+	// NOTE: env.go's mkEnv knows that the first three
+	// strings returned are "gcc", "-I", objdir (and cuts them off).
+	// コメント追加
 	// TODO: HOST_CC?
 	a := []string{"gcc", "-I", objdir, "-g", "-O2"}

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -279,9 +279,8 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
 	p.copyBuild(bp)
 
 	// The localPrefix is the path we interpret ./ imports relative to.
-	// Now that we've fixed the import path, it's just the import path.
 	// Synthesized main packages sometimes override this.
-	p.localPrefix = p.ImportPath // 変更前
+	p.localPrefix = dirToImportPath(p.Dir) // localPrefix を物理ディレクトリから導出
 
 	if err != nil {
 		p.Incomplete = true
@@ -343,6 +342,16 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
 		}
 		p1 := loadImport(path, p.Dir, stk, p.build.ImportPos[path])
 		if p1.local {
+			// 非ローカルパッケージがローカルパッケージをインポートしようとした場合のエラーチェック
+			if !p.local && p.Error == nil {
+				p.Error = &PackageError{
+					ImportStack: stk.copy(),
+					Err:         fmt.Sprintf("local import %q in non-local package", path),
+				}
+				pos := p.build.ImportPos[path]
+				if len(pos) > 0 {
+					p.Error.Pos = pos[0].String()
+				}
+			}
 			path = p1.ImportPath
 			importPaths[i] = path
 		}

コアとなるコードの解説

  • src/cmd/go/build.go の変更:

    • goFilesPackage 関数における pkg.local = true の追加は、go build . のようにカレントディレクトリのGoファイルをビルドする際に、そのパッケージがローカルなものであることを明示的にツールチェインに伝えるためのものです。これにより、pkg.go での検証ロジックが正しく機能するようになります。
    • リンカの実行コマンドにおける p.Dir から . への変更は、リンカが常にカレントディレクトリを基準にパスを解決するように強制します。これは、ローカルインポートが擬似パスとして扱われるべきであるという原則を強化し、パス解決の混乱を防ぎます。
  • src/cmd/go/pkg.go の変更:

    • p.localPrefix = dirToImportPath(p.Dir) への変更は、相対インポートの解決基準を、パッケージの物理的なディレクトリから導出された擬似パスに固定します。これにより、相対インポートが常に意図したローカルなパッケージに正確にマッピングされるようになります。
    • 最も重要な変更は、if p1.local { if !p.local && p.Error == nil { ... } } ブロックの追加です。これは、インポートされるパッケージ (p1) がローカルであるにもかかわらず、インポートする側のパッケージ (p) が非ローカルである場合にエラーを発生させるロジックです。このチェックにより、非ローカルパッケージがローカルインポートを使用する(そしてバイナリ重複を引き起こす)という問題のあるシナリオがビルド時に検出され、防止されるようになります。

これらの変更は、Goのビルドシステムにおけるローカルインポートのセマンティクスを明確にし、ビルドの信頼性と予測可能性を大幅に向上させました。

関連リンク

参考にした情報源リンク

  • Go Gerrit Change-ID: 5787055 (コミットメッセージに記載されているGerritの変更リストID)
  • Go言語のビルドプロセスに関する一般的な情報源(Goの公式ブログやドキュメントなど)
  • Goのソースコードリポジトリ: https://github.com/golang/go
  • Goのパッケージ構造とビルドに関する議論(Goコミュニティのフォーラムやメーリングリストなど)# [インデックス 12555] ファイルの概要

このコミットは、Go言語のビルドツールである cmd/go におけるローカルインポートの挙動に関する重要な修正を導入しています。具体的には、ローカルパッケージのインポートパスの解決方法を改善し、非ローカルパッケージからのローカルインポートを禁止することで、ビルド時の問題(特にバイナリ内のパッケージ重複)を解消することを目的としています。

コミット

commit cd7ae05d52e4434453be9a4af2888aedc75330aa
Author: Russ Cox <rsc@golang.org>
Date:   Sun Mar 11 15:53:42 2012 -0400

    cmd/go: local import fixes
    
    1) The -D argument should always be a pseudo-import path,
    like _/Users/rsc/foo/bar, never a standard import path,
    because we want local imports to always resolve to pseudo-paths.
    
    2) Disallow local imports in non-local packages.  Otherwise
    everything works but you get two copies of a package
    (the real one and the "local" one) in your binary.
    
    R=golang-dev, bradfitz, yiyu.jgl
    CC=golang-dev
    https://golang.org/cl/5787055

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/cd7ae05d52e4434453be9a4af2888aedc75330aa

元コミット内容

cmd/go: local import fixes

  1. -D 引数は常に _/Users/rsc/foo/bar のような擬似インポートパスであるべきで、標準のインポートパスであってはならない。これは、ローカルインポートが常に擬似パスに解決されるようにするためである。
  2. 非ローカルパッケージでのローカルインポートを禁止する。そうしないと、すべてが機能するように見えても、バイナリ内にパッケージのコピーが2つ(実際のパッケージと「ローカル」なパッケージ)できてしまう。

変更の背景

Go言語の初期のビルドシステムでは、ローカルディレクトリにあるパッケージ(例えば go build .go run main.go でビルドされるようなケース)の扱いに関して、いくつかの曖昧さや問題がありました。特に、以下の2つの主要な問題が認識されていました。

  1. ローカルインポートパスの不適切な解決: go コマンドが内部的に使用する -D 引数(これはビルドパスやディレクトリを示すものと推測されます)が、ローカルなソースディレクトリを指しているにもかかわらず、標準のインポートパスとして扱われてしまう可能性がありました。これにより、ローカルなパッケージがグローバルなパッケージと混同され、意図しない挙動を引き起こす原因となっていました。ローカルインポートは、その性質上、常にファイルシステム上の物理的なパスに紐づく「擬似インポートパス」として扱われるべきでした。
  2. バイナリ内のパッケージ重複: 最も深刻な問題は、非ローカルパッケージ(例えば、github.com/user/repo/mypackage のようなリモートリポジトリからインポートされるパッケージ)が、ローカルな相対パス(例: ./util)を使って別のパッケージをインポートしようとした場合に発生していました。この状況では、Goツールチェインは同じパッケージを2つの異なる方法で認識してしまい、結果として最終的な実行可能バイナリ内にそのパッケージのコードが二重にコンパイルされて含まれてしまうことがありました。これは、バイナリサイズの肥大化だけでなく、ランタイムにおける予期せぬ動作(例えば、パッケージレベルのグローバル変数が異なるインスタンスで存在し、状態が共有されないなど)を引き起こす可能性がありました。

これらの問題を解決し、Goのビルドシステムの一貫性と信頼性を向上させるために、本コミットの変更が導入されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびビルドシステムに関する概念を理解しておく必要があります。

  • Goパッケージとインポートパス: Goのコードはパッケージに組織化されており、他のパッケージのコードを利用するには import ステートメントを使用します。インポートパスは、パッケージを一意に識別するための文字列です。
    • 標準インポートパス: fmt, net/http のようにGoの標準ライブラリや、github.com/user/repo/mypackage のようにリモートリポジトリから取得されるパッケージのパス。これらは通常、GOPATH やモジュールキャッシュに基づいて解決されます。
    • ローカルインポートパス(相対インポートパス): ./util../common のように、現在のソースファイルからの相対パスで指定されるインポートパス。これらは通常、同じプロジェクト内のローカルなパッケージを指します。
  • 擬似インポートパス (Pseudo-import paths): Goツールチェインが内部的に使用する特別なインポートパスの形式です。これは、ファイルシステム上の絶対パスを基にして生成され、特にローカルなソースディレクトリからビルドされるパッケージ(例: go build . でビルドされるパッケージ)を、標準のインポートパスを持つパッケージと区別するために用いられます。例えば、/home/user/project/src/main.go からビルドされるパッケージは、内部的に _/home/user/project/src のような擬似インポートパスを持つことがあります。これにより、同じ名前のパッケージが異なる場所にある場合に衝突を避けることができます。
  • cmd/go: Go言語の公式コマンドラインツールであり、Goプログラムのビルド、テスト、依存関係管理など、Go開発のあらゆる側面を管理する中心的なツールです。このツールは、Goのソースコードをコンパイルし、リンクして実行可能バイナリを生成する複雑なプロセスを内部的に実行します。
  • リンカ (ld): コンパイルされたオブジェクトファイル(.o ファイルなど)を結合し、必要なライブラリとリンクして、最終的な実行可能バイナリを生成するツールです。Goのビルドプロセスにおいて重要な役割を担います。
  • Package 構造体: cmd/go 内部で、Goのパッケージに関するメタデータ(インポートパス、ディレクトリ、ソースファイルリスト、依存関係など)を保持するために使用されるデータ構造です。この構造体には、パッケージがローカルであるかどうかを示すフラグ(local)や、相対インポートを解決するためのプレフィックス(localPrefix)などのフィールドが含まれます。

技術的詳細

このコミットは、主に src/cmd/go/build.gosrc/cmd/go/pkg.go の2つのファイルにわたる変更を通じて、前述の問題に対処しています。

  1. ローカルパッケージの明示的なマーク付け (pkg.local = true):

    • src/cmd/go/build.gogoFilesPackage 関数内で、pkg.local = true が追加されました。この関数は、コマンドラインで直接指定されたGoファイル(例: go run main.go)からパッケージを構築する際に呼び出されます。これにより、これらのパッケージが「ローカル」であるという情報が Package 構造体に明示的に記録されるようになりました。この local フラグは、後述のローカルインポートの検証ロジックで利用されます。
  2. リンカの作業ディレクトリの変更:

    • src/cmd/go/build.go 内の gcToolchain.ld および gccgcToolchain.ld 関数において、リンカ (tool(archChar+"l")gccgo) を実行する際の作業ディレクトリが p.Dir から . (カレントディレクトリ) に変更されました。
    • この変更は、コミットメッセージの「-D 引数は常に擬似インポートパスであるべき」という点に関連しています。リンカがカレントディレクトリを基準に動作することで、内部的なパス解決がより一貫して擬似パスとして行われるようになり、ローカルなビルド環境におけるパスの曖昧さが解消されます。これにより、ローカルインポートが標準インポートパスとして誤って解釈されることを防ぎます。
  3. localPrefix の解決ロジックの変更:

    • src/cmd/go/pkg.goPackage.load 関数内で、p.localPrefix の設定方法が p.ImportPath から dirToImportPath(p.Dir) に変更されました。
    • localPrefix は、相対インポートパス(例: ./foo)を解決する際の基準となるパスです。以前はパッケージの ImportPath を使用していましたが、これはローカルパッケージの場合に擬似パスではない可能性がありました。dirToImportPath(p.Dir) を使用することで、localPrefix が常にパッケージの物理的なディレクトリから導出された擬似パスとなることが保証されます。これにより、相対インポートの解決が常にローカルなファイルシステム上のパスに正確にマッピングされるようになります。
  4. 非ローカルパッケージからのローカルインポートの禁止:

    • src/cmd/go/pkg.goPackage.load 関数内のインポート解決ロジックに、新しい検証が追加されました。
    • loadImport 関数でインポートされたパッケージ p1p1.local (ローカルパッケージ) であると判断された場合、さらに現在のパッケージ p!p.local (非ローカルパッケージ) であるかどうかをチェックします。
    • もし非ローカルパッケージがローカルパッケージをインポートしようとしている場合、PackageError が生成され、"local import %q in non-local package" というエラーメッセージが設定されます。
    • この変更は、バイナリ内のパッケージ重複問題に対する直接的な解決策です。非ローカルパッケージがローカルインポートを使用することを明示的に禁止することで、同じパッケージが異なるインポートパス(標準パスと擬似パス)で二重に認識され、コンパイルされるシナリオを防ぎます。これにより、ビルドの健全性が保たれ、予期せぬランタイムの問題が回避されます。

コアとなるコードの変更箇所

src/cmd/go/build.go

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -383,6 +383,7 @@ func goFilesPackage(gofiles []string) *Package {
 
 	bp, err := ctxt.ImportDir(dir, 0)
 	pkg := new(Package)
+	pkg.local = true // ここでローカルパッケージとしてマーク
 	pkg.load(&stk, bp, err)
 	pkg.localPrefix = dirToImportPath(dir)
 	pkg.ImportPath = "command-line-arguments"
@@ -1202,7 +1203,7 @@ func (gcToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s
 
 func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
 	importArgs := b.includeArgs("-L", allactions)
-	return b.run(p.Dir, p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg)
+	return b.run(".", p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg) // リンカの作業ディレクトリを '.' に変更
 }
 
 func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
@@ -1284,7 +1285,7 @@ func (tools gccgcToolchain) ld(b *builder, p *Package, out string, allactions []
 		ldflags = append(ldflags, afile)
 	}
 	ldflags = append(ldflags, cgoldflags...)
-	return b.run(p.Dir, p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(\", ldflags, \"-Wl,-)\")
+	return b.run(".", p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(\", ldflags, \"-Wl,-)\") // リンカの作業ディレクトリを '.' に変更
 }
 
 func (gccgcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
@@ -1308,6 +1309,9 @@ func (b *builder) gccld(p *Package, out string, flags []string, obj []string) er
 
 // gccCmd returns a gcc command line prefix
 func (b *builder) gccCmd(objdir string) []string {
+	// NOTE: env.go's mkEnv knows that the first three
+	// strings returned are "gcc", "-I", objdir (and cuts them off).
+	// コメント追加
 	// TODO: HOST_CC?
 	a := []string{"gcc", "-I", objdir, "-g", "-O2"}

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -279,9 +279,8 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
 	p.copyBuild(bp)
 
 	// The localPrefix is the path we interpret ./ imports relative to.
-	// Now that we've fixed the import path, it's just the import path.
 	// Synthesized main packages sometimes override this.
-	p.localPrefix = p.ImportPath // 変更前
+	p.localPrefix = dirToImportPath(p.Dir) // localPrefix を物理ディレクトリから導出
 
 	if err != nil {
 		p.Incomplete = true
@@ -343,6 +342,16 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
 		}
 		p1 := loadImport(path, p.Dir, stk, p.build.ImportPos[path])
 		if p1.local {
+			// 非ローカルパッケージがローカルパッケージをインポートしようとした場合のエラーチェック
+			if !p.local && p.Error == nil {
+				p.Error = &PackageError{
+					ImportStack: stk.copy(),
+					Err:         fmt.Sprintf("local import %q in non-local package", path),
+				}
+				pos := p.build.ImportPos[path]
+				if len(pos) > 0 {
+					p.Error.Pos = pos[0].String()
+				}
+			}
 			path = p1.ImportPath
 			importPaths[i] = path
 		}

コアとなるコードの解説

  • src/cmd/go/build.go の変更:

    • goFilesPackage 関数における pkg.local = true の追加は、go build . のようにカレントディレクトリのGoファイルをビルドする際に、そのパッケージがローカルなものであることを明示的にツールチェインに伝えるためのものです。これにより、pkg.go での検証ロジックが正しく機能するようになります。
    • リンカの実行コマンドにおける p.Dir から . へ変更は、リンカが常にカレントディレクトリを基準にパスを解決するように強制します。これは、ローカルインポートが擬似パスとして扱われるべきであるという原則を強化し、パス解決の混乱を防ぎます。
  • src/cmd/go/pkg.go の変更:

    • p.localPrefix = dirToImportPath(p.Dir) への変更は、相対インポートの解決基準を、パッケージの物理的なディレクトリから導出された擬似パスに固定します。これにより、相対インポートが常に意図したローカルなパッケージに正確にマッピングされるようになります。
    • 最も重要な変更は、if p1.local { if !p.local && p.Error == nil { ... } } ブロックの追加です。これは、インポートされるパッケージ (p1) がローカルであるにもかかわらず、インポートする側のパッケージ (p) が非ローカルである場合にエラーを発生させるロジックです。このチェックにより、非ローカルパッケージがローカルインポートを使用する(そしてバイナリ重複を引き起こす)という問題のあるシナリオがビルド時に検出され、防止されるようになります。

これらの変更は、Goのビルドシステムにおけるローカルインポートのセマンティクスを明確にし、ビルドの信頼性と予測可能性を大幅に向上させました。

関連リンク

参考にした情報源リンク

  • Go Gerrit Change-ID: 5787055 (コミットメッセージに記載されているGerritの変更リストID)
  • Go言語のビルドプロセスに関する一般的な情報源(Goの公式ブログやドキュメントなど)
  • Goのソースコードリポジトリ: https://github.com/golang/go
  • Goのパッケージ構造とビルドに関する議論(Goコミュニティのフォーラムやメーリングリストなど)