[インデックス 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言語のツールチェインにおけるいくつかの課題と、開発者の利便性向上の必要性がありました。
-
依存関係の管理と取得の自動化: 当時、Goプロジェクトの依存関係を手動で管理し、取得するのは煩雑な作業でした。特に、外部ライブラリを利用する際に、そのライブラリのソースコードを適切な場所に配置する必要がありました。
go getコマンドの導入は、このプロセスを自動化し、開発者がより簡単に外部パッケージを利用できるようにすることを目的としています。 -
壊れた依存関係を持つパッケージのツール利用: 以前のGoツールでは、パッケージの依存関係に問題がある場合(例: 存在しないインポートパス、ロードに失敗するパッケージ)、そのパッケージに対して
go fixやgo fmtといったツールを実行することが困難でした。これは、ツールがパッケージ情報を完全にロードできないと判断し、処理を中断してしまうためです。このコミットでは、エラー情報をPackage構造体内に保持することで、部分的にでもパッケージ情報を利用できるようにし、壊れた依存関係を持つパッケージに対してもツールが実行できるように改善されました。 -
Goツール自身のビルドプロセスの複雑化:
go tool(Goコマンド自体) がhttpパッケージに依存するようになったことで、netやcrypto/tlsといったパッケージも間接的に必要となりました。これらのパッケージはcgo(C言語との連携機能) を使用しているため、Goツール自身のビルドプロセスが複雑化するという問題が生じました。特に、ブートストラップビルド(Goコンパイラ自身をGoでビルドするプロセス)において、cgoのサポートを最初から組み込むのは困難でした。 -
go listコマンドの出力改善:go listコマンドでパッケージのドキュメント (p.Doc) を表示する際、パッケージ全体のドキュメントが表示されてしまい、特にgo list -jsonやgo list -f '{{.ImportPath}} {{.Doc}}'のような形式で利用する際に冗長でした。これをパッケージドキュメントの最初の1文に限定することで、より簡潔で有用な出力となるよう改善が求められました。 -
ビルドスクリプトの自己修復能力:
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: 指定されたパッケージの情報を表示します。パッケージのインポートパス、ソースファイルのリスト、依存関係などの詳細を取得できます。
GOROOTとGOPATH: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 boolとError *PackageErrorフィールドが追加されました。また、PackageError構造体とimportStack型が新しく定義されました。 - 目的: 以前は、パッケージのロード中にエラーが発生すると、そのパッケージに関する情報が全く返されないか、処理が中断されていました。この変更により、エラーが発生した場合でも
Package構造体自体は返され、エラーの詳細はErrorフィールドに格納されるようになりました。これにより、部分的にロードされたパッケージ情報でもgo fixやgo 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 -jsonやgo 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バージョンと互換性のあるパッケージバージョンを取得できます。 - キャッシュ:
downloadCacheとdownloadRootCacheを使用して、重複するダウンロード作業を避けます。 - 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フラグは、条件付きコンパイルに使用されるビルドタグを指定するために利用されます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に以下のファイルとセクションが重要です。
-
src/cmd/go/pkg.go:Package構造体へのIncompleteおよびErrorフィールドの追加。PackageError構造体とimportStack型の新規定義。loadPackage関数の変更: エラー発生時でもPackage構造体を返し、エラー情報を格納するように修正。reloadPackage関数の追加。
-
src/cmd/go/get.go(新規ファイル):cmdGetコマンドの定義とrunGet関数の実装。download関数: パッケージのダウンロードと依存関係の処理。downloadPackage関数: VCS を利用したリポジトリのクローン/更新ロジック。selectTag関数: Goバージョンに応じたVCSタグの選択ロジック。getD,getU,getFixフラグの定義。
-
src/cmd/go/list.go:cmdListコマンドのUsageLineに-eフラグの追加。listEフラグの定義。runList関数におけるpackagesAndErrorsの利用(-eフラグが指定された場合)。Package構造体のドキュメントにIncomplete,Error,DepsErrorsフィールドの記述を追加。
-
src/cmd/go/build.go:buildContext変数の導入とbuild.DefaultContextからの初期化。addBuildFlags関数における-tフラグの追加とbuildContext.BuildTagsへの設定。goFilesPackageおよびaction関数におけるbuildContextの利用。
-
src/cmd/go/vcs.go(新規ファイル):vcs構造体と、Git, Mercurial, Subversion, Bazaar などのVCS操作(create,download,tags,tagSync)を抽象化するメソッドの定義。vcsForImportPath関数: インポートパスから適切なVCSとリポジトリ情報を特定するロジック。
-
src/buildscript.shおよびsrc/buildscript/*.sh:go install -a -n cmd/goコマンドに-t cmd_go_bootstrapオプションが追加され、go_bootstrapという名前でビルドされるように変更。- 最終的な
goバイナリのコピー先がgo_bootstrapに変更。
-
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 fix や go 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 関連の機能がスタブ化されたバージョンとしてビルドされます。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
go getコマンドのドキュメント (現在のバージョン): https://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependenciesgo listコマンドのドキュメント (現在のバージョン): https://golang.org/cmd/go/#hdr-List_packages- Goのブートストラップビルドに関する情報: https://golang.org/doc/install/source
参考にした情報源リンク
- コミットメッセージ自体 (
./commit_data/11346.txt) - Go言語のソースコード (特に
src/cmd/go/ディレクトリ内のファイル) - Go言語の公式ドキュメント (現在のバージョンを参照し、当時の状況を推測)
- Go言語のメーリングリストやIssueトラッカー (当時の議論を検索)
- Goのビルドプロセスに関する一般的な情報源