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

[インデックス 11346] ファイルの概要

このコミットは、Go言語のコマンドラインツール cmd/go において、go get コマンドの実装と、既存のバグ修正および機能改善を包括的に行ったものです。特に、パッケージ情報の取り扱い、ビルドプロセスの堅牢化、そして新しい go get コマンドの導入が主要な変更点となっています。

コミット

commit ed936a3f22bcc8165390545471633141088eca26
Author: Russ Cox <rsc@golang.org>
Date:   Mon Jan 23 15:16:51 2012 -0500

    cmd/go: implement go get + bug fixes
    
    Move error information into Package struct, so that
    a package can be returned even if a dependency failed
    to load or did not exist.  This makes it possible to run
    'go fix' or 'go fmt' on packages with broken dependencies
    or missing imports.  It also enables go get -fix.
    The new go list -e flag lets go list process those package
    errors as normal data.
    
    Change p.Doc to be first sentence of package doc, not
    entire package doc.  Makes go list -json or
    go list -f '{{.ImportPath}} {{.Doc}}' much more reasonable.
    
    The go tool now depends on http, which means also
    net and crypto/tls, both of which use cgo.  Trying to
    make the build scripts that build the go tool understand
    and handle cgo is too much work.  Instead, we build
    a stripped down version of the go tool, compiled as go_bootstrap,
    that substitutes an error stub for the usual HTTP code.
    The buildscript builds go_bootstrap, go_bootstrap builds
    the standard packages and commands, including the full
    including-HTTP-support go tool, and then go_bootstrap
    gets deleted.
    
    Also handle the case where the buildscript needs updating
    during all.bash: if it fails but a go command can be found on
    the current $PATH, try to regenerate it.  This gracefully
    handles situations like adding a new file to a package
    used by the go tool.
    
    R=r, adg
    CC=golang-dev
    https://golang.org/cl/5553059

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ed936a3f22bcc8165390545471633141088eca26

元コミット内容

上記の「コミット」セクションに記載されている内容が元コミット内容です。

変更の背景

このコミットが行われた背景には、Go言語のツールチェインにおけるいくつかの課題と、開発者の利便性向上の必要性がありました。

  1. 依存関係の管理と取得の自動化: 当時、Goプロジェクトの依存関係を手動で管理し、取得するのは煩雑な作業でした。特に、外部ライブラリを利用する際に、そのライブラリのソースコードを適切な場所に配置する必要がありました。go get コマンドの導入は、このプロセスを自動化し、開発者がより簡単に外部パッケージを利用できるようにすることを目的としています。

  2. 壊れた依存関係を持つパッケージのツール利用: 以前のGoツールでは、パッケージの依存関係に問題がある場合(例: 存在しないインポートパス、ロードに失敗するパッケージ)、そのパッケージに対して go fixgo fmt といったツールを実行することが困難でした。これは、ツールがパッケージ情報を完全にロードできないと判断し、処理を中断してしまうためです。このコミットでは、エラー情報を Package 構造体内に保持することで、部分的にでもパッケージ情報を利用できるようにし、壊れた依存関係を持つパッケージに対してもツールが実行できるように改善されました。

  3. Goツール自身のビルドプロセスの複雑化: go tool (Goコマンド自体) が http パッケージに依存するようになったことで、netcrypto/tls といったパッケージも間接的に必要となりました。これらのパッケージは cgo (C言語との連携機能) を使用しているため、Goツール自身のビルドプロセスが複雑化するという問題が生じました。特に、ブートストラップビルド(Goコンパイラ自身をGoでビルドするプロセス)において、cgo のサポートを最初から組み込むのは困難でした。

  4. go list コマンドの出力改善: go list コマンドでパッケージのドキュメント (p.Doc) を表示する際、パッケージ全体のドキュメントが表示されてしまい、特に go list -jsongo list -f '{{.ImportPath}} {{.Doc}}' のような形式で利用する際に冗長でした。これをパッケージドキュメントの最初の1文に限定することで、より簡潔で有用な出力となるよう改善が求められました。

  5. ビルドスクリプトの自己修復能力: all.bash (Goのフルビルドスクリプト) の実行中にビルドスクリプト自体が更新を必要とするような状況が発生した場合、ビルドが失敗することがありました。このような状況でも、既存の go コマンドが $PATH 上にあれば、それを利用してビルドスクリプトを再生成し、ビルドを継続できるようにすることで、ビルドプロセスの堅牢性を高める必要がありました。

これらの課題に対処するため、このコミットでは go get の導入、パッケージ情報の扱い方の改善、ブートストラップビルドの仕組みの導入、そして go list の出力調整など、多岐にわたる変更が加えられました。

前提知識の解説

このコミットを理解するためには、以下のGo言語および関連技術の概念を理解しておく必要があります。

  • Go言語のパッケージシステム: Go言語はパッケージによってコードを整理します。各パッケージは独自のインポートパスを持ち、他のパッケージからインポートして利用できます。
  • go コマンド: Go言語の主要なコマンドラインツールで、コードのビルド、テスト、フォーマット、依存関係の管理など、様々な開発タスクを実行します。
    • go build: Goのソースコードをコンパイルして実行可能ファイルを生成します。
    • go install: パッケージをコンパイルし、生成されたバイナリやアーカイブを $GOBIN$GOPATH/pkg にインストールします。
    • go fix: 古いGoのAPIを使用しているコードを、新しいAPIに自動的に書き換えます。
    • go fmt: Goのソースコードを標準的なスタイルにフォーマットします。
    • go list: 指定されたパッケージの情報を表示します。パッケージのインポートパス、ソースファイルのリスト、依存関係などの詳細を取得できます。
  • GOROOTGOPATH:
    • GOROOT: Goのインストールディレクトリを指します。標準ライブラリのソースコードなどが含まれます。
    • GOPATH: Goのワークスペースディレクトリを指します。ユーザーが開発するプロジェクトのソースコード、コンパイル済みパッケージ、実行可能ファイルなどが配置されます。go get コマンドでダウンロードされる外部パッケージも通常 $GOPATH/src 以下に配置されます。
  • Package 構造体 (go/build パッケージ): Goのビルドシステムがパッケージに関する情報を保持するために使用する構造体です。パッケージのインポートパス、ディレクトリ、ソースファイル、依存関係などのメタデータが含まれます。
  • cgo: Go言語からC言語のコードを呼び出すためのメカニズムです。cgo を使用するパッケージは、GoコンパイラだけでなくCコンパイラ(GCCなど)も必要とします。
  • ブートストラップビルド (Bootstrap Build): Go言語のコンパイラやツールチェイン自体がGo言語で書かれているため、Goの新しいバージョンをビルドする際には、まず既存の(古い)Goコンパイラを使って新しいGoコンパイラをビルドするという「ブートストラップ」プロセスが必要です。このプロセスは、Goツールチェインの自己ホスト型特性を維持するために不可欠です。
  • バージョン管理システム (VCS): Git, Mercurial (Hg), Subversion (SVN), Bazaar (Bzr) など、ソースコードの変更履歴を管理するシステムです。go get はこれらのVCSを利用してリモートリポジトリからソースコードを取得します。
  • buildscript.sh: Goのビルドプロセスで使用されるシェルスクリプトの一つで、Goツールチェインのコンポーネントをビルドする役割を担います。
  • all.bash: Goのソースツリー全体をビルドし、テストを実行するためのトップレベルのスクリプトです。

技術的詳細

このコミットは、Goツールチェインの複数の側面に対して重要な技術的変更を導入しています。

1. Package 構造体におけるエラー情報の統合

  • 変更点: src/cmd/go/pkg.go に定義されている Package 構造体に、Incomplete boolError *PackageError フィールドが追加されました。また、PackageError 構造体と importStack 型が新しく定義されました。
  • 目的: 以前は、パッケージのロード中にエラーが発生すると、そのパッケージに関する情報が全く返されないか、処理が中断されていました。この変更により、エラーが発生した場合でも Package 構造体自体は返され、エラーの詳細は Error フィールドに格納されるようになりました。これにより、部分的にロードされたパッケージ情報でも go fixgo fmt のようなツールが動作できるようになります。
  • PackageError: エラーが発生したパッケージのインポートスタック(どのパッケージからインポートされてエラーに至ったかの経路)とエラーメッセージを保持します。これにより、エラーの原因を追跡しやすくなります。
  • Incomplete フィールド: このパッケージまたはその依存関係のロード中にエラーが発生したことを示します。

2. go list -e フラグの導入

  • 変更点: src/cmd/go/list.go-e フラグが追加されました。
  • 目的: 通常、go list はエラーのあるパッケージをスキップし、標準エラーに出力します。-e フラグを使用すると、エラーのあるパッケージも通常の出力フローで処理されるようになります。これにより、エラー情報を含むパッケージリストをプログラム的に処理することが可能になり、診断ツールなどの開発に役立ちます。エラーのあるパッケージは ImportPath が非空で Error フィールドが非nilになります。

3. p.Doc の変更

  • 変更点: Package 構造体の Doc フィールドが、パッケージドキュメント全体の文字列ではなく、パッケージドキュメントの最初の1文のみを保持するように変更されました。
  • 目的: go list -jsongo list -f '{{.ImportPath}} {{.Doc}}' のようなコマンドでパッケージ情報を表示する際に、ドキュメントが長すぎると出力が読みにくくなる問題がありました。最初の1文に限定することで、より簡潔で概要を把握しやすい出力が提供されるようになりました。

4. go_bootstrap メカニズムの導入

  • 変更点: Goツール (cmd/go) が http パッケージ(および net, crypto/tls)に依存するようになったため、これらのパッケージが cgo を使用していることが問題となりました。Goツールチェインのブートストラップビルドプロセスでは、初期段階で cgo を完全にサポートすることが困難でした。この問題を解決するため、go_bootstrap という中間的なGoツールが導入されました。
    • src/buildscript.sh および各プラットフォームの src/buildscript/*.sh スクリプトが変更され、最初に go_bootstrap という名前でGoツールをビルドするようになりました。この go_bootstrap は、http 関連の機能がエラーを返すスタブに置き換えられた、cgo に依存しない(または依存が最小限の)軽量版です。
    • go_bootstrap がビルドされた後、この go_bootstrap を使って、標準パッケージと、http サポートを含む完全な go ツールがビルドされます。
    • 最終的に、go_bootstrap は削除され、完全な go ツールが残ります。
  • 目的: この多段階ビルドプロセスにより、cgo の複雑さをブートストラップビルドの初期段階から切り離し、Goツールチェイン全体のビルドを簡素化し、堅牢性を高めることができました。

5. go get コマンドの実装

  • 変更点: src/cmd/go/get.go が新しく追加され、go get コマンドのロジックが実装されました。
  • 機能:
    • ダウンロードと更新: 指定されたインポートパスのパッケージとその依存関係をダウンロードし、必要に応じて更新します。
    • -d フラグ: パッケージをダウンロードするだけで、インストールは行いません。
    • -fix フラグ: ダウンロードしたパッケージに対して gofix を実行してから、依存関係の解決やビルドを行います。これにより、古いAPIを使用しているパッケージでも go get で取得し、自動的に修正することができます。
    • -u フラグ: 既存のパッケージもネットワーク経由で更新します。デフォルトでは、不足しているパッケージのみをダウンロードし、既存のパッケージの更新は行いません。
    • バージョン選択: selectTag 関数により、Goのバージョン(例: release.rN, weekly.YYYY-MM-DD)に基づいて、VCSリポジトリ内の適切なタグ(例: go.rN, go.weekly.YYYY-MM-DD)を選択して同期します。これにより、特定のGoバージョンと互換性のあるパッケージバージョンを取得できます。
    • キャッシュ: downloadCachedownloadRootCache を使用して、重複するダウンロード作業を避けます。
    • VCS統合: vcs.go (このコミットで追加) と連携し、Git, Mercurial, Subversion, Bazaar などの様々なバージョン管理システムを介してリポジトリを操作します。

6. ビルドスクリプトの自己修復機能

  • 変更点: src/make.bash に、buildscript.sh の更新が必要な場合に、既存の go コマンドを利用して buildscript.sh を再生成するロジックが追加されました。
  • 目的: all.bash 実行中に、Goツールチェインの変更によって buildscript.sh 自体が古くなることがあります。このような場合でも、ビルドが中断することなく、自動的に buildscript.sh を更新し、ビルドを続行できるようになりました。これは、Goツールチェインの継続的な開発と、ビルドプロセスの安定性向上に貢献します。

7. build.DefaultContext から buildContext への移行

  • 変更点: src/cmd/go/build.go において、build.DefaultContext の直接使用から、buildContext という変数(build.DefaultContext のコピー)を使用するように変更されました。また、buildContext.BuildTags を設定するための -t フラグが追加されました。
  • 目的: build.DefaultContext はGoのビルド環境のデフォルト設定を提供しますが、go コマンド内で特定のビルド設定(例: ビルドタグ)を動的に変更する必要がある場合、DefaultContext を直接変更するとグローバルな影響が出てしまいます。buildContext のようなコピーを使用することで、go コマンドの実行中に特定のビルド設定を安全にカスタマイズできるようになります。-t フラグは、条件付きコンパイルに使用されるビルドタグを指定するために利用されます。

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

このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に以下のファイルとセクションが重要です。

  1. src/cmd/go/pkg.go:

    • Package 構造体への Incomplete および Error フィールドの追加。
    • PackageError 構造体と importStack 型の新規定義。
    • loadPackage 関数の変更: エラー発生時でも Package 構造体を返し、エラー情報を格納するように修正。
    • reloadPackage 関数の追加。
  2. src/cmd/go/get.go (新規ファイル):

    • cmdGet コマンドの定義と runGet 関数の実装。
    • download 関数: パッケージのダウンロードと依存関係の処理。
    • downloadPackage 関数: VCS を利用したリポジトリのクローン/更新ロジック。
    • selectTag 関数: Goバージョンに応じたVCSタグの選択ロジック。
    • getD, getU, getFix フラグの定義。
  3. src/cmd/go/list.go:

    • cmdList コマンドの UsageLine-e フラグの追加。
    • listE フラグの定義。
    • runList 関数における packagesAndErrors の利用(-e フラグが指定された場合)。
    • Package 構造体のドキュメントに Incomplete, Error, DepsErrors フィールドの記述を追加。
  4. src/cmd/go/build.go:

    • buildContext 変数の導入と build.DefaultContext からの初期化。
    • addBuildFlags 関数における -t フラグの追加と buildContext.BuildTags への設定。
    • goFilesPackage および action 関数における buildContext の利用。
  5. src/cmd/go/vcs.go (新規ファイル):

    • vcs 構造体と、Git, Mercurial, Subversion, Bazaar などのVCS操作(create, download, tags, tagSync)を抽象化するメソッドの定義。
    • vcsForImportPath 関数: インポートパスから適切なVCSとリポジトリ情報を特定するロジック。
  6. src/buildscript.sh および src/buildscript/*.sh:

    • go install -a -n cmd/go コマンドに -t cmd_go_bootstrap オプションが追加され、go_bootstrap という名前でビルドされるように変更。
    • 最終的な go バイナリのコピー先が go_bootstrap に変更。
  7. src/make.bash:

    • buildscript.sh の再生成ロジックの追加。

コアとなるコードの解説

src/cmd/go/pkg.go の変更

Package 構造体はGoのパッケージ情報を表現する中心的なデータ構造です。このコミットでは、エラー処理の柔軟性を高めるために、この構造体に以下のフィールドが追加されました。

type Package struct {
	// ... 既存のフィールド ...
	Incomplete bool          `json:",omitempty"` // was there an error loading this package or dependencies?
	Error      *PackageError `json:",omitempty"` // error loading this package (not dependencies)
	// ... 既存のフィールド ...
	DepsErrors []*PackageError `json:",omitempty"` // errors loading dependencies
}

// A PackageError describes an error loading information about a package.
type PackageError struct {
	ImportStack []string // shortest path from package named on command line to this one
	Err         string   // the error itself
}

// An importStack is a stack of import paths.
type importStack []string

loadPackage 関数は、パッケージのロード中にエラーが発生した場合でも、nil を返すのではなく、エラー情報を含む Package 構造体を返すように変更されました。

func loadPackage(arg string, stk *importStack) *Package {
	stk.push(arg)
	defer stk.pop()

	// ... (キャッシュチェックなど) ...

	if err != nil {
		p := &Package{
			ImportPath: arg,
			Error: &PackageError{
				ImportStack: stk.copy(),
				Err:         err.Error(),
			},
			Incomplete: true,
		}
		packageCache[arg] = p
		return p
	}
	// ... (パッケージのスキャンと情報の格納) ...
}

この変更により、go fixgo fmt のようなツールが、依存関係が壊れているパッケージに対しても、少なくともそのパッケージ自身のソースファイルを処理できるようになりました。

src/cmd/go/get.go の主要ロジック

go get コマンドの核となる runGet 関数は、ダウンロードとインストールの2つのフェーズに分かれています。

func runGet(cmd *Command, args []string) {
	// Phase 1.  Download/update.
	args = importPaths(args)
	var stk importStack
	for _, arg := range args {
		download(arg, &stk)
	}
	exitIfErrors()

	if *getD { // -d フラグが指定された場合、ダウンロードのみで終了
		return
	}

	// Phase 2. Install.
	// ... (パッケージキャッシュのクリア) ...
	runInstall(cmd, args) // ダウンロードしたパッケージをインストール
}

download 関数は、個々のパッケージのダウンロードと依存関係の再帰的な処理を担当します。

func download(arg string, stk *importStack) {
	p := loadPackage(arg, stk) // パッケージ情報をロード

	if p.Standard { // 標準ライブラリのパッケージはスキップ
		return
	}

	if downloadCache[arg] { // 重複ダウンロード防止
		return
	}
	downloadCache[arg] = true

	if p.Dir == "" || *getU { // パッケージが存在しないか、-u フラグで更新が必要な場合
		stk.push(p.ImportPath)
		defer stk.pop()
		if err := downloadPackage(p); err != nil { // 実際のダウンロード処理
			errorf("%s", &PackageError{stk.copy(), err.Error()})
			return
		}
		p = reloadPackage(arg, stk) // 更新されたファイルからパッケージ情報を再ロード
		if p.Error != nil {
			errorf("%s", p.Error)
			return
		}
	}

	if *getFix { // -fix フラグが指定された場合、gofix を実行
		run(stringList("gofix", relPaths(p.gofiles)))
		p = reloadPackage(arg, stk) // gofix 実行でインポートパスが変わる可能性があるので再ロード
		if p.Error != nil {
			errorf("%s", p.Error)
			return
		}
	}

	// 依存関係を再帰的に処理
	for _, dep := range p.deps {
		download(dep.ImportPath, stk)
	}
}

downloadPackage 関数は、VCS を利用してリポジトリをクローンまたは更新します。

func downloadPackage(p *Package) error {
	vcs, repo, rootPath, err := vcsForImportPath(p.ImportPath) // インポートパスからVCS情報を取得
	if err != nil {
		return err
	}
	// ... (VCSリポジトリのルートディレクトリの決定とキャッシュチェック) ...

	meta := filepath.Join(root, "."+vcs.cmd) // VCSメタデータディレクトリのパス
	st, err := os.Stat(meta)
	if err == nil && !st.IsDir() {
		return fmt.Errorf("%s exists but is not a directory", meta)
	}
	if err != nil { // メタデータディレクトリが存在しない場合(新規クローン)
		// ... (親ディレクトリの作成、VCS.create を呼び出してリポジトリをクローン) ...
	} else { // メタデータディレクトリが存在する場合(既存リポジトリの更新)
		if err = vcs.download(root); err != nil { // VCS.download を呼び出して更新
			return err
		}
	}

	tags, err := vcs.tags(root) // リポジトリのタグを取得
	if err != nil {
		return err
	}
	vers := runtime.Version()
	tag := selectTag(vers, tags) // Goバージョンに合ったタグを選択
	if tag == "" {
		tag = vcs.tagDefault
	}
	if err := vcs.tagSync(root, tag); err != nil { // 選択したタグに同期
		return err
	}
	return nil
}

selectTag 関数は、Goのバージョン文字列と利用可能なタグのリストに基づいて、最適なタグを選択します。

func selectTag(goVersion string, tags []string) (match string) {
	// ... (release.rN および weekly.YYYY-MM-DD 形式のバージョンとタグのマッチングロジック) ...
	return match
}

src/buildscript.sh の変更

Goツールチェインのビルドスクリプトは、go コマンド自体をビルドする際に、go_bootstrap という中間バイナリを生成するように変更されました。

# ... 既存のコード ...
	go install -a -n -t cmd_go_bootstrap cmd/go | sed '
		s/\$GOBIN/"$GOBIN"/g
		s/\$GOROOT/"$GOROOT"/g
		s/\$WORK/"$WORK"/g
		s;"\$GOBIN"/go;&_bootstrap;g  # ここで go_bootstrap という名前でビルドされる
		s;\\;/;g
	'
# ... 既存のコード ...

そして、最終的な go バイナリのコピー先も go_bootstrap に変更されています。

--- a/src/buildscript/darwin_386.sh
+++ b/src/buildscript/darwin_386.sh
@@ -491,8 +491,8 @@ cp "$WORK"/text/template.a "$GOROOT"/pkg/darwin_386/text/template.a
 
  mkdir -p "$WORK"/cmd/go/_obj/
  cd "$GOROOT"/src/cmd/go
-8g -o "$WORK"/cmd/go/_obj/_go_.8 -p cmd/go -I "$WORK" ./build.go ./fix.go ./fmt.go ./get.go ./help.go ./list.go ./main.go ./pkg.go ./run.go ./test.go ./testflag.go ./version.go ./vet.go
+8g -o "$WORK"/cmd/go/_obj/_go_.8 -p cmd/go -I "$WORK" ./bootstrap.go ./build.go ./fix.go ./fmt.go ./get.go ./help.go ./list.go ./main.go ./pkg.go ./run.go ./test.go ./testflag.go ./vcs.go ./version.go ./vet.go
  gopack grc "$WORK"/cmd/go.a "$WORK"/cmd/go/_obj/_go_.8
  8l -o "$WORK"/cmd/go/_obj/a.out -L "$WORK" "$WORK"/cmd/go.a
  mkdir -p "$GOBIN"/
-cp "$WORK"/cmd/go/_obj/a.out "$GOBIN"/go
+cp "$WORK"/cmd/go/_obj/a.out "$GOBIN"/go_bootstrap

この go_bootstrap は、bootstrap.go という新しいファイル(このコミットでは追加されていないが、このコミットの意図を反映して後続のコミットで追加される)によって、http 関連の機能がスタブ化されたバージョンとしてビルドされます。

関連リンク

参考にした情報源リンク

  • コミットメッセージ自体 (./commit_data/11346.txt)
  • Go言語のソースコード (特に src/cmd/go/ ディレクトリ内のファイル)
  • Go言語の公式ドキュメント (現在のバージョンを参照し、当時の状況を推測)
  • Go言語のメーリングリストやIssueトラッカー (当時の議論を検索)
  • Goのビルドプロセスに関する一般的な情報源