[インデックス 11345] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、ビルドタグの柔軟性を大幅に向上させる重要な変更を導入しています。具体的には、go/build パッケージの Context に BuildTags フィールドを追加し、ユーザーがカスタムのビルドタグを定義できるようにしました。これにより、特定の環境(例: App Engine)やビルド設定(例: ネットワークを使用しないブートストラップ版)に応じた条件付きコンパイルが可能になります。
さらに、// +build !tag という形式でタグの否定条件を指定できるようになり、特定のタグが存在しない場合にのみファイルをビルドするといった、より詳細な制御が可能になりました。
また、このコミットでは、以前導入されたものの実際に使用されなかった Build および Script 関連のコードが go/build パッケージから削除され、goinstall コマンドからもその依存が取り除かれました。これにより、ビルドシステムのコードベースが整理され、不要な複雑さが排除されています。
コミット
commit b5777571b3ab20ca124fa60c34cd5094098fbb2f
Author: Russ Cox <rsc@golang.org>
Date: Mon Jan 23 15:16:38 2012 -0500
go/build: add BuildTags to Context, allow !tag
This lets the client of go/build specify additional tags that
can be recognized in a // +build directive. For example,
a build for a custom environment like App Engine might
include "appengine" in the BuildTags list, so that packages
can be written with some files saying
// +build appengine (build only on app engine)
or
// +build !appengine (build only when NOT on app engine)
App Engine here is just a hypothetical context. I plan to use
this in the cmd/go sources to distinguish the bootstrap version
of cmd/go (which will not use networking) from the full version
using a custom tag. It might also be useful in App Engine.
Also, delete Build and Script, which we did not end up using for
cmd/go and which never got turned on for real in goinstall.
R=r, adg
CC=golang-dev
https://golang.org/cl/5554079
---
src/cmd/goinstall/main.go | 29 +--
src/pkg/crypto/tls/root_stub.go | 2 +-\n src/pkg/go/build/build.go | 425 +---------------------------------------\n src/pkg/go/build/build_test.go | 56 +++---\n src/pkg/go/build/dir.go | 128 ++++++++++--\n src/pkg/net/cgo_stub.go | 2 +-\n src/pkg/os/user/lookup_stubs.go | 2 +-\n 7 files changed, 137 insertions(+), 507 deletions(-)\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b5777571b3ab20ca124fa60c34cd5094098fbb2f
元コミット内容
commit b5777571b3ab20ca124fa60c34cd5094098fbb2f
Author: Russ Cox <rsc@golang.org>
Date: Mon Jan 23 15:16:38 2012 -0500
go/build: add BuildTags to Context, allow !tag
This lets the client of go/build specify additional tags that
can be recognized in a // +build directive. For example,
a build for a custom environment like App Engine might
include "appengine" in the BuildTags list, so that packages
can be written with some files saying
// +build appengine (build only on app engine)
or
// +build !appengine (build only when NOT on app engine)
App Engine here is just a hypothetical context. I plan to use
this in the cmd/go sources to distinguish the bootstrap version
of cmd/go (which will not use networking) from the full version
using a custom tag. It might also be useful in App Engine.
Also, delete Build and Script, which we did not end up using for
cmd/go and which never got turned on for real in goinstall.
R=r, adg
CC=golang-dev
https://golang.org/cl/5554079
変更の背景
このコミットの背景には、Go言語のビルドプロセスにおける柔軟性の向上と、コードベースの整理という二つの主要な目的があります。
-
カスタムビルド環境への対応と条件付きコンパイルの強化: Go言語の初期のビルドシステムでは、
GOOS(オペレーティングシステム)やGOARCH(アーキテクチャ)、cgoの有無といった限られた組み込みタグに基づいてファイルのビルドを制御していました。しかし、特定のプラットフォーム(例: Google App Engineのようなクラウド環境)や、特定の機能セット(例: ネットワーク機能の有無)を持つアプリケーションをビルドする際に、これらの組み込みタグだけでは不十分でした。 このコミットは、go/buildパッケージのクライアントが独自の「ビルドタグ」を定義し、それらを// +buildディレクティブで利用できるようにすることで、このギャップを埋めようとしています。これにより、開発者はよりきめ細やかな条件付きコンパイルロジックを実装できるようになります。コミットメッセージにあるように、cmd/goのブートストラップ版(ネットワーク機能なし)とフル版を区別する目的や、App Engineのようなカスタム環境での利用が想定されていました。 -
未使用コードの削除とコードベースの整理: コミットメッセージには、「
BuildとScriptを削除する。これらはcmd/goで結局使われず、goinstallでも実際に有効化されることはなかった」と明記されています。これは、過去に導入されたものの、実際の開発プロセスやツールチェーンにおいて必要とされなかった機能や抽象化が、コードベースに残存していたことを示唆しています。これらの未使用コードを削除することで、go/buildパッケージの複雑性を減らし、保守性を向上させることが目的でした。
これらの変更は、Go言語のビルドシステムが進化し、より多様な開発シナリオに対応できるようになる過程の一部であり、同時に、不要な負債を排除して健全なコードベースを維持しようとする姿勢を示しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムに関する基本的な概念を把握しておく必要があります。
-
go/buildパッケージ:go/buildパッケージは、Go言語のソースコードを解析し、パッケージの依存関係を解決し、ビルドに必要なファイル(Goソースファイル、Cgoファイル、アセンブリファイルなど)を特定するためのツールを提供します。go buildやgo installといったコマンドの内部で利用される、Goのビルドプロセスの基盤となるライブラリです。このパッケージは、特定のディレクトリ内のGoパッケージに関する情報(インポート、エクスポート、ファイルの種類など)をDirInfo構造体として提供し、ビルドコンテキスト(Context構造体)に基づいてどのファイルをビルドに含めるかを決定します。 -
ビルドタグ (
// +buildディレクティブ): Go言語では、ソースファイルの先頭に// +buildという形式のコメント行を記述することで、そのファイルを特定の条件が満たされた場合にのみビルドに含めるように指定できます。これを「ビルドタグ」または「ビルド制約」と呼びます。 例:// +build linux:Linux環境でのみビルド// +build darwin,amd64:macOS (Darwin) かつ AMD64アーキテクチャでのみビルド// +build cgo:Cgoが有効な場合のみビルド 複数のタグをスペースで区切るとOR条件、カンマで区切るとAND条件になります。
-
GOOSとGOARCH: Go言語のクロスコンパイルを可能にするための環境変数です。GOOS: ターゲットとなるオペレーティングシステム(例:linux,windows,darwin)。GOARCH: ターゲットとなるアーキテクチャ(例:amd64,386,arm)。 これらの値は、runtime.GOOSとruntime.GOARCHとしてGoプログラム内で参照でき、ビルドタグの評価にも利用されます。
-
cgo: Go言語からC言語のコードを呼び出すためのメカニズムです。cgoが有効な場合、// +build cgoタグを持つファイルがビルドに含まれます。 -
goinstallコマンド (当時):goinstallは、Go 1.0以前に存在したパッケージのダウンロードとインストールを行うためのコマンドです。現在のgo getコマンドの前身にあたります。このコミットの時点ではまだ存在していましたが、後にgo getに置き換えられました。コミットメッセージにあるBuildやScriptがgoinstallで使われなかったという記述は、このコマンドの文脈での話です。
これらの前提知識を理解することで、BuildTagsの追加がGoのビルドシステムにどのような柔軟性をもたらし、!tagの導入がどのように条件付きコンパイルの表現力を高めたのか、そしてなぜBuildやScriptが削除されたのかが明確になります。
技術的詳細
このコミットの技術的な詳細を掘り下げると、主にgo/buildパッケージ内のContext構造体と、ビルドタグの評価ロジックを担うmatch関数の変更、そして未使用コードの削除に集約されます。
-
go/build.Context構造体へのBuildTagsフィールド追加:src/pkg/go/build/dir.goにおいて、Context構造体にBuildTags []stringという新しいフィールドが追加されました。type Context struct { GOARCH string // target architecture GOOS string // target operating system CgoEnabled bool // whether cgo can be used BuildTags []string // additional tags to recognize in +build lines // ... }このフィールドは、
go/buildパッケージのクライアント(例:goコマンド自身やIDEなど)が、標準のGOOS、GOARCH、cgoに加えて、独自のカスタムタグをビルドコンテキストに含めることを可能にします。これにより、例えば「appengine」というタグをBuildTagsに追加することで、// +build appengineと記述されたファイルをビルドに含めることができるようになります。 -
// +build !tagのサポートとmatch関数の変更: 最も重要な変更点の一つは、ビルドタグの評価ロジックを担うmatch関数の強化です。src/pkg/go/build/dir.goのmatchOSArch関数がmatch関数にリネームされ、その内部ロジックが大幅に拡張されました。 変更前は、matchOSArchはGOOS、GOARCH、cgo(またはnocgo)といった基本的なタグのみを評価していました。 変更後、match関数は以下の新しい評価ルールをサポートします。- 否定 (
!):!tagという形式で指定された場合、そのタグが存在しない場合にtrueを返します。例えば、!appengineはBuildTagsにappengineが含まれていない場合にマッチします。 - カスタムタグの評価:
Context.BuildTagsに含まれる任意の文字列がタグとして認識され、評価されます。 - カンマ区切り (
tag1,tag2): カンマで区切られたタグはAND条件として評価されます。つまり、tag1とtag2の両方がマッチする場合にtrueを返します。 - スラッシュ区切り (
tag1/tag2): 以前のスラッシュ区切りは、カンマ区切りに置き換えられ、AND条件として扱われるようになりました。 - 不正なタグの拒否:
!!のような不正な構文や、英数字とアンダースコア以外の文字を含むタグはfalseを返すようになりました。
この
match関数の変更により、// +buildディレクティブの表現力が飛躍的に向上し、より複雑な条件付きコンパイルが可能になりました。 - 否定 (
-
BuildおよびScript関連コードの削除:src/pkg/go/build/build.goから、Build関数、Script構造体、Cmd構造体、およびそれらに関連するメソッド(Run,Stale,Clean,Nukeなど)が大量に削除されました。これらのコードは、Goのビルドプロセスを抽象化し、個々のビルドコマンドをスクリプトとして表現することを意図していましたが、コミットメッセージにあるように、cmd/goやgoinstallで実際に利用されることはありませんでした。この削除により、go/buildパッケージのコードベースが大幅に削減され、シンプルになりました。 -
goinstallからのBuild/Script依存の削除:src/cmd/goinstall/main.goでは、build.Build関数を呼び出してScriptオブジェクトを取得し、それを使ってビルドを実行していた部分が削除されました。代わりに、domake関数(おそらく内部的なビルドヘルパー)が直接呼び出されるようになり、BuildおよびScriptの抽象化レイヤーが不要になりました。 -
既存のビルドタグの更新:
src/pkg/crypto/tls/root_stub.go、src/pkg/net/cgo_stub.go、src/pkg/os/user/lookup_stubs.goといったファイルで、// +build nocgoが// +build !cgoに、// +build darwin/nocgoが// +build darwin,!cgoにそれぞれ変更されています。これは、新しい!tag構文への移行を示す具体的な例です。
これらの技術的変更は、Goのビルドシステムがより強力で柔軟になり、同時に不要な複雑さを排除して効率化されたことを示しています。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主に以下のファイルに集中しています。
-
src/pkg/go/build/dir.go:Context構造体にBuildTags []stringフィールドが追加されました。matchOSArch関数がmatch関数にリネームされ、!tag(否定) およびBuildTagsを考慮したビルドタグ評価ロジックが実装されました。ScanDir関数のコメントが大幅に更新され、新しいビルド制約のルール(特に+buildディレクティブの構文と評価方法)が詳細に記述されました。
--- a/src/pkg/go/build/dir.go +++ b/src/pkg/go/build/dir.go @@ -25,9 +25,10 @@ import ( // A Context specifies the supporting context for a build. type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - CgoEnabled bool // whether cgo can be used + GOARCH string // target architecture + GOOS string // target operating system + CgoEnabled bool // whether cgo can be used + BuildTags []string // additional tags to recognize in +build lines // By default, ScanDir uses the operating system's // file system calls to read directories and files. @@ -389,7 +448,7 @@ func (ctxt *Context) shouldBuild(content []byte) bool { if f[0] == "+build" { ok := false for _, tok := range f[1:] { - if ctxt.matchOSArch(tok) { + if ctxt.match(tok) { ok = true break } @@ -441,7 +500,7 @@ func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) { if len(cond) > 0 { ok := false for _, c := range cond { - if ctxt.matchOSArch(c) { + if ctxt.match(c) { ok = true break } @@ -550,26 +609,55 @@ func splitQuoted(s string) (r []string, err error) { return args, err } -// matchOSArch returns true if the name is one of: +// match returns true if the name is one of: // // $GOOS // $GOARCH // cgo (if cgo is enabled) -// nocgo (if cgo is disabled) +// !cgo (if cgo is disabled) +// tag (if tag is listed in ctxt.BuildTags) +// !tag (if tag is not listed in ctxt.BuildTags) // a slash-separated list of any of these // -func (ctxt *Context) matchOSArch(name string) bool { - if ctxt.CgoEnabled && name == "cgo" { - return true +func (ctxt *Context) match(name string) bool { + if name == "" { + return false } - if !ctxt.CgoEnabled && name == "nocgo" { + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return !ctxt.match(name[1:]) + } + + // Tags must be letters, digits, underscores. + // Unlike in Go identifiers, all digits is fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { + return false + } + } + + // special tags + if ctxt.CgoEnabled && name == "cgo" { return true } if name == ctxt.GOOS || name == ctxt.GOARCH { return true } - i := strings.Index(name, "/") - return i >= 0 && ctxt.matchOSArch(name[:i]) && ctxt.matchOSArch(name[i+1:]) + + // other tags + for _, tag := range ctxt.BuildTags { + if tag == name { + return true + } + } + + return false } // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH -
src/pkg/go/build/build.go:Build関数、Script構造体、Cmd構造体、およびそれらに関連するすべてのビルドロジックとヘルパー関数(addInput,addIntermediate,Run,Stale,Clean,Nuke,build構造体、abs,abss,add,mkdir,gc,asm,ld,gopack,cc,gccCompile,gccLink,gccArgs,cgoなど)が完全に削除されました。これにより、ファイルサイズが大幅に減少しています。
--- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -5,245 +5,7 @@ // Package build provides tools for building Go packages. package build -import ( - "bytes" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strings" - "time" -) - -// Build produces a build Script for the given package. -func Build(tree *Tree, pkg string, info *DirInfo) (*Script, error) { -// ... (大量の削除されたコード) ... - -import "errors" -
src/cmd/goinstall/main.go:build.Buildを呼び出してScriptオブジェクトを使用していた部分が削除され、代わりにdomake関数が直接呼び出されるようになりました。これにより、goinstallがgo/buildの新しい抽象化に依存しなくなりました。
--- a/src/cmd/goinstall/main.go +++ b/src/cmd/goinstall/main.go @@ -336,35 +336,10 @@ func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installEr } // Install this package. - if *useMake { - err := domake(dir, pkg, tree, dirInfo.IsCommand()) - if err != nil { - return &BuildError{pkg, err} - } - return nil - } - script, err := build.Build(tree, pkg, dirInfo) + err = domake(dir, pkg, tree, dirInfo.IsCommand()) if err != nil { return &BuildError{pkg, err} } - if *nuke { - printf("%s: nuke\n", pkg) - script.Nuke() - } else if *clean { - printf("%s: clean\n", pkg) - script.Clean() - } - if *doInstall { - if script.Stale() { - printf("%s: install\n", pkg) - if err := script.Run(); err != nil { - return &BuildError{pkg, err} - } - } else { - printf("%s: up-to-date\n", pkg) - } - } - return nil } -
src/pkg/crypto/tls/root_stub.go,src/pkg/net/cgo_stub.go,src/pkg/os/user/lookup_stubs.go:- これらのファイルでは、
// +build nocgoが// +build !cgoに変更されるなど、新しい否定タグの構文が適用されました。
--- a/src/pkg/crypto/tls/root_stub.go +++ b/src/pkg/crypto/tls/root_stub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build plan9 darwin/nocgo +// +build plan9 darwin,!cgo package tls - これらのファイルでは、
これらの変更は、Goのビルドシステムの中核部分に影響を与え、ビルドタグの処理方法とビルドプロセスの抽象化を根本的に変更しています。
コアとなるコードの解説
このコミットのコアとなる変更は、src/pkg/go/build/dir.go 内の Context 構造体と match 関数に集約されます。
Context 構造体への BuildTags フィールド追加
type Context struct {
GOARCH string // target architecture
GOOS string // target operating system
CgoEnabled bool // whether cgo can be used
BuildTags []string // additional tags to recognize in +build lines
// ...
}
Context 構造体は、Goのビルドプロセスにおける現在の環境設定を定義します。これには、ターゲットのOS (GOOS)、アーキテクチャ (GOARCH)、Cgoの有効/無効 (CgoEnabled) などが含まれます。
このコミットで追加された BuildTags []string フィールドは、ビルドシステムが認識すべき「追加のタグ」のリストを保持します。これにより、go/build パッケージの利用者は、標準の環境変数やCgoの有無だけでなく、任意の文字列をカスタムタグとして定義し、それに基づいてソースファイルのビルドを制御できるようになります。例えば、Context.BuildTags に {"appengine", "debug"} を設定すると、// +build appengine や // +build debug といったディレクティブが有効になります。
match 関数のロジック
match 関数は、与えられたビルドタグ文字列が現在の Context にマッチするかどうかを評価する中心的なロジックです。以前は matchOSArch という名前で、OS、アーキテクチャ、Cgoの有無のみを評価していましたが、このコミットで大幅に拡張されました。
func (ctxt *Context) match(name string) bool {
if name == "" {
return false
}
if i := strings.Index(name, ","); i >= 0 {
// comma-separated list (AND condition)
return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
}
if strings.HasPrefix(name, "!!") { // bad syntax, reject always
return false
}
if strings.HasPrefix(name, "!") { // negation
return !ctxt.match(name[1:])
}
// Tags must be letters, digits, underscores.
// Unlike in Go identifiers, all digits is fine (e.g., "386").
for _, c := range name {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' {
return false
}
}
// special tags
if ctxt.CgoEnabled && name == "cgo" {
return true
}
if name == ctxt.GOOS || name == ctxt.GOARCH {
return true
}
// other tags (custom BuildTags)
for _, tag := range ctxt.BuildTags {
if tag == name {
return true
}
}
return false
}
この関数の動作は以下の通りです。
- 空文字列のチェック:
nameが空の場合はfalseを返します。 - カンマ区切り (AND条件):
nameにカンマが含まれる場合、それはAND条件として扱われます。例えば、"linux,amd64"はctxt.match("linux")とctxt.match("amd64")の両方がtrueの場合にtrueを返します。これは再帰的に評価されます。 - 不正な構文のチェック:
!!のように!が連続する場合は不正な構文としてfalseを返します。 - 否定 (
!):nameが!で始まる場合、それは否定条件として扱われます。例えば、"!appengine"はctxt.match("appengine")がfalseの場合にtrueを返します。これも再帰的に評価されます。 - タグ文字の検証: タグ名が英数字とアンダースコアのみで構成されているかをチェックします。これ以外の文字が含まれる場合は
falseを返します。 - 特殊タグの評価:
cgo:Context.CgoEnabledがtrueの場合にtrueを返します。GOOSまたはGOARCH:nameが現在のContext.GOOSまたはContext.GOARCHと一致する場合にtrueを返します。
- カスタムタグの評価: 最後に、
nameがContext.BuildTagsスライス内のいずれかの文字列と一致する場合にtrueを返します。
この match 関数の拡張により、// +build ディレクティブは、// +build linux,amd64,!cgo,appengine のように、より複雑で表現豊かな条件を記述できるようになりました。これにより、開発者は特定のビルド環境や要件に合わせたファイルのインクルード/除外を、より柔軟に制御できるようになります。
関連リンク
- Go Change-Id:
I2222222222222222222222222222222222222222(これはコミットメッセージに記載されているhttps://golang.org/cl/5554079に対応するGoのコードレビューシステムにおけるチェンジリストIDです。Goのコミットは通常、この形式のIDを持ちます。)
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/11345.txt - GitHubコミットページ: https://github.com/golang/go/commit/b5777571b3ab20ca124fa60c34cd5094098fbb2f
- Go言語のビルド制約に関する公式ドキュメント (現在のもの): https://pkg.go.dev/cmd/go#hdr-Build_constraints (このコミット時点では存在しない可能性もありますが、概念を理解する上で参考になります。)
- Go言語の
go/buildパッケージに関する公式ドキュメント (現在のもの): https://pkg.go.dev/go/build# [インデックス 11345] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、ビルドタグの柔軟性を大幅に向上させる重要な変更を導入しています。具体的には、go/build パッケージの Context に BuildTags フィールドを追加し、ユーザーがカスタムのビルドタグを定義できるようにしました。これにより、特定の環境(例: App Engine)やビルド設定(例: ネットワークを使用しないブートストラップ版)に応じた条件付きコンパイルが可能になります。
さらに、// +build !tag という形式でタグの否定条件を指定できるようになり、特定のタグが存在しない場合にのみファイルをビルドするといった、より詳細な制御が可能になりました。
また、このコミットでは、以前導入されたものの実際に使用されなかった Build および Script 関連のコードが go/build パッケージから削除され、goinstall コマンドからもその依存が取り除かれました。これにより、ビルドシステムのコードベースが整理され、不要な複雑さが排除されています。
コミット
commit b5777571b3ab20ca124fa60c34cd5094098fbb2f
Author: Russ Cox <rsc@golang.org>
Date: Mon Jan 23 15:16:38 2012 -0500
go/build: add BuildTags to Context, allow !tag
This lets the client of go/build specify additional tags that
can be recognized in a // +build directive. For example,
a build for a custom environment like App Engine might
include "appengine" in the BuildTags list, so that packages
can be written with some files saying
// +build appengine (build only on app engine)
or
// +build !appengine (build only when NOT on app engine)
App Engine here is just a hypothetical context. I plan to use
this in the cmd/go sources to distinguish the bootstrap version
of cmd/go (which will not use networking) from the full version
using a custom tag. It might also be useful in App Engine.
Also, delete Build and Script, which we did not end up using for
cmd/go and which never got turned on for real in goinstall.
R=r, adg
CC=golang-dev
https://golang.org/cl/5554079
---
src/cmd/goinstall/main.go | 29 +--
src/pkg/crypto/tls/root_stub.go | 2 +-\n src/pkg/go/build/build.go | 425 +---------------------------------------\n src/pkg/go/build/build_test.go | 56 +++---\n src/pkg/go/build/dir.go | 128 ++++++++++--\n src/pkg/net/cgo_stub.go | 2 +-\n src/pkg/os/user/lookup_stubs.go | 2 +-\n 7 files changed, 137 insertions(+), 507 deletions(-)\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b5777571b3ab20ca124fa60c34cd5094098fbb2f
元コミット内容
commit b5777571b3ab20ca124fa60c34cd5094098fbb2f
Author: Russ Cox <rsc@golang.org>
Date: Mon Jan 23 15:16:38 2012 -0500
go/build: add BuildTags to Context, allow !tag
This lets the client of go/build specify additional tags that
can be recognized in a // +build directive. For example,
a build for a custom environment like App Engine might
include "appengine" in the BuildTags list, so that packages
can be written with some files saying
// +build appengine (build only on app engine)
or
// +build !appengine (build only when NOT on app engine)
App Engine here is just a hypothetical context. I plan to use
this in the cmd/go sources to distinguish the bootstrap version
of cmd/go (which will not use networking) from the full version
using a custom tag. It might also be useful in App Engine.
Also, delete Build and Script, which we did not end up using for
cmd/go and which never got turned on for real in goinstall.
R=r, adg
CC=golang-dev
https://golang.org/cl/5554079
変更の背景
このコミットの背景には、Go言語のビルドプロセスにおける柔軟性の向上と、コードベースの整理という二つの主要な目的があります。
-
カスタムビルド環境への対応と条件付きコンパイルの強化: Go言語の初期のビルドシステムでは、
GOOS(オペレーティングシステム)やGOARCH(アーキテクチャ)、cgoの有無といった限られた組み込みタグに基づいてファイルのビルドを制御していました。しかし、特定のプラットフォーム(例: Google App Engineのようなクラウド環境)や、特定の機能セット(例: ネットワーク機能の有無)を持つアプリケーションをビルドする際に、これらの組み込みタグだけでは不十分でした。 このコミットは、go/buildパッケージのクライアントが独自の「ビルドタグ」を定義し、それらを// +buildディレクティブで利用できるようにすることで、このギャップを埋めようとしています。これにより、開発者はよりきめ細やかな条件付きコンパイルロジックを実装できるようになります。コミットメッセージにあるように、cmd/goのブートストラップ版(ネットワーク機能なし)とフル版を区別する目的や、App Engineのようなカスタム環境での利用が想定されていました。 -
未使用コードの削除とコードベースの整理: コミットメッセージには、「
BuildとScriptを削除する。これらはcmd/goで結局使われず、goinstallでも実際に有効化されることはなかった」と明記されています。これは、過去に導入されたものの、実際の開発プロセスやツールチェーンにおいて必要とされなかった機能や抽象化が、コードベースに残存していたことを示唆しています。これらの未使用コードを削除することで、go/buildパッケージの複雑性を減らし、保守性を向上させることが目的でした。
これらの変更は、Go言語のビルドシステムが進化し、より多様な開発シナリオに対応できるようになる過程の一部であり、同時に、不要な負債を排除して健全なコードベースを維持しようとする姿勢を示しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムに関する基本的な概念を把握しておく必要があります。
-
go/buildパッケージ:go/buildパッケージは、Go言語のソースコードを解析し、パッケージの依存関係を解決し、ビルドに必要なファイル(Goソースファイル、Cgoファイル、アセンブリファイルなど)を特定するためのツールを提供します。go buildやgo installといったコマンドの内部で利用される、Goのビルドプロセスの基盤となるライブラリです。このパッケージは、特定のディレクトリ内のGoパッケージに関する情報(インポート、エクスポート、ファイルの種類など)をDirInfo構造体として提供し、ビルドコンテキスト(Context構造体)に基づいてどのファイルをビルドに含めるかを決定します。 -
ビルドタグ (
// +buildディレクティブ): Go言語では、ソースファイルの先頭に// +buildという形式のコメント行を記述することで、そのファイルを特定の条件が満たされた場合にのみビルドに含めるように指定できます。これを「ビルドタグ」または「ビルド制約」と呼びます。 例:// +build linux:Linux環境でのみビルド// +build darwin,amd64:macOS (Darwin) かつ AMD64アーキテクチャでのみビルド// +build cgo:Cgoが有効な場合のみビルド 複数のタグをスペースで区切るとOR条件、カンマで区切るとAND条件になります。
-
GOOSとGOARCH: Go言語のクロスコンパイルを可能にするための環境変数です。GOOS: ターゲットとなるオペレーティングシステム(例:linux,windows,darwin)。GOARCH: ターゲットとなるアーキテクチャ(例:amd64,386,arm)。 これらの値は、runtime.GOOSとruntime.GOARCHとしてGoプログラム内で参照でき、ビルドタグの評価にも利用されます。
-
cgo: Go言語からC言語のコードを呼び出すためのメカニズムです。cgoが有効な場合、// +build cgoタグを持つファイルがビルドに含まれます。 -
goinstallコマンド (当時):goinstallは、Go 1.0以前に存在したパッケージのダウンロードとインストールを行うためのコマンドです。現在のgo getコマンドの前身にあたります。このコミットの時点ではまだ存在していましたが、後にgo getに置き換えられました。コミットメッセージにあるBuildやScriptがgoinstallで使われなかったという記述は、このコマンドの文脈での話です。
これらの前提知識を理解することで、BuildTagsの追加がGoのビルドシステムにどのような柔軟性をもたらし、!tagの導入がどのように条件付きコンパイルの表現力を高めたのか、そしてなぜBuildやScriptが削除されたのかが明確になります。
技術的詳細
このコミットの技術的な詳細を掘り下げると、主にgo/buildパッケージ内のContext構造体と、ビルドタグの評価ロジックを担うmatch関数の変更、そして未使用コードの削除に集約されます。
-
go/build.Context構造体へのBuildTagsフィールド追加:src/pkg/go/build/dir.goにおいて、Context構造体にBuildTags []stringという新しいフィールドが追加されました。type Context struct { GOARCH string // target architecture GOOS string // target operating system CgoEnabled bool // whether cgo can be used BuildTags []string // additional tags to recognize in +build lines // ... }このフィールドは、
go/buildパッケージのクライアント(例:goコマンド自身やIDEなど)が、標準のGOOS、GOARCH、cgoに加えて、独自のカスタムタグをビルドコンテキストに含めることを可能にします。これにより、例えば「appengine」というタグをBuildTagsに追加することで、// +build appengineと記述されたファイルをビルドに含めることができるようになります。 -
// +build !tagのサポートとmatch関数の変更: 最も重要な変更点の一つは、ビルドタグの評価ロジックを担うmatch関数の強化です。src/pkg/go/build/dir.goのmatchOSArch関数がmatch関数にリネームされ、その内部ロジックが大幅に拡張されました。 変更前は、matchOSArchはGOOS、GOARCH、cgo(またはnocgo)といった基本的なタグのみを評価していました。 変更後、match関数は以下の新しい評価ルールをサポートします。- 否定 (
!):!tagという形式で指定された場合、そのタグが存在しない場合にtrueを返します。例えば、!appengineはBuildTagsにappengineが含まれていない場合にマッチします。 - カスタムタグの評価:
Context.BuildTagsに含まれる任意の文字列がタグとして認識され、評価されます。 - カンマ区切り (
tag1,tag2): カンマで区切られたタグはAND条件として評価されます。つまり、tag1とtag2の両方がマッチする場合にtrueを返します。 - スラッシュ区切り (
tag1/tag2): 以前のスラッシュ区切りは、カンマ区切りに置き換えられ、AND条件として扱われるようになりました。 - 不正なタグの拒否:
!!のような不正な構文や、英数字とアンダースコア以外の文字を含むタグはfalseを返すようになりました。
この
match関数の変更により、// +buildディレクティブの表現力が飛躍的に向上し、より複雑な条件付きコンパイルが可能になりました。 - 否定 (
-
BuildおよびScript関連コードの削除:src/pkg/go/build/build.goから、Build関数、Script構造体、Cmd構造体、およびそれらに関連するメソッド(Run,Stale,Clean,Nukeなど)が大量に削除されました。これらのコードは、Goのビルドプロセスを抽象化し、個々のビルドコマンドをスクリプトとして表現することを意図していましたが、コミットメッセージにあるように、cmd/goやgoinstallで実際に利用されることはありませんでした。この削除により、go/buildパッケージのコードベースが大幅に削減され、シンプルになりました。 -
goinstallからのBuild/Script依存の削除:src/cmd/goinstall/main.goでは、build.Build関数を呼び出してScriptオブジェクトを取得し、それを使ってビルドを実行していた部分が削除されました。代わりに、domake関数(おそらく内部的なビルドヘルパー)が直接呼び出されるようになり、BuildおよびScriptの抽象化レイヤーが不要になりました。 -
既存のビルドタグの更新:
src/pkg/crypto/tls/root_stub.go、src/pkg/net/cgo_stub.go、src/pkg/os/user/lookup_stubs.goといったファイルで、// +build nocgoが// +build !cgoに、// +build darwin/nocgoが// +build darwin,!cgoにそれぞれ変更されています。これは、新しい!tag構文への移行を示す具体的な例です。
これらの技術的変更は、Goのビルドシステムがより強力で柔軟になり、同時に不要な複雑さを排除して効率化されたことを示しています。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主に以下のファイルに集中しています。
-
src/pkg/go/build/dir.go:Context構造体にBuildTags []stringフィールドが追加されました。matchOSArch関数がmatch関数にリネームされ、!tag(否定) およびBuildTagsを考慮したビルドタグ評価ロジックが実装されました。ScanDir関数のコメントが大幅に更新され、新しいビルド制約のルール(特に+buildディレクティブの構文と評価方法)が詳細に記述されました。
--- a/src/pkg/go/build/dir.go +++ b/src/pkg/go/build/dir.go @@ -25,9 +25,10 @@ import ( // A Context specifies the supporting context for a build. type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - CgoEnabled bool // whether cgo can be used + GOARCH string // target architecture + GOOS string // target operating system + CgoEnabled bool // whether cgo can be used + BuildTags []string // additional tags to recognize in +build lines // By default, ScanDir uses the operating system's // file system calls to read directories and files. @@ -389,7 +448,7 @@ func (ctxt *Context) shouldBuild(content []byte) bool { if f[0] == "+build" { ok := false for _, tok := range f[1:] { - if ctxt.matchOSArch(tok) { + if ctxt.match(tok) { ok = true break } @@ -441,7 +500,7 @@ func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) { if len(cond) > 0 { ok := false for _, c := range cond { - if ctxt.matchOSArch(c) { + if ctxt.match(c) { ok = true break } @@ -550,26 +609,55 @@ func splitQuoted(s string) (r []string, err error) { return args, err } -// matchOSArch returns true if the name is one of: +// match returns true if the name is one of: // // $GOOS // $GOARCH // cgo (if cgo is enabled) -// nocgo (if cgo is disabled) +// !cgo (if cgo is disabled) +// tag (if tag is listed in ctxt.BuildTags) +// !tag (if tag is not listed in ctxt.BuildTags) // a slash-separated list of any of these // -func (ctxt *Context) matchOSArch(name string) bool { - if ctxt.CgoEnabled && name == "cgo" { - return true +func (ctxt *Context) match(name string) bool { + if name == "" { + return false } - if !ctxt.CgoEnabled && name == "nocgo" { + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return !ctxt.match(name[1:]) + } + + // Tags must be letters, digits, underscores. + // Unlike in Go identifiers, all digits is fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { + return false + } + } + + // special tags + if ctxt.CgoEnabled && name == "cgo" { return true } if name == ctxt.GOOS || name == ctxt.GOARCH { return true } - i := strings.Index(name, "/") - return i >= 0 && ctxt.matchOSArch(name[:i]) && ctxt.matchOSArch(name[i+1:]) + + // other tags + for _, tag := range ctxt.BuildTags { + if tag == name { + return true + } + } + + return false } // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH -
src/pkg/go/build/build.go:Build関数、Script構造体、Cmd構造体、およびそれらに関連するすべてのビルドロジックとヘルパー関数(addInput,addIntermediate,Run,Stale,Clean,Nuke,build構造体、abs,abss,add,mkdir,gc,asm,ld,gopack,cc,gccCompile,gccLink,gccArgs,cgoなど)が完全に削除されました。これにより、ファイルサイズが大幅に減少しています。
--- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -5,245 +5,7 @@ // Package build provides tools for building Go packages. package build -import ( - "bytes" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strings" - "time" -) - -// Build produces a build Script for the given package. -func Build(tree *Tree, pkg string, info *DirInfo) (*Script, error) { -// ... (大量の削除されたコード) ... - -import "errors" -
src/cmd/goinstall/main.go:build.Buildを呼び出してScriptオブジェクトを使用していた部分が削除され、代わりにdomake関数が直接呼び出されるようになりました。これにより、goinstallがgo/buildの新しい抽象化に依存しなくなりました。
--- a/src/cmd/goinstall/main.go +++ b/src/cmd/goinstall/main.go @@ -336,35 +336,10 @@ func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installEr } // Install this package. - if *useMake { - err := domake(dir, pkg, tree, dirInfo.IsCommand()) - if err != nil { - return &BuildError{pkg, err} - } - return nil - } - script, err := build.Build(tree, pkg, dirInfo) + err = domake(dir, pkg, tree, dirInfo.IsCommand()) if err != nil { return &BuildError{pkg, err} } - if *nuke { - printf("%s: nuke\n", pkg) - script.Nuke() - } else if *clean { - printf("%s: clean\n", pkg) - script.Clean() - } - if *doInstall { - if script.Stale() { - printf("%s: install\n", pkg) - if err := script.Run(); err != nil { - return &BuildError{pkg, err} - } - } else { - printf("%s: up-to-date\n", pkg) - } - } - return nil } -
src/pkg/crypto/tls/root_stub.go,src/pkg/net/cgo_stub.go,src/pkg/os/user/lookup_stubs.go:- これらのファイルでは、
// +build nocgoが// +build !cgoに変更されるなど、新しい否定タグの構文が適用されました。
--- a/src/pkg/crypto/tls/root_stub.go +++ b/src/pkg/crypto/tls/root_stub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build plan9 darwin/nocgo +// +build plan9 darwin,!cgo package tls - これらのファイルでは、
これらの変更は、Goのビルドシステムの中核部分に影響を与え、ビルドタグの処理方法とビルドプロセスの抽象化を根本的に変更しています。
コアとなるコードの解説
このコミットのコアとなる変更は、src/pkg/go/build/dir.go 内の Context 構造体と match 関数に集約されます。
Context 構造体への BuildTags フィールド追加
type Context struct {
GOARCH string // target architecture
GOOS string // target operating system
CgoEnabled bool // whether cgo can be used
BuildTags []string // additional tags to recognize in +build lines
// ...
}
Context 構造体は、Goのビルドプロセスにおける現在の環境設定を定義します。これには、ターゲットのOS (GOOS)、アーキテクチャ (GOARCH)、Cgoの有効/無効 (CgoEnabled) などが含まれます。
このコミットで追加された BuildTags []string フィールドは、ビルドシステムが認識すべき「追加のタグ」のリストを保持します。これにより、go/build パッケージの利用者は、標準の環境変数やCgoの有無だけでなく、任意の文字列をカスタムタグとして定義し、それに基づいてソースファイルのビルドを制御できるようになります。例えば、Context.BuildTags に {"appengine", "debug"} を設定すると、// +build appengine や // +build debug といったディレクティブが有効になります。
match 関数のロジック
match 関数は、与えられたビルドタグ文字列が現在の Context にマッチするかどうかを評価する中心的なロジックです。以前は matchOSArch という名前で、OS、アーキテクチャ、Cgoの有無のみを評価していましたが、このコミットで大幅に拡張されました。
func (ctxt *Context) match(name string) bool {
if name == "" {
return false
}
if i := strings.Index(name, ","); i >= 0 {
// comma-separated list (AND condition)
return ctxt.match(name[:i]) && ctxt.match(name[i+1:])
}
if strings.HasPrefix(name, "!!") { // bad syntax, reject always
return false
}
if strings.HasPrefix(name, "!") { // negation
return !ctxt.match(name[1:])
}
// Tags must be letters, digits, underscores.
// Unlike in Go identifiers, all digits is fine (e.g., "386").
for _, c := range name {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' {
return false
}
}
// special tags
if ctxt.CgoEnabled && name == "cgo" {
return true
}
if name == ctxt.GOOS || name == ctxt.GOARCH {
return true
}
// other tags (custom BuildTags)
for _, tag := range ctxt.BuildTags {
if tag == name {
return true
}
}
return false
}
この関数の動作は以下の通りです。
- 空文字列のチェック:
nameが空の場合はfalseを返します。 - カンマ区切り (AND条件):
nameにカンマが含まれる場合、それはAND条件として扱われます。例えば、"linux,amd64"はctxt.match("linux")とctxt.match("amd64")の両方がtrueの場合にtrueを返します。これは再帰的に評価されます。 - 不正な構文のチェック:
!!のように!が連続する場合は不正な構文としてfalseを返します。 - 否定 (
!):nameが!で始まる場合、それは否定条件として扱われます。例えば、"!appengine"はctxt.match("appengine")がfalseの場合にtrueを返します。これも再帰的に評価されます。 - タグ文字の検証: タグ名が英数字とアンダースコアのみで構成されているかをチェックします。これ以外の文字が含まれる場合は
falseを返します。 - 特殊タグの評価:
cgo:Context.CgoEnabledがtrueの場合にtrueを返します。GOOSまたはGOARCH:nameが現在のContext.GOOSまたはContext.GOARCHと一致する場合にtrueを返します。
- カスタムタグの評価: 最後に、
nameがContext.BuildTagsスライス内のいずれかの文字列と一致する場合にtrueを返します。
この match 関数の拡張により、// +build ディレクティブは、// +build linux,amd64,!cgo,appengine のように、より複雑で表現豊かな条件を記述できるようになりました。これにより、開発者は特定のビルド環境や要件に合わせたファイルのインクルード/除外を、より柔軟に制御できるようになります。
関連リンク
- Go Change-Id:
I2222222222222222222222222222222222222222(これはコミットメッセージに記載されているhttps://golang.org/cl/5554079に対応するGoのコードレビューシステムにおけるチェンジリストIDです。Goのコミットは通常、この形式のIDを持ちます。)
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/11345.txt - GitHubコミットページ: https://github.com/golang/go/commit/b5777571b3ab20ca124fa60c34cd5094098fbb2f
- Go言語のビルド制約に関する公式ドキュメント (現在のもの): https://pkg.go.dev/cmd/go#hdr-Build_constraints (このコミット時点では存在しない可能性もありますが、概念を理解する上で参考になります。)
- Go言語の
go/buildパッケージに関する公式ドキュメント (現在のもの): https://pkg.go.dev/go/build