[インデックス 13842] ファイルの概要
このコミットは、Go言語のコマンドラインツール go
において、GOPATH
環境変数に相対パスが設定された場合にそれを拒否し、エラーとして扱うように変更を加えるものです。これにより、ユーザーが意図しない動作を避け、GOPATH
の設定に関する混乱を解消することを目的としています。
コミット
commit 916ccbf165d3322139381ccd4e0df923354785cd
Author: Francisco Souza <franciscossouza@gmail.com>
Date: Mon Sep 17 13:44:55 2012 -0700
cmd/go: reject relative values for GOPATH
Fixes #4062.
R=rsc, dave, r
CC=golang-dev, nicksaika
https://golang.org/cl/6488129
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/916ccbf165d3322139381ccd4e0df923354785cd
元コミット内容
cmd/go: reject relative values for GOPATH
Fixes #4062.
R=rsc, dave, r
CC=golang-dev, nicksaika
https://golang.org/cl/6488129
変更の背景
この変更は、Go言語のIssue #4062「go
command should reject relative GOPATH
entries」を修正するために行われました。
当時のGoのツールチェインでは、GOPATH
環境変数に相対パスを設定することが可能でした。しかし、これはユーザーにとって混乱の原因となることがありました。例えば、GOPATH=.
のように設定した場合、go
コマンドを実行するカレントディレクトリによって GOPATH
の実体が変化してしまい、ビルドの再現性や予測可能性が損なわれる可能性がありました。また、異なるディレクトリで同じコマンドを実行した場合に、異なる結果になるという問題も発生しやすかったです。
Goの設計思想として、ビルド環境は明確で予測可能であるべきという考え方があります。相対パスの GOPATH
はこの原則に反するため、明示的に絶対パスを要求することで、より堅牢で理解しやすい開発環境を提供することが目的とされました。
前提知識の解説
GOPATH
GOPATH
は、Go言語のワークスペースのルートディレクトリを指定する環境変数です。Go 1.11以前のモジュールシステムが導入される前は、Goのソースコード、コンパイルされたパッケージ、実行可能バイナリが配置される場所を定義するために非常に重要でした。
GOPATH
は通常、複数のディレクトリパスをコロン(Unix/Linux)またはセミコロン(Windows)で区切って指定できます。Goツールは、これらのパスを順番に検索して、インポートされたパッケージや依存関係を見つけます。
GOPATH
の各エントリは、以下のサブディレクトリを持つことが期待されます。
src
: Goのソースコードが配置されます。例えば、github.com/user/repo
というリポジトリは$GOPATH/src/github.com/user/repo
に配置されます。pkg
: コンパイルされたパッケージオブジェクト(.a
ファイルなど)が配置されます。bin
:go install
コマンドでビルドされた実行可能バイナリが配置されます。
このコミットの時点では、GOPATH
はGoプロジェクトの構造とビルドプロセスにおいて中心的な役割を担っていました。
GOROOT
GOROOT
は、Go言語のSDK(Standard Development Kit)がインストールされているルートディレクトリを指定する環境変数です。Goの標準ライブラリやツールチェインのバイナリなどが含まれています。通常、ユーザーが明示的に設定する必要はなく、Goのインストール時に自動的に設定されるか、デフォルトの場所にインストールされます。
このコミットのコードでは、GOPATH
が GOROOT
と同じ値に設定されている場合に警告を出す処理が含まれています。これは、GOPATH
を GOROOT
と同じにしても、ユーザーが期待するような効果が得られない(例えば、自分のプロジェクトコードが標準ライブラリと同じ場所に置かれることになり、管理が複雑になる)ためです。
build.IsLocalImport(p)
build.IsLocalImport(p)
は、Goの標準ライブラリ go/build
パッケージに含まれる関数です。この関数は、与えられたパス p
がローカルな(つまり、絶対パスではない)インポートパスであるかどうかを判定します。
具体的には、以下の条件のいずれかを満たす場合に true
を返します。
- パスがドット(
.
)またはドットスラッシュ(./
)で始まる場合(カレントディレクトリからの相対パス)。 - パスがドットドットスラッシュ(
../
)で始まる場合(親ディレクトリからの相対パス)。 - パスがスラッシュ(
/
)で始まらない場合(絶対パスではない)。
このコミットでは、GOPATH
の各エントリが絶対パスであるべきという要件を強制するために、この関数が利用されています。GOPATH
のエントリが build.IsLocalImport
によってローカルインポートと判定された場合、それは相対パスであると見なされ、エラーとして処理されます。
技術的詳細
このコミットの主要な変更は、go
コマンドの起動時に GOPATH
環境変数の値を検証するロジックを追加した点にあります。
従来の go
コマンドは、GOPATH
に相対パスが設定されていても、それをそのまま使用しようとしました。しかし、これは前述の通り、ビルドの予測不可能性や混乱を招く原因となっていました。例えば、GOPATH=./mygo
と設定した場合、go build
を実行するディレクトリによって mygo
が指す絶対パスが変わり、異なる場所でビルドすると依存関係の解決に失敗したり、意図しないパッケージが使われたりする可能性がありました。
この変更では、main.go
内で GOPATH
の値を取得し、それをコロン(:
)で分割して個々のパスエントリに分解します。そして、それぞれのパスエントリに対して build.IsLocalImport(p)
関数を呼び出し、そのエントリが相対パスであるかどうかをチェックします。
もし一つでも相対パスのエントリが見つかった場合、go
コマンドはエラーメッセージを標準エラー出力に表示し、終了コード 2
でプログラムを終了します。エラーメッセージは、「go: GOPATH entry is relative; must be absolute path: %q. Run 'go help gopath' for usage.
」という形式で、ユーザーに絶対パスを使用するよう促し、go help gopath
コマンドで詳細な情報を参照するように指示します。
この変更により、GOPATH
の設定ミスによる潜在的な問題を未然に防ぎ、Go開発環境の堅牢性と予測可能性が向上しました。また、test.bash
には、この新しい検証ロジックが正しく機能することを確認するためのテストケースが追加されています。具体的には、GOPATH=.
や GOPATH=:/path/to/testdata:.
のように相対パスを含む GOPATH
で go build
を実行した場合に、コマンドが失敗することを確認するテストが追加されました。
コアとなるコードの変更箇所
このコミットでは、主に以下の2つのファイルが変更されています。
src/cmd/go/main.go
:go
コマンドのメインロジックが含まれるファイル。GOPATH
の検証ロジックが追加されました。src/cmd/go/test.bash
:go
コマンドのテストスクリプト。GOPATH
の相対パス拒否に関する新しいテストケースが追加されました。
src/cmd/go/main.go
の変更
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -127,6 +127,13 @@ func main() {
// which is not what most people want when they do it.
if gopath := os.Getenv("GOPATH"); gopath == runtime.GOROOT() {
fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\\n", gopath)
+ } else {
+ for _, p := range strings.Split(gopath, ":") {
+ if build.IsLocalImport(p) {
+ fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\\nRun 'go help gopath' for usage.\\n", p)
+ os.Exit(2)
+ }
+ }
}
for _, cmd := range commands {
src/cmd/go/test.bash
の変更
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -125,6 +125,17 @@ elif ! test -x testdata/bin1/helloworld; then
\tok=false
fi
\n
+# Reject relative paths in GOPATH.
+if GOPATH=. ./testgo build testdata/src/go-cmd-test/helloworld.go; then
+ echo 'GOPATH="." go build should have failed, did not'
+ ok=false
+fi
+\n
+if GOPATH=:$(pwd)/testdata:. ./testgo build go-cmd-test; then
+ echo 'GOPATH=":'$(pwd)'/testdata:." go build should have failed, did not'
+ ok=false
+fi
+\n
if $ok; then
\techo PASS
else
コアとなるコードの解説
src/cmd/go/main.go
の変更点
追加されたコードブロックは、GOPATH
が GOROOT
と同じでない場合に実行されます。
} else {
for _, p := range strings.Split(gopath, ":") {
if build.IsLocalImport(p) {
fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\\nRun 'go help gopath' for usage.\\n", p)
os.Exit(2)
}
}
}
strings.Split(gopath, ":")
:os.Getenv("GOPATH")
で取得したGOPATH
の文字列を、コロン(:
)で分割し、個々のパスエントリの文字列スライスを作成します。for _, p := range ...
: 分割された各パスエントリp
についてループ処理を行います。if build.IsLocalImport(p)
: 各パスエントリp
が相対パスであるかどうかをbuild.IsLocalImport
関数を使ってチェックします。fmt.Fprintf(os.Stderr, ...)
: もしp
が相対パスであれば、エラーメッセージを標準エラー出力に表示します。メッセージは、GOPATH
のエントリが相対パスであり、絶対パスである必要があることを明確に伝えます。また、go help gopath
を参照するよう促します。os.Exit(2)
: エラーが検出された場合、プログラムは終了コード2
で即座に終了します。これにより、不正なGOPATH
設定でのビルドやコマンド実行を防ぎます。
src/cmd/go/test.bash
の変更点
追加されたテストケースは、GOPATH
に相対パスが含まれる場合に go
コマンドがエラーで終了することを確認します。
# Reject relative paths in GOPATH.
if GOPATH=. ./testgo build testdata/src/go-cmd-test/helloworld.go; then
echo 'GOPATH="." go build should have failed, did not'
ok=false
fi
if GOPATH=:$(pwd)/testdata:. ./testgo build go-cmd-test; then
echo 'GOPATH=":'$(pwd)'/testdata:." go build should have failed, did not'
ok=false
fi
- 最初のテストケース:
GOPATH=.
と設定し、./testgo build ...
を実行します。GOPATH=.
はカレントディレクトリを指す相対パスであるため、go build
は失敗するはずです。もし成功した場合はエラーメッセージを表示し、ok
フラグをfalse
に設定します。 - 2番目のテストケース:
GOPATH=:$(pwd)/testdata:.
と設定し、./testgo build ...
を実行します。このGOPATH
は、空のパス、絶対パス($(pwd)/testdata
)、そして相対パス(.
)を組み合わせたものです。相対パスが含まれているため、この場合もgo build
は失敗するはずです。同様に、成功した場合はエラーメッセージを表示し、ok
フラグをfalse
に設定します。
これらのテストケースは、GOPATH
の相対パス拒否機能が意図通りに動作することを保証するための重要な追加です。
関連リンク
- Go Issue #4062: https://github.com/golang/go/issues/4062
- Go CL 6488129: https://golang.org/cl/6488129
参考にした情報源リンク
- Go言語公式ドキュメント:
GOPATH
(当時のバージョンに基づく情報) go/build
パッケージのドキュメント:IsLocalImport
関数- Go言語のIssueトラッカー
- Go言語のソースコードリポジトリ
- Go言語に関する技術ブログやフォーラム(
GOPATH
の問題に関する議論) os.Getenv
およびstrings.Split
関数のGo言語ドキュメントfmt.Fprintf
およびos.Exit
関数のGo言語ドキュメント- Bashスクリプトの条件分岐と環境変数設定に関する一般的な知識