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

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

このコミットは、Go言語の初期のビルドツールである gobuild の機能を拡張するものです。具体的には、Go以外のファイル(例: C言語のソースファイル)内に記述されたパッケージ宣言を認識できるようにし、さらに特定のコメント gobuild: ignore を用いてビルド対象からファイルを除外する機能を追加しています。

コミット

commit 6c4d8f830965d04854dd42294c939c48d3d61d83
Author: Russ Cox <rsc@golang.org>
Date:   Mon Nov 24 11:21:56 2008 -0800

    gobuild:
            recognize "// package foo" in non-go files
            recognize "gobuild: ignore" to exclude from build
    
    R=r
    DELTA=10  (7 added, 2 deleted, 1 changed)
    OCL=19878
    CL=19905

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

https://github.com/golang/go/commit/6c4d8f830965d04854dd42294c939c48d3d61d83

元コミット内容

このコミットは、gobuild ツールに対して以下の2つの主要な変更を導入します。

  1. 非Goファイルにおけるパッケージ宣言の認識: .go 拡張子を持たないファイル(例: .c.s ファイル)内でも、// package foo の形式で記述されたパッケージ宣言を gobuild が認識できるようになります。これにより、Go以外の言語で書かれたソースファイルがGoのパッケージ構造にどのように属するかを gobuild に伝えることが可能になります。
  2. ビルドからのファイル除外機能: ファイル内に gobuild: ignore という文字列が含まれている場合、そのファイルを gobuild のビルドプロセスから除外する機能が追加されます。これは、特定のファイルを一時的にビルド対象外にしたり、特定のビルド条件でのみ含めたりする際に有用です。

変更の背景

Go言語の初期段階において、gobuild は現在の go build コマンドの前身となるビルドツールでした。Go言語はC言語との連携(cgo)やアセンブリ言語(.sファイル)の使用を考慮して設計されており、これらの非Go言語のソースファイルもGoのビルドシステムに統合する必要がありました。

このコミットの背景には、以下の課題があったと考えられます。

  • 異種言語ソースファイルの統合: Goプロジェクトでは、C言語のコードやアセンブリ言語のコードがGoのコードと共存することがあります。gobuild がこれらの非Goファイルに含まれるGoパッケージ情報を適切に処理できないと、ビルドプロセスが複雑になったり、エラーが発生したりする可能性がありました。特に、Goのパッケージシステムはディレクトリ構造に基づいていますが、非GoファイルがどのGoパッケージに属するかを明示的に指定するメカニズムが必要でした。
  • 柔軟なビルド制御: 開発の過程で、特定のファイルを一時的にビルドから除外したい、あるいは特定の条件でのみビルドに含めたいというニーズが生じます。例えば、プラットフォーム固有のコードや、まだ開発中の機能に関連するコードなどです。このような柔軟性を提供するために、ファイル自体にビルド指示を埋め込むメカニズムが求められていました。

このコミットは、これらの課題に対処し、gobuild の実用性と柔軟性を向上させることを目的としています。非Goファイルにおけるパッケージ宣言の認識は、Goと他の言語とのシームレスな連携を促進し、gobuild: ignore ディレクティブは、開発者がビルドプロセスをより細かく制御できるようにします。

前提知識の解説

gobuildgo build

gobuild は、Go言語の初期に存在したビルドツールです。現在の go build コマンドの機能の多くを担っていました。Go言語の進化とともに、より洗練された go コマンド(go build, go run, go test などを含む)に統合され、gobuild は廃止されました。しかし、このコミットは gobuild の設計思想や初期のGoビルドシステムの課題を理解する上で貴重な情報を提供します。

Goのパッケージシステム

Go言語は、コードをパッケージ(package)という単位で整理します。各Goソースファイルは必ず package 宣言を持ち、そのファイルがどのパッケージに属するかを示します。通常、同じディレクトリ内のGoファイルは同じパッケージに属します。ビルドツールは、このパッケージ宣言を読み取り、依存関係を解決し、実行可能ファイルやライブラリを構築します。

C言語とGoの連携 (cgo)

Go言語は cgo というメカニズムを通じてC言語のコードと連携できます。Goのソースファイル内でCの関数を呼び出したり、Cの構造体を使用したりすることが可能です。この場合、GoのビルドプロセスはCコンパイラを呼び出してCのコードをコンパイルし、Goのコードとリンクする必要があります。このコミットの変更は、cgo を使用する際に、CのソースファイルがどのGoパッケージに属するかを gobuild に伝えるための手段を提供します。

アセンブリ言語ファイル (.s)

Go言語は、パフォーマンスが重要な部分や、特定のハードウェア機能を利用するためにアセンブリ言語で書かれたコード(通常 .s 拡張子を持つファイル)をサポートしています。これらのファイルもGoのビルドプロセスに統合される必要があります。

suffix 関数 (C言語)

C言語のコードスニペットに見られる suffix(file, ".go") のような関数は、ファイル名が特定の拡張子で終わるかどうかをチェックするユーティリティ関数です。このコミットでは、ファイルが .go 拡張子を持つかどうかを判断するために使用されています。

strstr 関数 (C言語)

strstr(haystack, needle) はC標準ライブラリの関数で、haystack 文字列内で needle 文字列が最初に出現する位置へのポインタを返します。見つからない場合は NULL を返します。このコミットでは、行内に gobuild: ignore という文字列が含まれているかを検出するために使用されています。

技術的詳細

このコミットの技術的変更は、src/cmd/gobuild/gobuild.c ファイル内の getpkg 関数と main 関数に集中しています。

getpkg 関数の変更

getpkg 関数は、与えられたファイルからパッケージ名を抽出する役割を担っています。

変更前:

if(!suffix(file, ".go"))
    return nil;

このコードは、ファイルが .go 拡張子を持たない場合、即座に nil を返していました。これは、gobuild がGoソースファイル以外のファイルからパッケージ情報を読み取らないことを意味します。

変更後:

if(!suffix(file, ".go")) {
    if(*p != '/' || *(p+1) != '/')
        continue;
    p += 2;
}
if(strstr(p, "gobuild: ignore"))
    return "main";

この変更により、以下の2つの新しい動作が導入されました。

  1. 非Goファイルにおける // package foo の認識:

    • if(!suffix(file, ".go")) ブロックが追加されました。これにより、ファイルが .go 拡張子を持たない場合でも処理が続行されます。
    • if(*p != '/' || *(p+1) != '/') continue; p += 2; の行は、現在の行 p// で始まっているかをチェックします。もしそうでなければ、その行はスキップされ、次の行の処理に移ります (continue)。もし // で始まっていれば、ポインタ p を2文字進め(// をスキップ)、その後の文字列をパッケージ宣言として解析しようとします。
    • このメカニズムにより、C言語のソースファイルやアセンブリ言語ファイルなどの非Goファイル内に // package mypackage のようなコメント形式でGoのパッケージ宣言を埋め込むことが可能になり、gobuild がそれを認識できるようになります。
  2. gobuild: ignore ディレクティブによる除外:

    • if(strstr(p, "gobuild: ignore")) return "main"; の行が追加されました。これは、現在の行(// がスキップされた後)に文字列 gobuild: ignore が含まれているかをチェックします。
    • もしこの文字列が見つかった場合、getpkg 関数は "main" を返します。この "main" は、このファイルが通常のパッケージとして扱われるべきではないことを gobuild に伝えるための特別なシグナルであると考えられます。コミットメッセージの「exclude from build」という記述から、このファイルがビルドプロセスから除外されるか、あるいは特別な方法で扱われることを示唆しています。Goのビルドシステムでは、main パッケージは実行可能エントリポイントを意味しますが、ここでは「無視」の意図で使われている可能性が高いです。

main 関数の変更

main 関数では、テストファイルの処理に関する微細な変更が行われています。

変更前:

if(suffix(argv[i], "_test.go") != nil)

変更後:

if(suffix(argv[i], "_test.go"))

この変更は機能的なものではなく、C言語における慣用的な記述への修正です。suffix 関数は文字列へのポインタを返すか、マッチしない場合は NULL を返すと推測されます。C言語では、NULL ポインタはブールコンテキストで false と評価され、非NULL ポインタは true と評価されます。したがって、!= nil を明示的に書くことは冗長であり、if(suffix(...)) と書くだけで同じ論理的な結果が得られます。これはコードの可読性と簡潔性を向上させるためのクリーンアップです。

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

変更は src/cmd/gobuild/gobuild.c ファイルに集中しています。

--- a/src/cmd/gobuild/gobuild.c
+++ b/src/cmd/gobuild/gobuild.c
@@ -206,12 +206,17 @@ getpkg(char *file)
 	char *p, *q;
 	int i;
 
-	if(!suffix(file, ".go"))
-		return nil;
 	if((b = Bopen(file, OREAD)) == nil)
 		sysfatal("open %s: %r", file);
 	while((p = Brdline(b, '\n')) != nil) {
 		p[Blinelen(b)-1] = '\0';
+		if(!suffix(file, ".go")) {
+			if(*p != '/' || *(p+1) != '/')
+				continue;
+			p += 2;
+		}
+		if(strstr(p, "gobuild: ignore"))
+			return "main";
 		while(*p == ' ' || *p == '\t')
 			p++;
 		if(strncmp(p, "package", 7) == 0 && (p[7] == ' ' || p[7] == '\t')) {
@@ -487,7 +492,7 @@ main(int argc, char **argv)
 	njob = 0;
 	job = emalloc(argc*sizeof job[0]);
 	for(i=0; i<argc; i++) {
-		if(suffix(argv[i], "_test.go") != nil)
+		if(suffix(argv[i], "_test.go"))
 			continue;
 		job[njob].name = argv[i];
 		job[njob].pass = -1;

コアとなるコードの解説

getpkg 関数内の変更

  • 非Goファイル処理の追加:

    • 元の if(!suffix(file, ".go")) return nil; が削除され、非Goファイルでも行の読み込みと解析が続行されるようになりました。
    • if(!suffix(file, ".go")) { ... } ブロックが追加され、ファイルがGoファイルでない場合にのみ、行が // で始まるかどうかのチェックが行われます。これにより、C言語のコメント形式でGoのパッケージ宣言を記述できるようになります。
    • p += 2; は、// をスキップして、その後の文字列をパッケージ宣言として解析するためのポインタの移動です。
  • gobuild: ignore ディレクティブの追加:

    • if(strstr(p, "gobuild: ignore")) return "main"; が追加されました。これは、現在の行に gobuild: ignore という文字列が含まれている場合、getpkg 関数が "main" を返すようにします。この "main" は、このファイルがビルドから除外されるべきであるというシグナルとして機能します。

main 関数内の変更

  • テストファイルチェックの簡略化:
    • if(suffix(argv[i], "_test.go") != nil)if(suffix(argv[i], "_test.go")) に変更されました。これはC言語の慣用的な記述への修正であり、suffix 関数が返すポインタが NULL でない場合に真となる条件をより簡潔に表現しています。機能的な変更はありません。

これらの変更により、gobuild はGo以外のソースファイルもより柔軟に扱い、ビルドプロセスにおけるファイルの包含/除外をより細かく制御できるようになりました。

関連リンク

  • Go言語の初期のビルドシステムに関する議論やドキュメントは、Goプロジェクトのメーリングリストや初期の設計ドキュメントに散見されます。
  • cgo の現在のドキュメント: https://pkg.go.dev/cmd/cgo
  • Go言語のパッケージに関する公式ドキュメント: https://go.dev/doc/code

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • C言語の標準ライブラリ関数 strstr およびポインタの扱いに関する一般的な知識。
  • Go言語の初期のビルドツールに関するコミュニティの議論(Stack Overflow, Go Forumなど)。
  • gobuild の具体的な動作に関する情報は、現在の go コマンドのドキュメントには直接記載されていないため、Goの初期のコミットログや設計ドキュメントから推測しました。
  • suffix 関数の具体的な実装はコミットに含まれていませんが、C言語における一般的な文字列操作関数のパターンからその動作を推測しました。