[インデックス 17115] ファイルの概要
このコミットは、Go言語のコマンドラインツール go
のインストーラに関するバグ修正と、それに伴うテストの拡充を目的としています。具体的には、godoc
コマンドが正しいディレクトリにインストールされるように修正し、関連するインストールパスの決定ロジックを改善しています。
コミット
commit 51e9858a70e8783a3abb5f0736fa495f14590b26
Author: Russ Cox <rsc@golang.org>
Date: Thu Aug 8 23:48:03 2013 -0400
cmd/go: install godoc into correct directory
Fixes #6043.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12693043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/51e9858a70e8783a3abb5f0736fa495f14590b26
元コミット内容
このコミットの目的は、「cmd/go
: godoc
を正しいディレクトリにインストールする」ことです。これは、Goツールのインストール先に関する既存のバグ(Issue #6043)を修正するものです。
変更の背景
Go言語のツールチェインにおいて、go install
コマンドはソースコードからバイナリをビルドし、適切な場所に配置する役割を担っています。通常、ユーザーが開発したプログラムは $GOPATH/bin
または $GOBIN
にインストールされます。しかし、godoc
のようなGoプロジェクト自体が提供する一部のツールは、Goのインストールディレクトリ($GOROOT/bin
)にインストールされることが期待されていました。
このコミット以前は、godoc
が $GOROOT/bin
ではなく、ユーザーの $GOPATH/bin
にインストールされてしまうという問題がありました。これは、go install
がインストール先のディレクトリを決定するロジックに不備があったためです。特に、godoc
のようなGoのサブモジュールとして提供されるツールが、Goの標準ツールとして扱われるべきか、それとも通常のGoパッケージとして扱われるべきかという判断が曖昧でした。
この問題は、Goのツールチェインの整合性を損ない、ユーザーが godoc
を期待する場所で見つけられない、あるいは予期せぬ場所にインストールされてしまうという混乱を引き起こしていました。コミットメッセージにある Fixes #6043
は、この特定のバグ報告に対応するものであることを示しています。ただし、Goの公式Issue Trackerで #6043 の詳細な情報を直接見つけることはできませんでした。これは、非常に古いIssueであるか、あるいは内部的なトラッキング番号である可能性があります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の環境変数とツールのインストールに関する知識が必要です。
GOROOT
: GoのSDKがインストールされているルートディレクトリを指します。Goの標準ライブラリや、go
コマンド自体を含むGoの公式ツールがここに配置されます。GOPATH
: Goのワークスペースのルートディレクトリを指します。ユーザーが開発するGoのプロジェクトや、go get
で取得したサードパーティのパッケージのソースコードがここに配置されます。$GOPATH/bin
は、go install
でビルドされた実行可能ファイルがデフォルトでインストールされる場所です。GOBIN
:go install
コマンドが実行可能ファイルをインストールする特定のディレクトリを指定するための環境変数です。GOBIN
が設定されている場合、go install
は$GOBIN
にバイナリを配置します。設定されていない場合は、$GOPATH/bin
に配置されます。go install
: Goのソースコードをコンパイルし、実行可能ファイルを生成して、適切なバイナリディレクトリ($GOBIN
または$GOPATH/bin
)にインストールするコマンドです。godoc
: Goのソースコードからドキュメントを生成し、Webブラウザで閲覧できるようにするツールです。Goの標準ツールの一つとして広く利用されています。go tool
: Goの内部ツール(例:go tool compile
,go tool vet
,go tool api
など)を実行するためのコマンドです。これらのツールは通常、$GOROOT/pkg/tool/<GOOS_GOARCH>
ディレクトリにインストールされます。
このコミットの核心は、go install
がこれらの環境変数とツールの種類(標準ツール、go tool
で実行されるツール、ユーザープログラム)に基づいて、どのようにインストール先を決定するかというロジックにあります。
技術的詳細
このコミットの技術的な変更は、主に src/cmd/go/pkg.go
ファイル内の (*Package).load
メソッドに集中しています。このメソッドは、パッケージのビルド情報をロードし、そのパッケージが実行可能ファイルである場合に、そのインストール先パス (p.target
) を決定します。
変更前は、goTools
マップ(Goの標準ツールを識別するための内部マップ)の値に基づいて switch
ステートメントでインストール先を決定していました。
switch goTools[p.ImportPath] {
case toRoot: // default, if p.ImportPath not in goTools
if p.build.BinDir != "" {
p.target = filepath.Join(p.build.BinDir, elem)
}
case toTool:
p.target = filepath.Join(gorootPkg, "tool", full)
case toBin:
p.target = filepath.Join(gorootBin, elem)
}
このロジックでは、godoc
のようなツールが toBin
($GOROOT/bin
にインストールされるべき)とマークされていても、p.build.BinDir
が $GOBIN
や $GOPATH/bin
を指している場合に、そちらが優先されてしまう可能性がありました。
変更後のロジックは、より明確な優先順位と条件を導入しています。
if p.build.BinDir != gobin && goTools[p.ImportPath] == toBin {
// Override BinDir.
// This is from a subrepo but installs to $GOROOT/bin
// by default anyway (like godoc).
p.target = filepath.Join(gorootBin, elem)
} else if p.build.BinDir != "" {
// Install to GOBIN or bin of GOPATH entry.
p.target = filepath.Join(p.build.BinDir, elem)
}
if goTools[p.ImportPath] == toTool {
// This is for 'go tool'.
// Override all the usual logic and force it into the tool directory.
p.target = filepath.Join(gorootPkg, "tool", full)
}
この新しいロジックのポイントは以下の通りです。
-
godoc
のようなツールの優先処理:if p.build.BinDir != gobin && goTools[p.ImportPath] == toBin
という条件が追加されました。p.build.BinDir != gobin
: 現在のビルド設定で決定されるバイナリディレクトリが、Goの標準バイナリディレクトリ($GOROOT/bin
)ではない場合。goTools[p.ImportPath] == toBin
: しかし、そのパッケージがgoTools
マップで$GOROOT/bin
にインストールされるべきツール(例:godoc
)として明示的に指定されている場合。 この両方の条件が満たされる場合、p.target
は強制的にgorootBin
($GOROOT/bin
)に設定されます。これにより、godoc
が常に$GOROOT/bin
にインストールされることが保証されます。コメントにも「This is from a subrepo but installs to $GOROOT/bin by default anyway (like godoc).」とあり、godoc
がGoのサブリポジトリ(code.google.com/p/go.tools
)から提供されるにもかかわらず、Goの標準バイナリディレクトリにインストールされるべきであるという意図が明確に示されています。
-
一般的なインストールパスの決定:
else if p.build.BinDir != ""
のブロックは、上記の特別なケースに該当しない一般的なGoパッケージのインストールパスを決定します。これは、$GOBIN
が設定されていれば$GOBIN
に、そうでなければ$GOPATH/bin
にインストールされるという通常の動作をカバーします。 -
go tool
用ツールの強制的な配置:if goTools[p.ImportPath] == toTool
のブロックは、go tool
コマンドで実行されるツール(例:cmd/api
)を処理します。この条件は独立したif
ステートメントとして配置されており、他のインストールロジックよりも優先されます。これにより、これらのツールが常に$GOROOT/pkg/tool
ディレクトリにインストールされることが保証されます。コメントにも「Override all the usual logic and force it into the tool directory.」とあり、この意図が強調されています。
これらの変更により、go install
はGoの標準ツール、go tool
用ツール、および一般的なGoパッケージのインストール先を、より正確かつ意図通りに決定できるようになりました。
また、src/cmd/go/test.bash
ファイルには、これらの変更を検証するための多数の新しいテストケースが追加されています。特に、godoc
と cmd/api
のインストールパスを検証するテストが追加され、GOBIN
や GOPATH
の設定が異なる場合の挙動も確認されています。これにより、修正が正しく機能していること、および将来のリグレッションを防ぐための安全網が提供されています。
コアとなるコードの変更箇所
src/cmd/go/pkg.go
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -349,15 +349,19 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
// Install cross-compiled binaries to subdirectories of bin.
elem = full
}
- switch goTools[p.ImportPath] {
- case toRoot: // default, if p.ImportPath not in goTools
- if p.build.BinDir != "" {
- p.target = filepath.Join(p.build.BinDir, elem)
- }
- case toTool:
- p.target = filepath.Join(gorootPkg, "tool", full)
- case toBin:
+ if p.build.BinDir != gobin && goTools[p.ImportPath] == toBin {
+ // Override BinDir.
+ // This is from a subrepo but installs to $GOROOT/bin
+ // by default anyway (like godoc).
p.target = filepath.Join(gorootBin, elem)
+ } else if p.build.BinDir != "" {
+ // Install to GOBIN or bin of GOPATH entry.
+ p.target = filepath.Join(p.build.BinDir, elem)
+ }
+ if goTools[p.ImportPath] == toTool {
+ // This is for 'go tool'.
+ // Override all the usual logic and force it into the tool directory.
+ p.target = filepath.Join(gorootPkg, "tool", full)
}
if p.target != "" && buildContext.GOOS == "windows" {
p.target += ".exe"
src/cmd/go/test.bash
このファイルには多数のテストケースが追加されています。特に以下の部分が、godoc
や cmd/api
のインストールパスの修正を検証する新しいテストです。
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -126,24 +166,88 @@ elif ! test -x testdata/bin1/helloworld; then
\tok=false
fi
+TEST godoc installs into GOBIN
+d=$(mktemp -d -t testgoXXX)
+export GOPATH=$d
+mkdir $d/gobin
+GOBIN=$d/gobin ./testgo get code.google.com/p/go.tools/cmd/godoc
+if [ ! -x $d/gobin/godoc ]; then
+ echo did not install godoc to '$GOBIN'
+ GOBIN=$d/gobin ./testgo list -f 'Target: {{.Target}}' code.google.com/p/go.tools/cmd/godoc
+ ok=false
+fi
+
+TEST godoc installs into GOROOT
+rm -f $GOROOT/bin/godoc
+./testgo install code.google.com/p/go.tools/cmd/godoc
+if [ ! -x $GOROOT/bin/godoc ]; then
+ echo did not install godoc to '$GOROOT/bin'
+ ./testgo list -f 'Target: {{.Target}}' code.google.com/p/go.tools/cmd/godoc
+ ok=false
+fi
+
+TEST cmd/api installs into tool
+GOOS=$(./testgo env GOOS)
+GOARCH=$(./testgo env GOARCH)
+rm -f $GOROOT/pkg/tool/${GOOS}_${GOARCH}/api
+./testgo install cmd/api
+if [ ! -x $GOROOT/pkg/tool/${GOOS}_${GOARCH}/api ]; then
+ echo 'did not install cmd/api to $GOROOT/pkg/tool'
+ GOBIN=$d/gobin ./testgo list -f 'Target: {{.Target}}' cmd/api
+ ok=false
+fi
+rm -f $GOROOT/pkg/tool/${GOOS}_${GOARCH}/api
+GOBIN=$d/gobin ./testgo install cmd/api
+if [ ! -x $GOROOT/pkg/tool/${GOOS}_${GOARCH}/api ]; then
+ echo 'did not install cmd/api to $GOROOT/pkg/tool with $GOBIN set'
+ GOBIN=$d/gobin ./testgo list -f 'Target: {{.Target}}' cmd/api
+ ok=false
+fi
+
+TEST gopath program installs into GOBIN
+mkdir $d/src/progname
+echo 'package main; func main() {}' >$d/src/progname/p.go
+GOBIN=$d/gobin ./testgo install progname
+if [ ! -x $d/gobin/progname ]; then
+ echo 'did not install progname to $GOBIN/progname'
+ ./testgo list -f 'Target: {{.Target}}' cmd/api
+ ok=false
+fi
+rm -f $d/gobin/progname $d/bin/progname
+
+TEST gopath program installs into GOPATH/bin
+./testgo install progname
+if [ ! -x $d/bin/progname ]; then
+ echo 'did not install progname to $GOPATH/bin/progname'
+ ./testgo list -f 'Target: {{.Target}}' progname
+ ok=false
+fi
+
+unset GOPATH
+rm -rf $d
+
# Reject relative paths in GOPATH.
+TEST reject relative paths in GOPATH '(command-line package)'
if GOPATH=. ./testgo build testdata/src/go-cmd-test/helloworld.go; then
echo 'GOPATH="." go build should have failed, did not'
ok=false
fi
+TEST reject relative paths in GOPATH
if GOPATH=:$(pwd)/testdata:. ./testgo build go-cmd-test; then
echo 'GOPATH=":'$(pwd)/testdata:.'" go build should have failed, did not'
ok=false
fi
# issue 4104
+TEST go test with package listed multiple times
if [ $(./testgo test fmt fmt fmt fmt fmt | wc -l) -ne 1 ] ; then
echo 'go test fmt fmt fmt fmt fmt tested the same package multiple times'
ok=false
fi
# ensure that output of 'go list' is consistent between runs
+TEST go list is consistent
./testgo list std > test_std.list
if ! ./testgo list std | cmp -s test_std.list - ; then
echo "go list std ordering is inconsistent"
@@ -152,31 +256,37 @@ fi
rm -f test_std.list
# issue 4096. Validate the output of unsuccessful go install foo/quxx
+TEST unsuccessful go install should mention missing package
if [ $(./testgo install 'foo/quxx' 2>&1 | grep -c 'cannot find package "foo/quxx" in any of') -ne 1 ] ; then
echo 'go install foo/quxx expected error: .*cannot find package "foo/quxx" in any of'
ok=false
fi
# test GOROOT search failure is reported
+TEST GOROOT search failure reporting
if [ $(./testgo install 'foo/quxx' 2>&1 | egrep -c 'foo/quxx \(from \$GOROOT\)$') -ne 1 ] ; then
echo 'go install foo/quxx expected error: .*foo/quxx (from $GOROOT)'
ok=false
fi
# test multiple GOPATH entries are reported separately
+TEST multiple GOPATH entries reported separately
if [ $(GOPATH=$(pwd)/testdata/a:$(pwd)/testdata/b ./testgo install 'foo/quxx' 2>&1 | egrep -c 'testdata/./src/foo/quxx') -ne 2 ] ; then
echo 'go install foo/quxx expected error: .*testdata/a/src/foo/quxx (from $GOPATH)\n.*testdata/b/src/foo/quxx'
ok=false
fi
# test (from $GOPATH) annotation is reported for the first GOPATH entry
+TEST mention GOPATH in first GOPATH entry
if [ $(GOPATH=$(pwd)/testdata/a:$(pwd)/testdata/b ./testgo install 'foo/quxx' 2>&1 | egrep -c 'testdata/a/src/foo/quxx \(from \$GOPATH\)$') -ne 1 ] ; then
echo 'go install foo/quxx expected error: .*testdata/a/src/foo/quxx (from $GOPATH)'
ok=false
fi
# but not on the second
+TEST but not the second entry
if [ $(GOPATH=$(pwd)/testdata/a:$(pwd)/testdata/b ./testgo install 'foo/quxx' 2>&1 | egrep -c 'testdata/b/src/foo/quxx$') -ne 1 ] ; then
echo 'go install foo/quxx expected error: .*testdata/b/src/foo/quxx'
ok=false
fi
# test missing GOPATH is reported
+TEST missing GOPATH is reported
if [ $(GOPATH= ./testgo install 'foo/quxx' 2>&1 | egrep -c '\(\$GOPATH not set\)$') -ne 1 ] ; then
echo 'go install foo/quxx expected error: ($GOPATH not set)'
ok=false
@@ -184,6 +294,7 @@ fi
# issue 4186. go get cannot be used to download packages to $GOROOT
# Test that without GOPATH set, go get should fail
+TEST without GOPATH, go get fails
d=$(mktemp -d -t testgoXXX)
mkdir -p $d/src/pkg
if GOPATH= GOROOT=$d ./testgo get -d code.google.com/p/go.codereview/cmd/hgpatch ; then
@@ -191,7 +302,9 @@ if GOPATH= GOROOT=$d ./testgo get -d code.google.com/p/go.codereview/cmd/hgpatch
\tok=false
fi
rm -rf $d
+\n # Test that with GOPATH=$GOROOT, go get should fail
+TEST with GOPATH=GOROOT, go get fails
d=$(mktemp -d -t testgoXXX)
mkdir -p $d/src/pkg
if GOPATH=$d GOROOT=$d ./testgo get -d code.google.com/p/go.codereview/cmd/hgpatch ; then
@@ -200,7 +313,7 @@ if GOPATH=$d GOROOT=$d ./testgo get -d code.google.com/p/go.codereview/cmd/hgpat
fi
rm -rf $d
-# issue 3941: args with spaces\n+TEST ldflags arguments with spaces '(issue 3941)'
d=$(mktemp -d -t testgoXXX)
cat >$d/main.go<<EOF
package main
@@ -217,7 +330,7 @@ if ! grep -q '^hello world' hello.out; then
fi
rm -rf $d
-# test that go test -cpuprofile leaves binary behind\n+TEST go test -cpuprofile leaves binary behind
./testgo test -cpuprofile strings.prof strings || ok=false
if [ ! -x strings.test ]; then
echo "go test -cpuprofile did not create strings.test"
@@ -225,9 +338,10 @@ if [ ! -x strings.test ]; then
fi
rm -f strings.prof strings.test
-# issue 4568. test that symlinks don't screw things up too badly.\n+TEST symlinks do not confuse go list '(issue 4568)'
old=$(pwd)
-d=$(mktemp -d -t testgoXXX)\n+tmp=$(cd /tmp && pwd -P)
+d=$(TMPDIR=$tmp mktemp -d -t testgoXXX)
mkdir -p $d/src
(\n \tln -s $d $d/src/dir1
\techo package p >p.go
\texport GOPATH=$d
\tif [ "$($old/testgo list -f '{{.Root}}' .)" != "$d" ]; then
-\t\techo got lost in symlink tree:\n-\t\tpwd\n+\t\techo Confused by symlinks.
+\t\techo "Package in current directory $(pwd) should have Root $d"
\t\tenv|grep WD
\t\t$old/testgo list -json . dir1
\t\ttouch $d/failed
@@ -247,8 +361,8 @@ if [ -f $d/failed ]; then
fi
rm -rf $d
-# issue 4515.\n-d=$(mktemp -d -t testgoXXX)\n+TEST 'install with tags (issue 4515)'
+d=$(TMPDIR=/var/tmp mktemp -d -t testgoXXX)
mkdir -p $d/src/example/a $d/src/example/b $d/bin
cat >$d/src/example/a/main.go <<EOF
package main
@@ -280,8 +394,8 @@ fi
unset GOPATH
rm -rf $d
-# issue 4773. case-insensitive collisions\n-d=$(mktemp -d -t testgoXXX)\n+TEST case collisions '(issue 4773)'
+d=$(TMPDIR=/var/tmp mktemp -d -t testgoXXX)
export GOPATH=$d
mkdir -p $d/src/example/a $d/src/example/b
cat >$d/src/example/a/a.go <<EOF
@@ -318,22 +432,29 @@ elif ! grep "case-insensitive file name collision" $d/out >/dev/null; then
\techo go list example/b did not report file name collision.
\tok=false
fi
+\n+TEST go get cover
+./testgo get code.google.com/p/go.tools/cmd/cover
+\n unset GOPATH
rm -rf $d
# Only succeeds if source order is preserved.
+TEST source file name order preserved
./testgo test testdata/example[12]_test.go
# Check that coverage analysis works at all.
# Don't worry about the exact numbers
-./testgo test -coverpkg=strings strings regexp
-./testgo test -cover strings math regexp
+TEST coverage runs
+./testgo test -short -coverpkg=strings strings regexp
+./testgo test -short -cover strings math regexp
# clean up
+if $started; then stop; fi
rm -rf testdata/bin testdata/bin1
rm -f testgo
-if $ok; then
+if $allok; then
echo PASS
else
echo FAIL
コアとなるコードの解説
src/cmd/go/pkg.go
の変更は、go install
コマンドが実行可能ファイルのインストール先を決定するロジックを改善しています。
以前の switch
ステートメントは、goTools
マップで定義されたツールの種類(toRoot
, toTool
, toBin
)に基づいてインストールパスを決定していました。しかし、このロジックでは、godoc
のように $GOROOT/bin
にインストールされるべきツールが、GOPATH
や GOBIN
の設定によって誤って $GOPATH/bin
にインストールされてしまう問題がありました。
新しい if/else if
構造は、この問題を解決するために、より具体的な条件と優先順位を導入しています。
-
godoc
のようなGo標準ツールの優先処理:if p.build.BinDir != gobin && goTools[p.ImportPath] == toBin
この条件は、godoc
のような、Goの標準バイナリディレクトリ($GOROOT/bin
)にインストールされるべきツールを特別に扱います。p.build.BinDir != gobin
: 現在のビルドコンテキストが示すバイナリディレクトリが、Goの標準バイナリディレクトリではないことを意味します。例えば、GOPATH
が設定されている場合、p.build.BinDir
は$GOPATH/bin
を指す可能性があります。goTools[p.ImportPath] == toBin
: しかし、godoc
のような特定のツールは、goTools
マップによって$GOROOT/bin
にインストールされるべき (toBin
) とマークされています。 この両方の条件が真の場合、p.target
は強制的にfilepath.Join(gorootBin, elem)
、つまり$GOROOT/bin
に設定されます。これにより、godoc
が常に正しい場所にインストールされるようになります。
-
一般的なGoパッケージのインストール:
else if p.build.BinDir != ""
上記の特別なケースに該当しない一般的なGoパッケージの場合、p.build.BinDir
が設定されていれば、そのディレクトリにインストールされます。これは、$GOBIN
が設定されていれば$GOBIN
に、そうでなければ$GOPATH/bin
にインストールされるという通常のgo install
の挙動に対応します。 -
go tool
用ツールの強制的な配置:if goTools[p.ImportPath] == toTool
この独立したif
ステートメントは、go tool
コマンドで実行されるツール(例:cmd/api
)を処理します。これらのツールは、他のインストールロジックとは関係なく、常にfilepath.Join(gorootPkg, "tool", full)
、つまり$GOROOT/pkg/tool/<GOOS_GOARCH>
ディレクトリにインストールされるように強制されます。
これらの変更により、go install
は、Goのツールチェイン内の様々な種類の実行可能ファイルに対して、より正確で予測可能なインストールパスを提供できるようになりました。
src/cmd/go/test.bash
の変更は、これらのロジックの修正が正しく機能することを検証するためのものです。特に、godoc
が $GOBIN
と $GOROOT/bin
の両方のシナリオで正しくインストールされるか、cmd/api
が常に go tool
ディレクトリにインストールされるか、といった具体的なテストケースが追加されています。これにより、この修正が将来のリグレッションを引き起こさないようにするための堅牢なテストカバレッジが提供されています。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのGerrit変更リスト: https://golang.org/cl/12693043
参考にした情報源リンク
- Go言語のドキュメント(
go install
、GOPATH
、GOBIN
、GOROOT
に関する情報) - Go言語のソースコード(
src/cmd/go/pkg.go
およびsrc/cmd/go/test.bash
の変更点) - Go言語のIssue Tracker(Issue #6043 の詳細については、公開されている情報が見つかりませんでした)