[インデックス 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) に関する一般的な情報