[インデックス 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