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

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

このコミットは、Go言語のビルドシステムにおいて、ビルドタグの柔軟性を大幅に向上させる重要な変更を導入しています。具体的には、go/build パッケージの ContextBuildTags フィールドを追加し、ユーザーがカスタムのビルドタグを定義できるようにしました。これにより、特定の環境(例: 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言語のビルドプロセスにおける柔軟性の向上と、コードベースの整理という二つの主要な目的があります。

  1. カスタムビルド環境への対応と条件付きコンパイルの強化: Go言語の初期のビルドシステムでは、GOOS(オペレーティングシステム)やGOARCH(アーキテクチャ)、cgoの有無といった限られた組み込みタグに基づいてファイルのビルドを制御していました。しかし、特定のプラットフォーム(例: Google App Engineのようなクラウド環境)や、特定の機能セット(例: ネットワーク機能の有無)を持つアプリケーションをビルドする際に、これらの組み込みタグだけでは不十分でした。 このコミットは、go/buildパッケージのクライアントが独自の「ビルドタグ」を定義し、それらを// +buildディレクティブで利用できるようにすることで、このギャップを埋めようとしています。これにより、開発者はよりきめ細やかな条件付きコンパイルロジックを実装できるようになります。コミットメッセージにあるように、cmd/goのブートストラップ版(ネットワーク機能なし)とフル版を区別する目的や、App Engineのようなカスタム環境での利用が想定されていました。

  2. 未使用コードの削除とコードベースの整理: コミットメッセージには、「BuildScript を削除する。これらは cmd/go で結局使われず、goinstall でも実際に有効化されることはなかった」と明記されています。これは、過去に導入されたものの、実際の開発プロセスやツールチェーンにおいて必要とされなかった機能や抽象化が、コードベースに残存していたことを示唆しています。これらの未使用コードを削除することで、go/buildパッケージの複雑性を減らし、保守性を向上させることが目的でした。

これらの変更は、Go言語のビルドシステムが進化し、より多様な開発シナリオに対応できるようになる過程の一部であり、同時に、不要な負債を排除して健全なコードベースを維持しようとする姿勢を示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のビルドシステムに関する基本的な概念を把握しておく必要があります。

  1. go/build パッケージ: go/buildパッケージは、Go言語のソースコードを解析し、パッケージの依存関係を解決し、ビルドに必要なファイル(Goソースファイル、Cgoファイル、アセンブリファイルなど)を特定するためのツールを提供します。go buildgo installといったコマンドの内部で利用される、Goのビルドプロセスの基盤となるライブラリです。このパッケージは、特定のディレクトリ内のGoパッケージに関する情報(インポート、エクスポート、ファイルの種類など)をDirInfo構造体として提供し、ビルドコンテキスト(Context構造体)に基づいてどのファイルをビルドに含めるかを決定します。

  2. ビルドタグ (// +build ディレクティブ): Go言語では、ソースファイルの先頭に// +buildという形式のコメント行を記述することで、そのファイルを特定の条件が満たされた場合にのみビルドに含めるように指定できます。これを「ビルドタグ」または「ビルド制約」と呼びます。 例:

    • // +build linux:Linux環境でのみビルド
    • // +build darwin,amd64:macOS (Darwin) かつ AMD64アーキテクチャでのみビルド
    • // +build cgo:Cgoが有効な場合のみビルド 複数のタグをスペースで区切るとOR条件、カンマで区切るとAND条件になります。
  3. GOOSGOARCH: Go言語のクロスコンパイルを可能にするための環境変数です。

    • GOOS: ターゲットとなるオペレーティングシステム(例: linux, windows, darwin)。
    • GOARCH: ターゲットとなるアーキテクチャ(例: amd64, 386, arm)。 これらの値は、runtime.GOOSruntime.GOARCHとしてGoプログラム内で参照でき、ビルドタグの評価にも利用されます。
  4. cgo: Go言語からC言語のコードを呼び出すためのメカニズムです。cgoが有効な場合、// +build cgoタグを持つファイルがビルドに含まれます。

  5. goinstall コマンド (当時): goinstallは、Go 1.0以前に存在したパッケージのダウンロードとインストールを行うためのコマンドです。現在のgo getコマンドの前身にあたります。このコミットの時点ではまだ存在していましたが、後にgo getに置き換えられました。コミットメッセージにあるBuildScriptgoinstallで使われなかったという記述は、このコマンドの文脈での話です。

これらの前提知識を理解することで、BuildTagsの追加がGoのビルドシステムにどのような柔軟性をもたらし、!tagの導入がどのように条件付きコンパイルの表現力を高めたのか、そしてなぜBuildScriptが削除されたのかが明確になります。

技術的詳細

このコミットの技術的な詳細を掘り下げると、主にgo/buildパッケージ内のContext構造体と、ビルドタグの評価ロジックを担うmatch関数の変更、そして未使用コードの削除に集約されます。

  1. 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など)が、標準のGOOSGOARCHcgoに加えて、独自のカスタムタグをビルドコンテキストに含めることを可能にします。これにより、例えば「appengine」というタグをBuildTagsに追加することで、// +build appengineと記述されたファイルをビルドに含めることができるようになります。

  2. // +build !tag のサポートと match 関数の変更: 最も重要な変更点の一つは、ビルドタグの評価ロジックを担うmatch関数の強化です。src/pkg/go/build/dir.gomatchOSArch関数がmatch関数にリネームされ、その内部ロジックが大幅に拡張されました。 変更前は、matchOSArchGOOSGOARCHcgo(またはnocgo)といった基本的なタグのみを評価していました。 変更後、match関数は以下の新しい評価ルールをサポートします。

    • 否定 (!): !tagという形式で指定された場合、そのタグが存在しない場合にtrueを返します。例えば、!appengineBuildTagsappengineが含まれていない場合にマッチします。
    • カスタムタグの評価: Context.BuildTagsに含まれる任意の文字列がタグとして認識され、評価されます。
    • カンマ区切り (tag1,tag2): カンマで区切られたタグはAND条件として評価されます。つまり、tag1tag2の両方がマッチする場合にtrueを返します。
    • スラッシュ区切り (tag1/tag2): 以前のスラッシュ区切りは、カンマ区切りに置き換えられ、AND条件として扱われるようになりました。
    • 不正なタグの拒否: !!のような不正な構文や、英数字とアンダースコア以外の文字を含むタグはfalseを返すようになりました。

    このmatch関数の変更により、// +buildディレクティブの表現力が飛躍的に向上し、より複雑な条件付きコンパイルが可能になりました。

  3. Build および Script 関連コードの削除: src/pkg/go/build/build.goから、Build関数、Script構造体、Cmd構造体、およびそれらに関連するメソッド(Run, Stale, Clean, Nukeなど)が大量に削除されました。これらのコードは、Goのビルドプロセスを抽象化し、個々のビルドコマンドをスクリプトとして表現することを意図していましたが、コミットメッセージにあるように、cmd/gogoinstallで実際に利用されることはありませんでした。この削除により、go/buildパッケージのコードベースが大幅に削減され、シンプルになりました。

  4. goinstall からの Build/Script 依存の削除: src/cmd/goinstall/main.goでは、build.Build関数を呼び出してScriptオブジェクトを取得し、それを使ってビルドを実行していた部分が削除されました。代わりに、domake関数(おそらく内部的なビルドヘルパー)が直接呼び出されるようになり、BuildおよびScriptの抽象化レイヤーが不要になりました。

  5. 既存のビルドタグの更新: src/pkg/crypto/tls/root_stub.gosrc/pkg/net/cgo_stub.gosrc/pkg/os/user/lookup_stubs.goといったファイルで、// +build nocgo// +build !cgoに、// +build darwin/nocgo// +build darwin,!cgoにそれぞれ変更されています。これは、新しい!tag構文への移行を示す具体的な例です。

これらの技術的変更は、Goのビルドシステムがより強力で柔軟になり、同時に不要な複雑さを排除して効率化されたことを示しています。

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

このコミットのコアとなるコードの変更は、主に以下のファイルに集中しています。

  1. 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
    
  2. 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"
    
  3. src/cmd/goinstall/main.go:

    • build.Build を呼び出して Script オブジェクトを使用していた部分が削除され、代わりに domake 関数が直接呼び出されるようになりました。これにより、goinstallgo/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
     }
    
  4. 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
}

この関数の動作は以下の通りです。

  1. 空文字列のチェック: name が空の場合は false を返します。
  2. カンマ区切り (AND条件): name にカンマが含まれる場合、それはAND条件として扱われます。例えば、"linux,amd64"ctxt.match("linux")ctxt.match("amd64") の両方が true の場合に true を返します。これは再帰的に評価されます。
  3. 不正な構文のチェック: !! のように ! が連続する場合は不正な構文として false を返します。
  4. 否定 (!): name! で始まる場合、それは否定条件として扱われます。例えば、"!appengine"ctxt.match("appengine")false の場合に true を返します。これも再帰的に評価されます。
  5. タグ文字の検証: タグ名が英数字とアンダースコアのみで構成されているかをチェックします。これ以外の文字が含まれる場合は false を返します。
  6. 特殊タグの評価:
    • cgo: Context.CgoEnabledtrue の場合に true を返します。
    • GOOS または GOARCH: name が現在の Context.GOOS または Context.GOARCH と一致する場合に true を返します。
  7. カスタムタグの評価: 最後に、nameContext.BuildTags スライス内のいずれかの文字列と一致する場合に true を返します。

この match 関数の拡張により、// +build ディレクティブは、// +build linux,amd64,!cgo,appengine のように、より複雑で表現豊かな条件を記述できるようになりました。これにより、開発者は特定のビルド環境や要件に合わせたファイルのインクルード/除外を、より柔軟に制御できるようになります。

関連リンク

  • Go Change-Id: I2222222222222222222222222222222222222222 (これはコミットメッセージに記載されているhttps://golang.org/cl/5554079に対応するGoのコードレビューシステムにおけるチェンジリストIDです。Goのコミットは通常、この形式のIDを持ちます。)

参考にした情報源リンク

このコミットは、Go言語のビルドシステムにおいて、ビルドタグの柔軟性を大幅に向上させる重要な変更を導入しています。具体的には、go/build パッケージの ContextBuildTags フィールドを追加し、ユーザーがカスタムのビルドタグを定義できるようにしました。これにより、特定の環境(例: 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言語のビルドプロセスにおける柔軟性の向上と、コードベースの整理という二つの主要な目的があります。

  1. カスタムビルド環境への対応と条件付きコンパイルの強化: Go言語の初期のビルドシステムでは、GOOS(オペレーティングシステム)やGOARCH(アーキテクチャ)、cgoの有無といった限られた組み込みタグに基づいてファイルのビルドを制御していました。しかし、特定のプラットフォーム(例: Google App Engineのようなクラウド環境)や、特定の機能セット(例: ネットワーク機能の有無)を持つアプリケーションをビルドする際に、これらの組み込みタグだけでは不十分でした。 このコミットは、go/buildパッケージのクライアントが独自の「ビルドタグ」を定義し、それらを// +buildディレクティブで利用できるようにすることで、このギャップを埋めようとしています。これにより、開発者はよりきめ細やかな条件付きコンパイルロジックを実装できるようになります。コミットメッセージにあるように、cmd/goのブートストラップ版(ネットワーク機能なし)とフル版を区別する目的や、App Engineのようなカスタム環境での利用が想定されていました。

  2. 未使用コードの削除とコードベースの整理: コミットメッセージには、「BuildScript を削除する。これらは cmd/go で結局使われず、goinstall でも実際に有効化されることはなかった」と明記されています。これは、過去に導入されたものの、実際の開発プロセスやツールチェーンにおいて必要とされなかった機能や抽象化が、コードベースに残存していたことを示唆しています。これらの未使用コードを削除することで、go/buildパッケージの複雑性を減らし、保守性を向上させることが目的でした。

これらの変更は、Go言語のビルドシステムが進化し、より多様な開発シナリオに対応できるようになる過程の一部であり、同時に、不要な負債を排除して健全なコードベースを維持しようとする姿勢を示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のビルドシステムに関する基本的な概念を把握しておく必要があります。

  1. go/build パッケージ: go/buildパッケージは、Go言語のソースコードを解析し、パッケージの依存関係を解決し、ビルドに必要なファイル(Goソースファイル、Cgoファイル、アセンブリファイルなど)を特定するためのツールを提供します。go buildgo installといったコマンドの内部で利用される、Goのビルドプロセスの基盤となるライブラリです。このパッケージは、特定のディレクトリ内のGoパッケージに関する情報(インポート、エクスポート、ファイルの種類など)をDirInfo構造体として提供し、ビルドコンテキスト(Context構造体)に基づいてどのファイルをビルドに含めるかを決定します。

  2. ビルドタグ (// +build ディレクティブ): Go言語では、ソースファイルの先頭に// +buildという形式のコメント行を記述することで、そのファイルを特定の条件が満たされた場合にのみビルドに含めるように指定できます。これを「ビルドタグ」または「ビルド制約」と呼びます。 例:

    • // +build linux:Linux環境でのみビルド
    • // +build darwin,amd64:macOS (Darwin) かつ AMD64アーキテクチャでのみビルド
    • // +build cgo:Cgoが有効な場合のみビルド 複数のタグをスペースで区切るとOR条件、カンマで区切るとAND条件になります。
  3. GOOSGOARCH: Go言語のクロスコンパイルを可能にするための環境変数です。

    • GOOS: ターゲットとなるオペレーティングシステム(例: linux, windows, darwin)。
    • GOARCH: ターゲットとなるアーキテクチャ(例: amd64, 386, arm)。 これらの値は、runtime.GOOSruntime.GOARCHとしてGoプログラム内で参照でき、ビルドタグの評価にも利用されます。
  4. cgo: Go言語からC言語のコードを呼び出すためのメカニズムです。cgoが有効な場合、// +build cgoタグを持つファイルがビルドに含まれます。

  5. goinstall コマンド (当時): goinstallは、Go 1.0以前に存在したパッケージのダウンロードとインストールを行うためのコマンドです。現在のgo getコマンドの前身にあたります。このコミットの時点ではまだ存在していましたが、後にgo getに置き換えられました。コミットメッセージにあるBuildScriptgoinstallで使われなかったという記述は、このコマンドの文脈での話です。

これらの前提知識を理解することで、BuildTagsの追加がGoのビルドシステムにどのような柔軟性をもたらし、!tagの導入がどのように条件付きコンパイルの表現力を高めたのか、そしてなぜBuildScriptが削除されたのかが明確になります。

技術的詳細

このコミットの技術的な詳細を掘り下げると、主にgo/buildパッケージ内のContext構造体と、ビルドタグの評価ロジックを担うmatch関数の変更、そして未使用コードの削除に集約されます。

  1. 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など)が、標準のGOOSGOARCHcgoに加えて、独自のカスタムタグをビルドコンテキストに含めることを可能にします。これにより、例えば「appengine」というタグをBuildTagsに追加することで、// +build appengineと記述されたファイルをビルドに含めることができるようになります。

  2. // +build !tag のサポートと match 関数の変更: 最も重要な変更点の一つは、ビルドタグの評価ロジックを担うmatch関数の強化です。src/pkg/go/build/dir.gomatchOSArch関数がmatch関数にリネームされ、その内部ロジックが大幅に拡張されました。 変更前は、matchOSArchGOOSGOARCHcgo(またはnocgo)といった基本的なタグのみを評価していました。 変更後、match関数は以下の新しい評価ルールをサポートします。

    • 否定 (!): !tagという形式で指定された場合、そのタグが存在しない場合にtrueを返します。例えば、!appengineBuildTagsappengineが含まれていない場合にマッチします。
    • カスタムタグの評価: Context.BuildTagsに含まれる任意の文字列がタグとして認識され、評価されます。
    • カンマ区切り (tag1,tag2): カンマで区切られたタグはAND条件として評価されます。つまり、tag1tag2の両方がマッチする場合にtrueを返します。
    • スラッシュ区切り (tag1/tag2): 以前のスラッシュ区切りは、カンマ区切りに置き換えられ、AND条件として扱われるようになりました。
    • 不正なタグの拒否: !!のような不正な構文や、英数字とアンダースコア以外の文字を含むタグはfalseを返すようになりました。

    このmatch関数の変更により、// +buildディレクティブの表現力が飛躍的に向上し、より複雑な条件付きコンパイルが可能になりました。

  3. Build および Script 関連コードの削除: src/pkg/go/build/build.goから、Build関数、Script構造体、Cmd構造体、およびそれらに関連するメソッド(Run, Stale, Clean, Nukeなど)が大量に削除されました。これらのコードは、Goのビルドプロセスを抽象化し、個々のビルドコマンドをスクリプトとして表現することを意図していましたが、コミットメッセージにあるように、cmd/gogoinstallで実際に利用されることはありませんでした。この削除により、go/buildパッケージのコードベースが大幅に削減され、シンプルになりました。

  4. goinstall からの Build/Script 依存の削除: src/cmd/goinstall/main.goでは、build.Build関数を呼び出してScriptオブジェクトを取得し、それを使ってビルドを実行していた部分が削除されました。代わりに、domake関数(おそらく内部的なビルドヘルパー)が直接呼び出されるようになり、BuildおよびScriptの抽象化レイヤーが不要になりました。

  5. 既存のビルドタグの更新: src/pkg/crypto/tls/root_stub.gosrc/pkg/net/cgo_stub.gosrc/pkg/os/user/lookup_stubs.goといったファイルで、// +build nocgo// +build !cgoに、// +build darwin/nocgo// +build darwin,!cgoにそれぞれ変更されています。これは、新しい!tag構文への移行を示す具体的な例です。

これらの技術的変更は、Goのビルドシステムがより強力で柔軟になり、同時に不要な複雑さを排除して効率化されたことを示しています。

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

このコミットのコアとなるコードの変更は、主に以下のファイルに集中しています。

  1. 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
    
  2. 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"
    
  3. src/cmd/goinstall/main.go:

    • build.Build を呼び出して Script オブジェクトを使用していた部分が削除され、代わりに domake 関数が直接呼び出されるようになりました。これにより、goinstallgo/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
     }
    
  4. 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
}

この関数の動作は以下の通りです。

  1. 空文字列のチェック: name が空の場合は false を返します。
  2. カンマ区切り (AND条件): name にカンマが含まれる場合、それはAND条件として扱われます。例えば、"linux,amd64"ctxt.match("linux")ctxt.match("amd64") の両方が true の場合に true を返します。これは再帰的に評価されます。
  3. 不正な構文のチェック: !! のように ! が連続する場合は不正な構文として false を返します。
  4. 否定 (!): name! で始まる場合、それは否定条件として扱われます。例えば、"!appengine"ctxt.match("appengine")false の場合に true を返します。これも再帰的に評価されます。
  5. タグ文字の検証: タグ名が英数字とアンダースコアのみで構成されているかをチェックします。これ以外の文字が含まれる場合は false を返します。
  6. 特殊タグの評価:
    • cgo: Context.CgoEnabledtrue の場合に true を返します。
    • GOOS または GOARCH: name が現在の Context.GOOS または Context.GOARCH と一致する場合に true を返します。
  7. カスタムタグの評価: 最後に、nameContext.BuildTags スライス内のいずれかの文字列と一致する場合に true を返します。

この match 関数の拡張により、// +build ディレクティブは、// +build linux,amd64,!cgo,appengine のように、より複雑で表現豊かな条件を記述できるようになりました。これにより、開発者は特定のビルド環境や要件に合わせたファイルのインクルード/除外を、より柔軟に制御できるようになります。

関連リンク

  • Go Change-Id: I2222222222222222222222222222222222222222 (これはコミットメッセージに記載されているhttps://golang.org/cl/5554079に対応するGoのコードレビューシステムにおけるチェンジリストIDです。Goのコミットは通常、この形式のIDを持ちます。)

参考にした情報源リンク