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

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

このコミットは、Go言語のコマンドラインツール cmd/go におけるビルドプロセスの一部を修正するものです。具体的には、ビルド時にコンパイラに渡されるインクルードディレクトリのパスが重複して追加されるのを防ぐための変更が行われています。これにより、ビルドの効率性が向上し、潜在的なビルドエラーや警告が回避されます。

コミット

commit 1feecdd633c1943e5c4a0ced3a14788ee00f343c
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Mar 2 11:31:13 2012 -0500

    cmd/go: avoid repeated include dirs.
    
    Fixes #3171.
    
    R=golang-dev, rsc
    CC=golang-dev, remy
    https://golang.org/cl/5724045

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

https://github.com/golang/go/commit/1feecdd633c1943e5c4a0ced3a14788ee00f343c

元コミット内容

このコミットは、src/cmd/go/build.go ファイルに対して1行の追加と1行の削除を行っています。変更の目的は、go build コマンドがコンパイラに渡すインクルードディレクトリのリストに、同じディレクトリが複数回含まれることを避けることです。これは、Issue #3171 を修正するものです。

変更の背景

Go言語のビルドシステム、特に go build コマンドは、ソースコードをコンパイルし、実行可能ファイルやライブラリを生成する際に、必要なパッケージや依存関係を解決します。この過程で、コンパイラは特定のディレクトリ(インクルードディレクトリ)を検索して、ヘッダーファイルやその他の必要なリソースを見つけます。

このコミットが行われる前は、cmd/go のビルドロジックにおいて、インクルードディレクトリのパスを管理する際に、特定の条件下で同じパスが複数回リストに追加されてしまうバグが存在していました。これは、incMap というマップ(Go言語におけるハッシュマップ)を使用して重複を避ける試みがなされていたにもかかわらず発生していました。

具体的には、incMap[dir] = true という行が、dir 変数が filepath.Join によって変更される可能性のある処理の後に配置されていたため、元の dir の値が incMap に正しく記録されず、結果として重複したパスが inc スライスに追加される可能性がありました。このような重複は、ビルド時間のわずかな増加や、一部のコンパイラやツールチェーンで予期せぬ警告やエラーを引き起こす可能性がありました。

Issue #3171 はこの問題点を指摘しており、このコミットはその修正を目的としています。

前提知識の解説

  • cmd/go: Go言語の公式ツールチェーンの主要なコマンドラインインターフェースです。go build, go run, go test, go get など、Go開発者が日常的に使用する多くのサブコマンドを提供します。
  • インクルードディレクトリ (Include Directories): コンパイラがソースコードをコンパイルする際に、#include (C/C++) や import (Go) などのディレクティブで指定された外部ファイル(ヘッダーファイル、パッケージなど)を探すためのディレクトリのリストです。コンパイラはこれらのディレクトリを順番に検索し、必要なファイルを見つけます。
  • go build: Goのソースコードをコンパイルして実行可能バイナリを生成するコマンドです。このプロセスには、依存関係の解決、コンパイル、リンクが含まれます。
  • filepath.Join: Go言語の標準ライブラリ path/filepath パッケージに含まれる関数で、複数のパス要素を結合して単一のパスを生成します。OS固有のパス区切り文字(Windowsでは\、Unix系では/)を適切に処理します。
  • map (マップ): Go言語におけるキーと値のペアを格納するデータ構造で、他の言語のハッシュマップや辞書に相当します。キーの一意性を保証するために使用され、このコミットではインクルードディレクトリのパスの重複を検出・防止するために incMap として利用されています。
  • slice (スライス): Go言語における可変長配列です。このコミットでは、最終的にコンパイラに渡されるインクルードディレクトリのパスを格納する inc というスライスが使用されています。
  • gccgoToolchain: Go言語のコンパイラには、公式の gc コンパイラと、GCCをバックエンドとする gccgo コンパイラがあります。このコードは、どちらのツールチェーンが使用されているかに応じて、インクルードパスの構造を調整しています。

技術的詳細

src/cmd/go/build.go 内の builder.includeArgs 関数は、ビルドプロセス中にコンパイラに渡すインクルードディレクトリの引数を生成する役割を担っています。この関数は、all という *action のスライスを受け取り、それぞれの action に関連付けられたパッケージディレクトリ (a1.pkgdir) を処理します。

この関数は、incMap という map[string]bool 型のマップを使用して、既に追加されたディレクトリのパスを追跡し、重複を防ぐことを意図していました。また、inc という []string 型のスライスに、最終的なインクルードパスの引数を flagdir のペアとして追加します。

問題のコードブロックは以下の通りでした(変更前):

		if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
			if _, ok := buildToolchain.(gccgoToolchain); ok {
				dir = filepath.Join(dir, "gccgo")
			} else {
				dir = filepath.Join(dir, goos+"_"+goarch)
			}
			incMap[dir] = true // ここが問題の箇所
			inc = append(inc, flag, dir)
		}

このコードでは、incMap[dir] = true の行が、dir 変数が filepath.Join によって変更された後に実行されていました。

例えば、a1.pkgdir/path/to/pkg であったとします。

  1. if dir := a1.pkgdir; ...dir/path/to/pkg になります。
  2. !incMap[dir] のチェックが行われます。もし /path/to/pkg がまだ incMap になければ、条件は真となり、ブロックに入ります。
  3. dir = filepath.Join(dir, "gccgo") のような行で、dir の値が /path/to/pkg/gccgo に変更されます。
  4. 問題点: incMap[dir] = true が実行されるとき、dir は既に /path/to/pkg/gccgo になっています。したがって、incMap には /path/to/pkg/gccgo が追加されますが、元の /path/to/pkg は追跡されません。
  5. もし別の action で、同じ a1.pkgdir (/path/to/pkg) が処理され、かつ filepath.Join の結果が異なる場合(例えば、goos+"_"+goarch のパスが異なる場合)、または a1.pkgdirincMap に追加される前に別のパスとして処理された場合、元の a1.pkgdir が重複して処理される可能性がありました。

このバグは、incMapa1.pkgdir の元の値を正しく追跡できていなかったために発生しました。

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

変更は src/cmd/go/build.go ファイルの builder.includeArgs 関数内で行われました。

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -790,12 +790,12 @@ func (b *builder) includeArgs(flag string, all []*action) []string {
 	// Finally, look in the installed package directories for each action.
 	for _, a1 := range all {
 		if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
-			if _, ok := buildToolchain.(gccgoToolchain); ok {
-				dir = filepath.Join(dir, "gccgo")
-			} else {
-				dir = filepath.Join(dir, goos+"_"+goarch)
-			}
-			incMap[dir] = true
+			incMap[dir] = true // この行が移動した
+			if _, ok := buildToolchain.(gccgoToolchain); ok {
+				dir = filepath.Join(dir, "gccgo")
+			} else {
+				dir = filepath.Join(dir, goos+"_"+goarch)
+			}
 			inc = append(inc, flag, dir)
 		}
 	}

コアとなるコードの解説

変更は非常にシンプルですが、その影響は重要です。 incMap[dir] = true の行が、if dir := a1.pkgdir; ... の条件が真であった直後、かつ dir 変数が filepath.Join によって変更される前に移動されました。

変更後:

		if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
			incMap[dir] = true // ここに移動
			if _, ok := buildToolchain.(gccgoToolchain); ok {
				dir = filepath.Join(dir, "gccgo")
			} else {
				dir = filepath.Join(dir, goos+"_"+goarch)
			}
			inc = append(inc, flag, dir)
		}

この変更により、以下のようになります。

  1. if dir := a1.pkgdir; ... の条件が真となり、ブロックに入った直後、dir はまだ a1.pkgdir の元の値(例: /path/to/pkg)を保持しています。
  2. この時点で incMap[dir] = true が実行されるため、incMap には a1.pkgdir の元の値が正しく「既に追加済み」として記録されます。
  3. その後、dirfilepath.Join によって gccgogoos_goarch のサブディレクトリを含むパス(例: /path/to/pkg/gccgo)に変更されます。
  4. 最終的に、この変更された dir の値が inc スライスに追加されます。

これにより、incMapa1.pkgdir の元の値を正確に追跡できるようになり、同じ a1.pkgdir から派生するインクルードパスが複数回処理されることを効果的に防ぎます。結果として、コンパイラに渡されるインクルードディレクトリのリストから重複が排除され、ビルドプロセスの堅牢性と効率性が向上します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ
  • Go言語のIssueトラッカー
  • path/filepath パッケージのドキュメント
  • Go言語のマップとスライスに関する基本的な情報