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

[インデックス 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は「多すぎもせず、少なすぎもせず」という問題を抱えていました。これは、特定のユースケースでは情報が不足し、別のユースケースでは不要な情報が多すぎるという状況を指します。

具体的には、以下の点が課題となっていました。

  1. go コマンドの要件との乖離: go コマンドは、パッケージの依存関係解決、ビルド、テスト、インストールなど、多岐にわたるタスクを実行します。そのためには、パッケージに関する詳細かつ柔軟な情報取得メカニズムが必要でした。既存のAPIでは、これらの要件を効率的かつ正確に満たすことが困難になっていました。
  2. ローカルインポートの解決の複雑さ: import "./foo" のようなローカルインポートパスは、相対パスであるため、その解決には現在の作業ディレクトリやソースファイルの場所といったコンテキストが必要でした。既存のAPIでは、このコンテキストを適切に扱うための仕組みが不十分でした。
  3. $GOPATH 外のパッケージスキャン: Goのパッケージは通常 $GOPATH 内に配置されますが、特定のシナリオ(例: 一時的なディレクトリでの作業、単一ファイルのスクリプト実行)では、$GOPATH 外のパッケージをスキャンする必要がありました。既存のAPIでは、このような柔軟なスキャンが考慮されていませんでした。
  4. 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 パッケージに関する基本的な知識が必要です。

  1. Go言語のパッケージとインポートパス:

    • Goのコードは「パッケージ」という単位で整理されます。各Goファイルは package <name> で始まる必要があります。
    • 他のパッケージのコードを利用するには import "<path>" を使用します。インポートパスは、通常、Goモジュールパスや標準ライブラリのパス(例: "fmt", "net/http")です。
    • ローカルインポートパス: import "./foo"import "../bar" のように、相対パスで指定されるインポートパスです。これらは、同じリポジトリ内の別のディレクトリにあるパッケージを参照する際に使用されます。
    • main パッケージ: 実行可能なプログラムのエントリポイントとなるパッケージは main と命名され、main 関数を含みます。
  2. GOROOTGOPATH:

    • GOROOT: Goのインストールディレクトリを指します。Goの標準ライブラリのソースコードは $GOROOT/src 以下に配置されます。
    • GOPATH: Goのワークスペースディレクトリを指します。ユーザーが開発するGoのプロジェクトや、go get でダウンロードされたサードパーティのパッケージは、通常 $GOPATH/src 以下に配置されます。$GOPATH は複数のパスを設定できます。
  3. 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 を使用するファイルは、ビルドプロセスにおいて特別な処理が必要です。
  4. 以前の 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がどのように再設計されたかに集約されます。主要な変更点は以下の通りです。

  1. build.Tree の廃止と build.Package への統合:

    • 以前は build.Tree がGoのソースツリーの概念を表現していましたが、これが廃止されました。
    • 代わりに、build.DirInfo が拡張され、build.Package という新しい名前の構造体になりました。Package は、特定のパッケージに関する詳細な情報(ディレクトリ、パッケージ名、インポートパス、ソースファイルのリスト、依存関係など)をすべてカプセル化します。これにより、パッケージに関する情報が一元化され、より扱いやすくなりました。
    • Package 構造体には、Dir, Name, Doc, ImportPath, Root, SrcRoot, PkgRoot, BinDir, Goroot, PkgObj といったフィールドが追加され、パッケージの物理的な場所、論理的なインポートパス、関連するルートディレクトリ、バイナリファイルのパスなど、多岐にわたる情報を提供します。
  2. FindTreeScanDirImport および ImportDir への統合:

    • build.FindTreebuild.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) を呼び出します。
  3. build.Context の拡張と柔軟性の向上:

    • build.Context 構造体は、ビルド環境に関する情報(GOARCH, GOOS, GOROOT, GOPATH, CgoEnabled, BuildTags など)を保持します。
    • このコミットでは、Context にファイルシステム操作をカスタマイズするための関数フィールドが追加されました。
      • JoinPath, SplitPathList, IsAbsPath, IsDir, HasSubdir, ReadDir, OpenFile: これらは、ファイルパスの結合、パスリストの分割、絶対パスの判定、ディレクトリの判定、サブディレクトリの判定、ディレクトリの内容の読み込み、ファイルのオープンといった低レベルのファイルシステム操作を抽象化します。
      • これらのフィールドをnilに設定すると、デフォルトの filepathos パッケージの関数が使用されます。これにより、go/build パッケージが実際のファイルシステムだけでなく、仮想的なファイルシステムやテスト環境など、さまざまな環境で動作できるようになり、柔軟性とテスト容易性が大幅に向上しました。
  4. Path 型の削除 (Issue 2749の修正):

    • 以前存在した build.Path 型が完全に削除されました。これは、GOROOTGOPATH のエントリを管理するためのものでしたが、その設計が誤用を招く可能性があったためです。
    • この削除により、Goのビルドシステムにおけるパスの扱いがより明確になり、混乱やバグの原因が取り除かれました。

これらの変更により、go/build パッケージは、Goのビルドプロセスをより正確かつ効率的に制御するための、より堅牢で柔軟な基盤を提供するようになりました。特に、go コマンドが複雑なパッケージ解決ロジックを実装する上で不可欠な機能が提供されています。

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

このコミットのコアとなるコードの変更は、主に src/pkg/go/build/build.go ファイルに集中しています。

  1. build.go の変更概要:

    • build.Tree 構造体と関連する関数が削除されました。
    • build.DirInfo 構造体が build.Package にリネームされ、大幅に拡張されました。
    • ScanDir 関数が削除され、Import および ImportDir 関数が新しく追加されました。
    • Context 構造体に、ファイルシステム操作をカスタマイズするための新しい関数フィールドが多数追加されました。
    • isLocalPath 関数が IsLocalImport にリネームされ、そのロジックが変更されました。
    • defaultContext 関数が GOROOTGOPATH の設定を含むように更新されました。
  2. 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
    }
    
  3. 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)
    }
    
  4. Import 関数の追加:

    func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error) {
    	// ... (詳細なパッケージ検索と解析ロジック) ...
    }
    

    この関数は、パッケージのインポートパス、ソースディレクトリ、およびインポートモードに基づいて、パッケージ情報を取得する中心的なロジックを含んでいます。ローカルインポートの解決、GOROOTGOPATH 内の検索、ビルドタグの評価などがここで行われます。

  5. ImportDir 関数の追加:

    func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) {
    	return ctxt.Import(".", dir, mode)
    }
    
  6. 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 ファイル)のルートディレクトリ、バイナリのインストールディレクトリを示します。これにより、パッケージが GOROOTGOPATH のどこに属しているかを明確に識別できます。
  • GoFiles, CgoFiles, CFiles, HFiles, SFiles: パッケージを構成する様々な種類のソースファイルのリスト。ビルドタグやファイル名規則(例: _test.go)に基づいて適切に分類されます。
  • Imports, ImportPos: パッケージがインポートしている他のパッケージのリストと、そのインポート文がソースコードのどこに記述されているかの位置情報。
  • TestGoFiles, TestImports, XTestGoFiles, XTestImports: テストファイル(_test.go)に関する情報。Goのテストは、パッケージ内テストと外部テスト(_test サフィックスを持つパッケージ)に分かれるため、それぞれに対応するファイルとインポート情報が提供されます。

この Package 構造体は、Goのビルドツールがパッケージの依存関係を解決し、適切なファイルをコンパイルするために必要なすべてのコンテキストを提供します。

build.Context の拡張

Context 構造体は、ビルド環境(OS、アーキテクチャ、GOROOTGOPATH、ビルドタグなど)を定義します。このコミットで最も重要な追加は、ファイルシステム操作を抽象化する関数フィールドです。

  • JoinPath, SplitPathList, IsAbsPath, IsDir, HasSubdir, ReadDir, OpenFile: これらの関数は、ファイルパスの操作やファイルシステムへのアクセス方法をカスタマイズするためのフックを提供します。
    • 例えば、ReadDir をカスタム関数に設定することで、実際のディスクではなくメモリ上の仮想ファイルシステムからディレクトリの内容を読み込むことができます。これは、テストや特殊なビルド環境(例: クロスコンパイル環境でのリモートファイルシステムアクセス)において非常に強力な機能です。
    • これらのフィールドが nil の場合、filepathos パッケージの標準的な関数が使用されます。

この抽象化により、go/build パッケージは、ファイルシステムの実装に依存せず、より柔軟でテスト可能なコードベースとなりました。

build.Import 関数

Import 関数は、go/build パッケージの新しい主要なエントリポイントです。

func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error)
  1. path 引数:
    • これは、インポートするパッケージのパスです。標準的なインポートパス(例: "fmt")である場合もあれば、ローカルインポートパス(例: "./file")である場合もあります。
  2. src 引数:
    • path がローカルインポートパスである場合、この src 引数はそのローカルパスを解決するための基準となるディレクトリを示します。例えば、src/home/user/projectpath"./subpkg" であれば、Import/home/user/project/subpkg を探します。
    • path が標準的なインポートパスの場合、src は空文字列("")で構いません。
  3. mode 引数:
    • ImportMode 型のビットフラグで、Import 関数の動作を制御します。
      • FindOnly: パッケージのディレクトリを見つけるだけで、その中のファイルを解析しません。これは、パッケージの存在確認やパス解決のみが必要な場合に効率的です。
      • AllowBinary: ソースファイルが見つからない場合でも、コンパイル済みのパッケージオブジェクト(.a ファイル)があれば、それを有効なパッケージとして扱います。これは、バイナリ配布のみのパッケージを扱う場合に有用です。

Import 関数の内部では、以下の複雑なロジックが実行されます。

  • パスの正規化と解決: pathsrc を組み合わせて、パッケージの物理的なディレクトリを特定します。ローカルインポートパスの場合、src を基準に絶対パスを計算します。
  • GOROOTGOPATH の検索: 特定されたディレクトリが GOROOT または GOPATH のいずれかのエントリに属しているかを判断し、Package 構造体の Goroot および Root フィールドを設定します。これにより、パッケージが標準ライブラリの一部なのか、ユーザーのプロジェクトの一部なのかを区別できます。
  • ファイルのスキャンと解析: パッケージディレクトリ内のファイルを読み込み、Goソースファイル、Cgoファイル、C/H/Sファイルなどを識別します。各Goファイルについては、go/parser を使用してAST(抽象構文木)を解析し、パッケージ名、インポート文、ビルドタグなどを抽出します。
  • ビルドタグの評価: 各ファイルのビルドタグを ContextGOOS, GOARCH, BuildTags と比較し、そのファイルが現在のビルド環境で有効かどうかを判断します。これにより、プラットフォーム固有のコードや条件付きコンパイルがサポートされます。
  • Cgoディレクティブの処理: Cgoを使用するファイル(import "C" を含むファイル)から、#cgo ディレクティブ(CFLAGS, LDFLAGS, pkg-config など)を抽出し、Package 構造体に格納します。
  • エラーハンドリング: パッケージが見つからない場合、Goソースファイルがない場合、複数のパッケージ名が混在している場合など、様々なエラーシナリオを適切に処理し、詳細なエラーメッセージを返します。

この Import 関数は、Goのビルドシステムにおけるパッケージ情報の取得と解析の単一責任原則を体現しており、その柔軟性と堅牢性によって、go コマンドやその他のGoツールがより高度な機能を提供できるようになりました。

関連リンク

参考にした情報源リンク