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

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

このコミットは、Go言語のビルドシステムにおけるCgoファイルの扱いに関するバグ修正です。具体的には、CGO_ENABLED=0 の環境下でCgoファイルのみを含むディレクトリが、ビルド可能なGoソースファイルがないにも関わらずエラーを適切に報告しない問題を解決します。

コミット

go/build: Cgoが使用されていない場合、Cgoファイルのみを含むディレクトリを拒否する

以前の「Goファイルがない」ことのテストは p.Name == "" であり、これはGoパッケージのステートメントが見つからなかったことを意味していました。このテストは、Cgoファイルがパースされた(そしてパッケージ名が記録された)が、Cgoが利用できないために使用しないことを選択した場合に失敗します。

代わりに実際のファイルリストをテストします。

Fixes #6078.

R=golang-dev, iant CC=golang-dev https://golang.org/cl/13661043

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

https://github.com/golang/go/commit/611b182190e759879b3988077daf7d52eb831b5e

元コミット内容

commit 611b182190e759879b3988077daf7d52eb831b5e
Author: Russ Cox <rsc@golang.org>
Date:   Wed Sep 11 13:25:30 2013 -0400

    go/build: reject directory with only cgo files if cgo not in use
    
    The old test for "no Go files" was p.Name == "", meaning we never
    saw a Go package statement. That test fails if there are cgo files
    that we parsed (and recorded the package name) but then chose
    not to use (because cgo is not available).
    
    Test the actual file lists instead.
    
    Fixes #6078.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/13661043
---
 src/cmd/go/test.bash                 | 13 +++++++++++++
 src/cmd/go/testdata/src/cgotest/m.go |  5 +++++
 src/pkg/go/build/build.go            |  4 +++-\n 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/cmd/go/test.bash b/src/cmd/go/test.bash
index a2ba1ca95a..c5effe757e 100755
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -167,6 +167,19 @@ elif ! grep testdata/shadow/root1/src/foo testdata/err >/dev/null; then
 fi
 unset GOPATH
 
+TEST go install fails with no buildable files
+export GOPATH=$(pwd)/testdata
+export CGO_ENABLED=0
+if ./testgo install cgotest 2>testdata/err; then
+\techo "go install cgotest succeeded unexpectedly"
+elif ! grep 'no buildable Go source files' testdata/err >/dev/null; then
+\techo "go install cgotest did not report 'no buildable Go source files'"
+\tcat testdata/err
+\tok=false
+fi
+unset CGO_ENABLED
+unset GOPATH
+
 # Test that without $GOBIN set, binaries get installed
 # into the GOPATH bin directory.
 TEST install into GOPATH
diff --git a/src/cmd/go/testdata/src/cgotest/m.go b/src/cmd/go/testdata/src/cgotest/m.go
new file mode 100644
index 0000000000..4d68307cf0
--- /dev/null
+++ b/src/cmd/go/testdata/src/cgotest/m.go
@@ -0,0 +1,5 @@
+package cgotest
+
+import "C"
+
+var _ C.int
diff --git a/src/pkg/go/build/build.go b/src/pkg/go/build/build.go
index f259525f5e..be48df9d38 100644
--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -747,6 +747,8 @@ Found:\n \t\t\tallTags[\"cgo\"] = true
 \t\t\tif ctxt.CgoEnabled {\n \t\t\t\tp.CgoFiles = append(p.CgoFiles, name)\n+\t\t\t} else {\n+\t\t\t\tp.IgnoredGoFiles = append(p.IgnoredGoFiles, name)\n \t\t\t}\n \t\t} else if isXTest {\n \t\t\tp.XTestGoFiles = append(p.XTestGoFiles, name)\n@@ -756,7 +758,7 @@ Found:\n \t\t\tp.GoFiles = append(p.GoFiles, name)\n \t\t}\n \t}\n-\tif p.Name == \"\" {\n+\tif len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {\n \t\treturn p, &NoGoError{p.Dir}\n \t}\n \n```

## 変更の背景

このコミットは、GoのビルドシステムがCgoファイルのみを含むディレクトリを適切に処理できないというバグ(Issue #6078)を修正するために行われました。

従来のGoビルドシステムでは、ディレクトリ内にビルド可能なGoソースファイルが存在しないかどうかを判断するために、`Package.Name` フィールドが空であるかどうかをチェックしていました。`Package.Name` は、Goソースファイルから読み取られるパッケージ名を格納します。

しかし、Cgoファイル(`.go` ファイル内にC言語のコードを埋め込んだもの)は、Goのパーサーによって解析され、パッケージ名が抽出されます。このとき、たとえ環境変数 `CGO_ENABLED` が `0` に設定されており、Cgoが事実上無効化されている場合でも、パーサーはCgoファイルからパッケージ名を読み取ります。

この状況下で、ディレクトリ内にGoソースファイルが一切なく、Cgoファイルのみが存在する場合、`p.Name` はCgoファイルから読み取られたパッケージ名で設定されてしまうため、空になりません。結果として、ビルドシステムは「ビルド可能なGoソースファイルがない」というエラーを報告せず、あたかも有効なパッケージであるかのように振る舞ってしまい、ユーザーを混乱させる原因となっていました。

このコミットは、この誤った挙動を修正し、`CGO_ENABLED=0` の場合にCgoファイルがビルド対象から除外されることを考慮に入れ、真にビルド可能なGoソースファイルが存在しない場合にのみエラーを報告するように変更します。

## 前提知識の解説

### Goのビルドシステム (`go/build` パッケージ)

`go/build` パッケージは、Goのソースコードを解析し、パッケージの依存関係を解決し、ビルド可能なファイルを特定するための機能を提供します。`go build` や `go install` といったコマンドの基盤となる重要なパッケージです。

### Cgo

Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Goのソースファイル内に `import "C"` を記述することで、C言語の関数やデータ構造をGoから利用できるようになります。Cgoを使用すると、既存のCライブラリをGoプロジェクトに統合したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。

### `CGO_ENABLED` 環境変数

`CGO_ENABLED` は、Cgoの有効/無効を制御する環境変数です。
- `CGO_ENABLED=1` (デフォルト): Cgoが有効になり、Cgoファイルがビルドプロセスに含まれます。
- `CGO_ENABLED=0`: Cgoが無効になり、Cgoファイルはビルドプロセスから除外されます。これは、クロスコンパイル時や、Cコンパイラが利用できない環境でGoプログラムをビルドする際によく使用されます。

### `NoGoError`

`go/build` パッケージが、指定されたディレクトリ内にビルド可能なGoソースファイルを見つけられなかった場合に返すエラーです。このエラーは、ユーザーに対して、ビルド対象のディレクトリが空であるか、または有効なGoパッケージではないことを通知するために使用されます。

### `Package` 構造体

`go/build` パッケージの `Package` 構造体は、Goパッケージに関する様々な情報(パッケージ名、Goソースファイルのリスト、Cgoファイルのリスト、テストファイルのリストなど)を保持します。

- `p.Name`: パッケージ名。Goソースファイルから読み取られます。
- `p.GoFiles`: 通常のGoソースファイルのリスト。
- `p.CgoFiles`: Cgoソースファイルのリスト。
- `p.TestGoFiles`: 通常のテストファイルのリスト(`_test.go`)。
- `p.XTestGoFiles`: 外部テストファイルのリスト(`_test.go` で `package main` 以外のパッケージをテストするもの)。
- `p.IgnoredGoFiles`: ビルドタグや `CGO_ENABLED` の設定により無視されたGoソースファイルのリスト。

## 技術的詳細

このコミットの核心は、`go/build` パッケージ内の `build.go` ファイルにおけるパッケージのビルド可能性の判断ロジックの変更です。

### 変更前

変更前は、パッケージがビルド可能かどうかを判断する際に、主に `p.Name == ""` という条件を使用していました。これは、Goソースファイルが一つも存在しない場合、`p.Name` が設定されず空文字列になるという前提に基づいています。

```go
// src/pkg/go/build/build.go (変更前)
if p.Name == "" {
    return p, &NoGoError{p.Dir}
}

しかし、CgoファイルはGoのパーサーによって解析され、その中に記述された package 宣言から p.Name が設定されてしまいます。CGO_ENABLED=0 の場合、これらのCgoファイルはビルド対象から除外されるにも関わらず、p.Name は空ではないため、上記の条件では NoGoError が返されませんでした。

変更後

このコミットでは、p.Name == "" という曖昧なチェックを廃止し、代わりに Package 構造体内の実際のファイルリストの長さを確認するように変更されました。

// src/pkg/go/build/build.go (変更後)
if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
    return p, &NoGoError{p.Dir}
}

この新しい条件は、通常のGoソースファイル (p.GoFiles)、Cgoソースファイル (p.CgoFiles)、通常のテストファイル (p.TestGoFiles)、および外部テストファイル (p.XTestGoFiles) のいずれのリストも空である場合にのみ NoGoError を返すようにします。

さらに、CGO_ENABLED=0 の場合にCgoファイルが適切に p.IgnoredGoFiles リストに追加されるように、ファイル解析ロジックも修正されています。これにより、Cgoが無効な環境ではCgoファイルがビルド対象としてカウントされなくなり、ビルド可能性の判断がより正確になります。

// src/pkg/go/build/build.go (変更後)
if ctxt.CgoEnabled {
    p.CgoFiles = append(p.CgoFiles, name)
} else {
    p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
}

この変更により、CGO_ENABLED=0 の環境でCgoファイルのみを含むディレクトリをビルドしようとした場合、NoGoError が適切に報告されるようになり、ユーザーは「ビルド可能なGoソースファイルがない」という明確なエラーメッセージを受け取ることができます。

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

src/pkg/go/build/build.go

--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -747,6 +747,8 @@ Found:
 		allTags["cgo"] = true
 		if ctxt.CgoEnabled {
 			p.CgoFiles = append(p.CgoFiles, name)
+		} else {
+			p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
 		}
 	} else if isXTest {
 		p.XTestGoFiles = append(p.XTestGoFiles, name)
@@ -756,7 +758,7 @@ Found:
 		p.GoFiles = append(p.GoFiles, name)
 	}
 	}
-	if p.Name == "" {
+	if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
 		return p, &NoGoError{p.Dir}
 	}
 

src/cmd/go/test.bash

新しいテストケースが追加され、CGO_ENABLED=0 の環境でCgoファイルのみを含むディレクトリを go install しようとした場合に、no buildable Go source files エラーが適切に報告されることを確認しています。

src/cmd/go/testdata/src/cgotest/m.go

新しいテストケースで使用される、Cgoの import "C" を含むシンプルなGoファイルが追加されています。

package cgotest

import "C"

var _ C.int

コアとなるコードの解説

src/pkg/go/build/build.go の変更点

  1. Cgoファイルの処理の改善:

    if ctxt.CgoEnabled {
        p.CgoFiles = append(p.CgoFiles, name)
    } else {
        p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
    }
    

    この変更は、go/build パッケージがGoソースファイルを解析する際のロジックの一部です。ファイルがCgoファイルであると識別された場合、ctxt.CgoEnabled (Cgoが有効かどうかを示すコンテキスト情報) をチェックします。

    • ctxt.CgoEnabledtrue の場合(Cgoが有効)、そのファイルは p.CgoFiles リストに追加されます。これは、そのファイルがビルドプロセスでCgoとして扱われることを意味します。
    • ctxt.CgoEnabledfalse の場合(Cgoが無効)、そのファイルは p.IgnoredGoFiles リストに追加されます。これは、Cgoファイルであっても、現在のビルド設定では無視されるべきファイルであることを明示します。この変更により、CGO_ENABLED=0 の場合にCgoファイルがビルド対象として誤ってカウントされることがなくなります。
  2. ビルド可能性の判断ロジックの変更:

    -	if p.Name == "" {
    +	if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
    		return p, &NoGoError{p.Dir}
    	}
    

    これがこのコミットの最も重要な変更点です。

    • 変更前 (p.Name == ""): 以前は、パッケージ名 (p.Name) が空であるかどうかで、ディレクトリ内にビルド可能なGoソースファイルがないかを判断していました。しかし、前述の通り、Cgoファイルが存在すると p.Name が設定されてしまうため、このチェックは不正確でした。
    • 変更後 (len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0): 新しいロジックでは、Package 構造体内の実際のファイルリストの合計がゼロであるかどうかをチェックします。
      • p.GoFiles: 通常のGoソースファイル。
      • p.CgoFiles: Cgoソースファイル。
      • p.TestGoFiles: 通常のテストファイル。
      • p.XTestGoFiles: 外部テストファイル。 これらのリストのいずれかにファイルが含まれていれば、そのディレクトリはビルド可能なGoソースファイルを持つと判断されます。逆に、これらのリストがすべて空である場合、そのディレクトリにはビルド可能なGoソースファイルが一つも存在しないと判断され、NoGoError が返されます。 この変更により、CGO_ENABLED=0 の場合にCgoファイルが p.CgoFiles に追加されず、p.IgnoredGoFiles に追加されるようになったため、len(p.CgoFiles) はゼロになり、正確なビルド可能性の判断が可能になります。

src/cmd/go/test.bash の変更点

このシェルスクリプトはGoコマンドのテストスイートの一部です。追加されたテストケースは以下の手順を実行します。

  1. GOPATH を一時的に設定します。
  2. CGO_ENABLED=0 をエクスポートし、Cgoを無効にします。
  3. ./testgo install cgotest を実行します。cgotest は、新しく追加された src/cmd/go/testdata/src/cgotest/m.go のパッケージです。このファイルはCgoファイルのみを含んでいます。
  4. go install コマンドが予期せず成功した場合はエラーを報告します。
  5. go install コマンドの出力(標準エラー出力)に no buildable Go source files という文字列が含まれていることを確認します。この文字列が見つからない場合はエラーを報告します。

このテストケースは、CGO_ENABLED=0 の環境でCgoファイルのみを含むディレクトリをビルドしようとした際に、期待通りに NoGoError が発生し、適切なエラーメッセージが表示されることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go/buildパッケージ、cgoに関する情報)
  • Go言語のソースコード (特に src/pkg/go/build/build.go)
  • Go言語のIssueトラッカー (Issue #6078)
  • Go言語のCode Reviewシステム (CL 13661043)