[インデックス 11050] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、GOPATH
環境変数に重複するエントリが存在する場合に、それを検出し警告を発する機能を追加するものです。これにより、開発者はGOPATH
の設定ミスを早期に発見し、ビルドプロセスの健全性を保つことができます。
コミット
go/build: handle and warn of duplicate GOPATH entries
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/5519050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c7e91724c0e1f514982e90d7d08bb2c291a2bc43
元コミット内容
commit c7e91724c0e1f514982e90d7d08bb2c291a2bc43
Author: Andrew Gerrand <adg@golang.org>
Date: Mon Jan 9 14:24:05 2012 +1100
go/build: handle and warn of duplicate GOPATH entries
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/5519050
---
src/pkg/go/build/path.go | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/pkg/go/build/path.go b/src/pkg/go/build/path.go
index 7a281800c2..bb9b8ca642 100644
--- a/src/pkg/go/build/path.go
+++ b/src/pkg/go/build/path.go
@@ -157,6 +157,7 @@ func init() {
Path = []*Tree{t}
}\n \n+Loop:\n \tfor _, p := range filepath.SplitList(os.Getenv(\"GOPATH\")) {\n \t\tif p == \"\" {\n \t\t\tcontinue\n@@ -166,6 +167,21 @@ func init() {\n \t\t\tlog.Printf(\"invalid GOPATH %q: %v\", p, err)\n \t\t\tcontinue\n \t\t}\n+\n+\t\t// Check for dupes.\n+\t\t// TODO(alexbrainman): make this correct under windows (case insensitive).\n+\t\tfor _, t2 := range Path {\n+\t\t\tif t2.Path != t.Path {\n+\t\t\t\tcontinue\n+\t\t\t}\n+\t\t\tif t2.Goroot {\n+\t\t\t\tlog.Printf(\"GOPATH is the same as GOROOT: %q\", t.Path)\n+\t\t\t} else {\n+\t\t\t\tlog.Printf(\"duplicate GOPATH entry: %q\", t.Path)\n+\t\t\t}\n+\t\t\tcontinue Loop\n+\t\t}\n+\n \t\tPath = append(Path, t)\n \t\tgcImportArgs = append(gcImportArgs, \"-I\", t.PkgDir())\n \t\tldImportArgs = append(ldImportArgs, \"-L\", t.PkgDir())\n```
## 変更の背景
Go言語のビルドシステムにおいて、`GOPATH`環境変数は非常に重要な役割を果たします。これはGoのソースコード、コンパイル済みパッケージ、実行可能バイナリが配置されるワークスペースのパスを指定するものです。しかし、ユーザーが誤って`GOPATH`に同じディレクトリを複数回指定してしまうケースが考えられます。
このコミットが導入される以前は、`GOPATH`に重複するエントリが存在しても、Goのビルドシステムはそれを黙って処理していました。これは、ビルドの非効率性や、開発者が自身の環境設定に問題があることに気づきにくいという問題を引き起こす可能性がありました。例えば、同じパッケージが複数回スキャンされたり、予期しないパスからパッケージがロードされたりするリスクがありました。
この変更の背景には、Go開発者の利便性とビルドシステムの堅牢性を向上させる目的があります。重複する`GOPATH`エントリを検出し、明確な警告メッセージを出すことで、開発者は自身の環境設定の誤りを迅速に特定し、修正できるようになります。これにより、よりクリーンで効率的な開発環境が促進されます。
## 前提知識の解説
* **GOPATH**: `GOPATH`は、Go言語のワークスペースのルートディレクトリを指定する環境変数です。Goのツール群(`go build`, `go install`, `go get`など)は、この`GOPATH`で指定されたディレクトリ構造に基づいてソースコード、パッケージ、実行ファイルを検索・配置します。通常、`GOPATH`は`src`、`pkg`、`bin`の3つのサブディレクトリを持ちます。
* `src`: Goのソースコードが配置されます。`go get`で取得した外部パッケージもここにダウンロードされます。
* `pkg`: コンパイル済みのパッケージアーカイブが配置されます。
* `bin`: `go install`でビルドされた実行可能バイナリが配置されます。
`GOPATH`は複数のパスをコロン(Unix/Linux)またはセミコロン(Windows)で区切って指定できます。Goツールはこれらのパスを順番に検索します。
* **Go Build System**: Goのビルドシステムは、ソースコードをコンパイルし、実行可能ファイルやライブラリを生成する一連のプロセスです。`GOPATH`は、このビルドシステムが依存関係を解決し、ソースファイルを特定するために不可欠な情報を提供します。
* **`filepath.SplitList`**: Go標準ライブラリの`path/filepath`パッケージに含まれる関数です。この関数は、環境変数(例: `PATH`や`GOPATH`)のように、プラットフォーム固有のパス区切り文字(Unix/Linuxではコロン、Windowsではセミコロン)で区切られた文字列を個々のパスのリストに分割します。
* **`os.Getenv("GOPATH")`**: `os`パッケージの`Getenv`関数は、指定された環境変数の値を取得します。ここでは`GOPATH`環境変数の値を取得しています。
* **`log.Printf`**: Go標準ライブラリの`log`パッケージに含まれる関数で、フォーマットされた文字列を標準エラー出力(または設定された出力先)にログとして出力します。デバッグ情報や警告、エラーメッセージの表示によく使用されます。
## 技術的詳細
このコミットは、`src/pkg/go/build/path.go`ファイル内の`init()`関数(またはそれに相当する`GOPATH`を初期化するロジック)に、重複する`GOPATH`エントリを検出するための新しいロジックを追加しています。
変更の核心は、`GOPATH`の各エントリを処理するループ内で、既に処理済みのパスリスト(`Path`スライス)に対して現在のパスが重複していないかをチェックする部分です。
1. **`Loop:` ラベルの導入**: 新しい`Loop`ラベルが導入されています。これは、内側の重複チェックループ内で重複が検出された場合に、`continue Loop`ステートメントを使用して外側の`GOPATH`エントリを処理するループの次のイテレーションに直接ジャンプするために使用されます。これにより、重複するパスが`Path`スライスに誤って追加されるのを防ぎます。
2. **重複チェックロジック**:
```go
// Check for dupes.
// TODO(alexbrainman): make this correct under windows (case insensitive).
for _, t2 := range Path {
if t2.Path != t.Path {
continue
}
if t2.Goroot {
log.Printf("GOPATH is the same as GOROOT: %q", t.Path)
} else {
log.Printf("duplicate GOPATH entry: %q", t.Path)
}
continue Loop
}
```
このコードブロックは、現在処理中の`GOPATH`エントリ`t`のパス(`t.Path`)が、既に`Path`スライスに追加されている既存の`Tree`オブジェクト`t2`のパス(`t2.Path`)と一致するかどうかを確認します。
* `t2.Path != t.Path`の場合、重複ではないため、内側のループの次の`t2`に移動します。
* `t2.Path == t.Path`の場合、重複が検出されたことになります。
* `t2.Goroot`が`true`の場合(つまり、重複しているパスが`GOROOT`と同じである場合)、`"GOPATH is the same as GOROOT: %q"`という警告メッセージがログに出力されます。これは、`GOPATH`と`GOROOT`が同じパスを指しているという特殊なケースを警告します。
* それ以外の場合(一般的な`GOPATH`の重複)、`"duplicate GOPATH entry: %q"`という警告メッセージがログに出力されます。
* 警告メッセージが出力された後、`continue Loop`が実行され、外側の`GOPATH`エントリを処理するループの次のイテレーションにスキップします。これにより、重複するパスが`Path`スライスに二重に登録されるのを防ぎます。
この変更により、Goのビルドシステムは`GOPATH`の重複を検出し、ユーザーに通知することで、設定の誤りによる潜在的な問題を未然に防ぐことができるようになります。
## コアとなるコードの変更箇所
変更は`src/pkg/go/build/path.go`ファイルに集中しています。具体的には、`filepath.SplitList(os.Getenv("GOPATH"))`で`GOPATH`の各エントリをループ処理している箇所に、以下のコードブロックが追加されています。
```diff
--- a/src/pkg/go/build/path.go
+++ b/src/pkg/go/build/path.go
@@ -157,6 +157,7 @@ func init() {
Path = []*Tree{t}
}\n \n+Loop:\n \tfor _, p := range filepath.SplitList(os.Getenv(\"GOPATH\")) {\n \t\tif p == \"\" {\n \t\t\tcontinue\n@@ -166,6 +167,21 @@ func init() {\n \t\t\tlog.Printf(\"invalid GOPATH %q: %v\", p, err)\n \t\t\tcontinue\n \t\t}\n+\n+\t\t// Check for dupes.\n+\t\t// TODO(alexbrainman): make this correct under windows (case insensitive).\n+\t\tfor _, t2 := range Path {\n+\t\t\tif t2.Path != t.Path {\n+\t\t\t\tcontinue\n+\t\t\t}\n+\t\t\tif t2.Goroot {\n+\t\t\t\tlog.Printf(\"GOPATH is the same as GOROOT: %q\", t.Path)\n+\t\t\t} else {\n+\t\t\t\tlog.Printf(\"duplicate GOPATH entry: %q\", t.Path)\n+\t\t\t}\n+\t\t\tcontinue Loop\n+\t\t}\n+\n \t\tPath = append(Path, t)\n \t\tgcImportArgs = append(gcImportArgs, \"-I\", t.PkgDir())\n \t\tldImportArgs = append(ldImportArgs, \"-L\", t.PkgDir())\n```
## コアとなるコードの解説
追加されたコードは、`GOPATH`の各エントリが`Path`スライス(Goのビルドシステムが認識しているパスのリスト)に追加される前に、そのエントリが既に存在しないかを確認します。
1. **`Loop:`**: これはGoの`for`ループに付けられたラベルです。内側のループから外側の`for`ループの次のイテレーションに直接ジャンプするために使用されます。
2. **`for _, t2 := range Path`**: これは、現在までに処理され、`Path`スライスに追加されたすべての`Tree`オブジェクト(各`GOPATH`エントリを表す)を反復処理するループです。
3. **`if t2.Path != t.Path { continue }`**: この条件は、現在チェックしている`t2`のパスが、現在`GOPATH`から読み取って処理しようとしている`t`のパスと異なる場合、内側のループの次の`t2`にスキップします。つまり、同じパスが見つかるまで検索を続けます。
4. **重複検出と警告**:
* `if t2.Path == t.Path`が真の場合、重複する`GOPATH`エントリが見つかったことを意味します。
* `if t2.Goroot`は、重複しているパスが`GOROOT`(Goのインストールディレクトリ)と同じであるかどうかをチェックします。もしそうであれば、「GOPATHがGOROOTと同じです」という特定の警告メッセージが出力されます。これは、`GOPATH`を`GOROOT`と同じに設定することが推奨されないためです。
* そうでなければ、「重複するGOPATHエントリ」という一般的な警告メッセージが出力されます。
* `log.Printf`を使用して、これらの警告メッセージが標準エラー出力に表示されます。
5. **`continue Loop`**: 重複が検出され、警告が出力された後、このステートメントが実行されます。これにより、現在の`GOPATH`エントリ`t`は`Path`スライスに追加されず、外側の`for`ループは`GOPATH`の次のエントリの処理に進みます。これにより、`Path`スライスに重複するエントリが追加されるのを防ぎ、ビルドシステムが重複パスを複数回処理するのを避けます。
このコードは、`GOPATH`の解析と初期化の段階で、ユーザーが設定した`GOPATH`に問題がないかを事前にチェックし、問題があれば警告を出すことで、開発体験を向上させています。
## 関連リンク
* **Gerrit Change-ID**: `https://golang.org/cl/5519050` (GoプロジェクトのコードレビューシステムであるGerrit上の変更ページへのリンク)
## 参考にした情報源リンク
* Go言語公式ドキュメント (GOPATHに関する情報)
* Go言語のビルドシステムに関する一般的な知識
* `path/filepath`パッケージのドキュメント
* `os`パッケージのドキュメント
* `log`パッケージのドキュメント