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

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

このコミットは、Go言語のAPIチェッカーツールである cmd/api における、コンテキストの再収束(re-converging contexts)のハンドリングに関する修正と機能改善を目的としています。具体的には、異なるビルドコンテキスト(例: darwin-386darwin-amd64)で定義されたAPI要素が、より一般的なコンテキスト(例: pkg syscall)で再定義された場合に、APIの互換性チェックが正しく行われるように変更されています。

コミット

commit 71d9e956a00e95f734f633056882475832d534f4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Oct 30 13:12:59 2012 +0100

    cmd/api: handle contexts re-converging
    
    Fixes #4303
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/6816058

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/71d9e956a00e95f734f633056882475832d534f4

元コミット内容

このコミットの元の内容は以下の通りです。

    cmd/api: handle contexts re-converging
    
    Fixes #4303
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/6816058

これは、cmd/api ツールが「コンテキストの再収束」を処理するように変更されたことを示しています。Fixes #4303 は、Goプロジェクトのイシュートラッカーにおける問題 #4303 を修正するものであることを意味します。ただし、現在のGoイシュートラッカーではこの番号のイシューは公開されていません。これは古いイシュー番号であるか、内部的な参照である可能性があります。

変更の背景

Go言語の cmd/api ツールは、Goの標準ライブラリのAPI互換性をチェックするために使用されます。このツールは、新しいGoのバージョンがリリースされる際に、既存のコードが壊れないように、APIの変更を監視します。

以前の cmd/api ツールでは、特定のビルドコンテキスト(例: darwin-386darwin-amd64 のようなOSとアーキテクチャの組み合わせ)に特化したAPI要素が、より一般的なコンテキスト(例: pkg syscall のようにコンテキスト指定がないもの)で再定義された場合に、互換性チェックが正しく機能しない問題がありました。

例えば、pkg syscall (darwin-386), type RawSockaddrInet6 structpkg syscall (darwin-amd64), type RawSockaddrInet6 struct のように、異なるコンテキストで同じ構造体が定義されている場合を考えます。もし将来的に、これらのコンテキスト固有の定義が削除され、代わりに pkg syscall, type RawSockaddrInet6 struct のようにコンテキストに依存しない単一の定義に「再収束」した場合、cmd/api はこれを互換性のない変更として誤って報告してしまう可能性がありました。

このコミットは、このような「コンテキストの再収束」のシナリオを正しく認識し、APIの互換性が維持されている場合にはエラーを報告しないように cmd/api のロジックを修正することを目的としています。

前提知識の解説

  • Go言語のAPI互換性: Go言語は、後方互換性を非常に重視しています。新しいバージョンがリリースされても、既存のGoプログラムが問題なく動作し続けることが期待されています。cmd/api ツールは、この互換性を保証するための重要なツールの一つです。
  • cmd/api ツール: Goの標準ライブラリのAPI定義を抽出し、以前のバージョンと比較することで、APIの変更(追加、削除、変更)を検出するコマンドラインツールです。これにより、互換性のない変更が誤って導入されるのを防ぎます。
  • ビルドコンテキスト: Goのビルドシステムでは、OSやアーキテクチャなどの環境に応じて、異なるコードパスやAPI定義が選択されることがあります。例えば、syscall パッケージの定義は、darwin-386 (macOS 32-bit) と darwin-amd64 (macOS 64-bit) で異なる場合があります。API定義には、このようなコンテキスト情報が付加されることがあります。
  • API要素の表現: cmd/api ツールが扱うAPI要素は、通常、pkg <パッケージ名>, <要素の種類> <要素名> のような文字列形式で表現されます。コンテキスト情報が含まれる場合、pkg <パッケージ名> (<コンテキスト>), <要素の種類> <要素名> のようになります。
    • 例: pkg syscall (darwin-386), type RawSockaddrInet6 struct
    • 例: pkg syscall, type RawSockaddrInet6 struct (コンテキストなし)
  • regexp パッケージ: Go言語の標準ライブラリで提供される正規表現エンジンです。文字列のパターンマッチングや置換に使用されます。
  • map[string]bool (Set): Go言語でセット(集合)を表現する一般的なイディオムです。キーの存在を true または false でチェックすることで、要素がセットに含まれているかを効率的に判断できます。

技術的詳細

このコミットの主要な変更点は、src/cmd/api/goapi.gocompareAPI 関数におけるAPI要素の比較ロジックの改善です。

  1. set 関数の追加: func set(items []string) map[string]bool というヘルパー関数が追加されました。これは、文字列スライスを受け取り、その要素をキーとする map[string]bool を作成します。これにより、optionalSetexceptionSet の初期化が簡潔になり、コードの可読性が向上しました。

  2. spaceParensRx 正規表現の追加: var spaceParensRx = regexp.MustCompile( (\S+?))\n という正規表現が追加されました。この正規表現は、API要素の文字列から (コンテキスト) の部分(例: (darwin-386))を抽出するために使用されます。\s は空白文字、\(\) はリテラルの括弧、\S+? は非空白文字が1回以上出現する最短のマッチを意味します。

  3. featureWithoutContext 関数の追加: func featureWithoutContext(f string) string という関数が追加されました。この関数は、与えられたAPI要素の文字列 f に括弧 ( が含まれている場合、spaceParensRx を使用して (コンテキスト) の部分を削除した文字列を返します。これにより、コンテキストに依存しないAPI要素の「ベース名」を取得できるようになります。

  4. compareAPI 関数のロジック変更: compareAPI 関数内の for len(required) > 0 || len(features) > 0 ループ内の switch ステートメントが変更されました。

    • 以前は required[0] < features[0] の条件で、required に存在するが features に存在しないAPI要素(つまり削除されたAPI)を検出していました。
    • 変更後、このケースに else if featureSet[featureWithoutContext(feature)] という新しい条件が追加されました。これは、required にあるAPI要素が、features に存在するAPI要素の「コンテキストなしバージョン」と一致する場合、それを互換性のある変更と見なすことを意味します。
      • 具体的には、requiredpkg syscall (darwin-386), type RawSockaddrInet6 struct があり、featurespkg syscall, type RawSockaddrInet6 struct がある場合、featureWithoutContext を適用することで両者が一致し、互換性のある変更として扱われます。これにより、コンテキストの再収束が正しく処理されます。
    • optionalSetexceptionSet の初期化も、新しく追加された set 関数を使用するように変更され、より簡潔になりました。

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

src/cmd/api/goapi.go

--- a/src/cmd/api/goapi.go
+++ b/src/cmd/api/goapi.go
@@ -29,6 +29,7 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"sort"
 	"strconv"
@@ -192,17 +193,29 @@ func main() {
 	fail = !compareAPI(bw, features, required, optional, exception)
 }
 
+func set(items []string) map[string]bool {
+	s := make(map[string]bool)
+	for _, v := range items {
+		s[v] = true
+	}
+	return s
+}
+
+var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)\n
+
+func featureWithoutContext(f string) string {
+	if !strings.Contains(f, "(") {
+		return f
+	}
+	return spaceParensRx.ReplaceAllString(f, "")
+}
+
 func compareAPI(w io.Writer, features, required, optional, exception []string) (ok bool) {
 	ok = true
 
-	var optionalSet = make(map[string]bool)  // feature => true
-	var exceptionSet = make(map[string]bool) // exception => true
-	for _, f := range optional {
-		optionalSet[f] = true
-	}
-	for _, f := range exception {
-		exceptionSet[f] = true
-	}
+	optionalSet := set(optional)
+	exceptionSet := set(exception)
+	featureSet := set(features)
 
 	sort.Strings(features)
 	sort.Strings(required)
@@ -215,15 +228,17 @@ func compareAPI(w io.Writer, features, required, optional, exception []string) (
 
 	for len(required) > 0 || len(features) > 0 {
 		switch {
-\t\tcase len(features) == 0 || required[0] < features[0]:
+\t\tcase len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
 			feature := take(&required)
 			if exceptionSet[feature] {
 				fmt.Fprintf(w, "~%s\\n\", feature)
+\t\t\t} else if featureSet[featureWithoutContext(feature)] {
+\t\t\t\t// okay.
 			} else {
 				fmt.Fprintf(w, "-%s\\n\", feature)
 				ok = false // broke compatibility
 			}
-\t\tcase len(required) == 0 || required[0] > features[0]:
+\t\tcase len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
 			newFeature := take(&features)
 			if optionalSet[newFeature] {
 				// Known added feature to the upcoming release.

src/cmd/api/goapi_test.go

--- a/src/cmd/api/goapi_test.go
+++ b/src/cmd/api/goapi_test.go
@@ -84,10 +84,10 @@ func TestCompareAPI(t *testing.T) {
 	}{
 		{
 			name:     "feature added",
-\t\t\tfeatures: []string{"C", "A", "B"},
-\t\t\trequired: []string{"A", "C"},
+\t\t\tfeatures: []string{"A", "B", "C", "D", "E", "F"},
+\t\t\trequired: []string{"B", "D"},
 			ok:       true,
-\t\t\tout:      "+B\n",
+\t\t\tout:      "+A\n+C\n+E\n+F\n",
 		},
 		{
 			name:     "feature removed",
@@ -112,6 +112,21 @@ func TestCompareAPI(t *testing.T) {
 			ok:        true,
 			out:       "~B\n",
 		},
+\t\t{\n
+\t\t\t// http://golang.org/issue/4303\n
+\t\t\tname: "contexts reconverging",
+\t\t\trequired: []string{\n
+\t\t\t\t"A",\n
+\t\t\t\t"pkg syscall (darwin-386), type RawSockaddrInet6 struct",\n
+\t\t\t\t"pkg syscall (darwin-amd64), type RawSockaddrInet6 struct",\n
+\t\t\t},\n
+\t\t\tfeatures: []string{\n
+\t\t\t\t"A",\n
+\t\t\t\t"pkg syscall, type RawSockaddrInet6 struct",\n
+\t\t\t},\n
+\t\t\tok:  true,\n
+\t\t\tout: "+pkg syscall, type RawSockaddrInet6 struct\n",\n
+\t\t},\n
 	}\n 	for _, tt := range tests {
 		buf := new(bytes.Buffer)

コアとなるコードの解説

src/cmd/api/goapi.go の変更点

  • regexp パッケージのインポート: 正規表現を使用するために regexp パッケージが追加されました。
  • set 関数の導入:
    func set(items []string) map[string]bool {
        s := make(map[string]bool)
        for _, v := range items {
            s[v] = true
        }
        return s
    }
    
    この関数は、文字列のスライスを map[string]bool に変換するユーティリティです。これにより、要素の存在チェックを効率的に行えるようになります。optionalSetexceptionSet の初期化がこの関数を使うように変更され、コードがより簡潔になりました。また、featureSet という新しいセットが追加され、features スライスから作成されます。これは、現在のAPIセットに存在するすべてのAPI要素を効率的に検索するために使用されます。
  • spaceParensRxfeatureWithoutContext の導入:
    var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)\n
    
    func featureWithoutContext(f string) string {
        if !strings.Contains(f, "(") {
            return f
        }
        return spaceParensRx.ReplaceAllString(f, "")
    }
    
    spaceParensRx は、 (コンテキスト) のパターンにマッチする正規表現です。featureWithoutContext 関数は、API要素の文字列からこのコンテキスト部分を削除し、コンテキストに依存しないAPI要素の「ベース名」を抽出します。これは、異なるコンテキストで定義されたAPIが、より一般的なコンテキストで再定義された場合に、それらを同じAPIとして認識するために不可欠です。
  • compareAPI 関数のロジック修正: compareAPI 関数は、required (以前のAPIセット) と features (現在のAPIセット) を比較し、互換性のない変更を検出します。 変更された switch ステートメントの最初の case は、required に存在するが features に存在しないAPI要素(つまり削除されたAPI)を処理します。
    		case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
    			feature := take(&required)
    			if exceptionSet[feature] {
    				fmt.Fprintf(w, "~%s\n", feature)
    			} else if featureSet[featureWithoutContext(feature)] { // <-- 追加されたロジック
    				// okay.
    			} else {
    				fmt.Fprintf(w, "-%s\n", feature)
    				ok = false // broke compatibility
    			}
    
    ここで重要なのは、else if featureSet[featureWithoutContext(feature)] の行です。
    1. required から feature を取り出します。
    2. もし featureexceptionSet に含まれていれば、それは例外として扱われます。
    3. そうでなく、かつ feature のコンテキストなしバージョン (featureWithoutContext(feature)) が featureSet (現在のAPIセット) に存在する場合、これは「コンテキストの再収束」と見なされ、互換性のある変更として扱われます。つまり、特定のコンテキストに依存していたAPIが、より一般的な形で提供されるようになった場合、これは互換性を壊す変更ではないと判断されます。
    4. 上記のいずれにも該当しない場合、その feature は削除されたと見なされ、互換性のない変更として - プレフィックスで出力され、okfalse に設定されます。

src/cmd/api/goapi_test.go の変更点

  • 新しいテストケースの追加:
    		{
    			// http://golang.org/issue/4303
    			name: "contexts reconverging",
    			required: []string{
    				"A",
    				"pkg syscall (darwin-386), type RawSockaddrInet6 struct",
    				"pkg syscall (darwin-amd64), type RawSockaddrInet6 struct",
    			},
    			features: []string{
    				"A",
    				"pkg syscall, type RawSockaddrInet6 struct",
    			},
    			ok:  true,
    			out: "+pkg syscall, type RawSockaddrInet6 struct\n",
    		},
    
    この新しいテストケースは、まさに「コンテキストの再収束」のシナリオを検証するために追加されました。
    • required (以前のAPIセット) には、A という一般的なAPIと、darwin-386 および darwin-amd64 という特定のコンテキストを持つ RawSockaddrInet6 struct が含まれています。
    • features (現在のAPIセット) には、A と、コンテキストを持たない pkg syscall, type RawSockaddrInet6 struct が含まれています。
    • 期待される結果は ok: true であり、out: "+pkg syscall, type RawSockaddrInet6 struct\n" です。これは、コンテキスト固有のAPIが削除され、より一般的なAPIに置き換えられた場合でも、cmd/api がそれを互換性のある変更として認識し、新しいAPIが追加されたものとして報告することを示しています。

関連リンク

  • Go言語のAPI互換性に関するドキュメントやポリシー(公式ドキュメントを参照)
  • cmd/api ツールの詳細な説明(Goのソースコードリポジトリ内の関連ドキュメントやコメントを参照)

参考にした情報源リンク