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

[インデックス 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のインストール時に自動的に設定されるか、デフォルトの場所にインストールされます。

このコミットのコードでは、GOPATHGOROOT と同じ値に設定されている場合に警告を出す処理が含まれています。これは、GOPATHGOROOT と同じにしても、ユーザーが期待するような効果が得られない(例えば、自分のプロジェクトコードが標準ライブラリと同じ場所に置かれることになり、管理が複雑になる)ためです。

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:. のように相対パスを含む GOPATHgo build を実行した場合に、コマンドが失敗することを確認するテストが追加されました。

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

このコミットでは、主に以下の2つのファイルが変更されています。

  1. src/cmd/go/main.go: go コマンドのメインロジックが含まれるファイル。GOPATH の検証ロジックが追加されました。
  2. 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 の変更点

追加されたコードブロックは、GOPATHGOROOT と同じでない場合に実行されます。

	} 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)
			}
		}
	}
  1. strings.Split(gopath, ":"): os.Getenv("GOPATH") で取得した GOPATH の文字列を、コロン(:)で分割し、個々のパスエントリの文字列スライスを作成します。
  2. for _, p := range ...: 分割された各パスエントリ p についてループ処理を行います。
  3. if build.IsLocalImport(p): 各パスエントリ p が相対パスであるかどうかを build.IsLocalImport 関数を使ってチェックします。
  4. fmt.Fprintf(os.Stderr, ...): もし p が相対パスであれば、エラーメッセージを標準エラー出力に表示します。メッセージは、GOPATH のエントリが相対パスであり、絶対パスである必要があることを明確に伝えます。また、go help gopath を参照するよう促します。
  5. 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
  1. 最初のテストケース: GOPATH=. と設定し、./testgo build ... を実行します。GOPATH=. はカレントディレクトリを指す相対パスであるため、go build は失敗するはずです。もし成功した場合はエラーメッセージを表示し、ok フラグを false に設定します。
  2. 2番目のテストケース: GOPATH=:$(pwd)/testdata:. と設定し、./testgo build ... を実行します。この GOPATH は、空のパス、絶対パス($(pwd)/testdata)、そして相対パス(.)を組み合わせたものです。相対パスが含まれているため、この場合も go build は失敗するはずです。同様に、成功した場合はエラーメッセージを表示し、ok フラグを false に設定します。

これらのテストケースは、GOPATH の相対パス拒否機能が意図通りに動作することを保証するための重要な追加です。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント: GOPATH (当時のバージョンに基づく情報)
  • go/build パッケージのドキュメント: IsLocalImport 関数
  • Go言語のIssueトラッカー
  • Go言語のソースコードリポジトリ
  • Go言語に関する技術ブログやフォーラム(GOPATH の問題に関する議論)
  • os.Getenv および strings.Split 関数のGo言語ドキュメント
  • fmt.Fprintf および os.Exit 関数のGo言語ドキュメント
  • Bashスクリプトの条件分岐と環境変数設定に関する一般的な知識