[インデックス 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 amd64
はlinux
とamd64
の両方が有効な場合にのみファイルがビルドされます。 - 論理OR: カンマで区切られたタグは論理ORとして扱われます。例:
// +build linux,windows
はlinux
またはwindows
のいずれかが有効な場合にファイルがビルドされます。 - 否定: タグの前に
!
を付けると否定になります。例:// +build !darwin
はdarwin
以外のOSでビルドされます。 - 組み合わせ: これらのルールは組み合わせて使用できます。例:
// +build linux,windows !arm
は(linux OR windows) AND (NOT arm)
の場合にビルドされます。 - 特殊なタグ:
goos
: 現在のオペレーティングシステム (例:linux
,windows
,darwin
)goarch
: 現在のアーキテクチャ (例:amd64
,arm
,386
)cgo
: Cgoが有効な場合ignore
: 常にファイルを無視します。
- ファイル名によるタグ:
filename_GOOS.go
やfilename_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
タグなど)のリストを保持します。
変更の具体的な技術的詳細は以下の通りです。
-
Package
構造体の変更:src/pkg/go/build/build.go
内のPackage
構造体にAllTags []string
が追加されました。type Package struct { // ... 既存のフィールド ... AllTags []string // tags that can influence file selection in this directory }
-
タグ収集ロジックの追加:
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
マップに追加されます。
-
AllTags
フィールドへの格納とソート:Context.Import
メソッドの終盤で、収集されたallTags
マップからキー(タグ名)を抽出し、Package.AllTags
スライスに格納します。その後、このスライスはsort.Strings
を使用してアルファベット順にソートされます。これにより、AllTags
の順序が予測可能になり、テストや比較が容易になります。 -
テストの更新:
src/pkg/go/build/build_test.go
とsrc/pkg/go/build/syslist_test.go
のテストコードが、新しいallTags
パラメータを考慮するように更新されました。特にTestMatch
とTestShouldBuild
では、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
メソッドの最後に、収集したallTags
をp.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開発エコシステムの機能向上に貢献しています。
関連リンク
- Go言語のビルドタグに関する公式ドキュメント(Go 1.4以降の形式ですが、基本的な概念は同じです): https://pkg.go.dev/cmd/go#hdr-Build_constraints
go/build
パッケージのドキュメント: https://pkg.go.dev/go/build- このコミットのGo Gerritレビューページ: https://golang.org/cl/12703044
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/d9f93b0e0b351de6cfe05c03d8e8f78328178407
- Go言語のビルドタグに関する一般的な情報源(例: 各種ブログ記事、Go公式ブログなど)
go/build
パッケージのソースコード(コミット前後の比較)- Go言語のクロスコンパイルとビルドタグに関する解説記事