[インデックス 12391] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、GOROOT_FINAL環境変数が設定されている場合に、生成されるオブジェクトファイル内のファイル名情報を最終的なインストールパスに書き換える変更を導入しています。これにより、Goのソースツリーがビルドされた場所と異なる最終的なインストール先に配置される場合でも、オブジェクトファイルに含まれるソースファイルパスが正しく参照されるようになります。
コミット
cmd/gc: if $GOROOT_FINAL is set, rewrite file names in object files
GOROOT_FINAL is a build parameter that means "eventually
the Go tree will be installed here". Make the file name information
match that eventual location.
Fixes #3180.
R=ken, ken
CC=golang-dev
https://golang.org/cl/5742043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6e3a7930eb0fd16c10df1d12cf386b50e9dbb75e
元コミット内容
cmd/gc (Goコンパイラの一部) において、$GOROOT_FINAL 環境変数が設定されている場合、オブジェクトファイル内のファイル名を書き換えるようにします。
GOROOT_FINAL は、Goツリーが最終的にインストールされる場所を示すビルドパラメータです。この変更により、ファイル名情報がその最終的な場所に一致するようになります。
Issue #3180 を修正します。
変更の背景
Go言語のビルドプロセスでは、ソースコードがコンパイルされてオブジェクトファイルが生成されます。これらのオブジェクトファイルには、デバッグ情報やスタックトレースなどで使用される元のソースファイルのパス情報が含まれることがあります。
通常、Goのソースツリーをビルドした場所 (GOROOT) と、最終的にGoがインストールされる場所 (GOROOT_FINAL) は同じであるか、あるいはGOROOT_FINALが設定されない場合はGOROOTがそのまま使われます。しかし、特定のシナリオ、例えばクロスコンパイル環境や、ビルド環境と実行環境が異なる場合(例: コンテナ内でビルドし、ホストにデプロイする場合など)には、ビルド時のGOROOTと最終的なインストールパスが異なることがあります。
このような状況で、オブジェクトファイルにビルド時のGOROOTに基づくパスが埋め込まれていると、最終的なインストール先でデバッグ情報が正しく解決されなかったり、ツールがソースファイルを特定できなかったりする問題が発生します。このコミットは、この問題を解決するために、GOROOT_FINALが設定されている場合に、オブジェクトファイル内のパス情報を最終的なインストールパスに書き換えることで、デバッグやツールの互換性を向上させることを目的としています。
具体的には、Issue #3180で報告された問題に対応しています。この問題は、GOROOT_FINALが設定されている環境でビルドされたGoプログラムが、デバッグ時にソースファイルのパスを正しく解決できないというものでした。
前提知識の解説
GOROOT: Go言語のインストールディレクトリを指す環境変数です。Goのツールチェイン(コンパイラ、リンカなど)や標準ライブラリのソースコードがこのディレクトリに配置されます。Goのビルドシステムは、このGOROOTを基準にファイルのパスを解決します。GOROOT_FINAL: Goのビルド時に使用される特別な環境変数です。これは、Goのソースツリーが最終的にインストールされる「予定の」パスを示します。例えば、/usr/local/goにGoをインストールする予定だが、ビルドは一時ディレクトリ/tmp/go-buildで行う場合、GOROOTは/tmp/go-build、GOROOT_FINALは/usr/local/goとなります。この変数は、ビルドされたバイナリやオブジェクトファイルに埋め込まれるパス情報を調整するために利用されます。- Goのビルドプロセス: Goのビルドは複数のステージとツールによって行われます。
cmd/dist: Goの配布ツールであり、Goのソースツリーをビルドするための高レベルなスクリプトやユーティリティを提供します。make.bashやall.bashといったビルドスクリプトのバックエンドとして機能し、コンパイラやリンカの呼び出しを調整します。cmd/gc: Go言語のコンパイラです。Goのソースコードを中間表現に変換し、最終的にオブジェクトファイルを生成します。このオブジェクトファイルには、コンパイルされたコードだけでなく、デバッグ情報やシンボル情報なども含まれます。
- オブジェクトファイル: コンパイラによって生成されるバイナリファイルで、機械語コード、データ、およびメタデータ(シンボルテーブル、デバッグ情報など)を含みます。Goの場合、
.o拡張子を持つファイルがこれに該当します。デバッグ情報には、元のソースファイルのパスや行番号が含まれることが一般的です。 getenv(C言語関数): 指定された環境変数の値を取得する標準Cライブラリ関数です。strcmp(C言語関数): 2つの文字列を比較する標準Cライブラリ関数です。文字列が等しい場合は0を返します。strncmp(C言語関数): 2つの文字列の先頭から指定された文字数だけを比較する標準Cライブラリ関数です。smprint(Go内部関数): Goの内部で使用される文字列フォーマット関数で、sprintfに似ていますが、動的にメモリを割り当てて結果の文字列を返します。
技術的詳細
このコミットの主要な変更は、Goコンパイラ (cmd/gc) のオブジェクトファイル生成部分にあります。具体的には、src/cmd/gc/obj.cファイル内のouthist関数が修正されています。
outhist関数は、コンパイルされたオブジェクトファイルに、ソースファイルの履歴情報(ファイル名や行番号など)を書き込む役割を担っています。この関数内で、GOROOTとGOROOT_FINALという2つの環境変数の値がチェックされます。
-
環境変数の取得と初期化:
outhist関数が最初に呼び出された際に、GOROOTとGOROOT_FINALの環境変数の値を取得します。- もし
GOROOTが設定されていない場合は空文字列に、GOROOT_FINALが設定されていない場合はGOROOTの値にフォールバックします。 GOROOTとGOROOT_FINALが同じ値である場合、パスの書き換えは不要と判断され、以降の処理ではパス書き換えロジックがスキップされます。これは、ビルドパスと最終インストールパスが同じである通常のケースに対応します。
-
パスの書き換えロジック:
outhist関数は、オブジェクトファイルに書き込む各ソースファイルパス (h->name) をループ処理します。- もし
GOROOTとGOROOT_FINALが異なり、かつ現在のファイルパス (p) がGOROOTで始まり、その後にスラッシュ (/) が続く場合(例:/path/to/goroot/src/file.go)、そのパスはGoのソースツリー内のファイルであると判断されます。 - この条件が満たされた場合、
smprint関数を使用して新しいパスが生成されます。新しいパスは、GOROOTの部分をGOROOT_FINALに置き換え、残りの部分(GOROOT以降のパス)をそのまま引き継ぎます。例えば、/tmp/go-build/src/fmt/print.goというパスは、GOROOT_FINALが/usr/local/goであれば/usr/local/go/src/fmt/print.goに書き換えられます。 - 書き換えられた新しいパスは、オブジェクトファイルに書き込まれるファイル名情報として使用されます。
- 動的に割り当てられたメモリは、処理後に
freeされます。
-
src/cmd/dist/build.cの変更:- Goのビルドスクリプトを制御する
cmd/distの一部であるbuild.cに、GOROOTとGOROOT_FINALの環境変数を明示的に設定する行が追加されました。これにより、これらの変数がGoのビルドプロセス全体で確実に利用可能になります。
- Goのビルドスクリプトを制御する
-
test/runの変更:- テストスクリプト
test/runに、unset GOROOT_FINALの行が追加されました。これは、テストの実行環境が他のビルド設定に影響されないようにするため、または特定のテストシナリオでGOROOT_FINALが設定されていない状態をシミュレートするために行われたと考えられます。これにより、テストの独立性と再現性が向上します。
- テストスクリプト
この変更により、Goのビルドシステムは、最終的なインストールパスを考慮した正確なファイルパス情報をオブジェクトファイルに埋め込むことができるようになり、デバッグツールやその他のGoツールがより堅牢に動作するようになります。
コアとなるコードの変更箇所
src/cmd/dist/build.c
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -1351,6 +1351,9 @@ cmdbootstrap(int argc, char **argv)
goversion = findgoversion();
setup();
+ xsetenv("GOROOT", goroot);
+ xsetenv("GOROOT_FINAL", goroot_final);
+
// For the main bootstrap, building for host os/arch.
oldgoos = goos;
oldgoarch = goarch;
src/cmd/gc/obj.c
--- a/src/cmd/gc/obj.c
+++ b/src/cmd/gc/obj.c
@@ -126,10 +126,37 @@ outhist(Biobuf *b)
{
Hist *h;
char *p, ds[] = {'c', ':', '/', 0};
+ char *tofree;
+ int n;
+ static int first = 1;
+ static char *goroot, *goroot_final;
+ if(first) {
+ // Decide whether we need to rewrite paths from $GOROOT to $GOROOT_FINAL.
+ first = 0;
+ goroot = getenv("GOROOT");
+ goroot_final = getenv("GOROOT_FINAL");
+ if(goroot == nil)
+ goroot = "";
+ if(goroot_final == nil)
+ goroot_final = goroot;
+ if(strcmp(goroot, goroot_final) == 0) {
+ goroot = nil;
+ goroot_final = nil;
+ }
+ }
+
+ tofree = nil;
for(h = hist; h != H; h = h->link) {
p = h->name;
if(p) {
+ if(goroot != nil) {
+ n = strlen(goroot);
+ if(strncmp(p, goroot, strlen(goroot)) == 0 && p[n] == '/') {
+ tofree = smprint("%s%s", goroot_final, p+n);
+ p = tofree;
+ }
+ }
if(windows) {
// if windows variable is set, then, we know already,
// pathname is started with windows drive specifier
@@ -161,9 +188,12 @@ outhist(Biobuf *b)
outzfile(b, p);
}
}
-
}
zhist(b, h->line, h->offset);
+ if(tofree) {
+ free(tofree);
+ tofree = nil;
+ }
}
}
test/run
--- a/test/run
+++ b/test/run
@@ -29,6 +29,8 @@ export GOTRACEBACK=0
export LANG=C
unset GREP_OPTIONS # in case user has a non-standard set
+unset GOROOT_FINAL # breaks ./ imports
+
failed=0
PATH=${GOBIN:-$GOROOT/bin}:`pwd`:/bin:/usr/bin:/usr/local/bin
コアとなるコードの解説
src/cmd/dist/build.cの変更
cmdbootstrap関数内で、xsetenv("GOROOT", goroot); と xsetenv("GOROOT_FINAL", goroot_final); が追加されました。これは、Goのビルドプロセス全体でこれらの環境変数が確実に設定され、利用可能であることを保証するためのものです。特に、GOROOT_FINALがビルド時に正しく伝播されるようにすることで、後続のコンパイラ(cmd/gc)がこの値を利用できるようになります。
src/cmd/gc/obj.cの変更
outhist関数は、オブジェクトファイルにデバッグ情報としてソースファイルのパスを書き込む際に呼び出されます。
-
静的変数の導入:
static int first = 1;static char *goroot, *goroot_final;これらは、outhist関数が複数回呼び出されても、GOROOTとGOROOT_FINALの環境変数の取得と初期化を一度だけ行うためのフラグと変数です。 -
初回呼び出し時の環境変数処理:
if(first) { ... }ブロック内で、getenv("GOROOT")とgetenv("GOROOT_FINAL")を使って環境変数の値を取得します。goroot == nilの場合、goroot = "";となり、空文字列として扱われます。goroot_final == nilの場合、goroot_final = goroot;となり、GOROOT_FINALが設定されていなければGOROOTと同じとみなされます。strcmp(goroot, goroot_final) == 0の場合、つまりGOROOTとGOROOT_FINALが同じであれば、パスの書き換えは不要なので、goroot = nil;とgoroot_final = nil;に設定されます。これにより、以降のループでパス書き換えロジックがスキップされ、無駄な処理が省かれます。
-
パス書き換えロジック:
for(h = hist; h != H; h = h->link)ループ内で、各履歴エントリ(ソースファイルパス)について処理が行われます。if(goroot != nil):GOROOTとGOROOT_FINALが異なると判断された場合にのみ、このブロックが実行されます。n = strlen(goroot);:GOROOTの長さを取得します。if(strncmp(p, goroot, strlen(goroot)) == 0 && p[n] == '/'): 現在のファイルパスpがgorootで始まり、かつgorootの直後にスラッシュ (/) が続く場合(例:/home/user/go/src/pkg/foo.goの/home/user/goの部分がgorootと一致する場合)、Goのソースツリー内のファイルであると判断されます。tofree = smprint("%s%s", goroot_final, p+n);:smprint関数を使って新しいパスを生成します。goroot_finalに、元のパスのgoroot以降の部分 (p+n) を結合します。例えば、pが/tmp/go-build/src/fmt/print.goで、gorootが/tmp/go-build、goroot_finalが/usr/local/goの場合、p+nは/src/fmt/print.goとなり、結果として/usr/local/go/src/fmt/print.goがtofreeに格納されます。p = tofree;: 書き換えられた新しいパスが、オブジェクトファイルに書き込まれるファイル名として使用されます。if(tofree) { free(tofree); tofree = nil; }:smprintで動的に割り当てられたメモリは、各ループの最後に解放されます。これによりメモリリークを防ぎます。
test/runの変更
unset GOROOT_FINAL が追加されました。これは、テストスクリプトが実行される際に、GOROOT_FINAL環境変数が設定されていないことを保証するためのものです。これにより、テストが特定のビルド環境に依存せず、より独立して実行できるようになります。また、./で始まるインポートが壊れるのを防ぐというコメントがあり、これはGOROOT_FINALが設定されていると相対パスの解決に影響を与える可能性があることを示唆しています。
関連リンク
- Go Issue #3180: https://github.com/golang/go/issues/3180 - このコミットが修正した元の問題報告。
- Go Code Review (CL) 5742043: https://golang.org/cl/5742043 - この変更のコードレビューページ。
参考にした情報源リンク
- Go言語の公式ドキュメント (GOROOT, GOROOT_FINALに関する情報)
- Go言語のソースコード (cmd/dist, cmd/gcの関連ファイル)
- C言語の標準ライブラリ関数 (getenv, strcmp, strncmp, free) に関する一般的な情報