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

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

このコミットは、Go言語のビルドシステムにおいて、OS X 10.8以降の環境でCコンパイラとしてGCCの代わりにClangを使用するように変更するものです。これは、AppleがOS XにおけるGCCのサポートを非推奨とし、Clangへの移行を推進している状況に対応するための重要な変更です。

コミット

commit 2ddb672ddcbb050521a2c031630c6dc884a78314
Author: Russ Cox <rsc@golang.org>
Date:   Fri Aug 2 14:58:27 2013 -0400

    build: on OS X 10.8 and later, use clang instead of gcc
    
    Fixes #5822.
    Will no doubt cause other problems, but Apple has forced our hand.
    
    R=golang-dev, bradfitz, khr
    CC=golang-dev
    https://golang.org/cl/12350044

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

https://github.com/golang/go/commit/2ddb672ddcbb050521a2c031630c6dc884a78314

元コミット内容

build: on OS X 10.8 and later, use clang instead of gcc

Fixes #5822.
Will no doubt cause other problems, but Apple has forced our hand.

変更の背景

この変更の主な背景は、AppleがOS X(現在のmacOS)において、開発ツールチェーンのデフォルトC/C++/Objective-CコンパイラをGCCからClangへと移行したことにあります。特にOS X 10.8 (Mountain Lion) 以降では、Xcodeに同梱されるgccコマンドが実質的にClangへのシンボリックリンクまたはラッパーとなっており、従来のGCCとは異なる振る舞いをすることがありました。

Go言語のビルドプロセス、特にCgo(GoとCの相互運用機能)を使用する際には、システムにインストールされているCコンパイラに依存します。Appleのこの変更により、Goのビルドシステムが従来のGCCを前提とした動作を続けると、OS X 10.8以降の環境でビルドエラーや予期せぬ問題が発生する可能性がありました。

コミットメッセージにある「Apple has forced our hand.」という表現は、Goチームがこの変更を避けられない状況に追い込まれたことを示唆しています。GoのビルドシステムがOS Xの新しい開発環境に適合し、ユーザーがスムーズにGoをビルド・実行できるようにするために、Clangへの対応が必須となりました。また、関連するIssue #5822は、この問題がGoコミュニティ内で認識され、解決が求められていたことを示しています。

前提知識の解説

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Goの標準ライブラリの一部や、外部のCライブラリを利用するGoアプリケーションで広く使われています。Cgoを使用するGoプログラムをビルドする際には、Goコンパイラだけでなく、C/C++コンパイラ(通常はGCCやClang)も必要となります。Cgoは、GoコードとCコードをリンクするために、Cコンパイラを呼び出してCコードをコンパイルし、その結果をGoのビルドプロセスに統合します。

GCC (GNU Compiler Collection)

GCCは、GNUプロジェクトによって開発された、様々なプログラミング言語に対応するコンパイラ群です。C、C++、Objective-C、Fortran、Ada、Goなどの言語をサポートしています。長らくUnix系システムやLinuxディストリビューションの標準的なC/C++コンパイラとして広く利用されてきました。

Clang

Clangは、LLVMプロジェクトの一部として開発されているC、C++、Objective-C、Objective-C++コンパイラのフロントエンドです。Clangは、GCCと比較して、より高速なコンパイル、より優れたエラーメッセージ、よりモジュール化されたアーキテクチャなどの利点を持つとされています。Appleは、Xcode 4.2以降でClangをデフォルトのコンパイラとして採用し、OS Xの開発環境における主要なコンパイラとして推進してきました。

cmd/dist

cmd/distは、GoのソースコードからGoツールチェーン全体をビルドするためのプログラムです。Goのコンパイラ、リンカ、アセンブラ、その他のツール(goコマンド自体を含む)を構築する役割を担っています。Goのビルドプロセスにおいて、どのCコンパイラを使用するか、どのようなビルドフラグを渡すかといった低レベルな設定を管理する重要な部分です。

uname コマンドとOSバージョン

Unix系システムでは、unameコマンドを使用してシステム情報を取得できます。特にuname -rはカーネルのリリースバージョンを出力します。OS Xの場合、このリリースバージョンはOS Xのバージョン番号と関連付けられています。例えば、OS X 10.6はuname -r10.x.x、OS X 10.8は12.x.xといった形式で出力されます。このコミットでは、uname -rの出力からOS Xのバージョンを判断し、それに基づいて使用するCコンパイラを切り替えるロジックが導入されています。

技術的詳細

このコミットは、GoのビルドシステムがOS X環境でCコンパイラを選択するロジックを根本的に変更しています。

  1. Cコンパイラの動的な選択:

    • src/cmd/dist/unix.cにおいて、uname -rの出力からOS Xのバージョンを解析し、osx変数にOS Xのメジャーバージョン(例: 10.8なら8)を格納するようになりました。
    • osx >= 8(OS X 10.8以降)の場合、defaultclangという新しいグローバル変数をtrueに設定します。
    • このdefaultclangの値は、src/cmd/dist/build.cinit関数内で参照され、defaultccという新しいグローバル変数の初期値に影響を与えます。defaultclangtrueであればclangが、そうでなければgccdefaultccに設定されます。
    • defaultccは、Goツールチェーンのビルド時に使用されるデフォルトのCコンパイラ名として、zdefaultcc.goというファイルに書き込まれます。
  2. Cgoにおけるコンパイラ選択の変更:

    • src/cmd/cgo/gcc.go内のgccName関数がgccBaseCmd関数にリファクタリングされました。
    • gccBaseCmdは、環境変数CCGCCの順にチェックし、設定されていなければ、cmd/distによって生成されたdefaultCCzdefaultcc.goに定義されている)を使用するように変更されました。これにより、CgoがGoツールチェーン全体のビルド設定と一貫したコンパイラを使用するようになります。
    • gccCmdgccDefinesといったCgoの内部関数も、p.gccName()の代わりにp.gccBaseCmd()を使用するように更新され、コンパイラ名の取得方法が統一されました。
  3. __gcc_struct__属性の扱い:

    • src/cmd/cgo/out.goでは、__gcc_struct__属性の使用に関するロジックが変更されました。これは、特定のGCCのバグ(http://gcc.gnu.org/PR52991)やGoのIssue #5603に対応するためのもので、clangを使用している場合はこの属性を付与しないように調整されています。p.gccName()の代わりにp.gccBaseCmd()[0]を参照することで、新しいコンパイラ選択ロジックに対応しています。
  4. go buildコマンドの変更:

    • src/cmd/go/build.goでは、go buildコマンドがCコンパイラを決定する際に、ハードコードされた"gcc"ではなく、defaultCCzdefaultcc.goに定義されている)を使用するように変更されました。これにより、go buildもOS Xのバージョンに応じた適切なコンパイラを選択するようになります。
  5. テストスクリプトの調整:

    • src/run.bashでは、cgo -godefsのテスト実行条件に[ "$GOOS" == darwin ]が追加されました。これは、OS X上でClangを使用した場合にcgo -godefsがうまく動作しない可能性があるため、OS Xではこのテストをスキップする、または異なる振る舞いを許容するための調整と考えられます。

これらの変更により、GoのビルドシステムはOS Xのバージョンを検出し、それに応じて自動的に適切なCコンパイラ(OS X 10.8以降ではClang)を選択するようになり、ビルドの互換性と安定性が向上しました。

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

src/cmd/cgo/gcc.go

--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -646,19 +646,20 @@ func (p *Package) rewriteRef(f *File) {
 	}
 }
 
-// gccName returns the name of the compiler to run.  Use $CC if set in
-// the environment, otherwise just "gcc".
-
-func (p *Package) gccName() string {
+// gccBaseCmd returns the start of the compiler command line.
+// It uses $CC if set, or else $GCC, or else the compiler recorded
+// during the initial build as defaultCC.
+// defaultCC is defined in zdefaultcc.go, written by cmd/dist.
+func (p *Package) gccBaseCmd() []string {
 	// Use $CC if set, since that's what the build uses.
-	if ret := os.Getenv("CC"); ret != "" {
+	if ret := strings.Fields(os.Getenv("CC")); len(ret) > 0 {
 		return ret
 	}
-	// Fall back to $GCC if set, since that's what we used to use.
-	if ret := os.Getenv("GCC"); ret != "" {
+	// Try $GCC if set, since that's what we used to use.
+	if ret := strings.Fields(os.Getenv("GCC")); len(ret) > 0 {
 		return ret
 	}
-	return "gcc"
+	return strings.Fields(defaultCC)
 }
 
 // gccMachine returns the gcc -m flag to use, either "-m32", "-m64" or "-marm".
@@ -681,17 +682,16 @@ func gccTmp() string {
 // gccCmd returns the gcc command line to use for compiling
 // the input.
 func (p *Package) gccCmd() []string {
-	c := []string{
-		p.gccName(),
+	c := append(p.gccBaseCmd(),
 		"-Wall",                             // many warnings
 		"-Werror",                           // warnings are errors
-		"-o" + gccTmp(),                     // write object to tmp
+		"-o"+gccTmp(),                       // write object to tmp
 		"-gdwarf-2",                         // generate DWARF v2 debugging symbols
 		"-fno-eliminate-unused-debug-types", // gets rid of e.g. untyped enum otherwise
 		"-c",  // do not link
 		"-xc", // input language is C
-	}
-	if strings.Contains(p.gccName(), "clang") {
+	)
+	if strings.Contains(c[0], "clang") {
 		c = append(c,
 			"-ferror-limit=0",
 			// Apple clang version 1.7 (tags/Apple/clang-77) (based on LLVM 2.9svn)
@@ -800,7 +800,7 @@ func (p *Package) gccDebug(stdin []byte) (*dwarf.Data, binary.ByteOrder, []byte)
 // #defines that gcc encountered while processing the input
 // and its included files.
 func (p *Package) gccDefines(stdin []byte) string {
-	base := []string{p.gccName(), "-E", "-dM", "-xc"}
+	base := append(p.gccBaseCmd(), "-E", "-dM", "-xc")
 	base = append(base, p.gccMachine()...)\n 	stdout, _ := runGcc(stdin, append(append(base, p.GccOptions...), "-"))
 	return stdout

src/cmd/dist/build.c

--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -26,8 +26,9 @@ char *tooldir;
 char *gochar;
 char *goversion;
 char *slash;	// / for unix, \ for windows
-\n-bool	rebuildall = 0;
+char *defaultcc;
+bool	rebuildall;
+bool defaultclang;
 
 static bool shouldbuild(char*, char*);
 static void copy(char*, char*, int);
@@ -146,6 +147,20 @@ init(void)
 		if(!streq(goextlinkenabled, "0") && !streq(goextlinkenabled, "1"))
 			fatal("unknown $GO_EXTLINK_ENABLED %s", goextlinkenabled);
 	}
+	
+	xgetenv(&b, "CC");
+	if(b.len == 0) {
+		// Use clang on OS X, because gcc is deprecated there.
+		// Xcode for OS X 10.9 Mavericks will ship a fake "gcc" binary that
+		// actually runs clang. We prepare different command
+		// lines for the two binaries, so it matters what we call it.
+		// See golang.org/issue/5822.
+		if(defaultclang)
+			bprintf(&b, "clang");
+		else
+			bprintf(&b, "gcc");
+	}
+	defaultcc = btake(&b);
 
 	xsetenv("GOROOT", goroot);
 	xsetenv("GOARCH", goarch);
@@ -525,6 +540,9 @@ static struct {
 		"../ld/*",
 		"enam.c",
 	}},
+	{"cmd/go", {
+		"zdefaultcc.go",
+	}},
 	{"cmd/", {
 		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libmach.a",
 		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libbio.a",
@@ -557,6 +575,7 @@ static struct {
 	{"opnames.h", gcopnames},
 	{"enam.c", mkenam},
 	{"zasm_", mkzasm},
+	{"zdefaultcc.go", mkzdefaultcc},
 	{"zsys_", mkzsys},
 	{"zgoarch_", mkzgoarch},
 	{"zgoos_", mkzgoos},
@@ -616,10 +635,7 @@ install(char *dir)\n 
 	// set up gcc command line on first run.\n 	if(gccargs.len == 0) {\n-\t\txgetenv(&b, "CC");\n-\t\tif(b.len == 0)\n-\t\t\tbprintf(&b, "gcc");\n-\t\tclang = contains(bstr(&b), "clang");\n+\t\tbprintf(&b, "%s", defaultcc);\n 	\tsplitfields(&gccargs, bstr(&b));\n 	\tfor(i=0; i<nelem(proto_gccargs); i++)\n 	\t\tvadd(&gccargs, proto_gccargs[i]);\n@@ -1443,6 +1459,7 @@ cmdenv(int argc, char **argv)\n 	if(argc > 0)\n 	\tusage();\n \n+\txprintf(format, "CC", defaultcc);\n 	xprintf(format, "GOROOT", goroot);\n 	xprintf(format, "GOBIN", gobin);\n 	xprintf(format, "GOARCH", goarch);

src/cmd/dist/unix.c

--- a/src/cmd/dist/unix.c
+++ b/src/cmd/dist/unix.c
@@ -651,6 +651,7 @@ int
 main(int argc, char **argv)
 {
 	Buf b;
+	int osx;
 	struct utsname u;
 
 	setvbuf(stdout, nil, _IOLBF, 0);
@@ -700,17 +701,23 @@ main(int argc, char **argv)
 	if(strcmp(gohostarch, "arm") == 0)
 		maxnbg = 1;
 
-\t// The OS X 10.6 linker does not support external
-\t// linking mode; see
-\t// https://code.google.com/p/go/issues/detail?id=5130 .
-\t// The mapping from the uname release field to the OS X
-\t// version number is complicated, but basically 10 or under is
-\t// OS X 10.6 or earlier.
+\t// The OS X 10.6 linker does not support external linking mode.
+\t// See golang.org/issue/5130.
+\t//
+\t// OS X 10.6 does not work with clang either, but OS X 10.9 requires it.
+\t// It seems to work with OS X 10.8, so we default to clang for 10.8 and later.
+\t// See golang.org/issue/5822.
+\t//
+\t// Roughly, OS X 10.N shows up as uname release (N+4),
+\t// so OS X 10.6 is uname version 10 and OS X 10.8 is uname version 12.
 	if(strcmp(gohostos, "darwin") == 0) {
 		if(uname(&u) < 0)
 			fatal("uname: %s", strerror(errno));
-\t\tif(u.release[1] == '.' || hasprefix(u.release, "10"))
+\t\tosx = atoi(u.release) - 4;
+\t\tif(osx <= 6)
 			goextlinkenabled = "0";
+\t\tif(osx >= 8)
+\t\t\tdefaultclang = 1;
 	}
 
 	init();

コアとなるコードの解説

src/cmd/cgo/gcc.goの変更

  • gccNameからgccBaseCmdへのリファクタリング: 以前はgccNameという関数が単一の文字列(コンパイラ名)を返していましたが、gccBaseCmdという関数に変わり、コンパイラコマンドとその引数のスライス([]string)を返すようになりました。これにより、CCGCC環境変数に複数の単語(例: clang -arch x86_64)が設定されている場合にも対応できるようになりました。
  • defaultCCの導入: 環境変数CCGCCが設定されていない場合、以前はハードコードされた"gcc"を使用していましたが、この変更によりdefaultCCという新しい変数を使用するようになりました。このdefaultCCは、cmd/distによってビルド時に決定され、zdefaultcc.goというファイルに書き込まれます。これにより、Cgoのコンパイラ選択がビルドシステム全体の設定と同期されるようになります。
  • clang検出ロジックの変更: strings.Contains(p.gccName(), "clang")からstrings.Contains(c[0], "clang")に変更されました。これは、cスライスの最初の要素(コンパイラ名)をチェックすることで、clang固有のフラグ(-ferror-limit=0など)を追加するかどうかを判断します。

src/cmd/dist/build.cの変更

  • defaultccdefaultclangの導入: char *defaultcc;bool defaultclang;という新しいグローバル変数が追加されました。defaultccはGoツールチェーンが使用するデフォルトのCコンパイラ名を保持し、defaultclangはOS XでClangをデフォルトにするかどうかを示すフラグです。
  • init関数でのdefaultccの決定: init関数内で、環境変数CCが設定されていない場合、defaultclangの値に基づいてdefaultcc"clang"または"gcc"に設定されるようになりました。これは、OS X 10.9 (Mavericks) でgccが実質的にClangのラッパーとなることを見越した対応です。
  • zdefaultcc.goの生成: cmd/goのビルドターゲットにzdefaultcc.goが追加され、mkzdefaultcc関数が呼び出されるようになりました。この関数は、defaultccの値をGoのソースコードとして書き出し、Goのビルドプロセス全体で利用できるようにします。
  • install関数でのgccargs初期化の変更: gccargs(GCCコマンドライン引数)の初期化時に、以前は環境変数CCを直接読み込んでいましたが、defaultccを使用するように変更されました。これにより、ビルドシステム全体で一貫したコンパイラ設定が適用されます。

src/cmd/dist/unix.cの変更

  • OS Xバージョンの検出: main関数内で、uname(&u)で取得したシステム情報からu.release(カーネルリリースバージョン)を解析し、atoi(u.release) - 4という計算でOS Xのメジャーバージョン(例: 10.8なら8)をosx変数に格納するロジックが追加されました。
  • defaultclangの設定: osx >= 8(OS X 10.8以降)の場合にdefaultclang = 1;が設定されるようになりました。これにより、OS X 10.8以降の環境では、Goのビルドシステムが自動的にClangをデフォルトのCコンパイラとして選択するようになります。
  • 外部リンキングの無効化: OS X 10.6以前(osx <= 6)では、外部リンキングモードがサポートされていないため、goextlinkenabled = "0";が設定されます。これは既存のロジックですが、OS Xバージョンの検出ロジックと統合されました。

これらの変更により、GoのビルドシステムはOS Xのバージョンを正確に判断し、それに基づいて最適なCコンパイラ(特にClang)を自動的に選択・設定できるようになり、OS X環境でのGoのビルドの信頼性と互換性が大幅に向上しました。

関連リンク

参考にした情報源リンク