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

[インデックス 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を使い始めたばかりの新規ユーザーは混乱に直面することがありました。

  1. $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が意図しない挙動をする原因となっていました。

  2. 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 getgo buildgo 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を実行すると$GOPATHgo getプロセスに引き継がれず、結果としてgo get$GOPATHが設定されていないと判断し、意図しない動作(例: $GOROOTへのダウンロード試行)を引き起こす可能性がありました。

Go 1.1の変更点(doc/go1.1.htmlの変更から読み取れる情報)

このコミットは、Go 1.1のリリースノートの一部としてドキュメント化されています。doc/go1.1.htmlの変更は、Go 1.1で導入されるgoツールの改善点として、以下の3点を挙げています。

  1. パッケージが見つからない場合のエラーメッセージの改善: パッケージが見つからない場合に、検索されたパスのリストを含むより詳細なエラーメッセージが表示されるようになります。
  2. go get$GOROOTへのダウンロード禁止: go getコマンドは、パッケージソースをダウンロードする際のデフォルトの宛先として$GOROOTを許可しなくなります。有効な$GOPATHが必須となります。
  3. 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つのチェックが追加されました。

  1. $GOPATHが設定されていない場合のエラー: buildContext.GOPATHが空の場合(つまり$GOPATHが設定されていない場合)、"cannot download, $GOPATH not set. For more details see: go help gopath"というエラーメッセージを返します。これにより、go getを実行する前に$GOPATHが必須であることを明確に強制します。
  2. 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パスで見つからなかった場合)、ダウンロード先を決定するために以下のロジックを使用していました。

  1. $GOPATHが設定されており、その最初のパスが$GOROOTと異なる場合、$GOPATHの最初のパスをダウンロード先として使用します。
  2. それ以外の場合($GOPATHが設定されていないか、$GOPATHの最初のパスが$GOROOTと同じ場合)、$GOROOTをダウンロード先として使用します。この際、$GOROOTのディレクトリ構造(src/pkg)を考慮していました。

このロジックは、$GOROOTがGoパッケージのダウンロード先として機能することを許容していました。しかし、これはGo 1.0以降の$GOPATH中心のワークフローとは矛盾し、特にsudo使用時の環境変数マスク問題と相まって、ユーザーに混乱をもたらしていました。

変更後のコードでは、このロジックが大幅に簡素化され、より厳格な$GOPATHの利用が強制されています。

  1. list := filepath.SplitList(buildContext.GOPATH): まず、$GOPATH環境変数の値をパスのリストに分割します。buildContext.GOPATHは、現在のビルドコンテキストにおける$GOPATHの値を表します。
  2. if len(list) == 0: 分割されたパスのリストが空である場合、つまり$GOPATHが設定されていない場合、fmt.Errorf("cannot download, $GOPATH not set. For more details see: go help gopath")というエラーを返して処理を中断します。これにより、go getを実行するには$GOPATHが必須であることが明確に強制されます。
  3. 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を同じに設定する誤った慣行が防止されます。
  4. p.build.SrcRoot = filepath.Join(list[0], "src") および p.build.PkgRoot = filepath.Join(list[0], "pkg"): 上記のチェックを通過した場合、つまり有効な$GOPATHが設定されており、それが$GOROOTと異なる場合のみ、$GOPATHの最初のパスを基にソースルートとパッケージルートを設定します。

この変更により、go getは常にユーザーの$GOPATHワークスペースにパッケージをダウンロードするようになり、$GOROOTへの意図しないダウンロードや、$GOPATHが不適切に設定されていることによる問題を根本的に解決しています。

関連リンク

参考にした情報源リンク