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

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

このコミットは、Goコマンドラインツール (cmd/go) において、SWIG (Simplified Wrapper and Interface Generator) のバージョンが古すぎる場合にエラーを検出する機能を追加するものです。これにより、Goのビルドプロセスが古いSWIGバージョンに依存することによる潜在的な問題を未然に防ぎ、開発者が互換性のない環境で作業するのを防ぎます。

コミット

  • コミットハッシュ: 708304bea26bec83c443f7657ad1780b17c23790
  • 作者: Ian Lance Taylor iant@golang.org
  • コミット日時: 2014年5月21日 水曜日 10:39:23 -0700

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

https://github.com/golang/go/commit/708304bea26bec83c443f7657ad1780b17c23790

元コミット内容

cmd/go: check for SWIG version that is too old to use

Fixes #7983.

LGTM=crawshaw
R=golang-codereviews, crawshaw
CC=golang-codereviews, rsc
https://golang.org/cl/96540044

変更の背景

この変更の背景には、GoのビルドシステムがSWIGを利用する際に、特定の古いSWIGバージョンとの互換性の問題が発生していたことがあります。コミットメッセージにある Fixes #7983 は、この問題がGoのIssueトラッカーで報告されていたことを示しています。

SWIGは、C/C++のコードをGoを含む様々なプログラミング言語から呼び出すためのラッパーコードを生成するツールです。Goの cgo 機能と組み合わせて、既存のC/C++ライブラリをGoプロジェクトで利用する際に頻繁に用いられます。

SWIGのバージョンアップに伴い、生成されるコードの形式や、Goとの連携方法に互換性のない変更が導入されることがあります。特にSWIG 3.0.0以降では、Go言語バインディングに関する重要な改善や変更が含まれており、それ以前のバージョンではGoのビルドプロセスで問題が発生する可能性がありました。

このコミットは、開発者が古いSWIGバージョンを使用していることに気づかず、ビルドエラーや予期せぬ動作に遭遇するのを防ぐために導入されました。Goツールチェーンが自動的にSWIGのバージョンをチェックし、必要な最小バージョン(この場合は3.0.0)を満たしていない場合に明確なエラーメッセージを出すことで、開発体験を向上させ、デバッグの手間を省くことを目的としています。

前提知識の解説

SWIG (Simplified Wrapper and Interface Generator)

SWIGは、C/C++で書かれたライブラリやプログラムを、Go、Python、Java、Rubyなど、様々なスクリプト言語や高水準言語から呼び出すためのインターフェースコードを自動生成するオープンソースツールです。SWIGは、C/C++のヘッダーファイルを解析し、ターゲット言語のネイティブな型システムや関数呼び出し規約に合わせたラッパーコードを生成します。これにより、開発者はC/C++の複雑なAPIを直接扱うことなく、高水準言語の慣用的な方法で利用できるようになります。

GoにおけるSWIGとCgo

Go言語には、C言語のコードをGoから呼び出すための cgo という機能が組み込まれています。cgo は、Goのソースファイル内にCコードを直接記述したり、既存のCライブラリをリンクしたりすることを可能にします。SWIGは、この cgo と連携して使用されることが多く、特にC++の複雑なクラス階層やテンプレートをGoから利用する際にその真価を発揮します。SWIGが生成したGoのラッパーコードは、内部的に cgo を利用してC/C++の関数やデータ構造にアクセスします。

SWIG 3.0.0の重要性

SWIG 3.0.0は、2014年3月にリリースされたSWIGのメジャーバージョンアップです。このバージョンでは、Go言語バインディングに関して以下のような重要な変更や改善が導入されました。

  • Go言語サポートの強化: SWIG 3.0.0以降、Go言語のサポートがより成熟し、安定しました。特に、Goの型システムとのマッピングや、エラーハンドリングの改善が行われました。
  • C++11サポートの強化: C++11で導入された新しい機能(例: std::arrayenum class、ラムダ式など)に対するサポートが強化されました。GoからC++11の機能を利用する際に、より正確なラッパーコードが生成されるようになりました。
  • %go_extralib ディレクティブ: Goのビルドプロセスで追加のライブラリをリンクするための新しいディレクティブが導入されました。
  • バグ修正と安定性の向上: 以前のバージョンで存在した多くのバグが修正され、全体的な安定性が向上しました。

これらの変更により、GoプロジェクトでSWIGを使用する際には、SWIG 3.0.0以降のバージョンが推奨されるようになりました。古いバージョンでは、Goのビルドが失敗したり、生成されたラッパーコードが正しく動作しないなどの問題が発生する可能性がありました。

技術的詳細

このコミットによって導入された技術的詳細は、GoのビルドプロセスにおけるSWIGのバージョンチェック機構の実装にあります。

  1. SWIG実行ファイルの検出とバージョン取得: swigDoVersionCheck 関数は、システムにインストールされている swig コマンドを実行し、その -version オプションの出力を解析してSWIGのバージョン番号を取得します。正規表現 [vV]ersion +([\d]) を使用して、出力からメジャーバージョン番号(例: "3")を抽出します。

  2. バージョンチェックロジック: 抽出されたSWIGのメジャーバージョン番号が 3 未満である場合、つまりSWIG 2.x以前のバージョンである場合に、"must have SWIG version >= 3.0" というエラーを返します。これにより、Goのビルドプロセスは、互換性のない古いSWIGバージョンが使用されていることを検出し、処理を中断します。

  3. 一度だけ実行されるチェック: swigVersionCheck 関数は、sync.Once を使用して swigDoVersionCheck が一度だけ実行されるように保証します。これは、ビルドプロセス中にSWIGのバージョンチェックが複数回呼び出される可能性があるため、不必要なオーバーヘッドを避けるための最適化です。一度チェックが完了すれば、その結果(エラーまたはnil)がキャッシュされ、以降の呼び出しではキャッシュされた結果が返されます。

  4. 既存のエラーメッセージの改善: 以前のGoのビルドツールでは、SWIGの古いバージョンが原因で -intgosize オプションが認識されない場合に、"must have SWIG version >= 3.0\n" というエラーメッセージが表示されていました。このコミットでは、このエラーメッセージから不要な改行文字 (\n) を削除し、よりクリーンな出力になるように修正しています。これは、新しいバージョンチェック機構が導入されたことで、より早期にバージョン不一致を検出できるようになったため、既存のエラーメッセージもそれに合わせて調整されたものです。

このバージョンチェック機構は、Goのビルドプロセスに統合され、SWIGを利用するパッケージのビルドが開始される前に実行されます。これにより、開発者はビルドの早い段階でSWIGのバージョン問題を特定し、解決することができます。

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

変更は src/cmd/go/build.go ファイルに対して行われました。

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -2348,6 +2348,10 @@ func (b *builder) swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []stri
 	outObj = append(outObj, ofile)
 	}
 
+	if err := b.swigVersionCheck(); err != nil {
+		return nil, nil, err
+	}
+
 	intgosize, err := b.swigIntSize(obj)
 	if err != nil {
 		return nil, nil, err
@@ -2386,6 +2390,41 @@ func (b *builder) swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []stri
 	return outGo, outObj, nil
 }
 
+// Make sure SWIG is new enough.
+var (
+	swigCheckOnce sync.Once
+	swigCheck     error
+)
+
+func (b *builder) swigDoVersionCheck() error {
+	out, err := b.runOut("", "", nil, "swig", "-version")
+	if err != nil {
+		return err
+	}
+	re := regexp.MustCompile(`[vV]ersion +([\d])`)
+	matches := re.FindSubmatch(out)
+	if matches == nil {
+		// Can't find version number; hope for the best.
+		return nil
+	}
+	major, err := strconv.Atoi(string(matches[1]))
+	if err != nil {
+		// Can't find version number; hope for the best.
+		return nil
+	}
+	if major < 3 {
+		return errors.New("must have SWIG version >= 3.0")
+	}
+	return nil
+}
+
+func (b *builder) swigVersionCheck() error {
+	swigCheckOnce.Do(func() {
+		swigCheck = b.swigDoVersionCheck()
+	})
+	return swigCheck
+}
+
 // This code fails to build if sizeof(int) <= 32
 const swigIntSizeCode = `
 package main
@@ -2458,7 +2497,7 @@ func (b *builder) swigOne(p *Package, file, obj string, cxx bool, intgosize stri
 	if out, err := b.runOut(p.Dir, p.ImportPath, nil, "swig", args, file); err != nil {
 		if len(out) > 0 {
 			if bytes.Contains(out, []byte("Unrecognized option -intgosize")) {
-				return "", "", "", errors.New("must have SWIG version >= 3.0\n")
+				return "", "", "", errors.New("must have SWIG version >= 3.0")
 			}
 			b.showOutput(p.Dir, p.ImportPath, b.processOutput(out))
 			return "", "", "", errPrintedOutput

コアとなるコードの解説

swig(p *Package, obj string, gccfiles, gxxfiles, mfiles []string) ([]string, []string, error) 関数内の変更

swig 関数は、GoのビルドプロセスにおいてSWIGの処理をオーケストレーションする主要な関数です。この関数内で、SWIGのバージョンチェックが導入されました。

	if err := b.swigVersionCheck(); err != nil {
		return nil, nil, err
	}

このコードは、SWIGの処理を開始する前に b.swigVersionCheck() を呼び出します。もしこの関数がエラーを返した場合(つまり、SWIGのバージョンが古すぎる場合)、swig 関数はすぐにエラーを返してビルドプロセスを中断します。これにより、無駄な処理を避け、開発者に早期に問題を通達できます。

swigCheckOnceswigCheck 変数

var (
	swigCheckOnce sync.Once
	swigCheck     error
)

これらはパッケージレベルの変数です。

  • swigCheckOnce: sync.Once 型の変数で、swigDoVersionCheck 関数が一度だけ実行されることを保証するために使用されます。
  • swigCheck: error 型の変数で、swigDoVersionCheck の実行結果(エラーまたはnil)をキャッシュするために使用されます。

swigDoVersionCheck() error 関数

func (b *builder) swigDoVersionCheck() error {
	out, err := b.runOut("", "", nil, "swig", "-version")
	if err != nil {
		return err
	}
	re := regexp.MustCompile(`[vV]ersion +([\d])`)
	matches := re.FindSubmatch(out)
	if matches == nil {
		// Can't find version number; hope for the best.
		return nil
	}
	major, err := strconv.Atoi(string(matches[1]))
	if err != nil {
		// Can't find version number; hope for the best.
		return nil
	}
	if major < 3 {
		return errors.New("must have SWIG version >= 3.0")
	}
	return nil
}

この関数は、実際のSWIGバージョンチェックロジックを含んでいます。

  1. b.runOut("", "", nil, "swig", "-version") を実行して、swig -version コマンドの出力を取得します。
  2. 正規表現 [vV]ersion +([\d]) を使用して、出力からSWIGのメジャーバージョン番号を抽出します。
  3. 抽出したメジャーバージョン番号が整数に変換できない場合や、正規表現にマッチしない場合は、バージョンを特定できないため、エラーを返さずに処理を続行します(「最善を尽くす」というコメントがあります)。これは、SWIGのバージョン出力形式が予期しないものである場合でも、ビルドを完全にブロックしないためのフォールバックです。
  4. メジャーバージョンが 3 未満の場合、"must have SWIG version >= 3.0" というエラーを返します。

swigVersionCheck() error 関数

func (b *builder) swigVersionCheck() error {
	swigCheckOnce.Do(func() {
		swigCheck = b.swigDoVersionCheck()
	})
	return swigCheck
}

この関数は、sync.Once を利用して swigDoVersionCheck が一度だけ実行されることを保証します。

  • swigCheckOnce.Do(func() { ... }): このブロック内のコードは、プログラムの実行中に一度だけ実行されます。
  • swigCheck = b.swigDoVersionCheck(): swigDoVersionCheck の結果が swigCheck 変数に格納されます。 以降の swigVersionCheck の呼び出しでは、swigDoVersionCheck は再実行されず、キャッシュされた swigCheck の値が返されます。

既存のエラーメッセージの修正

-				return "", "", "", errors.New("must have SWIG version >= 3.0\n")
+				return "", "", "", errors.New("must have SWIG version >= 3.0")

この変更は、SWIGが -intgosize オプションを認識しない場合に表示されるエラーメッセージから、末尾の不要な改行文字 (\n) を削除するものです。これは、新しいバージョンチェック機構が導入されたことで、より統一されたエラーメッセージの形式にするための調整です。

これらの変更により、GoのビルドシステムはSWIGのバージョン互換性をより堅牢にチェックできるようになり、開発者はSWIG関連のビルド問題をより早期に、かつ明確に把握できるようになりました。

関連リンク

参考にした情報源リンク

  • Google Web Search: "Go SWIG version 3.0 changes"
  • Google Web Search: "Go issue 7983"
  • web_fetch for https://golang.org/cl/96540044