[インデックス 17081] ファイルの概要
このコミットは、Goプロジェクトのビルドシステムにおいて、cmd/api
ツールの実行方法を変更するものです。具体的には、cmd/api
の実行ロジックをシェルスクリプト(run.bash
とrun.bat
)から、新しく導入されたGoプログラム(src/cmd/api/run.go
)へ移行しています。これは、将来的に予定されているcmd/api
の書き換えに備え、その実行ポリシーとメカニズムを一元化し、より柔軟な制御を可能にすることを目的としています。
コミット
commit d5e97ea2f51f145e041a86db9eb7bfbc3f1adb75
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Aug 7 13:49:37 2013 -0700
build: change how cmd/api is run in run.bash and run.bat
In prep for Robert's forthcoming cmd/api rewrite which
depends on the go.tools subrepo, we'll need to be more
careful about how and when we run cmd/api.
Rather than implement this policy in both run.bash and
run.bat, this change moves the policy and mechanism into
cmd/api/run.go, which will then evolve.
The plan is in a TODO in run.go.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/12482044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d5e97ea2f51f145e041a86db9eb7bfbc3f1adb75
元コミット内容
build: change how cmd/api is run in run.bash and run.bat
In prep for Robert's forthcoming cmd/api rewrite which
depends on the go.tools subrepo, we'll need to be more
careful about how and when we run cmd/api.
Rather than implement this policy in both run.bash and
run.bat, this change moves the policy and mechanism into
cmd/api/run.go, which will then evolve.
The plan is in a TODO in run.go.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/12482044
変更の背景
この変更の主な背景には、Goプロジェクトにおけるcmd/api
ツールの重要な書き換え計画がありました。コミットメッセージに「Robert's forthcoming cmd/api rewrite which depends on the go.tools subrepo」とあるように、Robert Griesemer氏によるcmd/api
の再実装が予定されており、この新しいバージョンはgo.tools
サブモジュールに依存することが示唆されています。
従来のビルドスクリプト(run.bash
とrun.bat
)では、cmd/api
の実行ロジックが直接記述されていました。しかし、新しいcmd/api
はより複雑な依存関係や実行条件を持つことが予想されたため、既存のシェルスクリプトにそのロジックを直接組み込むのは保守性や拡張性の観点から望ましくありませんでした。
そこで、cmd/api
の実行に関する「ポリシーとメカニズム」を、シェルスクリプトから独立したGoプログラム(src/cmd/api/run.go
)に移管することで、以下の利点が得られます。
- 一元化と保守性の向上:
cmd/api
の実行ロジックが単一のGoファイルに集約されるため、変更やデバッグが容易になります。 - クロスプラットフォーム対応:
run.bash
(Linux/macOS)とrun.bat
(Windows)の両方で同じロジックを維持する必要がなくなり、Goプログラムがその役割を担うことで、プラットフォーム間の差異を吸収しやすくなります。 - 柔軟な制御: Goプログラム内で、より複雑な条件分岐や外部コマンドの実行、環境変数のチェックなど、高度なロジックを実装できるようになります。これにより、
go.tools
サブモジュールのチェックアウト状況やバージョンに応じた条件付き実行など、将来的な要件に対応しやすくなります。 - 進化の容易さ: コミットメッセージにある「which will then evolve」の通り、
run.go
はcmd/api
の書き換えに合わせて進化していく基盤となります。
この変更は、Goのビルドプロセスにおける重要なツールであるcmd/api
の将来的な発展を見据えた、基盤的な改善と言えます。
前提知識の解説
このコミットを理解するためには、以下のGoプロジェクトのビルドシステムと関連ツールに関する知識が必要です。
-
cmd/api
ツール:- Go言語のAPI互換性チェッカーツールです。Goの新しいバージョンがリリースされる際、既存のコードとの互換性が維持されているかを確認するために使用されます。
- 具体的には、Goの標準ライブラリの公開API(関数シグネチャ、構造体フィールドなど)が、以前のバージョンから変更されていないかをチェックします。これにより、Goのバージョンアップによって既存のユーザーコードが壊れることを防ぎます。
- 通常、
go tool api
コマンドとして実行されます。引数として、互換性をチェックする対象のAPI定義ファイル(例:go1.txt
,go1.1.txt
)や、次期バージョンのAPI定義ファイル(next.txt
)、例外リスト(except.txt
)などを指定します。
-
go.tools
サブモジュール (subrepo):- Goプロジェクトは、主要なGoリポジトリ(
golang/go
)の他に、関連するツールやライブラリを管理するための「サブモジュール(subrepo)」という概念を持っていました(現在はモジュールに移行)。 go.tools
は、Go言語開発に役立つ様々なツール(例:go vet
,go doc
,go generate
など)が含まれるリポジトリでした。cmd/api
の新しい実装がこのgo.tools
に依存するということは、ビルド時にgo.tools
リポジトリが適切にチェックアウトされ、利用可能である必要があることを意味します。
- Goプロジェクトは、主要なGoリポジトリ(
-
run.bash
とrun.bat
:- これらはGoプロジェクトのルートディレクトリにある、Goのテストスイート全体を実行するためのシェルスクリプトです。
run.bash
はUnix系システム(Linux, macOSなど)用、run.bat
はWindows用です。- これらのスクリプトは、Goのコンパイル、テストの実行、そして
cmd/api
によるAPI互換性チェックなど、Goのビルドと検証プロセス全体を自動化するために使用されます。
-
hg codereview extension
(Mercurial Codereview Extension):- Goプロジェクトはかつて、バージョン管理システムとしてMercurial(
hg
)を使用していました。hg codereview
は、Mercurialの拡張機能の一つで、コードレビュープロセスを支援するためのものです。 - この拡張機能がインストールされているかどうかは、開発者がGoのソースコードをどのように扱っているか(公式の開発者として作業しているか、あるいは単にソースをダウンロードしてビルドしているだけか)を示す指標の一つでした。
- コミットメッセージや
run.go
のコードにある「hg pq
」コマンドは、Mercurialのパッチキュー(patch queue)の状態を確認するためのもので、開発者が変更を管理している環境であるかを判断するために使われています。
- Goプロジェクトはかつて、バージョン管理システムとしてMercurial(
-
ビルドタグ (
+build
directive):- Goのソースファイルには、
// +build tag
のようなビルドタグを記述することができます。これは、特定のタグが有効な場合にのみそのファイルをコンパイル対象とするためのディレクティブです。 - このコミットでは、
src/cmd/api/run.go
の冒頭に// +build from_src_run
というタグが追加されています。これは、run.bash
やrun.bat
からgo run
コマンドでこのファイルを実行する際に、このタグを指定することで、意図的にこのファイルがコンパイル・実行されるように制御するためのものです。
- Goのソースファイルには、
これらの前提知識を理解することで、コミットがGoのビルドシステムに与える影響と、その背後にある設計思想をより深く把握できます。
技術的詳細
このコミットの技術的な核心は、cmd/api
の実行ロジックをシェルスクリプトからGoプログラムへ移管し、その実行をより堅牢かつ柔軟に制御することにあります。
1. cmd/api
実行ロジックのGoプログラムへの移管
-
新規ファイル
src/cmd/api/run.go
の導入:- このファイルは、
cmd/api
ツールをビルドし、実行するためのすべてのロジックをカプセル化します。 - ファイルの冒頭には
// +build from_src_run
というビルドタグが記述されています。これは、このファイルが特定のビルドコンテキストでのみコンパイルされることを示します。run.bash
やrun.bat
からgo run --tags=from_src_run
として呼び出されることで、このファイルが実行されます。 main
関数内で、環境変数GOROOT
のチェック、開発者環境の検出(hg pq
コマンドの実行結果による)、そしてGO_FORCE_API_CHECK
環境変数のチェックが行われます。- これらの条件に基づいて、
cmd/api
のビルド(go install --tags=api_tool cmd/api
)と実行(go tool api ...
)が制御されます。
- このファイルは、
-
シェルスクリプトからの呼び出し変更:
src/run.bash
とsrc/run.bat
から、直接go tool api
を呼び出す代わりに、新しく作成されたsrc/cmd/api/run.go
をgo run
コマンドで実行するように変更されました。run.bash
では、go run --tags=from_src_run $GOROOT/src/cmd/api/run.go
という形式で呼び出されます。run.bat
でも同様に、go run --tags=from_src_run "%GOROOT%\src\cmd\api\run.go"
という形式で呼び出されます。- これにより、
cmd/api
の実行に関するすべてのポリシー(いつ実行するか、どのような引数で実行するかなど)がrun.go
に集約され、シェルスクリプトは単にそのGoプログラムを起動するだけの役割になります。
2. 条件付き実行ロジック
src/cmd/api/run.go
には、cmd/api
を実行するかどうかを決定する以下のロジックが含まれています。
-
開発者環境の検出:
isGoDeveloper := exec.Command("hg", "pq").Run() == nil
- これは、Mercurialの
hg pq
コマンドが成功するかどうかで、現在の環境がGoの開発者が使用するパッチキューが有効な環境であるかを判断します。成功した場合、isGoDeveloper
はtrue
になります。 - Goの開発者でない場合(
isGoDeveloper
がfalse
)、APIチェックはスキップされます。
-
強制APIチェックの環境変数:
GO_FORCE_API_CHECK
という環境変数が設定されている場合、APIチェックが強制的に実行されます。これは、CI/CD環境(ビルドボットなど)で常にAPIチェックを実行したい場合に利用されます。forceAPICheck()
関数がstrconv.ParseBool(os.Getenv("GO_FORCE_API_CHECK"))
を使ってこの環境変数の真偽値をパースします。
-
APIチェックのスキップ条件:
if !isGoDeveloper && !forceAPICheck() { ... return }
- この条件は、「Go開発者環境ではない」かつ「
GO_FORCE_API_CHECK
が設定されていない」場合に、APIチェックをスキップすることを示しています。これにより、一般ユーザーがGoをビルドする際に、不要なAPIチェックの実行を避けることができます。
3. cmd/api
のビルドと実行
-
go install --tags=api_tool cmd/api
:cmd/api
ツール自体をビルドし、$GOBIN
(または$GOROOT/bin
)にインストールします。--tags=api_tool
は、cmd/api
のソースコード内で特定のビルドタグが指定されている場合に、そのコードを含めてコンパイルするためのものです。これにより、cmd/api
の特定の機能が有効になる可能性があります。
-
go tool api -c ... -next ... -except ...
:- インストールされた
cmd/api
ツールを実行します。 -c
,-next
,-except
といった引数は、API互換性チェックに必要なAPI定義ファイル(go1.txt
,go1.1.txt
,next.txt
,except.txt
)のパスを指定します。これらのパスは、file
ヘルパー関数によって$GOROOT/api/
ディレクトリからの相対パスに変換されます。
- インストールされた
4. misc/dist/bindist.go
の変更
misc/dist/bindist.go
は、Goのバイナリ配布物を作成する際にクリーンアップするファイルリストを定義しています。- このコミットでは、
src/cmd/api
がこのリストから削除されています。これは、cmd/api
がGoのビルドプロセスの一部としてより密接に統合され、バイナリ配布物の一部として扱われるようになったため、明示的にクリーンアップする必要がなくなったことを示唆しています。
これらの変更により、cmd/api
の実行はより制御され、将来の変更にも対応しやすい、堅牢なシステムへと進化しました。
コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されています。
-
misc/dist/bindist.go
:preBuildCleanFiles
という文字列スライスから、"src/cmd/api"
のエントリが削除されました。
--- a/misc/dist/bindist.go +++ b/misc/dist/bindist.go @@ -50,7 +50,6 @@ const ( var preBuildCleanFiles = []string{ "lib/codereview", "misc/dashboard/godashboard", - "src/cmd/api", "src/cmd/cov", "src/cmd/prof", "src/pkg/exp",
-
src/cmd/api/run.go
:- このファイルが新規作成されました。
+build from_src_run
ビルドタグが冒頭に記述されています。main
関数内で、GOROOT
のチェック、開発者環境の検出、GO_FORCE_API_CHECK
環境変数のチェック、cmd/api
のインストール、そしてgo tool api
の実行ロジックが含まれています。file
ヘルパー関数とforceAPICheck
ヘルパー関数が定義されています。
--- /dev/null +++ b/src/cmd/api/run.go @@ -0,0 +1,65 @@ +// +build from_src_run + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The run program is invoked via "go run" from src/run.bash or +// src/run.bat conditionally builds and runs the cmd/api tool. +// +// TODO(bradfitz): the "conditional" condition is always true. +// We should only do this if the user has the hg codereview extension +// enabled and verifies that the go.tools subrepo is checked out with +// a suitably recently version. In prep for the cmd/api rewrite. +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" +) + +var goroot string + +func main() { + log.SetFlags(0) + goroot = os.Getenv("GOROOT") // should be set by run.{bash,bat} + if goroot == "" { + log.Fatal("No $GOROOT set.") + } + isGoDeveloper := exec.Command("hg", "pq").Run() == nil + if !isGoDeveloper && !forceAPICheck() { + fmt.Println("Skipping cmd/api checks; hg codereview extension not available and GO_FORCE_API_CHECK not set") + return + } + + out, err := exec.Command("go", "install", "--tags=api_tool", "cmd/api").CombinedOutput() + if err != nil { + log.Fatalf("Error installing cmd/api: %v\n%s", err, out) + } + out, err = exec.Command("go", "tool", "api", + "-c", file("go1", "go1.1"), + "-next", file("next"), + "-except", file("except")).CombinedOutput() + if err != nil { + log.Fatalf("Error running API checker: %v\n%s", err, out) + } +} + +// file expands s to $GOROOT/api/s.txt. +// If there are more than 1, they're comma-separated. +func file(s ...string) string { + if len(s) > 1 { + return file(s[0]) + "," + file(s[1:]...) + } + return filepath.Join(goroot, "api", s[0]+".txt") +} + +// GO_FORCE_API_CHECK is set by builders. +func forceAPICheck() bool { + v, _ := strconv.ParseBool(os.Getenv("GO_FORCE_API_CHECK")) + return v +}
-
src/run.bash
:cmd/api
の実行部分が、直接go tool api
を呼び出す形式から、go run --tags=from_src_run $GOROOT/src/cmd/api/run.go
を呼び出す形式に変更されました。- API互換性チェックの条件分岐(
if [ -d "$GOROOT/src/cmd/api" ]
)が削除されました。
--- a/src/run.bash +++ b/src/run.bash @@ -180,12 +180,9 @@ unset GOMAXPROCS time go run run.go || exit 1 ) || exit $? -if [ -d "$GOROOT/src/cmd/api" ] -then - echo - echo '# Checking API compatibility.' - go tool api -c $GOROOT/api/go1.txt,$GOROOT/api/go1.1.txt -next $GOROOT/api/next.txt -except $GOROOT/api/except.txt -fi +echo +echo '# Checking API compatibility.' +go run --tags=from_src_run $GOROOT/src/cmd/api/run.go echo echo ALL TESTS PASSED
-
src/run.bat
:cmd/api
の実行部分が、直接go tool api
を呼び出す形式から、go run --tags=from_src_run "%GOROOT%\src\cmd\api\run.go"
を呼び出す形式に変更されました。
--- a/src/run.bat +++ b/src/run.bat @@ -121,7 +121,7 @@ set GOMAXPROCS=%OLDGOMAXPROCS%\n set OLDGOMAXPROCS=\n \n echo # Checking API compatibility.\n-go tool api -c ..\api\go1.txt,..\\api\go1.1.txt -next ..\api\next.txt -except ..\api\except.txt\n+go run --tags=from_src_run "%GOROOT%\src\cmd\api\run.go"\n if errorlevel 1 goto fail\n echo.\n \n ```
コアとなるコードの解説
このコミットの核となるのは、新しく追加されたsrc/cmd/api/run.go
ファイルです。このファイルは、GoのビルドプロセスにおけるAPI互換性チェックの実行を、より制御された方法で行うためのロジックを実装しています。
src/cmd/api/run.go
-
// +build from_src_run
:- これはGoのビルドタグです。このタグが存在することで、このファイルは特定のビルドコマンド(
go run --tags=from_src_run ...
)が実行された場合にのみコンパイル・実行されます。これにより、run.bash
やrun.bat
からのみこのロジックが起動されるように制御されています。
- これはGoのビルドタグです。このタグが存在することで、このファイルは特定のビルドコマンド(
-
package main
:- このファイルが独立した実行可能プログラムであることを示します。
-
import
文:fmt
: フォーマットされたI/O(主にfmt.Println
)log
: ロギング(エラーメッセージの出力)os
: オペレーティングシステムとのインタラクション(環境変数の取得、終了コード)os/exec
: 外部コマンドの実行(hg
,go
コマンドの実行)path/filepath
: ファイルパスの操作(filepath.Join
)strconv
: 文字列と数値の変換(環境変数のパース)
-
var goroot string
:GOROOT
環境変数の値を保持するためのグローバル変数です。
-
func main()
:- プログラムのエントリポイントです。
log.SetFlags(0)
: ログのタイムスタンプなどを非表示にし、メッセージのみを出力するように設定します。goroot = os.Getenv("GOROOT")
:GOROOT
環境変数を取得します。これはrun.bash
やrun.bat
によって設定されていることが期待されます。if goroot == "" { log.Fatal("No $GOROOT set.") }
:GOROOT
が設定されていない場合は致命的なエラーとして終了します。isGoDeveloper := exec.Command("hg", "pq").Run() == nil
:hg pq
コマンドを実行し、その終了ステータスが0(成功)であるかどうかを確認します。hg pq
はMercurialのパッチキューの状態を確認するコマンドで、Goの開発者が作業している環境では通常成功します。これにより、現在の環境がGoの開発者環境であるかを判断します。
if !isGoDeveloper && !forceAPICheck() { ... return }
:- この条件がAPIチェックをスキップする主要なロジックです。
!isGoDeveloper
: 現在の環境がGo開発者環境ではない場合。!forceAPICheck()
:GO_FORCE_API_CHECK
環境変数がtrue
に設定されていない場合。- 両方の条件が真の場合、APIチェックはスキップされ、その旨のメッセージが出力されてプログラムが終了します。これにより、一般ユーザーがGoをビルドする際に不要なAPIチェックを回避できます。
out, err := exec.Command("go", "install", "--tags=api_tool", "cmd/api").CombinedOutput()
:cmd/api
ツール自体をビルドし、インストールします。--tags=api_tool
は、cmd/api
のソースコード内の特定のビルドタグを有効にするために使用されます。CombinedOutput()
は、コマンドの標準出力と標準エラー出力を結合して返します。- エラーが発生した場合は、ログにエラーメッセージと出力内容を記録して終了します。
out, err = exec.Command("go", "tool", "api", ...).CombinedOutput()
:- インストールされた
cmd/api
ツールを実行します。 -c
,-next
,-except
引数には、file
ヘルパー関数によって生成されたAPI定義ファイルのパスが渡されます。- ここでもエラーが発生した場合は、ログにエラーメッセージと出力内容を記録して終了します。
- インストールされた
-
func file(s ...string) string
:- 可変長引数
s
を受け取るヘルパー関数です。 - 引数として渡されたファイル名(例: "go1", "go1.1", "next", "except")を、
$GOROOT/api/
ディレクトリ内の対応する.txt
ファイルパスに変換します。 - 複数のファイル名が渡された場合は、それらをカンマで区切った文字列として返します。これは
go tool api
コマンドが複数の-c
引数をカンマ区切りで受け取るためです。 - 例:
file("go1", "go1.1")
は$GOROOT/api/go1.txt,$GOROOT/api/go1.1.txt
のような文字列を返します。
- 可変長引数
-
func forceAPICheck() bool
:GO_FORCE_API_CHECK
環境変数の値をチェックするヘルパー関数です。os.Getenv("GO_FORCE_API_CHECK")
で環境変数の値を取得し、strconv.ParseBool
で真偽値に変換します。- この環境変数は、ビルドボットなどの自動化された環境でAPIチェックを強制的に実行するために使用されます。
src/run.bash
と src/run.bat
これらのシェルスクリプトの変更は非常にシンプルです。
-
旧コード:
if [ -d "$GOROOT/src/cmd/api" ]
のような条件分岐でcmd/api
ディレクトリの存在を確認し、その中で直接go tool api ...
を実行していました。
-
新コード:
- 条件分岐を削除し、代わりに
go run --tags=from_src_run $GOROOT/src/cmd/api/run.go
(またはWindows版)を直接呼び出すように変更されました。 - これにより、APIチェックの実行に関するすべての複雑なロジックは
run.go
に委譲され、シェルスクリプトは単にそのGoプログラムを起動するだけの役割になりました。
- 条件分岐を削除し、代わりに
この変更は、Goのビルドシステムにおける責任の分離と、より高度なロジックをGo言語で記述することによる柔軟性の向上を示しています。
関連リンク
- Goの公式リポジトリ: https://github.com/golang/go
- このコミットのGo Gerritレビューページ: https://golang.org/cl/12482044
参考にした情報源リンク
- Goのビルドタグに関するドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- Mercurialのパッチキュー(
hg pq
)に関する情報(一般的なMercurialのドキュメント) - Goの
os/exec
パッケージに関するドキュメント: https://pkg.go.dev/os/exec - Goの
strconv
パッケージに関するドキュメント: https://pkg.go.dev/strconv