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

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

このコミットは、Go言語のツールチェインの一部であるcmd/packのテストファイルsrc/cmd/pack/pack_test.goに対する変更です。具体的には、go envコマンドの出力からGOCHAR環境変数の値を解析する方法を改善し、Windows環境でのビルド問題を解決することを目的としています。

コミット

cmd/pack: go envの出力から引用符を探さないようにする。 少なくともWindowsは引用符を出力しない。 Windowsビルドを修正するかもしれない。

LGTM=bradfitz R=bradfitz CC=golang-codereviews https://golang.org/cl/66120046

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

https://github.com/golang/go/commit/2037756fcc6962808ce45e145b386ce70b41530c

元コミット内容

commit 2037756fcc6962808ce45e145b386ce70b41530c
Author: Rob Pike <r@golang.org>
Date:   Wed Feb 19 15:33:47 2014 -0800

    cmd/pack: don't look for " in output from go env
    Windows at least doesn't emit one.
    Maybe fix Windows build.
    
    LGTM=bradfitz
    R=bradfitz
    CC=golang-codereviews
    https://golang.org/cl/66120046
---
 src/cmd/pack/pack_test.go | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n
diff --git a/src/cmd/pack/pack_test.go b/src/cmd/pack/pack_test.go
index a073fa4521..cab236fa88 100644
--- a/src/cmd/pack/pack_test.go
+++ b/src/cmd/pack/pack_test.go
@@ -12,7 +12,7 @@ import (
  	"os"
  	"os/exec"
  	"path/filepath"
-	"strings"
+	"regexp"
  	"testing"
  	"time"
  	"unicode/utf8"
@@ -193,11 +193,15 @@ func TestHello(t *testing.T) {
  	}
  
  	out := run("go", "env")
-	i := strings.Index(out, "GOCHAR=\"")
-	if i < 0 {
+	re, err := regexp.Compile(`\s*GOCHAR="?(\w)"?`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fields := re.FindStringSubmatch(out)
+	if fields == nil {
  		t.Fatal("cannot find GOCHAR in 'go env' output:\n", out)
  	}
-	char := out[i+8 : i+9]
+	char := fields[1]
  	run("go", "build", "cmd/pack") // writes pack binary to dir
  	run("go", "tool", char+"g", "hello.a", "hello."+char)

変更の背景

このコミットの背景には、Go言語のビルドシステムが異なるオペレーティングシステム(特にWindows)で一貫して動作しないという問題がありました。go envコマンドはGoの環境変数を表示するために使用されますが、その出力形式はOSによって微妙に異なる場合があります。

具体的には、GOCHARという環境変数の値を取得する際に、従来のコードでは値が二重引用符(")で囲まれていることを前提としていました。しかし、Windows環境ではgo envの出力においてGOCHARの値が引用符で囲まれていないことが判明しました。この差異が原因で、src/cmd/pack/pack_test.go内のテストがWindowsで失敗し、ビルドプロセスに影響を与えていました。

このコミットは、go envの出力形式の差異を吸収し、より堅牢な方法でGOCHARの値を抽出することで、Windows環境でのビルドの安定性を向上させることを目的としています。

前提知識の解説

  • go envコマンド: go envはGo言語の環境変数を表示するコマンドです。Goのツールチェインがどのように設定されているか、どのバージョンのGoが使用されているか、ビルドに関するパスなどが確認できます。例えば、GOOS(オペレーティングシステム)、GOARCH(アーキテクチャ)、GOPATH(Goのワークスペース)、そしてこのコミットで問題となっているGOCHARなどが含まれます。
  • GOCHAR環境変数: GOCHARはGoのビルドシステム内部で使用される環境変数で、ターゲットアーキテクチャを示す単一の文字です。例えば、6はamd64、8は386、5はarmなどを示します。これは、go toolコマンドが特定のアーキテクチャ向けのツール(例: 6gはamd64向けのGoコンパイラ)を呼び出す際に利用されます。
  • src/cmd/pack: packコマンドは、Goのアーカイブファイル(.aファイル)を操作するためのツールです。Goのビルドプロセスにおいて、コンパイルされたオブジェクトファイルをまとめるために使用されます。
  • strings.Index: Go言語の標準ライブラリstringsパッケージに含まれる関数で、文字列内で指定された部分文字列が最初に出現するインデックスを返します。見つからない場合は-1を返します。
  • regexpパッケージ: Go言語の標準ライブラリregexpパッケージは、正規表現を扱うための機能を提供します。正規表現は、文字列のパターンマッチングや検索、置換を行うための強力なツールです。
    • regexp.Compile: 正規表現の文字列をコンパイルして*regexp.Regexp型のオブジェクトを生成します。これにより、正規表現のパターンマッチングを効率的に実行できます。
    • regexp.FindStringSubmatch: コンパイルされた正規表現オブジェクトのメソッドで、入力文字列に対してパターンマッチングを行い、マッチした部分文字列と、キャプチャグループ(正規表現内の括弧で囲まれた部分)にマッチした部分文字列のリストを返します。マッチしない場合はnilを返します。

技術的詳細

このコミットの核心は、go envコマンドの出力からGOCHARの値を抽出するロジックを、strings.Indexを用いた単純な文字列検索から、regexpパッケージを用いた正規表現マッチングへと変更した点にあります。

従来のコードでは、out := run("go", "env")で取得したgo envの出力文字列に対して、strings.Index(out, "GOCHAR=\"")を使用して"GOCHAR=\""という部分文字列の開始位置を探していました。このアプローチは、GOCHAR="x"のように値が引用符で囲まれている場合にのみ正しく機能します。しかし、Windows環境ではGOCHAR=xのように引用符なしで出力されることがあったため、strings.Index-1を返し、GOCHARが見つからないというエラーが発生していました。

新しいアプローチでは、regexp.Compileを用いて\s*GOCHAR="?(\w)"?という正規表現をコンパイルしています。この正規表現は以下のように解釈されます。

  • \s*: 0個以上の空白文字にマッチします。
  • GOCHAR=: リテラル文字列GOCHAR=にマッチします。
  • "?: 0個または1個の二重引用符にマッチします。これにより、引用符があってもなくても対応できます。
  • (\w): 1個の単語文字(英数字またはアンダースコア)にマッチし、これをキャプチャグループとしてfieldsスライスに格納します。これがGOCHARの実際の値になります。
  • "?: 再び0個または1個の二重引用符にマッチします。

re.FindStringSubmatch(out)は、この正規表現がgo envの出力文字列out内で最初に見つかったマッチを検索します。マッチが見つかると、fieldsスライスにはマッチ全体(fields[0])と、キャプチャグループにマッチした部分(fields[1])が格納されます。この場合、fields[1]GOCHARの実際の値(例: 68)となります。

この変更により、go envの出力がGOCHAR="x"であってもGOCHAR=xであっても、GOCHARの値を正しく抽出できるようになり、Windows環境でのテストの失敗が解消されました。正規表現を使用することで、将来的に出力形式がわずかに変更された場合でも、より柔軟に対応できる堅牢なコードになっています。

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

変更はsrc/cmd/pack/pack_test.goファイル内で行われています。

--- a/src/cmd/pack/pack_test.go
+++ b/src/cmd/pack/pack_test.go
@@ -12,7 +12,7 @@ import (
  	"os"
  	"os/exec"
  	"path/filepath"
-	"strings"
+	"regexp" // stringsパッケージからregexpパッケージへの変更
  	"testing"
  	"time"
  	"unicode/utf8"
@@ -193,11 +193,15 @@ func TestHello(t *testing.T) {
  	}
  
  	out := run("go", "env")
-	i := strings.Index(out, "GOCHAR=\"") // 従来の文字列検索
-	if i < 0 {
+	re, err := regexp.Compile(`\s*GOCHAR="?(\w)"?`) // 正規表現のコンパイル
+	if err != nil {
+		t.Fatal(err) // エラーハンドリングの追加
+	}
+	fields := re.FindStringSubmatch(out) // 正規表現によるマッチング
+	if fields == nil {
  		t.Fatal("cannot find GOCHAR in 'go env' output:\n", out)
  	}
-	char := out[i+8 : i+9] // 従来のGOCHAR値の抽出
+	char := fields[1] // 正規表現のキャプチャグループからGOCHAR値の抽出
  	run("go", "build", "cmd/pack") // writes pack binary to dir
  	run("go", "tool", char+"g", "hello.a", "hello."+char)

コアとなるコードの解説

変更されたコードブロックは、TestHello関数内にあります。

  1. インポートの変更: "strings"パッケージのインポートが削除され、代わりに"regexp"パッケージがインポートされています。これは、文字列操作から正規表現ベースのパターンマッチングへの移行を示しています。

  2. go env出力の解析ロジックの変更:

    • 旧コード:

      i := strings.Index(out, "GOCHAR=\"")
      if i < 0 {
          t.Fatal("cannot find GOCHAR in 'go env' output:\n", out)
      }
      char := out[i+8 : i+9]
      

      このコードは、go envの出力文字列outの中から"GOCHAR=\""という部分文字列を探し、その開始インデックスiを取得していました。もし見つからなければエラーを報告し、見つかった場合はi+8からi+9までの1文字をGOCHARの値として抽出していました。この+8というオフセットは、"GOCHAR=\""という文字列の長さ(8文字)に依存しており、非常に脆弱な実装でした。

    • 新コード:

      re, err := regexp.Compile(`\s*GOCHAR="?(\w)"?`)
      if err != nil {
          t.Fatal(err)
      }
      fields := re.FindStringSubmatch(out)
      if fields == nil {
          t.Fatal("cannot find GOCHAR in 'go env' output:\n", out)
      }
      char := fields[1]
      

      この新しいコードでは、まずregexp.Compileを使って正規表現パターン\s*GOCHAR="?(\w)"?をコンパイルしています。この正規表現は、行頭の空白(\s*)、リテラル文字列GOCHAR=、オプションの引用符("?)、そしてキャプチャグループ(\w)GOCHARの実際の値)とオプションの引用符("?)にマッチします。 コンパイルに失敗した場合はt.Fatal(err)でテストを終了します。 次に、re.FindStringSubmatch(out)を呼び出して、go envの出力から正規表現にマッチする部分を探します。FindStringSubmatchは、マッチした文字列全体と、正規表現内のキャプチャグループにマッチした部分文字列のリストを返します。 fieldsnilの場合(マッチが見つからなかった場合)は、GOCHARが見つからないというエラーを報告します。 マッチが見つかった場合、fields[1]にはキャプチャグループ(\w)にマッチした文字、つまりGOCHARの実際の値が格納されており、これがchar変数に代入されます。

この変更により、go envの出力形式が引用符の有無に関わらず、GOCHARの値を正確かつ堅牢に抽出できるようになりました。

関連リンク

  • Go言語のregexpパッケージのドキュメント: https://pkg.go.dev/regexp
  • Go言語のstringsパッケージのドキュメント: https://pkg.go.dev/strings
  • Go言語の環境変数に関する公式ドキュメント(go envについて言及されている可能性のある箇所): https://go.dev/doc/go1.1 (Go 1.1のリリースノートは、このコミットが作成された時期に近い情報源として関連する可能性があります。)

参考にした情報源リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語のコードレビューシステム(Gerrit)の変更リスト: https://golang.org/cl/66120046 (コミットメッセージに記載されているCLリンク)
  • Go言語のgo envコマンドに関する一般的な情報源(例: go help envの出力や、Goの環境変数に関するブログ記事など)
  • 正規表現の基本的な概念に関する情報源 (例: MDN Web Docsの正規表現ガイドなど)
  • Go言語のテストに関する情報源 (例: testingパッケージのドキュメントなど)
  • WindowsにおけるGoのビルドに関する一般的な情報源 (例: Goの公式ドキュメントのWindowsビルドに関するセクションなど)
  • GOCHAR環境変数に関する情報源 (Goのビルドシステム内部のドキュメントや議論など)