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

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

このコミットは、Go言語のツールである cmd/apigoapi.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 オブジェクトを順番に適用して処理を行うように変更された点です。

具体的には、以下のステップで処理が進みます。

  1. 複数のビルドコンテキストの定義: contexts というグローバル変数に、linux-386, linux-amd64, darwin-386, darwin-amd64, windows-amd64, windows-386 の各OS/ARCHの組み合わせに加え、Cgoが有効な場合と無効な場合の両方を含む build.Context のスライスが定義されます。これにより、主要なターゲット環境が網羅されます。
  2. コンテキストごとのAPI抽出: main 関数内のメインループが変更され、定義された contexts スライス内の各 build.Context オブジェクトに対してイテレーションを行います。
  3. Walker オブジェクトの初期化: 各イテレーションで、新しい Walker オブジェクトが作成され、現在の build.Context がその Walker に設定されます。Walker はGoのソースコードを走査し、API情報を抽出する役割を担います。
  4. パッケージの走査とAPI抽出: 各コンテキスト内で、指定されたパッケージ(Goの標準ライブラリパッケージ)が Walker.WalkPackage メソッドによって走査されます。この際、WalkPackage 内で build.Context.ScanDir が使用されるようになり、現在のビルドコンテキストに基づいてディレクトリ内のGoソースファイルがスキャンされます。これにより、ビルドタグなどによって特定のコンテキストでのみ有効なファイルが適切に処理されます。
  5. コンテキストごとのAPI特徴の収集: 各コンテキストで抽出されたAPIの特徴(関数、型、メソッドなど)は、featureCtx というマップに格納されます。このマップは、feature -> context name -> true という構造を持ち、どのAPIがどのコンテキストで利用可能かを示します。
  6. 共通APIの特定と差異の報告: すべてのコンテキストでのAPI抽出が完了した後、featureCtx を走査し、すべての定義済みコンテキストで共通して存在するAPI特徴を特定します。もし、あるAPI特徴がすべてのコンテキストで利用可能でない場合(つまり、特定のコンテキストでのみ存在したり、欠落したりする場合)、そのAPI特徴と、それが利用可能なコンテキストの名前が結合されて、最終的なAPIリストに追加されます。これにより、クロスコンテキストでのAPIの差異が明確に報告されます。
  7. 結果のソートと出力: 最終的に生成されたAPI特徴のリストはソートされ、標準出力に書き出されます。これにより、異なるビルドコンテキスト間でのAPIの互換性や差異が可視化されます。

この変更により、cmd/api は単一の環境でのAPIチェックから、Goがサポートする多様なビルド環境全体でのAPI互換性保証へとその機能を大幅に強化しました。

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

src/cmd/api/goapi.go ファイルが変更されています。

  1. 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 オブジェクトを定義するスライスが追加されました。

  2. contextName 関数の追加:

    func contextName(c *build.Context) string {
    	s := c.GOOS + "-" + c.GOARCH
    	if c.CgoEnabled {
    		return s + "-cgo"
    	}
    	return s
    }
    

    build.Context オブジェクトから人間が読める形式の名前を生成するヘルパー関数が追加されました。

  3. main 関数の変更:

    • NewWalker() の呼び出しがループの外から内へ移動し、各コンテキストで新しい Walker が作成されるようになりました。
    • Walkercontext フィールドが設定されるようになりました。
    • featureCtx マップが導入され、各コンテキストで抽出されたAPI特徴が格納されるようになりました。
    • for _, context := range contexts ループが追加され、各ビルドコンテキストでパッケージの走査とAPI抽出が行われるようになりました。
    • API特徴のフィルタリングと整形ロジックが変更され、すべてのコンテキストで共通のAPIと、特定のコンテキストに限定されるAPIが区別されるようになりました。
  4. Walker 構造体の変更:

    type Walker struct {
    	context         *build.Context
    	tree            *build.Tree
    	// ... (既存のフィールド)
    }
    

    build.Context を保持するための context フィールドが追加されました。

  5. 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ソースファイルを見つけます。このコミットでは、このスキャン処理が Walkercontext フィールドを利用するように変更されました。

	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がサポートする多様なビルド環境全体で一貫性を保っていることを、より詳細かつ正確に検証できるようになりました。

関連リンク

参考にした情報源リンク