[インデックス 11684] ファイルの概要
このコミットは、Go言語のツールである cmd/api
の goapi.go
ファイルに対する変更です。cmd/api
は、Goの標準ライブラリのAPIが異なるビルドコンテキスト(OS、アーキテクチャ、Cgoの有効/無効など)間で互換性があるかどうかを検証するためのツールです。
コミット
- コミットハッシュ:
f23a6dba5e7a477b15bb10c5f630df01b5f0ea88
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Tue Feb 7 18:13:11 2012 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f23a6dba5e7a477b15bb10c5f630df01b5f0ea88
元コミット内容
cmd/api: compare multiple contexts
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5626045
変更の背景
このコミットの主な目的は、cmd/api
ツールがGoの標準ライブラリのAPIを、単一のビルドコンテキストだけでなく、複数の異なるビルドコンテキスト(オペレーティングシステム、アーキテクチャ、Cgoの有効/無効の組み合わせ)で比較できるようにすることです。
Go言語はクロスプラットフォーム開発を強く意識しており、異なる環境間でのAPIの一貫性は非常に重要です。以前の cmd/api
ツールは、おそらく単一のデフォルトビルドコンテキストでしかAPIの抽出と比較を行っていませんでした。しかし、特定のOSやアーキテクチャ、あるいはCgoの利用有無によって、利用可能なAPIが異なる場合があります。例えば、Cgoが有効な場合のみ利用できる関数や、特定のOSでのみ存在するシステムコールをラップした関数などが考えられます。
このような差異が意図しないAPIの不整合や互換性の問題を引き起こすことを防ぐため、このコミットでは、主要なビルドコンテキストの組み合わせすべてでAPIを抽出し、それらを比較することで、より堅牢なAPI互換性チェックを実現しようとしています。これにより、Goの標準ライブラリが様々な環境で一貫したAPIを提供していることを保証し、開発者が安心してクロスプラットフォームアプリケーションを開発できる基盤を強化します。
前提知識の解説
cmd/api
ツール
cmd/api
は、Go言語の標準ライブラリの公開APIを抽出し、その互換性を検証するための内部ツールです。Go言語は「Go 1 Compatibility Promise」という原則を掲げており、Go 1.xリリース間で既存のコードが動作し続けることを保証しています。cmd/api
はこの互換性を維持するために、APIの変更を監視し、意図しない破壊的変更がないかを確認する役割を担っています。具体的には、Goのソースコードを解析し、エクスポートされた型、関数、メソッド、変数などの情報を抽出し、以前のバージョンと比較することで、APIの追加、削除、変更を検出します。
go/build.Context
構造体
go/build.Context
は、Goのビルドプロセスを制御するための重要な構造体です。これは、Goのパッケージがどのように検索され、解析され、コンパイルされるかに影響を与える環境とパラメータを定義します。build.Context
は、以下のようなビルドに関する情報を含みます。
GOOS
: ターゲットのオペレーティングシステム(例: "linux", "windows", "darwin")GOARCH
: ターゲットのアーキテクチャ(例: "amd64", "386", "arm")CgoEnabled
: Cgo(C言語との相互運用機能)が有効かどうかを示すブール値GOROOT
: GoのインストールディレクトリGOPATH
: Goのワークスペースディレクトリ- ファイルシステム操作をカスタマイズするための関数ポインタ(
IsDir
,ReadDir
,OpenFile
など)
build.Context
を利用することで、Goのツールは異なるビルド環境をシミュレートしたり、仮想ファイルシステム上で動作したりすることが可能になります。cmd/api
のようなツールが複数の環境でのAPI互換性をチェックする際には、この build.Context
を切り替えることで、それぞれの環境でのAPIの振る舞いを正確に把握することができます。
GoのクロスプラットフォームAPI互換性チェック
Go言語は、その設計とツールチェーンによって、クロスプラットフォーム開発に非常に適しています。
- クロスコンパイル: Goは、単一の開発マシンから様々なOSやアーキテクチャ向けの実行ファイルをビルドできるクロスコンパイル機能を標準でサポートしています。
- 標準ライブラリの一貫性: Goの標準ライブラリは、異なるプラットフォーム間で一貫したAPIを提供するように設計されており、ファイルI/O、ネットワーク、並行処理などの一般的な操作は統一された振る舞いをします。
- ビルドタグ (Build Constraints): 特定のプラットフォームに依存するコードが必要な場合、Goは「ビルドタグ」と呼ばれる特別なコメントを使用します。これにより、コンパイラはターゲットOSやアーキテクチャに基づいてファイルをビルドに含めるか除外するかを制御できます。
- Go Modulesとセマンティックバージョニング: Go Modulesは依存関係管理を標準化し、セマンティックバージョニング(SemVer)を使用してAPI互換性を維持します。破壊的変更を伴うメジャーバージョンアップの場合、モジュールパスにバージョンを含めることで、異なるメジャーバージョンのモジュールが共存できるようにします。
apidiff
ツール: Goプロジェクトは、2つのバージョンのパッケージまたはモジュールが互換性があるかどうかを判断するapidiff
ツールを提供しています。
このコミットは、これらのGoのクロスプラットフォーム機能の一部、特に build.Context
を活用して、cmd/api
がより包括的なAPI互換性チェックを実行できるように拡張するものです。
技術的詳細
このコミットの核心は、cmd/api
ツールがAPIを抽出する際に、固定された単一のビルドコンテキストではなく、事前に定義された複数の build.Context
オブジェクトを順番に適用して処理を行うように変更された点です。
具体的には、以下のステップで処理が進みます。
- 複数のビルドコンテキストの定義:
contexts
というグローバル変数に、linux-386
,linux-amd64
,darwin-386
,darwin-amd64
,windows-amd64
,windows-386
の各OS/ARCHの組み合わせに加え、Cgoが有効な場合と無効な場合の両方を含むbuild.Context
のスライスが定義されます。これにより、主要なターゲット環境が網羅されます。 - コンテキストごとのAPI抽出:
main
関数内のメインループが変更され、定義されたcontexts
スライス内の各build.Context
オブジェクトに対してイテレーションを行います。 Walker
オブジェクトの初期化: 各イテレーションで、新しいWalker
オブジェクトが作成され、現在のbuild.Context
がそのWalker
に設定されます。Walker
はGoのソースコードを走査し、API情報を抽出する役割を担います。- パッケージの走査とAPI抽出: 各コンテキスト内で、指定されたパッケージ(Goの標準ライブラリパッケージ)が
Walker.WalkPackage
メソッドによって走査されます。この際、WalkPackage
内でbuild.Context.ScanDir
が使用されるようになり、現在のビルドコンテキストに基づいてディレクトリ内のGoソースファイルがスキャンされます。これにより、ビルドタグなどによって特定のコンテキストでのみ有効なファイルが適切に処理されます。 - コンテキストごとのAPI特徴の収集: 各コンテキストで抽出されたAPIの特徴(関数、型、メソッドなど)は、
featureCtx
というマップに格納されます。このマップは、feature -> context name -> true
という構造を持ち、どのAPIがどのコンテキストで利用可能かを示します。 - 共通APIの特定と差異の報告: すべてのコンテキストでのAPI抽出が完了した後、
featureCtx
を走査し、すべての定義済みコンテキストで共通して存在するAPI特徴を特定します。もし、あるAPI特徴がすべてのコンテキストで利用可能でない場合(つまり、特定のコンテキストでのみ存在したり、欠落したりする場合)、そのAPI特徴と、それが利用可能なコンテキストの名前が結合されて、最終的なAPIリストに追加されます。これにより、クロスコンテキストでのAPIの差異が明確に報告されます。 - 結果のソートと出力: 最終的に生成されたAPI特徴のリストはソートされ、標準出力に書き出されます。これにより、異なるビルドコンテキスト間でのAPIの互換性や差異が可視化されます。
この変更により、cmd/api
は単一の環境でのAPIチェックから、Goがサポートする多様なビルド環境全体でのAPI互換性保証へとその機能を大幅に強化しました。
コアとなるコードの変更箇所
src/cmd/api/goapi.go
ファイルが変更されています。
-
contexts
変数の追加:var contexts = []*build.Context{ {GOOS: "linux", GOARCH: "386", CgoEnabled: true}, {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true}, {GOOS: "linux", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "386", CgoEnabled: true}, {GOOS: "darwin", GOARCH: "386"}, {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "386"}, }
複数の
build.Context
オブジェクトを定義するスライスが追加されました。 -
contextName
関数の追加:func contextName(c *build.Context) string { s := c.GOOS + "-" + c.GOARCH if c.CgoEnabled { return s + "-cgo" } return s }
build.Context
オブジェクトから人間が読める形式の名前を生成するヘルパー関数が追加されました。 -
main
関数の変更:NewWalker()
の呼び出しがループの外から内へ移動し、各コンテキストで新しいWalker
が作成されるようになりました。Walker
にcontext
フィールドが設定されるようになりました。featureCtx
マップが導入され、各コンテキストで抽出されたAPI特徴が格納されるようになりました。for _, context := range contexts
ループが追加され、各ビルドコンテキストでパッケージの走査とAPI抽出が行われるようになりました。- API特徴のフィルタリングと整形ロジックが変更され、すべてのコンテキストで共通のAPIと、特定のコンテキストに限定されるAPIが区別されるようになりました。
-
Walker
構造体の変更:type Walker struct { context *build.Context tree *build.Tree // ... (既存のフィールド) }
build.Context
を保持するためのcontext
フィールドが追加されました。 -
WalkPackage
メソッドの変更:func (w *Walker) WalkPackage(name string) { // ... var info *build.DirInfo var err error if ctx := w.context; ctx != nil { info, err = ctx.ScanDir(dir) } else { info, err = build.ScanDir(dir) } if err != nil { if strings.Contains(err.Error(), "no Go source files") { return } log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err) } // ... }
パッケージディレクトリをスキャンする際に、
Walker
に設定されたbuild.Context
が存在すればそれを使用し (ctx.ScanDir
)、そうでなければデフォルトのbuild.ScanDir
を使用するように変更されました。また、"no Go source files" エラーを無視するロジックが追加されました。
コアとなるコードの解説
contexts
変数と contextName
関数
contexts
変数は、cmd/api
がAPI互換性をチェックする対象となる、事前に定義されたビルドコンテキストの集合です。これには、主要なオペレーティングシステム(Linux, macOS, Windows)とアーキテクチャ(386, amd64)の組み合わせが含まれ、さらにCgoが有効な場合と無効な場合の両方が考慮されています。これにより、Goの標準ライブラリがサポートする多様な環境でのAPIの振る舞いを網羅的に検証できます。
contextName
関数は、各 build.Context
オブジェクトを一意に識別するための簡潔な文字列名を生成します。例えば、{GOOS: "linux", GOARCH: "amd64", CgoEnabled: true}
は "linux-amd64-cgo" となります。この名前は、featureCtx
マップのキーとして使用され、どのAPIがどのコンテキストで利用可能かを追跡するために役立ちます。
main
関数のリファクタリング
以前の main
関数は、単一の Walker
オブジェクトを使用してAPIを抽出していました。このコミットでは、main
関数が大幅にリファクタリングされ、contexts
スライスをループ処理する構造に変更されました。
var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
for _, context := range contexts {
w := NewWalker()
w.context = context // 各コンテキストをWalkerに設定
w.tree = tree
for _, pkg := range pkgs {
w.wantedPkg[pkg] = true
}
for _, pkg := range pkgs {
// ... (パッケージフィルタリング)
w.WalkPackage(pkg) // 各コンテキストでパッケージを走査
}
ctxName := contextName(context)
for _, f := range w.Features() {
if featureCtx[f] == nil {
featureCtx[f] = make(map[string]bool)
}
featureCtx[f][ctxName] = true // API特徴とコンテキスト名を関連付けて記録
}
}
この新しいループでは、各 build.Context
に対して新しい Walker
インスタンスが作成され、その Walker
に現在の context
が設定されます。これにより、各コンテキストが独立してAPIを抽出し、その結果が featureCtx
マップに集約されます。featureCtx
は、特定のAPI特徴(例: os.Open
関数)がどのビルドコンテキストで利用可能であるかを記録します。
ループの後半では、featureCtx
を分析し、すべてのコンテキストで共通して存在するAPI特徴と、特定のコンテキストでのみ存在するAPI特徴を区別します。
var features []string
for f, cmap := range featureCtx {
if len(cmap) == len(contexts) { // すべてのコンテキストで利用可能か
features = append(features, f)
continue
}
comma := strings.Index(f, ",")
for cname := range cmap {
f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:]) // コンテキスト名を付加
features = append(features, f2)
}
}
sort.Strings(features)
もしAPI特徴がすべての定義済みコンテキストで利用可能であれば、そのAPI特徴はそのままリストに追加されます。しかし、もし特定のコンテキストでのみ利用可能である場合、そのAPI特徴には利用可能なコンテキストの名前が括弧書きで付加されます(例: os.Open (linux-amd64-cgo)
)。これにより、最終的な出力でAPIのクロスコンテキスト互換性の差異が明確に示されます。
WalkPackage
メソッドでの build.Context.ScanDir
の利用
WalkPackage
メソッドは、指定されたパッケージのソースディレクトリをスキャンしてGoソースファイルを見つけます。このコミットでは、このスキャン処理が Walker
の context
フィールドを利用するように変更されました。
if ctx := w.context; ctx != nil {
info, err = ctx.ScanDir(dir)
} else {
info, err = build.ScanDir(dir)
}
build.Context.ScanDir
を使用することで、Goのビルドシステムが特定のビルドコンテキスト(GOOS
, GOARCH
, CgoEnabled
など)に基づいてファイルをどのように選択するかを正確にシミュレートできます。例えば、ビルドタグ(//go:build linux
など)によって特定のOSでのみコンパイルされるファイルがある場合、ctx.ScanDir
はそのコンテキストに合致するファイルのみを返します。これにより、cmd/api
は各ビルドコンテキストにおける実際のAPIセットを正確に把握し、比較することが可能になります。
また、if strings.Contains(err.Error(), "no Go source files") { return }
というエラーハンドリングが追加されました。これは、特定のビルドコンテキストにおいて、指定されたパッケージディレクトリにGoソースファイルが存在しない場合に発生するエラーを無視するためのものです。これは、例えば、あるパッケージが特定のOSでのみ実装されている場合などに有用です。
これらの変更により、cmd/api
はGoの標準ライブラリのAPIが、Goがサポートする多様なビルド環境全体で一貫性を保っていることを、より詳細かつ正確に検証できるようになりました。
関連リンク
参考にした情報源リンク
- Go projects: What is the purpose of the cmd directory? - Stack Overflow
- Go: The cmd directory - Medium
- go/build.Context struct explanation - go.dev
- Go API compatibility checking cross platform - Medium
- Go Build Constraints - DigitalOcean
- Go Modules and Semantic Versioning - go.dev
- Go 1 Compatibility Promise - go.dev