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

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

このコミットは、Goプロジェクトのビルドシステムにおいて、cmd/apiツールの実行方法を変更するものです。具体的には、cmd/apiの実行ロジックをシェルスクリプト(run.bashrun.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.bashrun.bat)では、cmd/apiの実行ロジックが直接記述されていました。しかし、新しいcmd/apiはより複雑な依存関係や実行条件を持つことが予想されたため、既存のシェルスクリプトにそのロジックを直接組み込むのは保守性や拡張性の観点から望ましくありませんでした。

そこで、cmd/apiの実行に関する「ポリシーとメカニズム」を、シェルスクリプトから独立したGoプログラム(src/cmd/api/run.go)に移管することで、以下の利点が得られます。

  1. 一元化と保守性の向上: cmd/apiの実行ロジックが単一のGoファイルに集約されるため、変更やデバッグが容易になります。
  2. クロスプラットフォーム対応: run.bash(Linux/macOS)とrun.bat(Windows)の両方で同じロジックを維持する必要がなくなり、Goプログラムがその役割を担うことで、プラットフォーム間の差異を吸収しやすくなります。
  3. 柔軟な制御: Goプログラム内で、より複雑な条件分岐や外部コマンドの実行、環境変数のチェックなど、高度なロジックを実装できるようになります。これにより、go.toolsサブモジュールのチェックアウト状況やバージョンに応じた条件付き実行など、将来的な要件に対応しやすくなります。
  4. 進化の容易さ: コミットメッセージにある「which will then evolve」の通り、run.gocmd/apiの書き換えに合わせて進化していく基盤となります。

この変更は、Goのビルドプロセスにおける重要なツールであるcmd/apiの将来的な発展を見据えた、基盤的な改善と言えます。

前提知識の解説

このコミットを理解するためには、以下のGoプロジェクトのビルドシステムと関連ツールに関する知識が必要です。

  1. cmd/apiツール:

    • Go言語のAPI互換性チェッカーツールです。Goの新しいバージョンがリリースされる際、既存のコードとの互換性が維持されているかを確認するために使用されます。
    • 具体的には、Goの標準ライブラリの公開API(関数シグネチャ、構造体フィールドなど)が、以前のバージョンから変更されていないかをチェックします。これにより、Goのバージョンアップによって既存のユーザーコードが壊れることを防ぎます。
    • 通常、go tool apiコマンドとして実行されます。引数として、互換性をチェックする対象のAPI定義ファイル(例: go1.txt, go1.1.txt)や、次期バージョンのAPI定義ファイル(next.txt)、例外リスト(except.txt)などを指定します。
  2. go.toolsサブモジュール (subrepo):

    • Goプロジェクトは、主要なGoリポジトリ(golang/go)の他に、関連するツールやライブラリを管理するための「サブモジュール(subrepo)」という概念を持っていました(現在はモジュールに移行)。
    • go.toolsは、Go言語開発に役立つ様々なツール(例: go vet, go doc, go generateなど)が含まれるリポジトリでした。cmd/apiの新しい実装がこのgo.toolsに依存するということは、ビルド時にgo.toolsリポジトリが適切にチェックアウトされ、利用可能である必要があることを意味します。
  3. run.bashrun.bat:

    • これらはGoプロジェクトのルートディレクトリにある、Goのテストスイート全体を実行するためのシェルスクリプトです。
    • run.bashはUnix系システム(Linux, macOSなど)用、run.batはWindows用です。
    • これらのスクリプトは、Goのコンパイル、テストの実行、そしてcmd/apiによるAPI互換性チェックなど、Goのビルドと検証プロセス全体を自動化するために使用されます。
  4. hg codereview extension (Mercurial Codereview Extension):

    • Goプロジェクトはかつて、バージョン管理システムとしてMercurial(hg)を使用していました。hg codereviewは、Mercurialの拡張機能の一つで、コードレビュープロセスを支援するためのものです。
    • この拡張機能がインストールされているかどうかは、開発者がGoのソースコードをどのように扱っているか(公式の開発者として作業しているか、あるいは単にソースをダウンロードしてビルドしているだけか)を示す指標の一つでした。
    • コミットメッセージやrun.goのコードにある「hg pq」コマンドは、Mercurialのパッチキュー(patch queue)の状態を確認するためのもので、開発者が変更を管理している環境であるかを判断するために使われています。
  5. ビルドタグ (+build directive):

    • Goのソースファイルには、// +build tagのようなビルドタグを記述することができます。これは、特定のタグが有効な場合にのみそのファイルをコンパイル対象とするためのディレクティブです。
    • このコミットでは、src/cmd/api/run.goの冒頭に// +build from_src_runというタグが追加されています。これは、run.bashrun.batからgo runコマンドでこのファイルを実行する際に、このタグを指定することで、意図的にこのファイルがコンパイル・実行されるように制御するためのものです。

これらの前提知識を理解することで、コミットがGoのビルドシステムに与える影響と、その背後にある設計思想をより深く把握できます。

技術的詳細

このコミットの技術的な核心は、cmd/apiの実行ロジックをシェルスクリプトからGoプログラムへ移管し、その実行をより堅牢かつ柔軟に制御することにあります。

1. cmd/api実行ロジックのGoプログラムへの移管

  • 新規ファイル src/cmd/api/run.go の導入:

    • このファイルは、cmd/apiツールをビルドし、実行するためのすべてのロジックをカプセル化します。
    • ファイルの冒頭には// +build from_src_runというビルドタグが記述されています。これは、このファイルが特定のビルドコンテキストでのみコンパイルされることを示します。run.bashrun.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.bashsrc/run.batから、直接go tool apiを呼び出す代わりに、新しく作成されたsrc/cmd/api/run.gogo 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の開発者が使用するパッチキューが有効な環境であるかを判断します。成功した場合、isGoDevelopertrueになります。
    • Goの開発者でない場合(isGoDeveloperfalse)、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つのファイルが変更されています。

  1. 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",
    
  2. 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
    +}
    
  3. 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
    
  4. 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.bashrun.batからのみこのロジックが起動されるように制御されています。
  • 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.bashrun.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.bashsrc/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言語で記述することによる柔軟性の向上を示しています。

関連リンク

参考にした情報源リンク