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

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

このコミットは、Go言語のビルドシステムにおけるファイルマッチングロジックの改善を目的としています。具体的には、go/buildパッケージにctxt.MatchFileという新しいメソッドを追加し、既存のファイル選択ロジックをこの新しいメソッドに集約することで、コードの重複を排除し、保守性を向上させています。これにより、特定のファイルがビルドプロセスに含まれるべきかどうかを、より柔軟かつ正確に判断できるようになります。

コミット

commit aa53b37fa6aeb4ea378b5fc833f0aea5d6f9711e
Author: Russ Cox <rsc@golang.org>
Date:   Sun Sep 15 11:29:47 2013 -0400

    go/build: add ctxt.MatchFile
    
    Fixes #6369.
    
    R=dsymonds, r
    CC=golang-dev
    https://golang.org/cl/13708043

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

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

元コミット内容

go/build: add ctxt.MatchFile

Fixes #6369.

変更の背景

このコミットの背景には、Go言語のビルドプロセスにおけるファイル選択ロジックの複雑さと重複がありました。Goのビルドシステムは、ソースファイルのファイル名(例: _test.go_windows.go)やファイル内のビルドタグ(// +build linux,amd64)に基づいて、どのファイルをビルドに含めるかを決定します。

以前のgo/buildパッケージでは、ImportDir関数内でファイルシステムを走査し、各ファイルがビルド対象となるべきかを判断するロジックが直接記述されていました。このロジックは、ファイル名によるフィルタリング(_.で始まるファイルの無視、OS/アーキテクチャ固有のファイル名の処理)と、ファイル内容を読み込んでビルドタグを解析する部分に分かれていました。

しかし、このファイル選択ロジックがImportDir内に密結合しているため、以下のような問題がありました。

  1. コードの重複: ImportDir以外の場所で同様のファイル選択ロジックが必要になった場合、コードを重複させる必要がありました。
  2. テストの困難さ: ImportDir全体をテストすることなく、特定のファイルがビルド対象となるかどうかの判断ロジックだけを単体でテストすることが困難でした。
  3. 保守性の低下: ロジックが分散しているため、変更やデバッグが複雑になりがちでした。

これらの問題を解決し、ファイル選択ロジックを再利用可能でテストしやすい形にするために、ctxt.MatchFileという新しい公開メソッドが導入されました。これにより、ファイル選択の責任がImportDirからMatchFileに分離され、よりクリーンな設計が実現されました。

関連するIssue #6369は、「go/buildパッケージに、特定のファイルがビルド対象となるかどうかを判断する関数を追加してほしい」という要望でした。このコミットは、その要望に応えるものです。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびビルドシステムに関する前提知識が必要です。

  1. go/buildパッケージ: Go言語の標準ライブラリの一つで、Goパッケージの構造を解析し、ビルドに必要な情報を取得するための機能を提供します。go buildコマンドやgo installコマンドなどの内部で利用されています。主な機能として、指定されたディレクトリからGoパッケージをインポートしたり、ソースファイルの依存関係を解決したりする能力があります。

  2. ビルドタグ (// +build コメント): Goのソースファイルには、ファイルの先頭に// +buildコメントを記述することで、そのファイルが特定のビルド条件(OS、アーキテクチャ、カスタムタグなど)が満たされた場合にのみビルドに含まれるように指定できます。例えば、// +build linux,amd64と書かれたファイルは、LinuxかつAMD64アーキテクチャの場合にのみビルドされます。複数のタグはスペースまたはカンマで区切られ、カンマはAND条件、スペースはOR条件として扱われます。

  3. OS/アーキテクチャ固有のファイル名: Goは、ファイル名にOS名やアーキテクチャ名を含めることで、特定の環境でのみビルドされるファイルを指定する慣習があります。例えば、foo_windows.goはWindowsでのみビルドされ、foo_amd64.goはAMD64アーキテクチャでのみビルドされます。foo_linux_amd64.goのように組み合わせることも可能です。

  4. Context構造体: go/buildパッケージのContext構造体は、ビルド環境に関する情報(現在のOS、アーキテクチャ、GOPATHなど)を保持します。ファイル選択ロジックは、このContextの情報に基づいて行われます。

  5. ImportDir関数: 指定されたディレクトリからGoパッケージの情報をインポートする関数です。この関数は、ディレクトリ内のファイルを走査し、ビルド対象となるGoソースファイル、C/C++/アセンブリソースファイル、その他の関連ファイルを識別します。

  6. readImportsreadComments: Goソースファイルからインポートパスを読み取るためのヘルパー関数(readImports)と、ファイルの先頭からコメントを読み取るためのヘルパー関数(readComments)です。ビルドタグはコメントとして扱われるため、これらの関数がファイル選択ロジックで利用されます。

技術的詳細

このコミットの主要な技術的変更は、go/buildパッケージにおけるファイル選択ロジックのモジュール化です。

  1. ctxt.MatchFileメソッドの導入: Context構造体に新しい公開メソッドMatchFile(dir, name string) (match bool, err error)が追加されました。このメソッドは、指定されたディレクトリ(dir)内のファイル名(name)が、現在のビルドコンテキスト(ctxt)においてビルド対象となるべきかどうかを判断します。

  2. 内部ヘルパー関数ctxt.matchFile: MatchFileメソッドの実体として、内部ヘルパー関数ctxt.matchFile(dir, name string, returnImports bool, allTags map[string]bool)が導入されました。この関数は、以下のロジックをカプセル化しています。

    • ファイル名のフィルタリング: _または.で始まるファイル、および既知の拡張子を持たないファイルを無視します。
    • OS/アーキテクチャ固有のファイル名のチェック: ctxt.goodOSArchFileを呼び出し、ファイル名が現在のOS/アーキテクチャに合致するかどうかを判断します。この際、allTagsマップが提供されていれば、ファイル名から検出されたOS/アーキテクチャタグを記録します。
    • ファイル拡張子による初期フィルタリング: .go, .c, .cc, .cxx, .cpp, .s, .h, .hh, .hpp, .hxx, .S, .swig, .swigcxxなどのソースファイル、および.sysoファイル(バイナリオブジェクト)を対象とします。.sysoファイルは内容を読み込まずにマッチと判断されます。
    • ファイル内容の読み込みとビルドタグの解析: 対象となるソースファイルの場合、ctxt.openFileでファイルを開き、readImports(Goファイルの場合)またはreadComments(その他のファイルの場合)を使用してファイル内容の冒頭部分を読み込みます。
    • ビルドタグによる最終判断: 読み込んだ内容から// +buildコメントを解析し、ctxt.shouldBuildを呼び出して、現在のビルドコンテキストでファイルがビルドされるべきかを最終的に判断します。
  3. ImportDirからのロジックの抽出: 既存のImportDir関数から、各ファイルを処理する際のファイル選択ロジックがctxt.matchFileの呼び出しに置き換えられました。これにより、ImportDirのコードが大幅に簡素化され、ファイル選択の複雑な詳細がmatchFile内に隠蔽されました。

  4. nameExtヘルパー関数の追加: ファイル名から拡張子を安全に抽出するための小さなヘルパー関数nameExt(name string) stringが追加されました。これは、ファイル名に.が含まれない場合でも適切に空文字列を返すように設計されています。

  5. テストの追加: go/build/build_test.goTestMatchFileという新しいテスト関数が追加されました。このテストは、さまざまなファイル名とビルドタグの組み合わせに対してctxt.MatchFileが正しく動作するかどうかを検証します。これにより、ファイル選択ロジックの単体テストが可能になり、将来の変更に対する回帰テストの基盤が強化されました。

これらの変更により、go/buildパッケージのファイル選択ロジックはよりモジュール化され、再利用性、テスト容易性、および保守性が向上しました。

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

このコミットにおけるコアとなるコードの変更は、主に以下の2つのファイルに集中しています。

  1. src/pkg/go/build/build.go:

    • nameExtヘルパー関数の追加。
    • ctxt.MatchFile公開メソッドの追加。
    • ctxt.matchFile内部ヘルパー関数の追加(これが実際のファイル選択ロジックをカプセル化)。
    • ImportDir関数内のファイル走査ループから、既存のファイル選択ロジックを削除し、新しく追加されたctxt.matchFileの呼び出しに置き換え。
    • ctxt.goodOSArchFile関数内で、allTagsマップがnilでない場合にのみタグを記録するように変更(matchFileからの呼び出しでallTagsnilの場合があるため)。
  2. src/pkg/go/build/build_test.go:

    • readNopCloserというテスト用のヘルパー型を追加。これはio.ReadCloserインターフェースを満たし、テスト中にファイルの内容をシミュレートするために使用されます。
    • matchFileTestsというテストケースの配列を追加。
    • TestMatchFileという新しいテスト関数を追加。この関数は、matchFileTestsの各ケースに対してctxt.MatchFileを呼び出し、期待される結果と比較します。テストでは、ctxt.OpenFilectxt.JoinPathをモックすることで、実際のファイルシステムアクセスなしにテストを実行できるようにしています。

src/pkg/go/build/build.go の変更点(抜粋)

--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -408,6 +408,14 @@ func (e *NoGoError) Error() string {
 	return "no buildable Go source files in " + e.Dir
 }
 
+func nameExt(name string) string {
+	i := strings.LastIndex(name, ".")
+	if i < 0 {
+		return ""
+	}
+	return name[i:]
+}
+
 // Import returns details about the Go package named by the import path,
 // interpreting local import paths relative to the srcDir directory.
 // If the path is a local import path naming a package that can be imported
@@ -591,58 +599,15 @@ Found:
 		if d.IsDir() {
 			continue
 		}
-		name := d.Name()
-		if strings.HasPrefix(name, "_") ||
-			strings.HasPrefix(name, ".") {
-			continue
-		}
-
-		i := strings.LastIndex(name, ".")
-		if i < 0 {
-			i = len(name)
-		}
-		ext := name[i:]
-
-		if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles {
-			if ext == ".go" {
-				p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
-			}
-			continue
-		}
-
-		switch ext {
-		case ".go", ".c", ".cc", ".cxx", ".cpp", ".s", ".h", ".hh", ".hpp", ".hxx", ".S", ".swig", ".swigcxx":
-			// tentatively okay - read to make sure
-		case ".syso":
-			// binary objects to add to package archive
-			// Likely of the form foo_windows.syso, but
-			// the name was vetted above with goodOSArchFile.
-			p.SysoFiles = append(p.SysoFiles, name)
-			continue
-		default:
-			// skip
-			continue
-		}
+		name := d.Name()
+		ext := nameExt(name)
 
-		filename := ctxt.joinPath(p.Dir, name)
-		f, err := ctxt.openFile(filename)
+		match, data, filename, err := ctxt.matchFile(p.Dir, name, true, allTags)
 		if err != nil {
 			return p, err
 		}
-
-		var data []byte
-		if strings.HasSuffix(filename, ".go") {
-			data, err = readImports(f, false)
-		} else {
-			data, err = readComments(f)
-		}
-		f.Close()
-		if err != nil {
-			return p, fmt.Errorf("read %s: %v", filename, err)
-		}
-
-		// Look for +build comments to accept or reject the file.
-		if !ctxt.shouldBuild(data, allTags) && !ctxt.UseAllFiles {
+		if !match {
 			if ext == ".go" {
 				p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
 			}
@@ -672,6 +637,79 @@ Found:
 		case ".swigcxx":
 			p.SwigCXXFiles = append(p.SwigCXXFiles, name)
 			continue
+		case ".syso":
+			// binary objects to add to package archive
+			// Likely of the form foo_windows.syso, but
+			// the name was vetted above with goodOSArchFile.
+			p.SysoFiles = append(p.SysoFiles, name)
+			continue
 		}
 
 		pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
@@ -782,6 +753,79 @@ Found:
 	return p, pkgerr
 }
 
+// MatchFile reports whether the file with the given name in the given directory
+// matches the context and would be included in a Package created by ImportDir
+// of that directory.
+//
+// MatchFile considers the name of the file and may use ctxt.OpenFile to
+// read some or all of the file's content.
+func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) {
+	match, _, _, err = ctxt.matchFile(dir, name, false, nil)
+	return
+}
+
+// matchFile determines whether the file with the given name in the given directory
+// should be included in the package being constructed.
+// It returns the data read from the file.
+// If returnImports is true and name denotes a Go program, matchFile reads
+// until the end of the imports (and returns that data) even though it only
+// considers text until the first non-comment.
+// If allTags is non-nil, matchFile records any encountered build tag
+// by setting allTags[tag] = true.
+func (ctxt *Context) matchFile(dir, name string, returnImports bool, allTags map[string]bool) (match bool, data []byte, filename string, err error) {
+	if strings.HasPrefix(name, "_") ||
+		strings.HasPrefix(name, ".") {
+		return
+	}
+
+	i := strings.LastIndex(name, ".")
+	if i < 0 {
+		i = len(name)
+	}
+	ext := name[i:]
+
+	if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles {
+		return
+	}
+
+	switch ext {
+	case ".go", ".c", ".cc", ".cxx", ".cpp", ".s", ".h", ".hh", ".hpp", ".hxx", ".S", ".swig", ".swigcxx":
+		// tentatively okay - read to make sure
+	case ".syso":
+		// binary, no reading
+		match = true
+		return
+	default:
+		// skip
+		return
+	}
+
+	filename = ctxt.joinPath(dir, name)
+	f, err := ctxt.openFile(filename)
+	if err != nil {
+		return
+	}
+
+	if strings.HasSuffix(filename, ".go") {
+		data, err = readImports(f, false)
+	} else {
+		data, err = readComments(f)
+	}
+	f.Close()
+	if err != nil {
+		err = fmt.Errorf("read %s: %v", filename, err)
+		return
+	}
+
+	// Look for +build comments to accept or reject the file.
+	if !ctxt.shouldBuild(data, allTags) && !ctxt.UseAllFiles {
+		return
+	}
+
+	match = true
+	return
+}
+
 func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
 	all := make([]string, 0, len(m))
 	for path := range m {
@@ -1114,16 +1158,22 @@ func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
 	}\n := len(l)
 	if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
-		allTags[l[n-2]] = true
-		allTags[l[n-1]] = true
+		if allTags != nil {
+			allTags[l[n-2]] = true
+			allTags[l[n-1]] = true
+		}
 		return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
 	}
 	if n >= 1 && knownOS[l[n-1]] {
-		allTags[l[n-1]] = true
+		if allTags != nil {
+			allTags[l[n-1]] = true
+		}
 		return l[n-1] == ctxt.GOOS
 	}
 	if n >= 1 && knownArch[l[n-1]] {
-		allTags[l[n-1]] = true
+		if allTags != nil {
+			allTags[l[n-1]] = true
+		}
 		return l[n-1] == ctxt.GOARCH
 	}
 	return true

src/pkg/go/build/build_test.go の変更点(抜粋)

--- a/src/pkg/go/build/build_test.go
+++ b/src/pkg/go/build/build_test.go
@@ -5,10 +5,12 @@
 package build
 
 import (
+	"io"
 	"os"
 	"path/filepath"
 	"reflect"
 	"runtime"
+	"strings"
 	"testing"
 )
 
@@ -142,3 +144,43 @@ func TestShouldBuild(t *testing.T) {
 		t.Errorf("shoudBuild(file3) tags = %v, want %v", m, want3)\n 	}\n }\n+\n+type readNopCloser struct {\n+\tio.Reader\n+}\n+\n+func (r readNopCloser) Close() error {\n+\treturn nil\n+}\n+\n+var matchFileTests = []struct {\n+\tname  string\n+\tdata  string\n+\tmatch bool\n+}{\n+\t{\"foo_arm.go\", \"\", true},\n+\t{\"foo1_arm.go\", \"// +build linux\\n\\npackage main\\n\", false},\n+\t{\"foo_darwin.go\", \"\", false},\n+\t{\"foo.go\", \"\", true},\n+\t{\"foo1.go\", \"// +build linux\\n\\npackage main\\n\", false},\n+\t{\"foo.badsuffix\", \"\", false},\n+}\n+\n+func TestMatchFile(t *testing.T) {\n+\tfor _, tt := range matchFileTests {\n+\t\tctxt := Context{GOARCH: "arm", GOOS: "plan9"}\n+\t\tctxt.OpenFile = func(path string) (r io.ReadCloser, err error) {\n+\t\t\tif path != "x+"+tt.name {\n+\t\t\t\tt.Fatalf("OpenFile asked for %q, expected %q", path, "x+"+tt.name)\n+\t\t\t}\n+\t\t\treturn &readNopCloser{strings.NewReader(tt.data)}, nil\n+\t\t}\n+\t\tctxt.JoinPath = func(elem ...string) string {\n+\t\t\treturn strings.Join(elem, "+")\n+\t\t}\n+\t\tmatch, err := ctxt.MatchFile("x", tt.name)\n+\t\tif match != tt.match || err != nil {\n+\t\t\tt.Fatalf("MatchFile(%q) = %v, %v, want %v, nil", tt.name, match, err, tt.match)\n+\t\t}\n+\t}\n+}\n```

## コアとなるコードの解説

### `nameExt` 関数

```go
func nameExt(name string) string {
	i := strings.LastIndex(name, ".")
	if i < 0 {
		return ""
	}
	return name[i:]
}

この小さなヘルパー関数は、与えられたファイル名から拡張子を抽出します。strings.LastIndexで最後の.の位置を見つけ、それ以降の文字列を返します。もし.が見つからなければ(つまり拡張子がないファイル名であれば)、空文字列を返します。これは、以前のImportDir内のロジックで拡張子を抽出していた部分を共通化したものです。

Context.MatchFile メソッド

func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) {
	match, _, _, err = ctxt.matchFile(dir, name, false, nil)
	return
}

これはgo/buildパッケージの外部から呼び出されることを意図した公開APIです。内部のctxt.matchFileを呼び出していますが、returnImportsfalseに、allTagsnilに設定されています。これは、MatchFileが単にファイルがマッチするかどうかを報告するだけで、インポート情報を返す必要がなく、またビルドタグの収集もこのメソッドの主要な目的ではないためです。

Context.matchFile メソッド (内部ヘルパー)

func (ctxt *Context) matchFile(dir, name string, returnImports bool, allTags map[string]bool) (match bool, data []byte, filename string, err error) {
	// 1. ファイル名のフィルタリング
	if strings.HasPrefix(name, "_") ||
		strings.HasPrefix(name, ".") {
		return // "_" または "." で始まるファイルは無視
	}

	// 2. 拡張子の抽出
	i := strings.LastIndex(name, ".")
	if i < 0 {
		i = len(name)
	}
	ext := name[i:]

	// 3. OS/アーキテクチャ固有のファイル名のチェック
	if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles {
		return // 現在のOS/アーキテクチャに合致しないファイルは無視
	}

	// 4. 拡張子による初期フィルタリング
	switch ext {
	case ".go", ".c", ".cc", ".cxx", ".cpp", ".s", ".h", ".hh", ".hpp", ".hxx", ".S", ".swig", ".swigcxx":
		// 暫定的にOK - 内容を読んで最終確認
	case ".syso":
		// バイナリファイルは内容を読まずにマッチと判断
		match = true
		return
	default:
		return // その他の拡張子は無視
	}

	// 5. ファイルの読み込み
	filename = ctxt.joinPath(dir, name)
	f, err := ctxt.openFile(filename)
	if err != nil {
		return // ファイルを開けない場合はエラー
	}

	if strings.HasSuffix(filename, ".go") {
		data, err = readImports(f, false) // Goファイルはインポート部分まで読み込む
	} else {
		data, err = readComments(f) // その他のファイルはコメント部分まで読み込む
	}
	f.Close()
	if err != nil {
		err = fmt.Errorf("read %s: %v", filename, err)
		return // ファイル読み込みエラー
	}

	// 6. ビルドタグによる最終判断
	if !ctxt.shouldBuild(data, allTags) && !ctxt.UseAllFiles {
		return // ビルドタグの条件を満たさないファイルは無視
	}

	match = true // 全ての条件をクリアした場合、マッチと判断
	return
}

この関数は、ファイルがビルド対象となるべきかを判断する中心的なロジックを含んでいます。

  • ファイル名のプレフィックスチェック: _.で始まるファイルは、通常、一時ファイルや隠しファイルとして扱われるため、ビルド対象から除外されます。
  • 拡張子抽出: nameExt関数を使って拡張子を取得します。
  • OS/アーキテクチャチェック: ctxt.goodOSArchFileを呼び出し、ファイル名が現在のOSやアーキテクチャに合致するかどうかを確認します。例えば、foo_windows.goはWindows環境でのみマッチします。allTagsnilでない場合、検出されたOS/アーキテクチャタグがそのマップに記録されます。
  • 拡張子による初期分類: .go.c.sなどのソースファイルは、さらに内容を読み込んでビルドタグをチェックする必要があります。.sysoのようなバイナリファイルは内容を読まずにマッチと判断されます。その他の不明な拡張子は無視されます。
  • ファイル内容の読み込み: ctxt.openFileでファイルを開き、GoファイルであればreadImports(インポート文まで)、それ以外であればreadComments(コメント部分まで)を呼び出して、ビルドタグが含まれる可能性のある冒頭部分を読み込みます。
  • ビルドタグの評価: 読み込んだデータとctxt.shouldBuildを使って、ファイル内の// +buildコメントが現在のビルドコンテキストに合致するかどうかを判断します。
  • 最終的なマッチ判断: 上記の全てのチェックを通過した場合にのみ、ファイルはマッチすると判断されます。

ImportDir 関数の変更

ImportDir関数では、以前はmatchFileのロジックが直接記述されていましたが、このコミットにより、その部分がctxt.matchFileの呼び出しに置き換えられました。

// 変更前 (簡略化)
// ...
// for _, d := range list {
//     name := d.Name()
//     // ここにファイル名、拡張子、OS/Arch、ビルドタグのチェックロジックが直接記述されていた
//     // ...
//     if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { ... }
//     // ...
//     if !ctxt.shouldBuild(data, allTags) && !ctxt.UseAllFiles { ... }
//     // ...
// }

// 変更後 (簡略化)
// ...
// for _, d := range list {
//     name := d.Name()
//     ext := nameExt(name) // 新しいヘルパー関数を使用
//     match, data, filename, err := ctxt.matchFile(p.Dir, name, true, allTags) // matchFileを呼び出し
//     if err != nil {
//         return p, err
//     }
//     if !match {
//         if ext == ".go" {
//             p.IgnoredGoFiles = append(p.IgnoredGoFiles, name)
//         }
//         continue
//     }
//     // ... (マッチしたファイルの処理は継続)
// }

この変更により、ImportDirはファイル選択の詳細から解放され、より高レベルなパッケージインポートのロジックに集中できるようになりました。

ctxt.goodOSArchFile の変更

--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -1114,16 +1158,22 @@ func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
 	}\n := len(l)
 	if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
-		allTags[l[n-2]] = true
-		allTags[l[n-1]] = true
+		if allTags != nil { // allTagsがnilでない場合のみ記録
+			allTags[l[n-2]] = true
+			allTags[l[n-1]] = true
+		}
 		return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH
 	}
 	if n >= 1 && knownOS[l[n-1]] {
-		allTags[l[n-1]] = true
+		if allTags != nil { // allTagsがnilでない場合のみ記録
+			allTags[l[n-1]] = true
+		}
 		return l[n-1] == ctxt.GOOS
 	}
 	if n >= 1 && knownArch[l[n-1]] {
-		allTags[l[l-1]] = true
+		if allTags != nil { // allTagsがnilでない場合のみ記録
+			allTags[l[n-1]] = true
+		}
 		return l[n-1] == ctxt.GOARCH
 	}
 	return true

ctxt.goodOSArchFile関数は、ファイル名からOSやアーキテクチャのタグを抽出し、それらが現在のビルドコンテキストに合致するかどうかを判断します。このコミットでは、allTagsマップがnilでない場合にのみ、抽出されたタグをそのマップに記録するように変更されました。これは、ctxt.MatchFileのようにタグ収集が不要な呼び出し元からallTagsnilで渡される可能性があるため、nilポインタ参照を防ぐための安全策です。

関連リンク

参考にした情報源リンク