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

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

このコミットは、Go言語の初期のビルドツールである gobuild に、コードカバレッジ計測のためのルールを追加し、main パッケージに属するファイルの取り扱いを改善するものです。具体的には、gobuild がパッケージリストを生成する際に main パッケージを無視するように変更され、また、Goの標準ライブラリの各Makefileにコードカバレッジを計測するための coverage ターゲットが追加されています。

コミット

commit 87b112440739f219f706bd7e48bc05327d88eee6
Author: Russ Cox <rsc@golang.org>
Date:   Wed Nov 19 12:52:30 2008 -0800

    gobuild: add coverage rule, ignore files in package main.
    
    R=r
    DELTA=55  (41 added, 11 deleted, 3 changed)
    OCL=19594
    CL=19598

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

https://github.com/golang/go/commit/87b112440739f219f706bd7e48bc05327d88eee6

元コミット内容

gobuild: add coverage rule, ignore files in package main.

このコミットメッセージは、gobuild ツールにカバレッジ計測のルールを追加し、main パッケージ内のファイルを無視するように変更したことを簡潔に示しています。

変更の背景

このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の非常に初期の段階です。当時のGoのビルドシステムは現在とは異なり、gobuild のようなツールが使われていました。

変更の背景には以下の点が考えられます。

  1. コード品質の向上: ソフトウェア開発において、テストカバレッジはコードの品質と信頼性を測る重要な指標です。Go言語の開発初期段階から、テストとカバレッジ計測の仕組みをビルドシステムに組み込むことで、開発者がより堅牢なコードを書くことを奨励し、バグの早期発見に繋げようとしたと考えられます。
  2. main パッケージの特殊性への対応: Go言語において、package main は実行可能なプログラムのエントリポイントとなる特殊なパッケージです。通常のライブラリパッケージとは異なり、他のパッケージからインポートされることを意図していません。gobuild がパッケージリストを処理する際に main パッケージを通常のライブラリパッケージと同様に扱ってしまうと、不必要なビルド依存関係や誤解を招く挙動が発生する可能性があります。そのため、main パッケージに属するファイルを適切に無視することで、ビルドプロセスの正確性と効率性を向上させる必要がありました。
  3. ビルドシステムの進化: Go言語のビルドシステムは、gobuild から go build コマンドへと進化していきます。このコミットは、その進化の過程で、ビルドツールがより洗練され、Go言語の特性(特にパッケージシステム)をより適切に扱うように改善されていく一環と見ることができます。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびビルドシステムに関する初期の概念を理解しておく必要があります。

  • Go言語のパッケージシステム: Go言語はコードをパッケージという単位で管理します。関連する機能は同じパッケージにまとめられ、他のパッケージからインポートして利用できます。
    • package main: 特別なパッケージで、実行可能なプログラムのエントリポイント(main 関数)を含みます。main パッケージは通常、他のパッケージからインポートされることはありません。
  • gobuild: このコミットが行われた2008年当時のGo言語の初期のビルドツールです。現在の go build コマンドの前身にあたります。Goのソースコードをコンパイルし、実行可能ファイルやライブラリを生成する役割を担っていました。
  • Makefile: make ユーティリティが使用するビルド自動化スクリプトです。Go言語の初期のプロジェクトでは、ビルドプロセスを定義するために広く使われていました。ターゲット(例: all, clean, test, install)と、そのターゲットをビルドするためのコマンドが記述されます。
  • gotest: Go言語のテストフレームワークを実行するためのコマンドです。現在の go test コマンドに相当します。Goのテストコード(_test.go ファイルに記述)を実行し、テスト結果を出力します。
  • 6cov: Go言語の初期のコードカバレッジツールです。当時のGoコンパイラが 6g (64-bit Go compiler) などと呼ばれていたことに由来します。gotest と組み合わせて使用され、テストが実行された際に、どのコード行が実行されたか(カバレッジ情報)を収集・報告します。
  • コードカバレッジ: ソフトウェアテストにおいて、テストケースが実行された際に、プログラムのソースコードのうちどれくらいの割合が実行されたかを示す指標です。カバレッジが高いほど、テストがコードの多くの部分を網羅していることを意味し、潜在的なバグを見つけやすくなります。

技術的詳細

このコミットの技術的な変更点は大きく分けて二つあります。

  1. gobuild における main パッケージの無視:

    • src/cmd/gobuild/gobuild.c 内の getpkg 関数が変更されています。この関数は、与えられたファイルがどのGoパッケージに属するかを判断し、そのパッケージ名を返す役割を担っています。
    • 変更前は、main パッケージも他の通常のパッケージと同様にパッケージリストに追加されていました。
    • 変更後は、strcmp(p, "main") == 0 という条件が追加され、もしパッケージ名が "main" であれば、そのパッケージをパッケージリストに追加せず、単に "main" という文字列を返すように変更されています。
    • さらに、main 関数内でジョブを処理するループにおいても、job[njob].pkg"main" であれば、そのジョブをスキップする continue 文が追加されています。
    • これにより、gobuild は実行可能ファイルのエントリポイントである main パッケージを、ライブラリパッケージとは異なる特殊なものとして扱い、ビルドプロセスにおいて不必要な処理や依存関係の生成を避けることができます。
  2. コードカバレッジルールの追加:

    • src/cmd/gobuild/gobuild.cpreamble (これはおそらく、gobuild が生成するMakefileのテンプレートの一部) に、coverage という新しいターゲットが追加されています。
    • この coverage ターゲットは、以下のコマンドを実行します。
      • gotest: テストを実行します。
      • 6cov -g pwd | grep -v '^test.*\\.go:': 6cov ツールを使ってカバレッジ情報を生成します。-g pwd`` は現在の作業ディレクトリをカバレッジ計測の対象として指定していると考えられます。grep -v '^test.*\\.go:' は、テストファイル自体のカバレッジ情報(テストコードが実行されたことによるカバレッジ)を除外するためのフィルタリングです。これにより、純粋にプロダクションコードのカバレッジのみを報告しようとしています。
    • 同様に、src/lib/fmt/Makefilesrc/lib/http/Makefilesrc/lib/math/Makefilesrc/lib/net/Makefilesrc/lib/os/Makefilesrc/lib/reflect/Makefilesrc/lib/strconv/Makefilesrc/lib/syscall/Makefile といったGo標準ライブラリの各Makefileにも、この coverage ターゲットが追加されています。これにより、各ライブラリのテストカバレッジを簡単に計測できるようになりました。
    • src/lib/http/Makefile では、main.atriv.$O といった、おそらく main パッケージや簡単な実行可能ファイルに関連するビルドターゲットが削除されています。これは、main パッケージのビルドを gobuild の一般的なパッケージビルドフローから切り離し、より適切に処理するための変更の一部である可能性が高いです。

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

src/cmd/gobuild/gobuild.c

// getpkg 関数内
@@ -227,6 +227,9 @@ getpkg(char *file)
 					return pkg[i];
 				}
 			}
+			// don't put main in the package list
+			if(strcmp(p, "main") == 0)
+				return "main";
 			npkg++;
 			pkg = erealloc(pkg, npkg*sizeof pkg[0]);
 			pkg[i] = emalloc(strlen(p)+1);

// preamble 配列内 (Makefile テンプレート)
@@ -285,6 +288,10 @@ char preamble[] =
 	"test: packages\\n"
 	"\\tgotest\\n"
 	"\\n"
+	"coverage: packages\\n"
+	"\\tgotest\\n"
+	"\\t6cov -g `pwd` | grep -v '^test.*\\\\.go:'\\n"
+	"\\n"
 	"%%.$O: %%.go\\n"
 	"\\t$(GC) $*.go\\n"
 	"\\n"

// main 関数内
@@ -485,6 +492,8 @@ main(int argc, char **argv)
 		job[njob].name = argv[i];
 		job[njob].pass = -1;
 		job[njob].pkg = getpkg(argv[i]);
+		if(job[njob].pkg && strcmp(job[njob].pkg, "main") == 0)
+			continue;
 		njob++;
 	}

src/lib/fmt/Makefile (他のライブラリのMakefileも同様)

@@ -18,6 +18,10 @@ clean:
 test: packages
 	gotest
 
+coverage: packages
+	gotest
+	6cov -g `pwd` | grep -v '^test.*\.go:'
+
 %.$O: %.go
 	$(GC) $*.go

src/lib/http/Makefile (一部削除)

@@ -39,11 +43,7 @@ O3=\
 O4=\
 	server.$O\
 
-O5=\
-\ttriv.$O\\\
-\n-http.a: a1 a2 a3 a4 a5
-main.a: a1 a2 a3 a4 a5
+http.a: a1 a2 a3 a4
 
 a1:	$(O1)\
 	$(AR) grc http.a url.$O
@@ -61,26 +61,19 @@ a4:\t$(O4)\
 	$(AR) grc http.a server.$O\
 	rm -f $(O4)\
 
-a5:\t$(O5)\
-\t$(AR) grc main.a triv.$O\
-\trm -f $(O5)\
-\
 newpkg: clean
 	$(AR) grc http.a
-\t$(AR) grc main.a
 
 $(O1): newpkg
 $(O2): a1
 $(O3): a2
 $(O4): a3
-$(O5): a4
 
 nuke: clean
-\trm -f $(GOROOT)/pkg/http.a $(GOROOT)/pkg/main.a
+\trm -f $(GOROOT)/pkg/http.a
 
-packages: http.a main.a
+packages: http.a
 
 install: packages
 \tcp http.a $(GOROOT)/pkg/http.a
-\tcp main.a $(GOROOT)/pkg/main.a

コアとなるコードの解説

src/cmd/gobuild/gobuild.c の変更

  • getpkg 関数の変更:
    • if(strcmp(p, "main") == 0) return "main"; の追加は、gobuild がソースファイルからパッケージ名を抽出する際に、もしそれが "main" パッケージであれば、特別な処理を行うことを示しています。これにより、main パッケージが通常のライブラリパッケージのリストに誤って含まれることを防ぎます。これは、main パッケージが実行可能ファイルのエントリポイントであり、他のパッケージからインポートされることを意図していないというGoの設計思想に合致させるための重要な変更です。
  • preamble (Makefile テンプレート) への coverage ルールの追加:
    • この変更は、gobuild が生成するMakefileに、コードカバレッジ計測のための標準的なターゲットを組み込むものです。
    • coverage: packages は、カバレッジ計測を行う前に、まず packages ターゲット(通常はすべてのパッケージをビルドする)を実行することを意味します。
    • gotest はGoのテストを実行します。
    • 6cov -g pwd | grep -v '^test.*\\.go:' は、テスト実行後に 6cov ツールを使ってカバレッジレポートを生成します。grep -v '^test.*\\.go:' は、テストコード自体のカバレッジ情報(テストコードが実行されたことによるカバレッジ)を除外するためのフィルタリングです。これにより、純粋にプロダクションコードのカバレッジのみを報告しようとしています。これは、テストコードのカバレッジは通常、プロダクションコードの品質指標としては重要ではないため、ノイズを除去する目的があります。
  • main 関数内のジョブ処理の変更:
    • if(job[njob].pkg && strcmp(job[njob].pkg, "main") == 0) continue; の追加は、gobuild がビルドジョブを処理する際に、main パッケージに属するファイル(ジョブ)をスキップすることを意味します。これは getpkg 関数の変更と連携しており、main パッケージが通常のライブラリパッケージとは異なる方法で扱われるべきであることを強調しています。これにより、main パッケージのビルドが重複したり、不適切な依存関係が生成されたりするのを防ぎます。

src/lib/*/Makefile の変更

  • Go標準ライブラリの各Makefileに coverage ターゲットが追加されたことは、Go言語の初期段階から、各ライブラリのテストカバレッジを簡単に計測できる環境を整備しようとしていたことを示しています。これは、ライブラリの品質保証と継続的な改善にとって不可欠な要素です。

src/lib/http/Makefile の変更

  • O5 変数からの triv.$O の削除、http.a および main.a のビルドターゲットからの a5 の削除、そして main.a 関連のクリーンアップやインストール処理の削除は、http パッケージが main パッケージや特定の「trivial」な実行可能ファイルと密接に結合してビルドされる必要がなくなったことを示唆しています。これは、main パッケージのビルドが gobuild のより一般的なメカニズムによって処理されるようになったか、あるいは http パッケージのビルドプロセスがよりモジュール化され、独立性が高まったことを意味する可能性があります。

これらの変更は、Go言語のビルドシステムが初期段階から、パッケージの特性を考慮し、テストと品質保証の仕組みを組み込みながら進化していった過程を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のGitHubリポジトリのコミット履歴
  • 一般的なソフトウェア開発におけるコードカバレッジの概念