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

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

このコミットは、Go言語のビルドシステムにおいて、パッケージのビルド時にファイル選択に影響を与える可能性のある全てのビルドタグを収集し、go/build.Package 構造体に AllTags フィールドとして追加する変更です。これにより、Goパッケージをスキャンするツールが、利用可能な様々なビルドバリアントとその種類をより正確に把握できるようになります。

コミット

commit d9f93b0e0b351de6cfe05c03d8e8f78328178407
Author: Russ Cox <rsc@golang.org>
Date:   Fri Aug 9 18:34:08 2013 -0400

    go/build: add AllTags to Package
    
    AllTags lists all the tags that can affect the decision
    about which files to include. Tools scanning packages
    can use this to decide how many variants there are
    and what they are.
    
    R=bradfitz
    CC=golang-dev
    https://golang.org/cl/12703044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d9f93b0e0b351de6cfe05c03d8e8f78328178407

元コミット内容

go/build: add AllTags to Package

AllTags lists all the tags that can affect the decision
about which files to include. Tools scanning packages
can use this to decide how many variants there are
and what they are.

R=bradfitz
CC=golang-dev
https://golang.org/cl/12703044

変更の背景

Go言語のビルドシステムでは、特定のファイルがビルドに含まれるかどうかを制御するために「ビルドタグ (build tags)」が使用されます。これらはソースファイルの先頭に // +build tag の形式で記述され、コンパイル時にGoツールチェインが現在の環境(OS、アーキテクチャ、カスタムタグなど)に基づいてどのファイルをビルドに含めるかを決定します。

このコミットが導入される以前は、go/build パッケージがGoソースコードを解析してパッケージ情報を抽出する際に、実際にビルドに採用されたファイルに関連するタグの情報は保持していましたが、そのパッケージ内でファイル選択に影響を与えうる「全ての」タグのリストを直接取得する手段がありませんでした。

この機能が求められた背景には、Goのツールエコシステムが成熟するにつれて、より高度な分析やIDEの機能が求められるようになったことがあります。例えば、あるパッケージがWindowsとLinuxの両方に対応するコードを含んでいる場合、IDEはその両方のバリアントを理解し、適切なコード補完やエラーチェックを提供する必要があります。また、静的解析ツールは、特定のビルドタグが有効になった場合にどのようなコードパスが実行されるかを把握し、より正確な分析を行う必要がありました。

AllTags フィールドの追加は、このようなツールがGoパッケージの多様なビルドバリアントを網羅的に理解し、よりインテリジェントな機能を提供するための基盤を築くことを目的としています。

前提知識の解説

Goのビルドタグ (Build Tags)

Go言語では、ソースファイルの先頭に // +build ディレクティブを記述することで、そのファイルが特定のビルド条件を満たす場合にのみコンパイルされるように制御できます。これはクロスプラットフォーム開発や、特定の環境に依存するコードを分離する際に非常に有用です。

ビルドタグの基本的なルールは以下の通りです。

  • 形式: // +build tag1,tag2 !tag3 のように記述します。
  • 論理AND: スペースで区切られたタグは論理ANDとして扱われます。例: // +build linux amd64linuxamd64 の両方が有効な場合にのみファイルがビルドされます。
  • 論理OR: カンマで区切られたタグは論理ORとして扱われます。例: // +build linux,windowslinux または windows のいずれかが有効な場合にファイルがビルドされます。
  • 否定: タグの前に ! を付けると否定になります。例: // +build !darwindarwin 以外のOSでビルドされます。
  • 組み合わせ: これらのルールは組み合わせて使用できます。例: // +build linux,windows !arm(linux OR windows) AND (NOT arm) の場合にビルドされます。
  • 特殊なタグ:
    • goos: 現在のオペレーティングシステム (例: linux, windows, darwin)
    • goarch: 現在のアーキテクチャ (例: amd64, arm, 386)
    • cgo: Cgoが有効な場合
    • ignore: 常にファイルを無視します。
  • ファイル名によるタグ: filename_GOOS.gofilename_GOOS_GOARCH.go のようにファイル名にOSやアーキテクチャを含めることでも、ビルドタグと同様のファイル選択が可能です。

go/build パッケージ

go/build パッケージは、Goのソースコードツリーを解析し、Goパッケージに関する情報(ソースファイルのリスト、インポートパス、ビルドタグなど)を抽出するための標準ライブラリです。Goツールチェイン自体がこのパッケージを使用して、依存関係の解決、ビルド対象ファイルの特定、パッケージ情報の表示などを行います。

このパッケージの主要な構造体の一つが build.Package です。これは、特定のディレクトリで見つかったGoパッケージの詳細な情報(ディレクトリパス、パッケージ名、ソースファイル、テストファイル、インポートなど)を保持します。このコミットでは、この build.Package 構造体に AllTags フィールドが追加されました。

技術的詳細

このコミットの主要な変更点は、go/build.Package 構造体に AllTags []string フィールドが追加されたことです。このフィールドは、特定のパッケージディレクトリ内のファイル選択に影響を与える可能性のある全てのビルドタグ(// +build ディレクティブで指定されたタグ、ファイル名に含まれるOS/アーキテクチャタグ、cgo タグなど)のリストを保持します。

変更の具体的な技術的詳細は以下の通りです。

  1. Package 構造体の変更: src/pkg/go/build/build.go 内の Package 構造体に AllTags []string が追加されました。

    type Package struct {
        // ... 既存のフィールド ...
        AllTags    []string // tags that can influence file selection in this directory
    }
    
  2. タグ収集ロジックの追加: Context.Import メソッド(パッケージのインポート処理を行う主要な関数)内で、allTags という map[string]bool 型のローカル変数が導入されました。このマップは、パッケージ内の各ソースファイルをスキャンする際に発見された全てのユニークなタグを記録するために使用されます。

    • Context.shouldBuild メソッドのシグネチャが変更され、allTags map[string]bool パラメータを受け取るようになりました。このメソッドは、// +build ディレクティブを解析し、そこで指定されたタグを allTags マップに追加します。
    • Context.match メソッドのシグネチャも変更され、allTags map[string]bool パラメータを受け取るようになりました。このメソッドは、ビルドタグの条件(AND/OR/NOT)を評価する際に、評価対象となる個々のタグを allTags マップに追加します。
    • Context.goodOSArchFile メソッドのシグネチャも変更され、allTags map[string]bool パラメータを受け取るようになりました。このメソッドは、ファイル名に含まれるOSやアーキテクチャのタグ(例: _linux.go, _amd64.go)を解析し、それらを allTags マップに追加します。
    • Cgoが有効なファイルが見つかった場合、明示的に "cgo" タグが allTags マップに追加されます。
  3. AllTags フィールドへの格納とソート: Context.Import メソッドの終盤で、収集された allTags マップからキー(タグ名)を抽出し、Package.AllTags スライスに格納します。その後、このスライスは sort.Strings を使用してアルファベット順にソートされます。これにより、AllTags の順序が予測可能になり、テストや比較が容易になります。

  4. テストの更新: src/pkg/go/build/build_test.gosrc/pkg/go/build/syslist_test.go のテストコードが、新しい allTags パラメータを考慮するように更新されました。特に TestMatchTestShouldBuild では、match および shouldBuild メソッドが実際に収集したタグのマップが期待通りであるかを reflect.DeepEqual を用いて検証するようになりました。

この変更により、go/build パッケージは、特定のビルドコンテキスト(Context.BuildTags)で実際にビルドされるファイルだけでなく、そのパッケージがサポートしうる全てのビルドバリアントを構成するタグの集合を公開できるようになりました。これは、GoのツールチェインやIDEが、より包括的なパッケージ分析を行うための重要な情報源となります。

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

src/pkg/go/build/build.go

  • Package 構造体に AllTags []string フィールドを追加。
  • Context.Import メソッド内で allTags := make(map[string]bool) を初期化し、ファイルスキャン中にタグを収集。
  • Context.Import メソッドの最後に、収集した allTagsp.AllTags にコピーし、ソートするロジックを追加。
  • Context.shouldBuild メソッドのシグネチャを func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) bool に変更。
  • Context.match メソッドのシグネチャを func (ctxt *Context) match(name string, allTags map[string]bool) bool に変更。
  • Context.goodOSArchFile メソッドのシグネチャを func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool に変更。
  • 上記の各メソッド内で、引数として渡された allTags マップに発見したタグを追加するロジックを組み込み。特に、cgo タグの明示的な追加もここで行われる。

src/pkg/go/build/build_test.go

  • TestMatch 関数内の match および nomatch ヘルパー関数が、allTags マップを引数として受け取り、reflect.DeepEqual を用いて収集されたタグが期待値と一致するかを検証するように変更。
  • TestShouldBuild 関数内で、shouldBuild メソッドの呼び出しに allTags マップを渡し、その結果を検証するロジックを追加。

src/pkg/go/build/syslist_test.go

  • TestGoodOSArch 関数内の Default.goodOSArchFile の呼び出しに make(map[string]bool) を追加。

コアとなるコードの解説

src/pkg/go/build/build.go の変更点

Package 構造体への AllTags 追加

type Package struct {
	// ... 既存のフィールド ...
	AllTags    []string // tags that can influence file selection in this directory
}

Package 構造体は、Goパッケージのメタデータを保持する中心的なデータ構造です。ここに AllTags スライスが追加されたことで、パッケージのビルドに影響を与える可能性のある全てのタグが、この構造体を通じて外部に公開されるようになりました。

Context.Import メソッド内のタグ収集と格納

// ... (Context.Import メソッドの途中) ...
	allTags := make(map[string]bool) // 新しく追加されたマップ

	// ... (ファイルスキャンとタグ収集のロジック) ...

	// Cgoファイルが見つかった場合、"cgo"タグを追加
	if isCgo {
		allTags["cgo"] = true
		if ctxt.CgoEnabled {
			p.CgoFiles = append(p.CgoFiles, name)
		}
	}

	// ... (その他の処理) ...

	// 収集したタグをPackage.AllTagsに格納し、ソート
	for tag := range allTags {
		p.AllTags = append(p.AllTags, tag)
	}
	sort.Strings(p.AllTags) // 順序を保証するためソート

Context.Import は、指定されたディレクトリからGoパッケージを読み込む主要な関数です。この関数内で allTags マップが初期化され、各ソースファイルの解析中に shouldBuild, match, goodOSArchFile といったヘルパー関数を通じてタグが収集されます。Cgoが有効なファイルが見つかった場合は、明示的に "cgo" タグが追加されます。最後に、収集された全てのユニークなタグが Package.AllTags スライスにコピーされ、アルファベット順にソートされます。これにより、AllTags フィールドには、そのパッケージがサポートする全てのビルドタグが網羅的に含まれることになります。

Context.shouldBuild メソッドの変更

func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) bool {
	// ... (既存のコメント解析ロジック) ...

	// Pass 2.  Process each line in the run.
	// ...
	allok := true // 新しく追加されたフラグ
	for len(p) > 0 {
		// ...
		if bytes.HasPrefix(line, buildComment) {
			f := strings.Fields(string(line))
			if len(f) > 1 {
				if f[0] == "+build" {
					ok := false
					for _, tok := range f[1:] {
						if ctxt.match(tok, allTags) { // allTagsを渡す
							ok = true
						}
					}
					if !ok {
						allok = false // 以前はここで return false していた
					}
				}
			}
		}
		// ...
	}
	return allok // 以前は return true していた
}

shouldBuild メソッドは、ファイルの // +build コメントを解析し、現在のビルドコンテキストでそのファイルがビルドされるべきかを判断します。変更前は、条件に合致しないタグが見つかった時点で return false していましたが、変更後は allok = false を設定し、全てのタグを最後まで処理するように変更されました。これにより、ファイルがビルドされない場合でも、そのファイルに含まれる全てのタグを allTags マップに収集し続けることが可能になります。

Context.match メソッドの変更

func (ctxt *Context) match(name string, allTags map[string]bool) bool {
	if name == "" {
		if allTags != nil {
			allTags[name] = true // 空のタグも収集
		}
		return false
	}
	if i := strings.Index(name, ","); i >= 0 {
		// comma-separated list (OR)
		ok1 := ctxt.match(name[:i], allTags) // allTagsを渡す
		ok2 := ctxt.match(name[i+1:], allTags) // allTagsを渡す
		return ok1 && ok2 // 論理ANDの評価は変わらない
	}
	if strings.HasPrefix(name, "!") { // negation
		return len(name) > 1 && !ctxt.match(name[1:], allTags) // allTagsを渡す
	}

	if allTags != nil {
		allTags[name] = true // 個々のタグを収集
	}

	// ... (既存のタグマッチングロジック) ...
}

match メソッドは、個々のビルドタグ文字列が現在のビルドコンテキストに合致するかを判断します。このメソッドも allTags マップを受け取るようになり、name 引数で渡されたタグを allTags マップに追加するようになりました。これにより、+build コメント内の全てのタグ(肯定、否定、カンマ区切りなどに関わらず)が allTags に記録されます。

Context.goodOSArchFile メソッドの変更

func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
	// ... (既存のファイル名解析ロジック) ...

	if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
		allTags[l[n-2]] = true // OSタグを収集
		allTags[l[n-1]] = true // Archタグを収集
		return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
	}
	if n >= 1 && knownOS[l[n-1]] {
		allTags[l[n-1]] = true // OSタグを収集
		return l[n-1] == ctxt.GOOS
	}
	if n >= 1 && knownArch[l[n-1]] {
		allTags[l[n-1]] = true // Archタグを収集
		return l[n-1] == ctxt.GOARCH
	}
	return true
}

goodOSArchFile メソッドは、ファイル名に含まれるOSやアーキテクチャのサフィックス(例: _linux_amd64.go)を解析し、現在の環境に合致するかを判断します。このメソッドも allTags マップを受け取るようになり、ファイル名から抽出されたOSタグとアーキテクチャタグを allTags マップに追加するようになりました。

src/pkg/go/build/build_test.go の変更点

テストコードは、新しい allTags パラメータを考慮するように更新され、reflect.DeepEqual を使用して、収集されたタグのマップが期待される値と一致するかを厳密に検証するようになりました。これにより、タグ収集ロジックの正確性が保証されます。

これらの変更により、go/build パッケージは、Goのビルドシステムがどのようにファイルを選択するかをより詳細に、かつ網羅的にツールに提供できるようになり、Go開発エコシステムの機能向上に貢献しています。

関連リンク

参考にした情報源リンク

  • GitHub上のコミットページ: https://github.com/golang/go/commit/d9f93b0e0b351de6cfe05c03d8e8f78328178407
  • Go言語のビルドタグに関する一般的な情報源(例: 各種ブログ記事、Go公式ブログなど)
  • go/build パッケージのソースコード(コミット前後の比較)
  • Go言語のクロスコンパイルとビルドタグに関する解説記事