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

[インデックス 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)
		}

この新しいロジックのポイントは以下の通りです。

  1. 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の標準バイナリディレクトリにインストールされるべきであるという意図が明確に示されています。
  2. 一般的なインストールパスの決定: else if p.build.BinDir != "" のブロックは、上記の特別なケースに該当しない一般的なGoパッケージのインストールパスを決定します。これは、$GOBIN が設定されていれば $GOBIN に、そうでなければ $GOPATH/bin にインストールされるという通常の動作をカバーします。

  3. 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 ファイルには、これらの変更を検証するための多数の新しいテストケースが追加されています。特に、godoccmd/api のインストールパスを検証するテストが追加され、GOBINGOPATH の設定が異なる場合の挙動も確認されています。これにより、修正が正しく機能していること、および将来のリグレッションを防ぐための安全網が提供されています。

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

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

このファイルには多数のテストケースが追加されています。特に以下の部分が、godoccmd/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 にインストールされるべきツールが、GOPATHGOBIN の設定によって誤って $GOPATH/bin にインストールされてしまう問題がありました。

新しい if/else if 構造は、この問題を解決するために、より具体的な条件と優先順位を導入しています。

  1. 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 が常に正しい場所にインストールされるようになります。
  2. 一般的なGoパッケージのインストール: else if p.build.BinDir != "" 上記の特別なケースに該当しない一般的なGoパッケージの場合、p.build.BinDir が設定されていれば、そのディレクトリにインストールされます。これは、$GOBIN が設定されていれば $GOBIN に、そうでなければ $GOPATH/bin にインストールされるという通常の go install の挙動に対応します。

  3. 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言語のドキュメント(go installGOPATHGOBINGOROOT に関する情報)
  • Go言語のソースコード(src/cmd/go/pkg.go および src/cmd/go/test.bash の変更点)
  • Go言語のIssue Tracker(Issue #6043 の詳細については、公開されている情報が見つかりませんでした)