[インデックス 11408] ファイルの概要
このコミットは、Go言語の公式リポジトリに cmd/goapi
という新しいツールを導入するものです。このツールは、Goパッケージのエクスポートされた(公開された)APIを時系列で追跡し、将来のリリースにおけるAPIの互換性を保証することを目的としています。
コミット
commit 5c04272ff33d90f2417c1db40be8675dd74fdad9
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Jan 25 17:47:57 2012 -0800
cmd/goapi: new tool for tracking exported API over time
The idea is that we add files to the api/ directory which
are sets of promises for the future. Each line in a file
is a stand-alone feature description.
When we do a release, we make sure we haven't broken or changed
any lines from the past (only added them).
We never change old files, only adding new ones. (go-1.1.txt,
etc)
R=dsymonds, adg, r, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/5570051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5c04272ff33d90f2417c1db40be8675dd74fdad9
元コミット内容
cmd/goapi
: エクスポートされたAPIを時系列で追跡するための新しいツール。
このツールの目的は、api/
ディレクトリに将来のAPIの約束(promises)を記述したファイルを追加することです。各ファイル内の各行は、独立した機能記述を表します。
リリースを行う際には、過去のAPI記述が壊れたり変更されたりしていないことを確認します(追加のみが許可されます)。
古いファイルは決して変更せず、常に新しいファイル(例: go-1.1.txt
など)を追加していきます。
変更の背景
Go言語は、その初期段階から後方互換性を非常に重視してきました。特に、標準ライブラリのAPIの安定性は、Goエコシステム全体の健全性を保つ上で極めて重要です。このコミットが行われた2012年1月は、Go 1のリリースが近づいていた時期であり、APIの安定化と将来にわたる互換性の保証が喫緊の課題でした。
cmd/goapi
ツールの導入は、以下の課題を解決するために考案されました。
- APIの意図しない変更の防止: 開発者がコードを変更する際に、意図せず公開APIを変更したり削除したりするリスクを低減する。
- リリースプロセスの支援: リリース時に、APIの互換性が維持されていることを自動的に検証するメカニズムを提供する。
- API変更の明確な記録: どのAPIがどのバージョンで追加されたかを明確に記録し、開発者やユーザーがAPIの進化を追跡できるようにする。
- 後方互換性の保証: Go 1のリリース以降、標準ライブラリのAPIは「Go 1互換性保証」として知られる厳格な後方互換性ポリシーの対象となります。このツールは、そのポリシーを技術的に強制し、違反を検出するための基盤となります。
このツールは、Go言語が長期的な安定性と信頼性を提供するための重要なインフラの一部として位置づけられています。
前提知識の解説
このコミットの理解には、以下のGo言語およびソフトウェア開発の概念に関する知識が役立ちます。
- Go言語のパッケージとエクスポートされた識別子: Go言語では、パッケージ内の識別子(変数、定数、関数、型、メソッドなど)が、その名前の最初の文字が大文字である場合に「エクスポートされる」(公開される)と見なされます。エクスポートされた識別子のみが、そのパッケージをインポートする他のパッケージからアクセス可能です。
cmd/goapi
は、このエクスポートされたAPIを対象とします。 - GoのAST (Abstract Syntax Tree): Goコンパイラは、ソースコードを解析して抽象構文木(AST)を生成します。ASTは、プログラムの構造を木構造で表現したものです。
cmd/goapi
は、go/ast
、go/parser
、go/token
といった標準ライブラリのパッケージを使用して、GoソースコードのASTを解析し、エクスポートされたAPIの情報を抽出します。 go/doc
パッケージ: Goの標準ライブラリには、Goソースコードからドキュメントを生成するためのgo/doc
パッケージがあります。このパッケージは、ASTからパッケージ、型、関数、メソッドなどの情報を抽出し、構造化された形式で提供します。cmd/goapi
もこのパッケージを利用してAPI情報を取得しています。- 後方互換性 (Backward Compatibility): ソフトウェアの新しいバージョンが、古いバージョン向けに作成されたデータ、コード、またはシステムと引き続き連携できる特性を指します。Go言語の標準ライブラリでは、Go 1以降、厳格な後方互換性ポリシーが適用されており、既存のコードを壊すようなAPI変更は原則として行われません。
- API (Application Programming Interface): ソフトウェアコンポーネントが互いに通信するために使用する一連の定義とプロトコル。Go言語の文脈では、主に公開された関数、型、メソッド、変数などを指します。
go list
コマンド: Goのビルドシステムの一部であり、Goパッケージに関する情報を表示するために使用されます。このコミットでは、go list std
を使用して標準ライブラリのパッケージリストを取得しています。go/build
パッケージ: Goのビルドプロセスをプログラム的に操作するためのパッケージです。ソースファイルの検索やパッケージの解決などに使用されます。
技術的詳細
cmd/goapi
ツールは、Go言語のソースコードを静的に解析し、各パッケージからエクスポートされたAPI要素(関数、メソッド、型、定数、変数)を特定します。これらのAPI要素は、特定のフォーマットで「機能記述(feature description)」として表現され、api/
ディレクトリ内のテキストファイルに保存されます。
ツールの主要なロジックは以下の通りです。
- パッケージの走査:
main.go
のmain
関数は、引数で指定されたパッケージ、またはgo list std
で取得した標準ライブラリの全パッケージを走査します。cmd/
、exp/
、old/
で始まるパッケージはスキップされます。 - ASTの解析:
Walker
構造体がGoソースファイルのASTを走査します。go/parser.ParseFile
を使用してソースファイルを解析し、go/ast
パッケージの機能を利用してASTノードを巡回します。 - エクスポートされた識別子の特定:
ast.IsExported
関数を使用して、識別子がエクスポートされているかどうかを判断します。エクスポートされた識別子のみがAPIとして考慮されます。 - API要素の抽出とフォーマット:
- 定数 (
const
):walkConst
関数が処理します。定数の名前とその型(例:ideal-int
,string
など)を抽出します。型推論が難しいケース(iota
や特定のパッケージの定数)にはハードコードされたロジックも含まれます。 - 変数 (
var
):walkVar
関数が処理します。変数の名前とその型を抽出します。 - 型 (
type
):walkTypeSpec
関数が処理します。- 構造体 (
struct
) の場合:walkStructType
が構造体名と、エクスポートされたフィールドの名前と型を抽出します。埋め込みフィールドも考慮されます。 - インターフェース (
interface
) の場合:walkInterfaceType
がインターフェース名と、エクスポートされたメソッドのシグネチャを抽出します。 - その他の型の場合: 型名とその基底型を抽出します。
- 構造体 (
- 関数 (
func
) およびメソッド:go/doc.New
を使用してパッケージのドキュメントモデルを生成した後、dpkg.Funcs
とt.Methods
から関数とメソッドの宣言を取得し、walkFuncDecl
で処理します。関数名またはメソッド名、レシーバ(メソッドの場合)、および関数シグネチャ(引数と戻り値の型)を抽出します。引数や戻り値の変数名はAPIの一部ではないため、namelessType
関数で除去されます。
- 定数 (
- 機能記述の生成: 抽出されたAPI要素は、
pkg <package_name>, <element_type> <element_name> <signature/type>
のような形式の文字列として表現され、Walker.features
マップに保存されます。例えば、pkg p1, func Bar(int8, int16, int64)
のようになります。 - APIファイルの比較と出力:
-c <filename>
フラグが指定された場合、現在のAPIと指定されたファイル(過去のAPIスナップショット)を比較します。差分(追加されたAPIと削除されたAPI)を出力します。これにより、APIの互換性違反を検出できます。- フラグが指定されない場合、現在のパッケージから抽出された全てのAPI機能記述を標準出力に出力します。
このツールは、GoのASTとドキュメンテーション生成のメカニズムを深く利用しており、Go言語のセマンティクスを理解した上でAPIを正確に抽出するよう設計されています。特に、定数や変数の型推論、埋め込みフィールドの処理、関数シグネチャの正規化など、Go言語の特性に合わせた複雑なロジックが含まれています。
コアとなるコードの変更箇所
このコミットでは、主に以下の4つの新しいファイルが追加されています。
-
src/cmd/goapi/goapi.go
:main
関数: コマンドライン引数の解析、パッケージの走査、Walker
の初期化と実行、APIの比較または出力ロジックが含まれます。Walker
構造体: AST走査の状態(ファイルセット、スコープ、抽出された機能、定数の型情報など)を保持します。NewWalker
関数:Walker
のコンストラクタ。Features
メソッド: 抽出された全てのAPI機能記述をソートして返します。WalkPackage
,walkFile
,walkConst
,walkVar
,walkTypeSpec
,walkStructType
,walkInterfaceType
,walkFuncDecl
などのwalk
メソッド群: ASTを再帰的に走査し、エクスポートされたAPI要素を特定して抽出します。constValueType
,varValueType
: 定数や変数の値から型を推論するロジック。resolveName
: 識別子を解決し、対応するASTノードを検索するヘルパー関数。nodeString
,nodeDebug
: ASTノードを文字列に変換するヘルパー関数。funcSigString
,namelessType
,namelessFieldList
,namelessField
: 関数シグネチャを正規化し、変数名を除去するためのヘルパー関数。emitFeature
: 抽出されたAPI機能記述をWalker.features
マップに追加する関数。pushScope
: スコープ管理のためのヘルパー関数。hardCodedConstantType
: 型推論が難しい特定の定数に対するハードコードされた型情報。
-
src/cmd/goapi/goapi_test.go
:TestGolden
関数:goapi
ツールのテストスイート。testdata
ディレクトリ内のパッケージに対してgoapi
を実行し、生成されたAPI記述がgolden.txt
ファイルの内容と一致するかを検証します。-updategolden
フラグでgolden.txt
を更新する機能も提供します。
-
src/cmd/goapi/testdata/p1/golden.txt
:p1
パッケージの期待されるAPI記述を含むゴールデンファイル。goapi_test.go
によって参照されます。
-
src/cmd/goapi/testdata/p1/p1.go
:goapi
ツールのテストに使用されるサンプルGoパッケージ。様々なGoの言語機能(定数、変数、構造体、インターフェース、関数、メソッド、埋め込みフィールドなど)を含むように設計されており、goapi
がこれらの要素を正しく抽出できるかを検証します。
これらのファイルは、cmd/goapi
ツールの完全な実装とテストケースを構成しています。
コアとなるコードの解説
goapi.go
の主要なロジックは、Walker
構造体とそのwalk
メソッド群に集約されています。
Walker
構造体
type Walker struct {
fset *token.FileSet
scope []string
features map[string]bool // set
lastConstType string
curPackageName string
curPackage *ast.Package
prevConstType map[string]string // identifer -> "ideal-int"
}
fset
:go/token.FileSet
は、ソースファイル内の位置情報を管理します。ASTノードから正確な位置情報を取得するために必要です。scope
: 現在走査しているASTのスコープ(例:pkg compress/gzip
,type MyStruct struct
など)を文字列のスタックとして保持します。これにより、生成される機能記述にコンテキストを追加できます。features
: 抽出された全てのAPI機能記述を保持するマップ。重複を避けるためにセットとして使用されます。lastConstType
,prevConstType
: Goの定数宣言におけるiota
や型推論の挙動を正確に追跡するために、直前の定数の型や以前に解決された定数の型を記憶します。curPackageName
,curPackage
: 現在処理中のパッケージの名前とAST表現。
walkFuncDecl
関数 (メソッドの例)
func (w *Walker) walkFuncDecl(f *ast.FuncDecl) {
if !ast.IsExported(f.Name.Name) {
return
}
if f.Recv != nil {
// Method.
recvType := w.nodeString(f.Recv.List[0].Type)
keep := ast.IsExported(recvType) ||
(strings.HasPrefix(recvType, "*") &&
ast.IsExported(recvType[1:]))
if !keep {
return
}
w.emitFeature(fmt.Sprintf("method (%s) %s%s", recvType, f.Name.Name, w.funcSigString(f.Type)))
return
}
// Else, a function
w.emitFeature(fmt.Sprintf("func %s%s", f.Name.Name, w.funcSigString(f.Type)))
}
この関数は、関数またはメソッドの宣言を処理します。
ast.IsExported(f.Name.Name)
で、関数/メソッド名がエクスポートされているかを確認します。エクスポートされていない場合はスキップします。f.Recv != nil
で、それがメソッドであるか(レシーバを持つか)を判断します。- メソッドの場合、レシーバの型(例:
(MyType)
や(*MyType)
) を抽出し、その型自体がエクスポートされているかを確認します。エクスポートされていない型に紐づくメソッドはAPIとして扱われません。 fmt.Sprintf
とw.funcSigString(f.Type)
を使って、正規化された関数/メソッドシグネチャ(引数名を含まない型のみのシグネチャ)を生成し、emitFeature
で記録します。
funcSigString
関数
func (w *Walker) funcSigString(ft *ast.FuncType) string {
var b bytes.Buffer
b.WriteByte('(')
if ft.Params != nil {
for i, f := range ft.Params.List {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(w.nodeString(w.namelessType(f.Type)))
}
}
b.WriteByte(')')
if ft.Results != nil {
if nr := len(ft.Results.List); nr > 0 {
b.WriteByte(' ')
if nr > 1 {
b.WriteByte('(')
}
for i, f := range ft.Results.List {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(w.nodeString(w.namelessType(f.Type)))
}
if nr > 1 {
b.WriteByte(')')
}
}
}
return b.String()
}
この関数は、*ast.FuncType
(関数シグネチャのAST表現)を受け取り、引数名や戻り値の変数名を含まない、型のみの正規化された文字列シグネチャを生成します。これは、APIの互換性をチェックする際に、変数名の変更がAPIの変更と見なされないようにするために重要です。w.namelessType(f.Type)
が、この「変数名除去」の役割を担っています。
これらのコアな変更箇所は、Go言語のASTを深く理解し、APIの定義と互換性に関するGoの設計思想を反映したものです。
関連リンク
- Go 1 Compatibility Guarantee: Go言語の公式ドキュメントにおける後方互換性保証に関する説明。
cmd/goapi
はこの保証を技術的に支えるツールの一つです。 - Go AST Package Documentation:
go/ast
パッケージの公式ドキュメント。 - Go Doc Package Documentation:
go/doc
パッケージの公式ドキュメント。
参考にした情報源リンク
- golang/go GitHub Repository: コミットが属するGo言語の公式リポジトリ。
- Gerrit Change-Id for this commit: コミットメッセージに記載されているGerritの変更リンク。Goプロジェクトでは、GitHubにプッシュされる前にGerritでコードレビューが行われます。
- Go 1 Release Notes (relevant sections on API stability): Go 1のリリースノートには、APIの互換性保証に関する重要な情報が含まれています。
- Go Blog - Go 1 and the Future of Go Programs: Go 1の互換性保証について解説している公式ブログ記事。
- Go source code for
cmd/goapi
(current version): 現在のcmd/goapi
のソースコードは、このコミット以降も進化している可能性があります。 - Go source code for
go/ast
andgo/doc
:goapi
が依存している標準ライブラリのソースコード。