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

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

このコミットは、Go言語のビルドツール (cmd/go) における、$GOROOT 内の標準パッケージの扱いに関する挙動を修正するものです。具体的には、$GOROOT に存在するコードは常に最新であると仮定し、コンパイラやリンカのタイムスタンプに基づく陳腐化チェックを行わないように変更します。これにより、Goバイナリのタイムスタンプが保持されていない環境(例:一部のバイナリ配布)で、書き込み不可能な標準パッケージが不必要に再ビルドされる問題を回避します。

コミット

commit e80fccb441ac73a206bd99fb7f0dbea3eb9cc149
Author: Joel Sing <jsing@google.com>\nDate:   Thu Sep 27 00:00:50 2012 +1000

    cmd/go: assume that code in $GOROOT is up to date
    
    Do not check compiler/linker timestamps for packages that are in the
    $GOROOT. Avoids trying to rebuild non-writable standard packages when
    timestamps have not been retained on the Go binaries.
    
    Fixes #4106.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6533053

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

https://github.com/golang/go/commit/e80fccb441ac73a206bd99fb7f0dbea3eb9cc149

元コミット内容

cmd/go: $GOROOT 内のコードは最新であると仮定する。

$GOROOT 内のパッケージに対して、コンパイラ/リンカのタイムスタンプチェックを行わない。これにより、Goバイナリのタイムスタンプが保持されていない場合に、書き込み不可能な標準パッケージが再ビルドされようとするのを回避する。

Fixes #4106.

変更の背景

この変更は、主にGoのビルドシステムがパッケージの「陳腐化 (stale)」を判断する際の挙動に関連しています。Goのビルドツールは、ソースファイルやコンパイル済みパッケージが、それらを生成したコンパイラやリンカよりも古い場合に「陳腐化している」と判断し、再ビルドを試みます。

しかし、このヒューリスティックにはいくつかの問題がありました。

  1. Issue 3036: コンパイラやリンカのバイナリが「バックデート」されている(つまり、ファイルシステム上のタイムスタンプが実際のビルド日時よりも古い)場合、このヒューリスティックが正しく機能しないことが指摘されていました。これは、特にバイナリ配布版のGoを使用している場合に発生しうる問題です。
  2. Issue 4106: $GOROOT にインストールされている標準ライブラリのパッケージは、通常、ユーザーが直接変更するものではなく、Goのインストール時に提供されるものです。これらのパッケージはしばしば書き込み保護されており、ユーザーが再ビルドすることは意図されていません。しかし、コンパイラやリンカのタイムスタンプが何らかの理由で標準パッケージのタイムスタンプよりも新しくなってしまうと、go build コマンドがこれらの書き込み不可能なパッケージを再ビルドしようとしてエラーになる、という問題が発生していました。これは、特にGoのバージョンアップグレード後や、異なる環境でビルドされたGoバイナリを使用した場合に顕著でした。

このコミットは、Issue 4106に直接対処し、$GOROOT 内のパッケージに対する不必要な再ビルド試行を防ぐことを目的としています。

前提知識の解説

  • $GOROOT: Goのインストールディレクトリを指す環境変数です。Goの標準ライブラリのソースコードや、Goツールチェイン(コンパイラ、リンカなど)のバイナリが格納されています。
  • Goのビルドプロセス: Goのビルドツール (go build, go install など) は、依存関係を解決し、ソースコードをコンパイルして実行可能なバイナリやパッケージアーカイブを生成します。この際、効率化のために、既にコンパイル済みのパッケージが最新であるかどうかを判断するメカニズムを持っています。
  • 陳腐化 (Staleness) チェック: Goツールは、パッケージが再ビルドを必要とするかどうかを判断するために、いくつかのヒューリスティックを使用します。その一つが、コンパイル済みパッケージのタイムスタンプと、それらを生成したコンパイラやリンカのタイムスタンプを比較する方法です。
    • コンパイラより古い場合: 通常のパッケージ(ライブラリ)は、それらをコンパイルしたコンパイラバイナリよりも古い場合、陳腐化していると見なされます。
    • リンカより古い場合: コマンド(実行可能ファイル)は、それらをリンクしたリンカバイナリよりも古い場合、陳腐化していると見なされます。
  • タイムスタンプの重要性: ファイルの最終更新タイムスタンプは、ビルドシステムが依存関係の変更を検出し、必要な部分だけを再ビルドするための重要な情報源です。しかし、ファイルコピーやアーカイブ展開の際にタイムスタンプが保持されない場合や、システム時刻のずれなどによって、この情報が信頼できなくなることがあります。

技術的詳細

Goのビルドツール (cmd/go) の内部では、isStale という関数がパッケージが陳腐化しているかどうかを判断しています。この関数は、パッケージのビルド情報と、現在のツールチェイン(コンパイラ、リンカ)のタイムスタンプを比較します。

変更前のコードでは、isStale 関数内で、パッケージがコンパイラやリンカよりも古いかどうかを無条件にチェックしていました。

if olderThan(buildToolchain.compiler()) {
    return true
}
if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
    return true
}

ここで olderThan は、対象のファイル(この場合はコンパイル済みパッケージ)が引数で指定されたファイル(コンパイラやリンカ)よりも古いタイムスタンプを持つ場合に true を返します。

このコミットでは、この陳腐化チェックのロジックに条件を追加しました。具体的には、パッケージのルートディレクトリが $GOROOT と異なる場合のみ、このタイムスタンプに基づく陳腐化チェックを行うように変更しました。

if p.Root != goroot { // goroot は $GOROOT のパス
    if olderThan(buildToolchain.compiler()) {
        return true
    }
    if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
        return true
    }
}

この変更により、$GOROOT 内のパッケージ(p.Root == goroot の場合)は、コンパイラやリンカのタイムスタンプに関わらず、常に最新であると見なされるようになります。これにより、書き込み不可能な標準パッケージが不必要に再ビルドされようとする問題が解決されます。

このアプローチは、$GOROOT 内のコードがGoの配布物の一部であり、ユーザーが変更することを意図していないという前提に基づいています。したがって、これらのパッケージが「陳腐化」していると判断されることは、通常、ビルド環境の問題(タイムスタンプの不整合など)に起因すると考えられます。

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

src/cmd/go/pkg.go ファイルの isStale 関数が変更されています。

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -521,14 +521,19 @@ func isStale(p *Package, topRoot map[string]bool) bool {
  	// As a courtesy to developers installing new versions of the compiler
  	// frequently, define that packages are stale if they are
  	// older than the compiler, and commands if they are older than
-	// the linker.  This heuristic will not work if the binaries are back-dated,\n-	// as some binary distributions may do, but it does handle a very\n-	// common case.  See issue 3036.\n-	if olderThan(buildToolchain.compiler()) {\n-	\treturn true\n-	}\n-	if p.build.IsCommand() && olderThan(buildToolchain.linker()) {\n-	\treturn true\n+	// the linker.  This heuristic will not work if the binaries are
+	// back-dated, as some binary distributions may do, but it does handle
+	// a very common case.
+	// See issue 3036.
+	// Assume code in $GOROOT is up to date, since it may not be writeable.
+	// See issue 4106.
+	if p.Root != goroot {
+		if olderThan(buildToolchain.compiler()) {
+			return true
+		}
+		if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
+			return true
+		}
 	}
 
  	// Have installed copy, probably built using current compilers,\n

コアとなるコードの解説

変更は src/cmd/go/pkg.goisStale 関数内で行われています。

  • 変更前:

    if olderThan(buildToolchain.compiler()) {
        return true
    }
    if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
        return true
    }
    

    この部分では、パッケージがコンパイラやリンカよりも古い場合に、無条件に true(陳腐化している)を返していました。これは、開発者が頻繁に新しいバージョンのコンパイラをインストールする際に、パッケージを最新の状態に保つための「おもてなし」的なヒューリスティックでした。しかし、バイナリがバックデートされている場合(Issue 3036)や、$GOROOT 内の書き込み不可能なパッケージ(Issue 4106)で問題を引き起こしていました。

  • 変更後:

    // Assume code in $GOROOT is up to date, since it may not be writeable.
    // See issue 4106.
    if p.Root != goroot {
        if olderThan(buildToolchain.compiler()) {
            return true
        }
        if p.build.IsCommand() && olderThan(buildToolchain.linker()) {
            return true
        }
    }
    

    追加された if p.Root != goroot { ... } という条件ブロックが最も重要な変更点です。

    • p.Root は現在処理しているパッケージのルートディレクトリを示します。
    • goroot$GOROOT 環境変数の値、つまりGoのインストールディレクトリのパスです。
    • この条件 p.Root != goroot は、「現在のパッケージが $GOROOT の外にある場合」を意味します。
    • この条件が true の場合、つまりパッケージが $GOROOT の外にあるユーザーのコードである場合にのみ、以前のコンパイラ/リンカのタイムスタンプに基づく陳腐化チェックが実行されます。
    • 逆に、p.Root == goroot の場合、つまりパッケージが $GOROOT 内の標準ライブラリである場合は、この条件ブロック全体がスキップされ、タイムスタンプに基づく陳腐化チェックは行われません。これにより、$GOROOT 内のパッケージは常に最新であると見なされ、不必要な再ビルドが回避されます。

この変更は、Goのビルドシステムが、ユーザーが開発しているコードと、Goの配布物として提供される標準ライブラリとを区別し、それぞれに適切な陳腐化判断ロジックを適用するように改善されたことを示しています。

関連リンク

参考にした情報源リンク

  • Go issue tracker (上記Issueへのリンク)
  • Goのソースコード (特に src/cmd/go/pkg.go の関連部分)
  • Goのビルドプロセスに関する一般的なドキュメントや解説記事 (具体的なURLは検索結果によるため省略)
  • $GOROOT およびGoの環境変数に関するドキュメント (具体的なURLは検索結果によるため省略)