[インデックス 12636] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
における、ディレクトリパスからインポートパスへの変換ロジックのバグを修正するものです。具体的には、ファイルシステム上のディレクトリ名にGoのインポートパスとして不正な文字が含まれている場合に、正しくインポートパスを生成できない問題を解決します。これにより、特殊文字を含むディレクトリからのパッケージのビルドやテストが正常に行われるようになります。
コミット
commit 95a8bab7b6db9c63281a0055b1a2471818129cd5
Author: Russ Cox <rsc@golang.org>
Date: Wed Mar 14 15:12:57 2012 -0400
cmd/go: fix directory->import path conversion
Fixes #3306.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5821048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/95a8bab7b6db9c63281a0055b1a2471818129cd5
元コミット内容
cmd/go: fix directory->import path conversion
Fixes #3306.
変更の背景
Go言語では、ソースコードのパッケージはファイルシステム上のディレクトリ構造と密接に関連しています。go build
や go install
などのコマンドは、指定されたディレクトリパスをGoのインポートパスに変換して処理を行います。しかし、従来の dirToImportPath
関数は、ディレクトリ名に含まれる特定の特殊文字(例: :
)を単純に _
に置換するだけでした。
このアプローチでは、Goのインポートパスとして許可されていない文字(例: !
, "
, #
, $
, %
, &
, '
, (
, )
, *
, ,
, :
, ;
, <
, =
, >
, ?
, [
, \
, ]
, ^
, {
, |
, }
, `
, \uFFFD
(Unicode replacement character) など)がディレクトリ名に含まれていた場合、生成されるインポートパスがGoの仕様に準拠せず、ビルドエラーや予期せぬ動作を引き起こす可能性がありました。
このコミットは、この問題を解決し、ディレクトリパスから常に有効なGoインポートパスを生成できるようにすることで、より堅牢なGo開発環境を提供することを目的としています。特に、ファイルシステムが許容する文字とGoのインポートパスが許容する文字の間のギャップを埋めることが重要でした。
前提知識の解説
- Goインポートパス: Go言語では、パッケージはインポートパスによって識別されます。これは通常、リポジトリのURLやファイルシステム上のパスに似た文字列です(例:
github.com/user/repo/package
や.
、./subpackage
)。Goのインポートパスには、使用できる文字に厳密な制限があります。これは、Goの仕様、コンパイラ、およびgo/parser
パッケージのisValidImport
関数によって定義されています。 filepath.ToSlash
: Goの標準ライブラリpath/filepath
パッケージの関数で、オペレーティングシステム固有のパス区切り文字(Windowsの\
など)をスラッシュ (/
) に変換します。これにより、パスがプラットフォームに依存しない形式になります。pathpkg.Join
: Goの標準ライブラリpath
パッケージの関数で、複数のパス要素を結合してクリーンなパスを生成します。これは、ファイルシステムパスではなく、スラッシュ区切りのパス(URLパスなど)を扱うのに適しています。unicode
パッケージ: Goの標準ライブラリunicode
パッケージは、Unicode文字のプロパティを扱うための関数を提供します。unicode.IsGraphic(r rune) bool
: ルーンr
がグラフィック文字(表示可能な文字)であるかどうかを判定します。unicode.IsSpace(r rune) bool
: ルーンr
が空白文字であるかどうかを判定します。
strings.ContainsRune(s string, r rune) bool
: 文字列s
にルーンr
が含まれているかどうかを判定します。strings.Map(mapping func(rune) rune, s string) string
: 文字列s
の各ルーンにmapping
関数を適用し、その結果として新しい文字列を生成します。これは、文字列内の文字を変換する強力な方法です。go install
: Goのコマンドで、パッケージをコンパイルし、その結果の実行可能ファイルまたはアーカイブファイルを$GOPATH/bin
または$GOPATH/pkg
にインストールします。このコマンドは、インポートパスの解決に依存しています。
技術的詳細
このコミットの主要な変更点は、src/cmd/go/pkg.go
ファイル内の dirToImportPath
関数に makeImportValid
という新しいヘルパー関数を導入したことです。
変更前:
dirToImportPath
関数は、ディレクトリパスをスラッシュ形式に変換した後、コロン (:
) をアンダースコア (_
) に置換するだけでした。
func dirToImportPath(dir string) string {
return pathpkg.Join("_", strings.Replace(filepath.ToSlash(dir), ":", "_", -1))
}
この単純な置換では、Goのインポートパスとして不正な他の多くの特殊文字が処理されませんでした。
変更後:
新しい makeImportValid
関数が導入され、strings.Map
を使用してディレクトリパスの各文字を検証・変換するようになりました。
func dirToImportPath(dir string) string {
return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
}
func makeImportValid(r rune) rune {
// Should match Go spec, compilers, and ../../pkg/go/parser/parser.go:/isValidImport.
const illegalChars = `!"#$%&'()*,:;<=>?[\\]^{|}` + "`\uFFFD"
if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
return '_'
}
return r
}
makeImportValid
関数は以下のロジックで動作します。
- Goのインポートパスとして不正な文字のセット
illegalChars
を定義します。これには、Goの仕様、コンパイラ、およびgo/parser/parser.go
のisValidImport
関数で定義されている不正な文字が含まれます。 - 入力されたルーン
r
が以下のいずれかの条件を満たす場合、そのルーンをアンダースコア (_
) に変換します。!unicode.IsGraphic(r)
: ルーンがグラフィック文字ではない場合(例: 制御文字)。unicode.IsSpace(r)
: ルーンが空白文字である場合。strings.ContainsRune(illegalChars, r)
: ルーンがillegalChars
定数に含まれる不正な文字である場合。
- 上記の条件に当てはまらない場合、ルーンはそのまま返されます。
この変更により、dirToImportPath
は、ファイルシステム上のディレクトリ名にどのような文字が含まれていても、Goのインポートパスとして常に有効な文字列を生成するようになりました。
また、src/cmd/go/test.bash
にも変更が加えられ、この修正を検証するための新しいテストケースが追加されました。特に、ディレクトリ名に不正な文字(#$%:, &()*;<=>?\^{}
)を含むパスで go build
や go install
が正しく動作するかを確認する testlocal
関数が導入され、その関数が bad
という変数で定義された特殊文字を含むディレクトリ名で呼び出されています。これにより、この修正が意図通りに機能していることが保証されます。
コアとなるコードの変更箇所
src/cmd/go/pkg.go
の変更:
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -17,6 +17,7 @@ import (
"sort"
"strings"
"time"
+ "unicode"
)
// A Package describes a single package found in a directory.
@@ -174,7 +175,16 @@ func reloadPackage(arg string, stk *importStack) *Package {
// a special case, so that all the code to deal with ordinary imports works
// automatically.
func dirToImportPath(dir string) string {
- return pathpkg.Join("_", strings.Replace(filepath.ToSlash(dir), ":", "_", -1))
+ return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
+}
+
+func makeImportValid(r rune) rune {
+ // Should match Go spec, compilers, and ../../pkg/go/parser/parser.go:/isValidImport.
+ const illegalChars = `!"#$%&'()*,:;<=>?[\\]^{|}` + "`\uFFFD"
+ if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
+ return '_'
+ }
+ return r
}
// loadImport scans the directory named by path, which must be an import path,
src/cmd/go/test.bash
の変更:
テストスクリプトに testlocal
関数が追加され、特殊文字を含むディレクトリ名でのテストが実行されるようになりました。
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -22,37 +22,50 @@ do
done
# Test local (./) imports.
-./testgo build -o hello testdata/local/easy.go
-./hello >hello.out
-if ! grep -q '^easysub\.Hello' hello.out; then
- echo "testdata/local/easy.go did not generate expected output"
- cat hello.out
- ok=false
-fi
-
-./testgo build -o hello testdata/local/easysub/main.go
-./hello >hello.out
-if ! grep -q '^easysub\.Hello' hello.out; then
- echo "testdata/local/easysub/main.go did not generate expected output"
- cat hello.out
- ok=false
-fi
-
-./testgo build -o hello testdata/local/hard.go
-./hello >hello.out
-if ! grep -q '^sub\.Hello' hello.out || ! grep -q '^subsub\.Hello' hello.out ; then
- echo "testdata/local/hard.go did not generate expected output"
- cat hello.out
- ok=false
-fi
+testlocal() {
+ local="`$1`"
+ ./testgo build -o hello "testdata/$local/easy.go"
+ ./hello >hello.out
+ if ! grep -q '^easysub\.Hello' hello.out; then
+ echo "testdata/$local/easy.go did not generate expected output"
+ cat hello.out
+ ok=false
+ fi
+
+ ./testgo build -o hello "testdata/$local/easysub/main.go"
+ ./hello >hello.out
+ if ! grep -q '^easysub\.Hello' hello.out; then
+ echo "testdata/$local/easysub/main.go did not generate expected output"
+ cat hello.out
+ ok=false
+ fi
+
+ ./testgo build -o hello "testdata/$local/hard.go"
+ ./hello >hello.out
+ if ! grep -q '^sub\.Hello' hello.out || ! grep -q '^subsub\.Hello' hello.out ; then
+ echo "testdata/$local/hard.go did not generate expected output"
+ cat hello.out
+ ok=false
+ fi
+
+ rm -f err.out hello.out hello
+
+ # Test that go install x.go fails.
+ if ./testgo install "testdata/$local/easy.go" >/dev/null 2>&1; then
+ echo "go install testdata/$local/easy.go succeeded"
+ ok=false
+ fi
+}
-rm -f err.out hello.out hello
+# Test local imports
+testlocal local
-# Test that go install x.go fails.
-if ./testgo install testdata/local/easy.go >/dev/null 2>&1; then
- echo "go install testdata/local/easy.go succeeded"
- ok=false
-fi
+# Test local imports again, with bad characters in the directory name.
+bad='#$%:, &()*;<=>?\^{}'
+rm -rf "testdata/$bad"
+cp -R testdata/local "testdata/$bad"
+testlocal "$bad"
+rm -rf "testdata/$bad"
# Test tests with relative imports.
if ! ./testgo test ./testdata/testimport; then
コアとなるコードの解説
このコミットの核心は、dirToImportPath
関数が strings.Map
と makeImportValid
関数を組み合わせて、ディレクトリパスの各文字をGoのインポートパスの要件に適合するように変換する点にあります。
-
dirToImportPath
関数:- まず
filepath.ToSlash(dir)
を呼び出し、OS固有のパス区切り文字をスラッシュに統一します。 - 次に、
strings.Map(makeImportValid, ...)
を使用して、変換されたパス文字列の各ルーン(Unicodeコードポイント)に対してmakeImportValid
関数を適用します。これにより、パス内のすべての文字がGoのインポートパスとして有効な形式に変換されます。 - 最後に
pathpkg.Join("_", ...)
を使用して、変換されたパスの先頭に_
を付加します。これは、Goの内部的なインポートパスの処理における慣習的なプレフィックスであり、通常のインポートパスとの衝突を避ける目的があります。
- まず
-
makeImportValid
関数:- この関数は、Goのインポートパスの文字セットに関する厳密なルールを実装しています。
illegalChars
定数には、Goのインポートパスとして明示的に禁止されている文字が列挙されています。これには、Goの仕様やコンパイラ、そしてGoのパーサーがインポートパスを検証する際に使用するルールが反映されています。!unicode.IsGraphic(r)
は、表示できない制御文字などを除外します。unicode.IsSpace(r)
は、空白文字を除外します。strings.ContainsRune(illegalChars, r)
は、定義された不正な文字セットに含まれる文字を除外します。- これらの条件のいずれかに合致する文字は、すべてアンダースコア (
_
) に置換されます。これにより、不正な文字がインポートパスに含まれることを防ぎ、常に有効なインポートパスが生成されるようになります。
この修正により、Goツールチェーンは、ファイルシステム上の多様なディレクトリ名(特に特殊文字を含むもの)に対しても、堅牢かつ予測可能なインポートパスの変換を提供できるようになりました。
関連リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/95a8bab7b6db9c63281a0055b1a2471818129cd5
- Go CL (Code Review): https://golang.org/cl/5821048
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/12636.txt
- Go言語の公式ドキュメント (Goの仕様、パッケージ、インポートパスに関する情報)
- Goのソースコード (
src/cmd/go/pkg.go
,src/cmd/go/test.bash
,go/parser/parser.go
)