[インデックス 12308] ファイルの概要
このコミットは、Go言語のビルドシステムを司る go/build
パッケージのAPIを大幅に刷新するものです。既存の FindTree
, ScanDir
, Tree
, DirInfo
といった型や関数を廃止し、より柔軟で強力な Import
関数と Package
型に置き換えることで、パッケージの検索、解析、およびビルドプロセスの基盤を改善しています。特に、go
コマンドの要件に合わせて設計されており、ローカルインポートの解決や $GOPATH
外のパッケージスキャンを可能にしています。
コミット
commit ebe1664d2789cd4ea0ded0eccb9067c729378cc5
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 1 12:12:09 2012 -0500
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ebe1664d2789cd4ea0ded0eccb9067c729378cc5
元コミット内容
このコミットは、go/build
パッケージの既存APIである FindTree
, ScanDir
, Tree
, DirInfo
を、新しい Import
関数と Package
型に置き換えるものです。これは、go
コマンドの要件を満たすために約束されていたAPI変更であり、以前のAPIが「多すぎもせず、少なすぎもせず」という問題を抱えていた点を改善します。新しい Import
APIは、ソースディレクトリの指定、ローカルインポートの解決、および $GOPATH
外のパッケージスキャンをサポートします。また、Path
型が削除され、誤用を防ぐようになっています(Issue 2749を修正)。この変更は、go
コマンド以外の go/build
クライアントに適用され、go
コマンド自体の変更は別のコミットで同時に提出されます。
変更の背景
この変更の主な背景は、Go言語の公式ビルドツールである go
コマンドの進化と、それに伴う go/build
パッケージへの要求の変化です。コミットメッセージにもあるように、以前の go/build
APIは「多すぎもせず、少なすぎもせず」という問題を抱えていました。これは、特定のユースケースでは情報が不足し、別のユースケースでは不要な情報が多すぎるという状況を指します。
具体的には、以下の点が課題となっていました。
go
コマンドの要件との乖離:go
コマンドは、パッケージの依存関係解決、ビルド、テスト、インストールなど、多岐にわたるタスクを実行します。そのためには、パッケージに関する詳細かつ柔軟な情報取得メカニズムが必要でした。既存のAPIでは、これらの要件を効率的かつ正確に満たすことが困難になっていました。- ローカルインポートの解決の複雑さ:
import "./foo"
のようなローカルインポートパスは、相対パスであるため、その解決には現在の作業ディレクトリやソースファイルの場所といったコンテキストが必要でした。既存のAPIでは、このコンテキストを適切に扱うための仕組みが不十分でした。 $GOPATH
外のパッケージスキャン: Goのパッケージは通常$GOPATH
内に配置されますが、特定のシナリオ(例: 一時的なディレクトリでの作業、単一ファイルのスクリプト実行)では、$GOPATH
外のパッケージをスキャンする必要がありました。既存のAPIでは、このような柔軟なスキャンが考慮されていませんでした。build.Path
の誤用:build.Path
は、Goのソースツリーのパスを管理するためのものでしたが、その設計上、誤った方法で使用される可能性がありました。コミットメッセージで「Path is gone, so it can no longer be misused. (Fixes issue 2749.)」と明記されているように、この誤用を防ぐための抜本的な解決策が必要でした。Issue 2749は、go/build
パッケージが提供するパス情報が、ユーザーが期待する形式と異なる場合があるという問題に関連していると考えられます。
これらの課題を解決し、go
コマンドがより堅牢で効率的なビルドシステムを構築できるようにするために、go/build
パッケージのAPIを根本的に見直し、Import
関数と Package
型を導入する必要がありました。
前提知識の解説
このコミットを理解するためには、Go言語のパッケージ管理、ビルドプロセス、および go/build
パッケージに関する基本的な知識が必要です。
-
Go言語のパッケージとインポートパス:
- Goのコードは「パッケージ」という単位で整理されます。各Goファイルは
package <name>
で始まる必要があります。 - 他のパッケージのコードを利用するには
import "<path>"
を使用します。インポートパスは、通常、Goモジュールパスや標準ライブラリのパス(例:"fmt"
,"net/http"
)です。 - ローカルインポートパス:
import "./foo"
やimport "../bar"
のように、相対パスで指定されるインポートパスです。これらは、同じリポジトリ内の別のディレクトリにあるパッケージを参照する際に使用されます。 main
パッケージ: 実行可能なプログラムのエントリポイントとなるパッケージはmain
と命名され、main
関数を含みます。
- Goのコードは「パッケージ」という単位で整理されます。各Goファイルは
-
GOROOT
とGOPATH
:GOROOT
: Goのインストールディレクトリを指します。Goの標準ライブラリのソースコードは$GOROOT/src
以下に配置されます。GOPATH
: Goのワークスペースディレクトリを指します。ユーザーが開発するGoのプロジェクトや、go get
でダウンロードされたサードパーティのパッケージは、通常$GOPATH/src
以下に配置されます。$GOPATH
は複数のパスを設定できます。
-
go/build
パッケージ:go/build
パッケージは、Goのソースコードを解析し、パッケージの構造、依存関係、ビルド制約などを特定するための機能を提供します。これは、go
コマンドやIDE、その他のGoツールがGoのプロジェクトを理解するために利用する低レベルのAPIです。- ビルドタグ (Build Tags): Goのソースファイルには、
// +build <tag>
の形式でビルドタグを記述できます。これにより、特定のOS、アーキテクチャ、またはカスタムタグが有効な場合にのみ、そのファイルをビルドに含めるかどうかを制御できます。例えば、// +build linux,amd64
はLinuxかつAMD64アーキテクチャの場合にのみファイルを含めます。 cgo
: GoコードからC言語のコードを呼び出すための仕組みです。import "C"
を使用することで、Cの関数や型をGoから利用できます。cgo
を使用するファイルは、ビルドプロセスにおいて特別な処理が必要です。
-
以前の
go/build
API (このコミットで置き換えられるもの):build.Tree
: Goのソースツリー(GOROOT
またはGOPATH
のエントリ)を表す構造体。ソースディレクトリやパッケージディレクトリへのパスを提供しました。build.DirInfo
: 特定のディレクトリ内のGoパッケージに関する情報(パッケージ名、インポートパス、Goファイル、Cgoファイル、インポートリストなど)を保持する構造体。build.FindTree
: 指定されたインポートパスまたはファイルシステムパスに基づいて、対応するbuild.Tree
とパッケージのインポートパスを検索する関数。build.ScanDir
: 指定されたディレクトリをスキャンし、そのディレクトリ内のGoパッケージに関するbuild.DirInfo
を返す関数。
これらの前提知識を理解することで、このコミットが go/build
パッケージのどの部分をどのように改善し、Goのビルドシステム全体にどのような影響を与えるかを深く把握できます。
技術的詳細
このコミットの技術的詳細は、go/build
パッケージのAPIがどのように再設計されたかに集約されます。主要な変更点は以下の通りです。
-
build.Tree
の廃止とbuild.Package
への統合:- 以前は
build.Tree
がGoのソースツリーの概念を表現していましたが、これが廃止されました。 - 代わりに、
build.DirInfo
が拡張され、build.Package
という新しい名前の構造体になりました。Package
は、特定のパッケージに関する詳細な情報(ディレクトリ、パッケージ名、インポートパス、ソースファイルのリスト、依存関係など)をすべてカプセル化します。これにより、パッケージに関する情報が一元化され、より扱いやすくなりました。 Package
構造体には、Dir
,Name
,Doc
,ImportPath
,Root
,SrcRoot
,PkgRoot
,BinDir
,Goroot
,PkgObj
といったフィールドが追加され、パッケージの物理的な場所、論理的なインポートパス、関連するルートディレクトリ、バイナリファイルのパスなど、多岐にわたる情報を提供します。
- 以前は
-
FindTree
とScanDir
のImport
およびImportDir
への統合:build.FindTree
とbuild.ScanDir
は廃止され、より汎用的なbuild.Import
関数とbuild.ImportDir
関数に置き換えられました。build.Import(path string, srcDir string, mode ImportMode)
:- この関数は、指定された
path
(インポートパスまたはローカルパス)とsrcDir
(ローカルパス解決のための基準ディレクトリ)に基づいて、Goパッケージの詳細な情報を*build.Package
として返します。 srcDir
引数の導入により、import "./foo"
のようなローカルインポートパスを正確に解決できるようになりました。これは、以前のAPIでは困難だった点です。$GOPATH
外のパッケージもスキャンできるようになりましたが、その場合Package
に含まれる情報が少なくなる可能性があると明記されています。mode
引数(ImportMode
型)は、Import
関数の動作を制御します。FindOnly
: パッケージのソースが格納されているディレクトリを特定するだけで、ファイルの内容は読み込みません。これは、以前のFindTree
の機能に相当します。AllowBinary
: 対応するソースがない場合でも、コンパイル済みのパッケージオブジェクト(.a
ファイルなど)で要求を満たすことを許可します。
- この関数は、指定された
build.ImportDir(dir string, mode ImportMode)
:- これは
build.Import
の糖衣構文(シンタックスシュガー)であり、特定のディレクトリ内のGoパッケージを処理するために使用されます。内部的にはbuild.Import(".", dir, mode)
を呼び出します。
- これは
-
build.Context
の拡張と柔軟性の向上:build.Context
構造体は、ビルド環境に関する情報(GOARCH
,GOOS
,GOROOT
,GOPATH
,CgoEnabled
,BuildTags
など)を保持します。- このコミットでは、
Context
にファイルシステム操作をカスタマイズするための関数フィールドが追加されました。JoinPath
,SplitPathList
,IsAbsPath
,IsDir
,HasSubdir
,ReadDir
,OpenFile
: これらは、ファイルパスの結合、パスリストの分割、絶対パスの判定、ディレクトリの判定、サブディレクトリの判定、ディレクトリの内容の読み込み、ファイルのオープンといった低レベルのファイルシステム操作を抽象化します。- これらのフィールドをnilに設定すると、デフォルトの
filepath
やos
パッケージの関数が使用されます。これにより、go/build
パッケージが実際のファイルシステムだけでなく、仮想的なファイルシステムやテスト環境など、さまざまな環境で動作できるようになり、柔軟性とテスト容易性が大幅に向上しました。
-
Path
型の削除 (Issue 2749の修正):- 以前存在した
build.Path
型が完全に削除されました。これは、GOROOT
やGOPATH
のエントリを管理するためのものでしたが、その設計が誤用を招く可能性があったためです。 - この削除により、Goのビルドシステムにおけるパスの扱いがより明確になり、混乱やバグの原因が取り除かれました。
- 以前存在した
これらの変更により、go/build
パッケージは、Goのビルドプロセスをより正確かつ効率的に制御するための、より堅牢で柔軟な基盤を提供するようになりました。特に、go
コマンドが複雑なパッケージ解決ロジックを実装する上で不可欠な機能が提供されています。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主に src/pkg/go/build/build.go
ファイルに集中しています。
-
build.go
の変更概要:build.Tree
構造体と関連する関数が削除されました。build.DirInfo
構造体がbuild.Package
にリネームされ、大幅に拡張されました。ScanDir
関数が削除され、Import
およびImportDir
関数が新しく追加されました。Context
構造体に、ファイルシステム操作をカスタマイズするための新しい関数フィールドが多数追加されました。isLocalPath
関数がIsLocalImport
にリネームされ、そのロジックが変更されました。defaultContext
関数がGOROOT
とGOPATH
の設定を含むように更新されました。
-
build.Package
構造体の定義 (旧DirInfo
):type Package struct { Dir string // directory containing package sources Name string // package name Doc string // documentation synopsis ImportPath string // import path of package ("" if unknown) Root string // root of Go tree where this package lives SrcRoot string // package source root directory ("" if unknown) PkgRoot string // package install root directory ("" if unknown) BinDir string // command install directory ("" if unknown) Goroot bool // package found in Go root PkgObj string // installed .a file // Source files GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) CgoFiles []string // .go source files that import "C" CFiles []string // .c source files HFiles []string // .h source files SFiles []string // .s source files // Cgo directives CgoPkgConfig []string // Cgo pkg-config directives CgoCFLAGS []string // Cgo CFLAGS directives CgoLDFLAGS []string // Cgo LDFLAGS directives // Dependency information Imports []string // imports from GoFiles, CgoFiles ImportPos map[string][]token.Position // line information for Imports // Test information TestGoFiles []string // _test.go files in package TestImports []string // imports from TestGoFiles TestImportPos map[string][]token.Position // line information for TestImports XTestGoFiles []string // _test.go files outside package XTestImports []string // imports from XTestGoFiles XTestImportPos map[string][]token.Position // line information for XTestImports }
-
Context
構造体の変更:type Context struct { GOARCH string // target architecture GOOS string // target operating system GOROOT string // Go root GOPATH string // Go path CgoEnabled bool // whether cgo can be used BuildTags []string // additional tags to recognize in +build lines UseAllFiles bool // use files regardless of +build lines, file names Gccgo bool // assume use of gccgo when computing object paths // By default, Import uses the operating system's file system calls // to read directories and files. To read from other sources, // callers can set the following functions. They all have default // behaviors that use the local file system, so clients need only set // the functions whose behaviors they wish to change. JoinPath func(elem ...string) string SplitPathList func(list string) []string IsAbsPath func(path string) bool IsDir func(path string) bool HasSubdir func(root, dir string) (rel string, ok bool) ReadDir func(dir string) (fi []os.FileInfo, err error) OpenFile func(path string) (r io.ReadCloser, err error) }
-
Import
関数の追加:func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error) { // ... (詳細なパッケージ検索と解析ロジック) ... }
この関数は、パッケージのインポートパス、ソースディレクトリ、およびインポートモードに基づいて、パッケージ情報を取得する中心的なロジックを含んでいます。ローカルインポートの解決、
GOROOT
とGOPATH
内の検索、ビルドタグの評価などがここで行われます。 -
ImportDir
関数の追加:func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { return ctxt.Import(".", dir, mode) }
-
path.go
ファイルの削除:src/pkg/go/build/path.go
ファイル全体が削除されました。このファイルには、Tree
型やFindTree
関数など、古いAPIに関連するコードが含まれていました。
これらの変更は、go/build
パッケージの内部構造と外部APIの両方に大きな影響を与え、Goのビルドシステムがより現代的な要件に対応できるようになりました。
コアとなるコードの解説
このコミットの核心は、src/pkg/go/build/build.go
に導入された新しい Import
関数と Package
構造体、そして Context
の拡張です。
build.Package
構造体
build.Package
は、以前の build.DirInfo
を置き換えるもので、Goパッケージに関するあらゆる情報を集約します。
Dir
: パッケージのソースファイルが置かれている物理的なディレクトリのパス。Name
: パッケージ名(例:"fmt"
,"main"
)。ImportPath
: パッケージの標準的なインポートパス(例:"fmt"
,"github.com/user/repo/pkg"
)。ローカルインポートパス(./foo
)の場合でも、可能であれば正規のインポートパスが設定されます。Root
,SrcRoot
,PkgRoot
,BinDir
: パッケージが見つかったGoツリーのルートディレクトリ、ソースルートディレクトリ、パッケージオブジェクト(.a
ファイル)のルートディレクトリ、バイナリのインストールディレクトリを示します。これにより、パッケージがGOROOT
とGOPATH
のどこに属しているかを明確に識別できます。GoFiles
,CgoFiles
,CFiles
,HFiles
,SFiles
: パッケージを構成する様々な種類のソースファイルのリスト。ビルドタグやファイル名規則(例:_test.go
)に基づいて適切に分類されます。Imports
,ImportPos
: パッケージがインポートしている他のパッケージのリストと、そのインポート文がソースコードのどこに記述されているかの位置情報。TestGoFiles
,TestImports
,XTestGoFiles
,XTestImports
: テストファイル(_test.go
)に関する情報。Goのテストは、パッケージ内テストと外部テスト(_test
サフィックスを持つパッケージ)に分かれるため、それぞれに対応するファイルとインポート情報が提供されます。
この Package
構造体は、Goのビルドツールがパッケージの依存関係を解決し、適切なファイルをコンパイルするために必要なすべてのコンテキストを提供します。
build.Context
の拡張
Context
構造体は、ビルド環境(OS、アーキテクチャ、GOROOT
、GOPATH
、ビルドタグなど)を定義します。このコミットで最も重要な追加は、ファイルシステム操作を抽象化する関数フィールドです。
JoinPath
,SplitPathList
,IsAbsPath
,IsDir
,HasSubdir
,ReadDir
,OpenFile
: これらの関数は、ファイルパスの操作やファイルシステムへのアクセス方法をカスタマイズするためのフックを提供します。- 例えば、
ReadDir
をカスタム関数に設定することで、実際のディスクではなくメモリ上の仮想ファイルシステムからディレクトリの内容を読み込むことができます。これは、テストや特殊なビルド環境(例: クロスコンパイル環境でのリモートファイルシステムアクセス)において非常に強力な機能です。 - これらのフィールドが
nil
の場合、filepath
やos
パッケージの標準的な関数が使用されます。
- 例えば、
この抽象化により、go/build
パッケージは、ファイルシステムの実装に依存せず、より柔軟でテスト可能なコードベースとなりました。
build.Import
関数
Import
関数は、go/build
パッケージの新しい主要なエントリポイントです。
func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error)
path
引数:- これは、インポートするパッケージのパスです。標準的なインポートパス(例:
"fmt"
)である場合もあれば、ローカルインポートパス(例:"./file"
)である場合もあります。
- これは、インポートするパッケージのパスです。標準的なインポートパス(例:
src
引数:path
がローカルインポートパスである場合、このsrc
引数はそのローカルパスを解決するための基準となるディレクトリを示します。例えば、src
が/home/user/project
でpath
が"./subpkg"
であれば、Import
は/home/user/project/subpkg
を探します。path
が標準的なインポートパスの場合、src
は空文字列(""
)で構いません。
mode
引数:ImportMode
型のビットフラグで、Import
関数の動作を制御します。FindOnly
: パッケージのディレクトリを見つけるだけで、その中のファイルを解析しません。これは、パッケージの存在確認やパス解決のみが必要な場合に効率的です。AllowBinary
: ソースファイルが見つからない場合でも、コンパイル済みのパッケージオブジェクト(.a
ファイル)があれば、それを有効なパッケージとして扱います。これは、バイナリ配布のみのパッケージを扱う場合に有用です。
Import
関数の内部では、以下の複雑なロジックが実行されます。
- パスの正規化と解決:
path
とsrc
を組み合わせて、パッケージの物理的なディレクトリを特定します。ローカルインポートパスの場合、src
を基準に絶対パスを計算します。 GOROOT
とGOPATH
の検索: 特定されたディレクトリがGOROOT
またはGOPATH
のいずれかのエントリに属しているかを判断し、Package
構造体のGoroot
およびRoot
フィールドを設定します。これにより、パッケージが標準ライブラリの一部なのか、ユーザーのプロジェクトの一部なのかを区別できます。- ファイルのスキャンと解析: パッケージディレクトリ内のファイルを読み込み、Goソースファイル、Cgoファイル、C/H/Sファイルなどを識別します。各Goファイルについては、
go/parser
を使用してAST(抽象構文木)を解析し、パッケージ名、インポート文、ビルドタグなどを抽出します。 - ビルドタグの評価: 各ファイルのビルドタグを
Context
のGOOS
,GOARCH
,BuildTags
と比較し、そのファイルが現在のビルド環境で有効かどうかを判断します。これにより、プラットフォーム固有のコードや条件付きコンパイルがサポートされます。 - Cgoディレクティブの処理: Cgoを使用するファイル(
import "C"
を含むファイル)から、#cgo
ディレクティブ(CFLAGS
,LDFLAGS
,pkg-config
など)を抽出し、Package
構造体に格納します。 - エラーハンドリング: パッケージが見つからない場合、Goソースファイルがない場合、複数のパッケージ名が混在している場合など、様々なエラーシナリオを適切に処理し、詳細なエラーメッセージを返します。
この Import
関数は、Goのビルドシステムにおけるパッケージ情報の取得と解析の単一責任原則を体現しており、その柔軟性と堅牢性によって、go
コマンドやその他のGoツールがより高度な機能を提供できるようになりました。
関連リンク
- Go CL 5713043: https://golang.org/cl/5713043
- Go Issue 2749:
go/build
package path handling (このコミットで修正された問題)
参考にした情報源リンク
- Go言語の公式ドキュメント (go/buildパッケージ): https://pkg.go.dev/go/build
- Go Modules (GOPATHの現代的な代替): https://go.dev/blog/using-go-modules (このコミットの時点ではGo Modulesは存在しませんが、GOPATHの理解を深める上で関連します)
- Go Build Constraints: https://pkg.go.dev/go/build#hdr-Build_Constraints
- Cgo: https://go.dev/blog/c-go-is-not-go