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

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

このコミットは、Go言語のツールチェインの一部である cmd/pack のテストファイル pack_test.go における変更です。cmd/pack は、Goのパッケージアーカイブを操作するためのコマンドであり、このテストは、その機能が正しく動作することを確認するためのものです。

コミット

commit 576318c7bd9c1deeb5877df65dc26aeb53999ee2
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 17 16:25:38 2014 -0400

    cmd/pack: avoid ./ import in test (fix Windows build)
    
    It is possible to use ./ imports on Windows but it
    requires some extra command-line work
    ('go build' does this automatically, but we can't use 'go build' here).
    
    Instead, use an ordinary import and -I/-L, which are easier to use.
    
    LGTM=iant
    R=iant
    CC=golang-codereviews
    https://golang.org/cl/89040043

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

https://github.com/golang/go/commit/576318c7bd9c1deeb5877df65dc26aeb53999ee2

元コミット内容

cmd/pack のテストにおいて、相対パスでのインポート (./) を使用しないように変更し、Windowsでのビルド問題を修正しました。Windowsでは ./ インポートも可能ですが、go build が自動的に処理するような追加のコマンドライン作業が必要となります。このテストでは go build を直接使用できないため、代わりに通常のインポートパスと -I/-L フラグを使用するように変更されました。これにより、より簡単にテストを実行できるようになります。

変更の背景

この変更の背景には、Go言語のビルドシステムにおけるパス解決の挙動、特にWindows環境での特殊性があります。Goのパッケージは通常、GOPATHやモジュールパスに基づいて解決されますが、テストコード内などで一時的なパッケージを扱う場合、相対パス (./) を使用することがあります。

しかし、Windows環境では、コマンドラインツールが相対パスを解釈する方法に違いがあり、./ を含むインポートパスが正しく解決されない、または追加の作業が必要となるケースがありました。具体的には、go build コマンドはこのような相対パスを自動的に処理する内部ロジックを持っていますが、cmd/pack のテストのように、go build を直接使わずに go tool コマンド(例えば go tool 6ggo tool 6l)を直接呼び出す場合、この自動処理の恩恵を受けられません。

このコミットは、Windows環境でのビルドの安定性を確保し、テストの実行を容易にすることを目的としています。相対パスのインポートに依存するのではなく、より汎用的なインポートメカニズムと、コンパイラ・リンカへの明示的なパス指定(-I および -L フラグ)を使用することで、プラットフォーム間の互換性を高めています。

前提知識の解説

Go言語のパッケージとインポートパス

Go言語では、コードは「パッケージ」という単位で管理されます。他のパッケージのコードを利用するには、import ステートメントを使用します。インポートパスは、通常、GoモジュールパスやGOPATHからの相対パスで指定されます。例えば、import "fmt" は標準ライブラリの fmt パッケージをインポートします。

相対インポートパス (./)

Goでは、同じモジュール内のローカルなパッケージをインポートする際に、import "./mypackage" のように相対パスを使用することが可能です。これは、特にテストや一時的なコードで、GOPATHやモジュールパスに依存せずにローカルなディレクトリ構造を直接参照したい場合に便利です。しかし、この相対パスの解決は、ビルドツールやOSによって挙動が異なる場合があります。

go build コマンド

go build は、Goのソースコードをコンパイルして実行可能ファイルを生成するための主要なコマンドです。このコマンドは、依存関係の解決、パッケージのビルド順序の決定、プラットフォーム固有の調整など、多くの複雑な処理を自動的に行います。相対インポートパスの解決も、go build が内部で適切に処理する機能の一つです。

go tool コマンド

go tool は、Goツールチェインに含まれる低レベルなツール(コンパイラ、リンカ、アセンブラなど)を直接実行するためのコマンドです。例えば、go tool compile はGoコンパイラを、go tool link はGoリンカを呼び出します。これらのツールを直接使用する場合、go build が提供するような高レベルな自動処理は行われないため、必要なオプション(例えばインクルードパスやライブラリパス)を明示的に指定する必要があります。

コンパイラ/リンカの -I および -L フラグ

  • -I (Include path): コンパイラ(例: go tool compile または 6g)に、パッケージの定義ファイル(.a ファイルなど)を探すディレクトリを指定します。Goのコンパイラは、インポートされたパッケージの型情報などを .a ファイルから読み込みます。
  • -L (Library path): リンカ(例: go tool link または 6l)に、リンクするライブラリファイル(.a ファイルなど)を探すディレクトリを指定します。リンカは、実行可能ファイルを生成する際に、必要なライブラリのコードを結合します。

これらのフラグは、go tool を直接使用してコンパイルやリンクを行う際に、依存するパッケージやライブラリの場所を明示的に指定するために不可欠です。

Windowsにおけるパス解決の特殊性

Windowsでは、パスの区切り文字がバックスラッシュ(\)であることや、コマンドプロンプトやPowerShellでのパスの解釈方法がUnix系OSと異なる場合があります。特に、相対パスの解釈や、特定のコマンドが期待するパス形式には注意が必要です。このコミットで言及されている「./ imports on Windows but it requires some extra command-line work」とは、このようなWindows環境特有のパス解決の複雑さを指しています。

技術的詳細

このコミットの技術的な核心は、Goのビルドプロセスにおけるパッケージの解決方法を、プラットフォームに依存しない、より堅牢な方法に変更した点にあります。

元のコードでは、pack_test.go 内でテスト対象のGoプログラムを生成する際に、import "./large" という相対インポートパスを使用していました。これは、テストのために一時的に作成された large パッケージをインポートするためのものでした。

// 変更前
package main
import "./large"
var V large.T
func main() {
    println("ok")
}

この相対インポートは、go build コマンドを使用する通常の開発フローでは問題ありません。go build は、内部的にこの ./ を現在のディレクトリに解決し、適切にコンパイルパスを設定します。

しかし、pack_test.go のテストでは、go build を直接使用せず、go tool コマンド(具体的には go tool 6g (コンパイラ) と go tool 6l (リンカ))を直接呼び出してコンパイルとリンクを行っていました。

// 変更前
run("go", "tool", char+"g", "main.go") // コンパイル
run("go", "tool", char+"l", "-o", "a.out", "main."+char) // リンク

この go tool を直接呼び出す方法では、go build が提供する相対パスの自動解決機能が利用できません。特にWindows環境では、./ インポートが正しく解釈されず、コンパイルエラーやリンクエラーが発生する可能性がありました。コミットメッセージにある「It is possible to use ./ imports on Windows but it requires some extra command-line work」とは、この go tool を使う場合に、手動でパスを設定する必要があることを示唆しています。

この問題を解決するため、コミットでは以下の2つの変更が行われました。

  1. インポートパスの変更: import "./large"import "large" に変更しました。これにより、large パッケージは相対パスではなく、通常のパッケージ名として扱われるようになります。

    // 変更後
    package main
    import "large" // 相対パスから通常のパッケージ名へ変更
    var V large.T
    func main() {
        println("ok")
    }
    
  2. コンパイラ/リンカへのパス指定: go tool コマンドを呼び出す際に、-I (インクルードパス) と -L (ライブラリパス) フラグを追加し、現在のディレクトリ (.) を明示的に指定しました。

    // 変更後
    run("go", "tool", char+"g", "-I", ".", "main.go") // コンパイル時に現在のディレクトリをインクルードパスに追加
    run("go", "tool", char+"l", "-L", ".", "-o", "a.out", "main."+char) // リンク時に現在のディレクトリをライブラリパスに追加
    

この変更により、go toollarge パッケージを通常のインポートとして扱い、-I .-L . によって、現在のディレクトリに large パッケージのコンパイル済みアーカイブ(.a ファイル)が存在することを明示的に伝えることができます。これにより、Windows環境を含むすべてのプラットフォームで、pack_test.go が安定して動作するようになりました。

このアプローチは、go build のような高レベルなコマンドが提供する抽象化を利用できない場合に、低レベルなツール(go tool)を適切に設定する方法を示す良い例です。

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

--- a/src/cmd/pack/pack_test.go
+++ b/src/cmd/pack/pack_test.go
@@ -247,7 +247,7 @@ func TestLargeDefs(t *testing.T) {
 	main := filepath.Join(dir, "main.go")
 	prog := `
 		package main
-		import "./large"
+		import "large"
 		var V large.T
 		func main() {
 			println("ok")
@@ -267,8 +267,8 @@ func TestLargeDefs(t *testing.T) {
 	run("go", "build", "cmd/pack") // writes pack binary to dir
 	run("go", "tool", char+"g", "large.go")
 	run("./pack", "grc", "large.a", "large."+char)
-\trun("go", "tool", char+"g", "main.go")
-\trun("go", "tool", char+"l", "-o", "a.out", "main."+char)
+\trun("go", "tool", char+"g", "-I", ".", "main.go")
+\trun("go", "tool", char+"l", "-L", ".", "-o", "a.out", "main."+char)
 	out := run("./a.out")
 	if out != "ok\\n" {
 		t.Fatal("incorrect output: %q, want %q", out, "ok\\n")

コアとなるコードの解説

上記の差分は、src/cmd/pack/pack_test.go ファイル内の TestLargeDefs 関数における変更を示しています。

  1. インポートパスの変更:

    -		import "./large"
    +		import "large"
    

    この変更は、テスト内で動的に生成される main.go ファイル内のインポートステートメントを修正しています。元々は import "./large" と相対パスで large パッケージをインポートしていましたが、これを import "large" という通常のパッケージ名でのインポートに変更しました。これにより、Goのツールチェインがパッケージを解決する際の挙動が、相対パスの特殊な処理から、より標準的なパッケージ解決メカニズムに移行します。

  2. コンパイラとリンカのオプション追加:

    -\trun("go", "tool", char+"g", "main.go")
    -\trun("go", "tool", char+"l", "-o", "a.out", "main."+char)
    +\trun("go", "tool", char+"g", "-I", ".", "main.go")
    +\trun("go", "tool", char+"l", "-L", ".", "-o", "a.out", "main."+char)
    

    この変更は、go tool コマンドを直接呼び出してコンパイル(char+"g"6g8g など、アーキテクチャに応じたGoコンパイラを指す)およびリンク(char+"l"6l8l など、アーキテクチャに応じたGoリンカを指す)を行う部分にオプションを追加しています。

    • char+"g", "-I", ".", "main.go": コンパイラ (6g など) を実行する際に、-I . オプションを追加しています。これは、コンパイラに対して、現在のディレクトリ (.) をインポートパス(パッケージの定義ファイルを探す場所)として含めるように指示します。これにより、import "large" で参照される large パッケージのコンパイル済みファイル(large.a など)が現在のディレクトリから見つけられるようになります。
    • char+"l", "-L", ".", "-o", "a.out", "main."+char): リンカ (6l など) を実行する際に、-L . オプションを追加しています。これは、リンカに対して、現在のディレクトリ (.) をライブラリパス(リンクするライブラリファイルを探す場所)として含めるように指示します。これにより、main パッケージが依存する large パッケージのコンパイル済みファイルが、最終的な実行可能ファイル a.out に正しくリンクされるようになります。

これらの変更により、pack_test.go は、go build の自動的なパス解決に依存することなく、低レベルな go tool コマンドを直接使用する場合でも、Windowsを含むすべてのプラットフォームで安定して動作するようになりました。

関連リンク

参考にした情報源リンク