[インデックス 14842] ファイルの概要
このコミットは、Go言語のgo
コマンドにおけるgo get
の挙動を改善し、特に新規ユーザーが直面しがちな環境設定の問題を解決することを目的としています。具体的には、go get
コマンドが$GOROOT
にパッケージをダウンロードすることを禁止し、$GOPATH
が$GOROOT
と同じ値に設定されている場合にエラーを出すように変更されました。これにより、$GOPATH
の適切な設定を強制し、sudo
使用時の環境変数マスク問題などによる混乱を防ぎます。
コミット
commit 593d8b0c1409d044472a2848de199eec498100b1
Author: Dave Cheney <dave@cheney.net>
Date: Thu Jan 10 09:57:01 2013 +1100
cmd/go: remove $GOROOT as a go get target
Fixes #4186.
Back in the day, before the Go 1.0 release, $GOROOT was mandatory for building from source. Fast forward to now and $GOPATH is mandatory and $GOROOT is optional, and mainly used by those who use the binary distribution in uncommon places.
For example, most novices at least know about `sudo` as they would have used it to install the binary tarball into /usr/local. It is logical they would use the `sudo` hammer to `go get` other Go packages when faced with a permission error talking about the path they just had to use `sudo` on last time.
Even if they had read the documentation and set $GOPATH, go get will not work as expected as `sudo` masks most environment variables.
llucky(~) % ~/go/bin/go env | grep GOPATH
GOPATH="/home/dfc"
lucky(~) % sudo ~/go/bin/go env | grep GOPATH
GOPATH=""
This CL therefore proposes to remove support for using `go get` to download source into $GOROOT.
This CL also proposes an error when GOPATH=$GOROOT, as this is another place where new Go users can get stuck.
Further discussion: https://groups.google.com/d/topic/golang-nuts/VIg3fjHiHRI/discussion
R=rsc, adg, minux.ma
CC=golang-dev
https://golang.org/cl/6941058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/593d8b0c1409d044472a2848de199eec498100b1
元コミット内容
cmd/go: remove $GOROOT as a go get target
このコミットは、go get
コマンドが$GOROOT
ディレクトリにソースコードをダウンロードする機能を削除します。また、$GOPATH
環境変数が$GOROOT
と同じ値に設定されている場合にエラーを発生させるように変更します。これは、Go 1.0リリース以前の$GOROOT
が必須だった時代から、$GOPATH
が必須となった現在のGoの環境設定の変更に対応し、特に新規ユーザーが直面する可能性のある混乱や権限の問題を解決することを目的としています。sudo
コマンドが環境変数をマスクする挙動が、go get
の動作を妨げる具体的な例として挙げられています。
変更の背景
この変更の背景には、Go言語の進化と、それによって生じたユーザー体験上の課題があります。
Go 1.0リリース以前は、Goのソースコードからビルドを行う際、$GOROOT
環境変数が必須でした。しかし、Go 1.0以降、Goのパッケージ管理と開発ワークフローは大きく変化し、$GOPATH
がGoパッケージのソースコード、ビルド済みバイナリ、およびパッケージオブジェクトを管理するための主要な場所として導入され、必須となりました。一方で$GOROOT
は、Goの標準ライブラリやツールチェーンがインストールされている場所を指すようになり、バイナリディストリビューションを使用するユーザーにとっては、その設定は任意となりました。
この移行期において、特にGoを使い始めたばかりの新規ユーザーは混乱に直面することがありました。
-
$GOROOT
へのgo get
: 多くの新規ユーザーは、Goのバイナリディストリビューションを/usr/local
のようなシステムディレクトリにsudo
を使ってインストールします。その後、go get
を使って外部パッケージをダウンロードしようとした際に、権限エラーに遭遇することがありました。彼らは、以前Goをインストールした際にsudo
を使った経験から、今回もsudo go get
を実行してしまう傾向がありました。しかし、sudo
はデフォルトで多くのユーザー環境変数をマスクするため、たとえユーザーが$GOPATH
を正しく設定していても、sudo
環境下では$GOPATH
が認識されず、go get
が$GOROOT
(通常はシステムディレクトリ)にダウンロードしようとして権限エラーとなる、という問題が発生していました。 コミットメッセージの例:llucky(~) % ~/go/bin/go env | grep GOPATH GOPATH="/home/dfc" lucky(~) % sudo ~/go/bin/go env | grep GOPATH GOPATH=""
この例が示すように、
sudo
を実行するとGOPATH
が空になり、go get
が意図しない挙動をする原因となっていました。 -
GOPATH=$GOROOT
の問題: 一部のユーザーは、$GOPATH
と$GOROOT
を同じディレクトリに設定してしまうことがありました。これは、Goのパッケージ管理の概念を完全に理解していない場合に起こりがちでした。しかし、$GOPATH
はユーザーが開発するプロジェクトのソースコードを配置する場所であり、$GOROOT
はGoの標準ライブラリが配置される場所であるため、これらを同じにすることは推奨されず、予期せぬ問題を引き起こす可能性がありました。
これらの問題は、Goの学習曲線における障壁となり、新規ユーザーのフラストレーションの原因となっていました。このコミットは、これらの一般的な落とし穴を排除し、go get
の挙動をより予測可能で堅牢なものにすることで、ユーザー体験を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の環境変数とコマンドに関する知識が不可欠です。
$GOROOT
$GOROOT
は、Go言語のインストールディレクトリを指す環境変数です。Goのコンパイラ、標準ライブラリ、およびその他のツールがこのディレクトリに配置されます。
- Go 1.0以前: Goのソースコードからビルドを行う場合、
$GOROOT
は必須であり、ユーザーのGoプロジェクトもこのディレクトリ構造内に配置されることが一般的でした。 - Go 1.0以降: Goのバイナリディストリビューションを使用する場合、
$GOROOT
は通常、Goのインストール時に自動的に設定されるか、デフォルトの場所にインストールされるため、明示的に設定する必要がない場合が多いです。$GOROOT
はGoの標準ライブラリの場所を示すものであり、ユーザーが開発するGoプロジェクトのソースコードを配置する場所ではありません。
$GOPATH
$GOPATH
は、Go言語のワークスペースのルートディレクトリを指す環境変数です。Go 1.0以降、Goのパッケージ管理と開発ワークフローの中心となりました。$GOPATH
で指定されたディレクトリは、以下の3つのサブディレクトリを持つことが期待されます。
src
: Goのソースコード(ユーザーが開発するプロジェクトやgo get
でダウンロードされた外部パッケージ)が配置されます。pkg
: コンパイルされたパッケージオブジェクトファイル(.a
ファイルなど)が配置されます。bin
:go install
でビルドされた実行可能バイナリが配置されます。
$GOPATH
は、Goのプロジェクトを開発する上で必須の概念であり、go get
、go build
、go install
などのコマンドは、この$GOPATH
の構造に基づいて動作します。複数のパスをコロン(Unix/Linux)またはセミコロン(Windows)で区切って設定することも可能で、Goツールはこれらのパスを順番に検索します。
go get
コマンド
go get
は、Goのパッケージをリモートリポジトリ(GitHub, Bitbucketなど)からダウンロードし、$GOPATH
で指定されたワークスペースのsrc
ディレクトリにインストールするコマンドです。依存関係も自動的に解決し、ダウンロードします。
sudo
コマンドと環境変数
sudo
(superuser do)は、Unix系オペレーティングシステムにおいて、他のユーザー(通常はrootユーザー)の権限でコマンドを実行するためのプログラムです。セキュリティ上の理由から、sudo
はデフォルトで、実行されるコマンドに渡される環境変数を制限またはリセットします。これは、悪意のある環境変数が特権コマンドの動作に影響を与えるのを防ぐためです。
この挙動により、ユーザーが$GOPATH
を正しく設定していても、sudo go get
を実行すると$GOPATH
がgo get
プロセスに引き継がれず、結果としてgo get
が$GOPATH
が設定されていないと判断し、意図しない動作(例: $GOROOT
へのダウンロード試行)を引き起こす可能性がありました。
Go 1.1の変更点(doc/go1.1.html
の変更から読み取れる情報)
このコミットは、Go 1.1のリリースノートの一部としてドキュメント化されています。doc/go1.1.html
の変更は、Go 1.1で導入されるgo
ツールの改善点として、以下の3点を挙げています。
- パッケージが見つからない場合のエラーメッセージの改善: パッケージが見つからない場合に、検索されたパスのリストを含むより詳細なエラーメッセージが表示されるようになります。
go get
の$GOROOT
へのダウンロード禁止:go get
コマンドは、パッケージソースをダウンロードする際のデフォルトの宛先として$GOROOT
を許可しなくなります。有効な$GOPATH
が必須となります。GOPATH=$GOROOT
の場合のエラー:$GOPATH
と$GOROOT
が同じ値に設定されている場合、go get
コマンドは失敗するようになります。
これらの変更は、Goのツールチェーンがより堅牢になり、ユーザーが環境設定の一般的な落とし穴にはまるのを防ぐことを目的としています。
技術的詳細
このコミットは、主にsrc/cmd/go/get.go
ファイルのロジックを変更することで、go get
コマンドの挙動を修正しています。また、関連するドキュメントファイル(doc/go1.1.html
, src/cmd/go/doc.go
, src/cmd/go/help.go
)も更新され、新しい挙動が反映されています。
src/cmd/go/get.go
の変更
このファイルはgo get
コマンドの主要なロジックを含んでいます。変更の核心は、パッケージのダウンロード先を決定する部分にあります。
変更前は、パッケージが見つからない場合、$GOPATH
の最初のディレクトリ、または$GOROOT
のいずれかをダウンロード先として考慮していました。特にGOPATH=$GOROOT
の場合の特別な処理も含まれていました。
変更後は、$GOROOT
をダウンロード先として考慮するロジックが完全に削除されました。代わりに、以下の2つのチェックが追加されました。
$GOPATH
が設定されていない場合のエラー:buildContext.GOPATH
が空の場合(つまり$GOPATH
が設定されていない場合)、"cannot download, $GOPATH not set. For more details see: go help gopath"
というエラーメッセージを返します。これにより、go get
を実行する前に$GOPATH
が必須であることを明確に強制します。GOPATH=$GOROOT
の場合のエラー:buildContext.GOPATH
の最初のパスがgoroot
($GOROOT
の値)と同じである場合、"cannot download, $GOPATH must not be set to $GOROOT. For more details see: go help gopath"
というエラーメッセージを返します。これにより、$GOPATH
と$GOROOT
を同じに設定する誤った慣行を防ぎます。
これらの変更により、go get
は常に有効な$GOPATH
が存在し、それが$GOROOT
と異なる場合にのみ動作するようになります。
doc/go1.1.html
の変更
Go 1.1のリリースノートに相当するこのHTMLファイルには、go
ツールの変更点として、go get
の新しい挙動が明記されました。特に、$GOPATH
が設定されていない場合と$GOPATH
が$GOROOT
と同じ場合に表示される具体的なエラーメッセージの例が追加されています。これにより、ユーザーは新しい挙動と、それに伴うエラーメッセージを事前に確認できるようになります。
src/cmd/go/doc.go
およびsrc/cmd/go/help.go
の変更
これらのファイルは、go help
コマンドで表示されるドキュメントや、Goツールの内部ドキュメントを定義しています。
変更点として、GOPATH must be set to build and install packages outside the standard Go tree.
という記述が、GOPATH must be set to get, build and install packages outside the standard Go tree.
に変更されました。これは、go get
も$GOPATH
に依存することを明示的に示すための修正です。
これらの技術的な変更は、Goのツールチェーンの堅牢性を高め、ユーザーがGoの環境設定で直面する可能性のある一般的な問題を未然に防ぐことを目的としています。
コアとなるコードの変更箇所
このコミットの主要な変更は、src/cmd/go/get.go
ファイルにあります。
--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -247,16 +247,17 @@ func downloadPackage(p *Package) error {
}
if p.build.SrcRoot == "" {
- // Package not found. Put in first directory of $GOPATH or else $GOROOT.
- // Guard against people setting GOPATH=$GOROOT. We have to use
- // $GOROOT's directory hierarchy (src/pkg, not just src) in that case.
- if list := filepath.SplitList(buildContext.GOPATH); len(list) > 0 && list[0] != goroot {
- p.build.SrcRoot = filepath.Join(list[0], "src")
- p.build.PkgRoot = filepath.Join(list[0], "pkg")
- } else {
- p.build.SrcRoot = filepath.Join(goroot, "src", "pkg")
- p.build.PkgRoot = filepath.Join(goroot, "pkg")
+ // Package not found. Put in first directory of $GOPATH.
+ list := filepath.SplitList(buildContext.GOPATH)
+ if len(list) == 0 {
+ return fmt.Errorf("cannot download, $GOPATH not set. For more details see: go help gopath")
}
+ // Guard against people setting GOPATH=$GOROOT.
+ if list[0] == goroot {
+ return fmt.Errorf("cannot download, $GOPATH must not be set to $GOROOT. For more details see: go help gopath")
+ }
+ p.build.SrcRoot = filepath.Join(list[0], "src")
+ p.build.PkgRoot = filepath.Join(list[0], "pkg")
}
root := filepath.Join(p.build.SrcRoot, rootPath)
// If we've considered this repository already, don't do it again.
コアとなるコードの解説
上記のコードスニペットは、src/cmd/go/get.go
ファイル内のdownloadPackage
関数の一部です。この関数は、指定されたGoパッケージをダウンロードする際のロジックを処理します。
変更前のコードでは、パッケージのソースルート(p.build.SrcRoot
)が空の場合(つまり、パッケージが既存のGoパスで見つからなかった場合)、ダウンロード先を決定するために以下のロジックを使用していました。
$GOPATH
が設定されており、その最初のパスが$GOROOT
と異なる場合、$GOPATH
の最初のパスをダウンロード先として使用します。- それ以外の場合(
$GOPATH
が設定されていないか、$GOPATH
の最初のパスが$GOROOT
と同じ場合)、$GOROOT
をダウンロード先として使用します。この際、$GOROOT
のディレクトリ構造(src/pkg
)を考慮していました。
このロジックは、$GOROOT
がGoパッケージのダウンロード先として機能することを許容していました。しかし、これはGo 1.0以降の$GOPATH
中心のワークフローとは矛盾し、特にsudo
使用時の環境変数マスク問題と相まって、ユーザーに混乱をもたらしていました。
変更後のコードでは、このロジックが大幅に簡素化され、より厳格な$GOPATH
の利用が強制されています。
list := filepath.SplitList(buildContext.GOPATH)
: まず、$GOPATH
環境変数の値をパスのリストに分割します。buildContext.GOPATH
は、現在のビルドコンテキストにおける$GOPATH
の値を表します。if len(list) == 0
: 分割されたパスのリストが空である場合、つまり$GOPATH
が設定されていない場合、fmt.Errorf("cannot download, $GOPATH not set. For more details see: go help gopath")
というエラーを返して処理を中断します。これにより、go get
を実行するには$GOPATH
が必須であることが明確に強制されます。if list[0] == goroot
:$GOPATH
の最初のパスが$GOROOT
の値(goroot
変数に格納されている)と同じである場合、fmt.Errorf("cannot download, $GOPATH must not be set to $GOROOT. For more details see: go help gopath")
というエラーを返して処理を中断します。これにより、$GOPATH
と$GOROOT
を同じに設定する誤った慣行が防止されます。p.build.SrcRoot = filepath.Join(list[0], "src")
およびp.build.PkgRoot = filepath.Join(list[0], "pkg")
: 上記のチェックを通過した場合、つまり有効な$GOPATH
が設定されており、それが$GOROOT
と異なる場合のみ、$GOPATH
の最初のパスを基にソースルートとパッケージルートを設定します。
この変更により、go get
は常にユーザーの$GOPATH
ワークスペースにパッケージをダウンロードするようになり、$GOROOT
への意図しないダウンロードや、$GOPATH
が不適切に設定されていることによる問題を根本的に解決しています。
関連リンク
- Go CL (Change List): https://golang.org/cl/6941058
- Go Issue: https://golang.org/issue/4186
- Google Groups Discussion: https://groups.google.com/d/topic/golang-nuts/VIg3fjHiHRI/discussion
参考にした情報源リンク
- GitHub Commit: https://github.com/golang/go/commit/593d8b0c1409d044472a2848de199eec498100b1
- Go 1.1 Release Notes (doc/go1.1.html): コミットに含まれる
doc/go1.1.html
の変更差分 - Go Modules (Go 1.11以降のパッケージ管理): このコミットの時点ではGo Modulesは存在しませんが、Goのパッケージ管理の歴史的文脈を理解する上で重要です。
- The Go Programming Language Specification:
- Effective Go:
- Go Command Documentation:
sudo
man page (一般的なLinuxシステム):sudo
の環境変数に関する挙動について。man sudo
(コマンドラインで実行)- https://www.sudo.ws/docs/man/sudo.man/ (オンライン版)