[インデックス 11735] ファイルの概要
このコミットは、Goプロジェクトの継続的インテグレーション(CI)システムであるGo BuilderにおけるWindows環境でのビルドプロセスに関する複数の問題を修正することを目的としています。具体的には、コマンドの実行方法、ファイルパスの操作、および設定ファイルの検索ロジックをWindowsの慣習に合わせて調整し、クロスプラットフォーム互換性を向上させています。
コミット
commit 947ea6f750809302534bdb57afd2091767bd4038
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Feb 9 14:52:01 2012 +1100
gobuilder: fix windows builder
Do not rewrite commands if they have .bash extnsion.
Use path/filepath to manipulate file paths everywhere.
Use all.bat on windows, not all.bash.
Use HOMEDRIVE/HOMEPATH to find .gobuildkey on windows.
R=rsc
CC=golang-dev
https://golang.org/cl/5630062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/947ea6f750809302534bdb57afd2091767bd4038
元コミット内容
gobuilder: fix windows builder
Do not rewrite commands if they have .bash extnsion.
Use path/filepath to manipulate file paths everywhere.
Use all.bat on windows, not all.bash.
Use HOMEDRIVE/HOMEPATH to find .gobuildkey on windows.
変更の背景
GoプロジェクトのビルドシステムであるGo Builderは、様々なプラットフォームでGoのコードをビルドし、テストする役割を担っています。このコミットが作成された当時、Windows環境でのビルドプロセスにはいくつかの不具合が存在していました。
主な問題点は以下の通りです。
.bash
拡張子の扱い: Unix系システムではシェルスクリプトに.bash
拡張子が付与されることがありますが、Windowsでは通常、バッチファイルには.bat
や.cmd
が使用されます。Go Builderが.bash
拡張子を持つコマンドを無条件にbash
で実行しようとすると、Windows環境では問題が発生する可能性がありました。- ファイルパスの操作: Goの標準ライブラリには、パス操作のための
path
パッケージとpath/filepath
パッケージが存在します。path
パッケージは主にUnixスタイルのパス(/
区切り)を扱うのに対し、path/filepath
パッケージは実行環境のOSに応じたパス区切り文字(Windowsでは\
、Unix系では/
)を適切に処理します。Go Builderのコードベースでpath
パッケージが不適切に使用されている箇所があり、Windows環境でのパス解決に問題を引き起こしていました。 - 設定ファイルの検索: Go Builderは
.gobuildkey
という設定ファイルをユーザーのホームディレクトリから検索します。Unix系システムではHOME
環境変数がホームディレクトリを指しますが、WindowsではHOMEDRIVE
とHOMEPATH
の組み合わせがこれに相当します。HOME
のみに依存するロジックでは、Windowsで正しく設定ファイルを見つけられない問題がありました。
これらの問題により、Windows環境でのGo Builderの信頼性が低下し、ビルドが失敗する原因となっていました。このコミットは、これらのクロスプラットフォーム互換性の問題を解決し、Windows上でのGo Builderの安定稼働を保証することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下の技術的な前提知識が役立ちます。
1. Go Builder / Go Dashboard
Go Builderは、Go言語の公式プロジェクトにおける継続的インテグレーション(CI)システムの一部です。Goのソースコードが変更されるたびに、様々なオペレーティングシステム(Windows, Linux, macOSなど)やアーキテクチャ(amd64, armなど)の組み合わせで自動的にビルドとテストを実行します。その結果はGo Dashboard(build.golang.org
)に表示され、Goプロジェクトの健全性を監視するために利用されます。Go Builderは、Goのリリースプロセスにおいて、安定したバイナリとテスト結果を保証する上で不可欠な役割を担っています。
2. Go言語におけるパス操作 (path
vs path/filepath
)
Go言語には、ファイルパスを操作するための2つの主要な標準パッケージがあります。
path
パッケージ: このパッケージは、スラッシュ (/
) をパス区切り文字とするUnixスタイルのパスを扱います。これは、URLパスや、OSに依存しないパス表現が必要な場合に適しています。例えば、path.Join("dir", "file")
は常に"dir/file"
を返します。path/filepath
パッケージ: このパッケージは、実行中のオペレーティングシステムに固有のパス区切り文字(Windowsではバックスラッシュ\
、Unix系ではスラッシュ/
)を使用してパスを操作します。ファイルシステム上の実際のパスを扱う際には、このパッケージを使用することが推奨されます。例えば、Windows上でfilepath.Join("dir", "file")
は"dir\\file"
を返し、Linux上で実行すると"dir/file"
を返します。クロスプラットフォーム対応のアプリケーションを開発する際には、path/filepath
の使用が不可欠です。
このコミットでは、path
パッケージからpath/filepath
パッケージへの移行が主要な変更点の一つとなっています。
3. WindowsとUnix系OSにおけるシェルスクリプトと環境変数
- シェルスクリプトの拡張子:
- Unix系OS (Linux, macOSなど): シェルスクリプトは通常、拡張子を持たないか、
.sh
、.bash
などの拡張子を持ちます。実行時には、ファイルの先頭にあるシバン(#!
)行によって使用するインタプリタ(例:#!/bin/bash
)が指定されます。 - Windows: バッチファイルは通常、
.bat
または.cmd
の拡張子を持ちます。これらはWindowsのコマンドプロンプト(cmd.exe
)によって直接実行されます。Git BashなどのUnix系シェル環境がインストールされている場合、.bash
スクリプトも実行できますが、ネイティブなWindows環境では.bat
が一般的です。
- Unix系OS (Linux, macOSなど): シェルスクリプトは通常、拡張子を持たないか、
- ホームディレクトリを示す環境変数:
- Unix系OS: ユーザーのホームディレクトリは通常、
HOME
環境変数によって指定されます。 - Windows: ユーザーのホームディレクトリは、通常
HOMEDRIVE
(例:C:
)とHOMEPATH
(例:\Users\YourUsername
)の組み合わせによって構成されます。HOME
環境変数が設定されている場合もありますが、これはGitなどのツールによって設定されることが多く、ネイティブなWindowsアプリケーションではHOMEDRIVE
とHOMEPATH
の組み合わせを使用するのがより堅牢です。
- Unix系OS: ユーザーのホームディレクトリは通常、
このコミットは、これらのOS間の違いを吸収し、Go BuilderがWindows環境で正しく動作するように修正しています。
技術的詳細
このコミットは、Go BuilderのWindows環境での動作を改善するために、以下の主要な技術的変更を導入しています。
-
.bash
拡張子を持つコマンドの自動書き換えの停止:- 以前の
misc/dashboard/builder/exec.go
にはuseBash
という関数が存在し、実行しようとするコマンドの最初の引数が.bash
で終わる場合、そのコマンドの前にbash
を自動的に追加していました。これは、Unix系システムで.bash
スクリプトを明示的にbash
で実行するためのロジックでした。 - しかし、Windows環境では、
.bash
拡張子を持つファイルが必ずしもbash
で実行されるとは限りません。また、Windowsのビルドプロセスでは.bat
ファイルが使用されることが想定されており、この自動書き換えが不必要な、あるいは誤ったコマンド実行を引き起こす可能性がありました。 - このコミットでは、
useBash
関数とその呼び出しが完全に削除されました。これにより、コマンドは指定された通りに実行され、Windows環境での.bat
スクリプトの実行が妨げられることがなくなりました。
- 以前の
-
path/filepath
パッケージの全面的使用:misc/dashboard/builder/main.go
内のファイルパス操作において、path
パッケージ(Unixスタイルのパスを扱う)からpath/filepath
パッケージ(OS固有のパスを扱う)への移行が行われました。- 具体的には、
flag.String
で定義されるbuildroot
やbuildCmd
のデフォルト値、goroot
のパス結合、.gobuildkey
ファイルのパス構築、作業ディレクトリのパス、ソースディレクトリのパス、ログファイルのパスなど、ほぼ全てのパス操作がfilepath.Join
を使用するように変更されました。 - これにより、Windows環境ではパス区切り文字として
\
が、Unix系環境では/
が自動的に使用されるようになり、クロスプラットフォームでのパス解決の堅牢性が大幅に向上しました。
-
Windowsでのビルドコマンドの動的選択 (
all.bat
の使用):- 以前は、ビルドコマンドとして
./all.bash
がハードコードされていました。 - このコミットでは、
main.go
にallCmd
、cleanCmd
、suffix
という新しいグローバル変数が導入されました。 defaultSuffix()
関数が追加され、runtime.GOOS
がwindows
であれば.bat
を、そうでなければ.bash
を返すようになりました。- これにより、
allCmd
はWindowsではall.bat
に、それ以外のOSではall.bash
に解決されるようになり、buildCmd
やcleanCmd
の実行時に適切なスクリプトが選択されるようになりました。
- 以前は、ビルドコマンドとして
-
Windowsでの
.gobuildkey
ファイルの検索ロジックの改善:- 以前は、
.gobuildkey
ファイルを検索する際にos.Getenv("HOME")
に依存していました。 - Windows環境では
HOME
環境変数が常に設定されているとは限らず、またその値がユーザーのホームディレクトリを正確に指さない場合がありました。 - このコミットでは、
runtime.GOOS == "windows"
の場合にos.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
を使用してホームディレクトリのパスを構築するように変更されました。これにより、Windowsの標準的な方法でユーザーのホームディレクトリを特定し、.gobuildkey
ファイルを確実に検索できるようになりました。
- 以前は、
これらの変更は、Go BuilderがWindows環境でより安定して動作するための基盤を強化し、クロスプラットフォーム開発におけるGoの強みをさらに引き出すものです。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、misc/dashboard/builder/exec.go
とmisc/dashboard/builder/main.go
の2つのファイルに集中しています。
misc/dashboard/builder/exec.go
run
関数とrunLog
関数から、argv = useBash(argv)
の行が削除されました。useBash
関数自体がファイルから完全に削除されました。
--- a/misc/dashboard/builder/exec.go
+++ b/misc/dashboard/builder/exec.go
@@ -10,7 +10,6 @@ import (
"log"
"os"
"os/exec"
- "strings"
)
// run is a simple wrapper for exec.Run/Close
@@ -18,7 +17,6 @@ func run(envv []string, dir string, argv ...string) error {
if *verbose {
log.Println("run", argv)
}
- argv = useBash(argv)
cmd := exec.Command(argv[0], argv[1:]...)
cmd.Dir = dir
cmd.Env = envv
@@ -35,7 +33,6 @@ func runLog(envv []string, logfile, dir string, argv ...string) (string, int, er
if *verbose {
log.Println("runLog", argv)
}
- argv = useBash(argv)
b := new(bytes.Buffer)
var w io.Writer = b
@@ -62,13 +59,3 @@ func runLog(envv []string, logfile, dir string, argv ...string) (string, int, er
}
return b.String(), 0, err
}
-
-// useBash prefixes a list of args with 'bash' if the first argument
-// is a bash script.
-func useBash(argv []string) []string {
- // TODO(brainman): choose a more reliable heuristic here.
- if strings.HasSuffix(argv[0], ".bash") {
- argv = append([]string{"bash"}, argv...)
- }
- return argv
-}
misc/dashboard/builder/main.go
path
パッケージのインポートが削除され、path/filepath
パッケージが使用されるようになりました。buildroot
、goroot
、buildCmd
の定義でpath.Join
がfilepath.Join
に置き換えられました。allCmd
、cleanCmd
、suffix
という新しいグローバル変数が追加され、defaultSuffix()
関数が導入されました。.gobuildkey
ファイルのパスを決定するロジックが、Windows環境変数を考慮するように変更されました。- ビルド時の作業パス、ソースパス、ログファイルパスの生成に
filepath.Join
が使用されるようになりました。 - ビルドコマンドの実行時に、
buildCmd
が絶対パスでない場合にsrcDir
と結合するロジックが追加されました。 - クリーンアップコマンドの実行時に、
cleanCmd
が使用されるようになりました。 - リリースアーカイブのアップロードスクリプトのパスに
filepath.Join
が使用されるようになりました。 commitPoll
関数内のpkgRoot
のパス結合にfilepath.Join
が使用されるようになりました。
--- a/misc/dashboard/builder/main.go
+++ b/misc/dashboard/builder/main.go
@@ -12,7 +12,6 @@ import (
"io/ioutil"
"log"
"os"
- "path"
"path/filepath"
"regexp"
"runtime"
@@ -49,12 +48,12 @@ type Builder struct {
}
var (
- buildroot = flag.String("buildroot", path.Join(os.TempDir(), "gobuilder"), "Directory under which to build")
+ buildroot = flag.String("buildroot", filepath.Join(os.TempDir(), "gobuilder"), "Directory under which to build")
commitFlag = flag.Bool("commit", false, "upload information about new commits")
dashboard = flag.String("dashboard", "build.golang.org", "Go Dashboard Host")
buildRelease = flag.Bool("release", false, "Build and upload binary release archives")
buildRevision = flag.String("rev", "", "Build specified revision and exit")
- buildCmd = flag.String("cmd", "./all.bash", "Build command (specify absolute or relative to go/src/)")
+ buildCmd = flag.String("cmd", filepath.Join(".", allCmd), "Build command (specify relative to go/src/)")
external = flag.Bool("external", false, "Build external packages")
parallel = flag.Bool("parallel", false, "Build multiple targets in parallel")
verbose = flag.Bool("v", false, "verbose")
@@ -64,6 +63,9 @@ var (
goroot string
binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`)
releaseRe = regexp.MustCompile(`^release\.r[0-9\-.]+`)
+ allCmd = "all" + suffix
+ cleanCmd = "clean" + suffix
+ suffix = defaultSuffix()
)
func main() {
@@ -76,7 +78,7 @@ func main() {
if len(flag.Args()) == 0 && !*commitFlag {
flag.Usage()
}
- goroot = path.Join(*buildroot, "goroot")
+ goroot = filepath.Join(*buildroot, "goroot")
builders := make([]*Builder, len(flag.Args()))
for i, builder := range flag.Args() {
b, err := NewBuilder(builder)
@@ -171,7 +173,13 @@ func NewBuilder(builder string) (*Builder, error) {
}
// read keys from keyfile
- fn := path.Join(os.Getenv("HOME"), ".gobuildkey")
+ fn := ""
+ if runtime.GOOS == "windows" {
+ fn = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+ } else {
+ fn = os.Getenv("HOME")
+ }
+ fn = filepath.Join(fn, ".gobuildkey")
if s := fn + "-" + b.name; isFile(s) { // builder-specific file
fn = s
}
@@ -257,7 +265,7 @@ func (b *Builder) buildHash(hash string) error {
log.Println(b.name, "building", hash)
// create place in which to do work
- workpath := path.Join(*buildroot, b.name+"-"+hash[:12])
+ workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
return err
}
@@ -269,16 +277,20 @@ func (b *Builder) buildHash(hash string) error {
}
// update to specified revision
- if err := run(nil, path.Join(workpath, "go"), "hg", "update", hash); err != nil {
+ if err := run(nil, filepath.Join(workpath, "go"), "hg", "update", hash); err != nil {
return err
}
- srcDir := path.Join(workpath, "go", "src")
+ srcDir := filepath.Join(workpath, "go", "src")
// build
- logfile := path.Join(workpath, "build.log")
+ logfile := filepath.Join(workpath, "build.log")
+ cmd := *buildCmd
+ if !filepath.IsAbs(cmd) {
+ cmd = filepath.Join(srcDir, cmd)
+ }
startTime := time.Now()
- buildLog, status, err := runLog(b.envv(), logfile, srcDir, *buildCmd)
+ buildLog, status, err := runLog(b.envv(), logfile, srcDir, cmd)
runTime := time.Now().Sub(startTime)
if err != nil {
return fmt.Errorf("%s: %s", *buildCmd, err)
@@ -314,15 +326,16 @@ func (b *Builder) buildHash(hash string) error {
if hash == releaseHash {
// clean out build state
- if err := run(b.envv(), srcDir, "./clean.bash", "--nopkg"); err != nil {
- return fmt.Errorf("clean.bash: %s", err)
+ cmd := filepath.Join(srcDir, cleanCmd)
+ if err := run(b.envv(), srcDir, cmd, "--nopkg"); err != nil {
+ return fmt.Errorf("%s: %s", cleanCmd, err)
}
// upload binary release
fn := fmt.Sprintf("go.%s.%s-%s.tar.gz", release, b.goos, b.goarch)
if err := run(nil, workpath, "tar", "czf", fn, "go"); err != nil {
return fmt.Errorf("tar: %s", err)
}
- err := run(nil, workpath, path.Join(goroot, codePyScript),
+ err := run(nil, workpath, filepath.Join(goroot, codePyScript),
"-s", release,
"-p", codeProject,
"-u", b.codeUsername,
@@ -556,7 +569,7 @@ func commitPoll(key, pkg string) {
pkgRoot := goroot
if pkg != "" {
- pkgRoot = path.Join(*buildroot, pkg)
+ pkgRoot = filepath.Join(*buildroot, pkg)
if !hgRepoExists(pkgRoot) {
if err := hgClone(repoURL(pkg), pkgRoot); err != nil {
log.Printf("%s: hg clone failed: %v", pkg, err)
@@ -719,3 +732,12 @@ func repoURL(importPath string) string {
}
return "https://code.google.com/p/\" + m[1]
}
+
+// defaultSuffix returns file extension used for command files in
+// current os environment.
+func defaultSuffix() string {
+ if runtime.GOOS == "windows" {
+ return ".bat"
+ }
+ return ".bash"
+}
コアとなるコードの解説
misc/dashboard/builder/exec.go
の変更
useBash
関数の削除:- この関数は、コマンドが
.bash
で終わる場合に、そのコマンドの前にbash
を付加して実行しようとするものでした。これはUnix系システムでは有効な場合がありますが、Windowsでは.bat
ファイルが標準的な実行形式であり、.bash
拡張子を持つファイルが必ずしもbash
で実行されるとは限りません。 - この関数を削除することで、Go Builderはコマンドをより直接的に、OSのデフォルトの関連付けに従って実行するようになります。これにより、Windows上で
all.bat
のようなバッチファイルが正しく実行されるようになります。
- この関数は、コマンドが
misc/dashboard/builder/main.go
の変更
-
path
からpath/filepath
への移行:path
パッケージはUnixスタイルのパス(/
区切り)を前提としており、Windowsのパス(\
区切り)とは互換性がありません。filepath
パッケージは、実行中のOSに応じて適切なパス区切り文字を自動的に使用するため、クロスプラットフォーム対応のアプリケーションには必須です。buildroot
、goroot
、workpath
、srcDir
、logfile
、pkgRoot
など、ファイルシステム上の実際のパスを扱う全ての箇所でfilepath.Join
を使用するように変更されたことで、Windows環境でのパス解決の不具合が解消されました。例えば、filepath.Join("C:\\", "Users", "gobuilder")
はWindowsでC:\Users\gobuilder
を生成し、Linuxで/Users/gobuilder
を生成します。
-
ビルドコマンドの動的選択 (
all.bat
/all.bash
):defaultSuffix()
関数が導入され、runtime.GOOS
(実行中のOS)がwindows
であれば.bat
を、そうでなければ.bash
を返すようになりました。- この
suffix
変数を使ってallCmd
とcleanCmd
が定義されるため、Windowsではall.bat
とclean.bat
が、Unix系ではall.bash
とclean.bash
が自動的に選択されます。 - これにより、OSごとに異なるビルドスクリプトの拡張子を適切に処理できるようになり、Windowsでのビルドが正しく起動するようになりました。
-
.gobuildkey
ファイルの検索ロジックの改善:- Windowsでは
HOME
環境変数が常にユーザーのホームディレクトリを指すとは限りません。より信頼性の高い方法は、HOMEDRIVE
とHOMEPATH
環境変数を組み合わせることです。 - この変更により、Windows環境では
HOMEDRIVE
とHOMEPATH
を結合してホームディレクトリのパスを構築し、そこに.gobuildkey
ファイルを結合するようになりました。これにより、Windows上での設定ファイルの検索がより堅牢になりました。
- Windowsでは
-
ビルドコマンドの絶対パス解決:
buildCmd
が相対パスで指定された場合(例:all.bat
)、それがsrcDir
(Goのソースコードルート)からの相対パスとして解釈されるように、filepath.Join(srcDir, cmd)
というロジックが追加されました。これにより、ビルドコマンドが常に正しい場所から実行されることが保証されます。
これらの変更は、Go BuilderがWindows環境で直面していたパス解決、コマンド実行、および設定ファイル検索に関する根本的な問題を解決し、クロスプラットフォームでの一貫した動作を実現しています。
関連リンク
- Go Builder: https://build.golang.org/
- Go Dashboard: https://build.golang.org/
- このコミットのGerritレビューページ: https://golang.org/cl/5630062
参考にした情報源リンク
- Go言語
path
パッケージのドキュメント: https://pkg.go.dev/path - Go言語
path/filepath
パッケージのドキュメント: https://pkg.go.dev/path/filepath - Go言語
os
パッケージのドキュメント (環境変数関連): https://pkg.go.dev/os - Go言語
runtime
パッケージのドキュメント (runtime.GOOS
): https://pkg.go.dev/runtime - Windowsの環境変数
HOMEDRIVE
とHOMEPATH
に関する情報 (一般的なWindowsのドキュメントやフォーラム) - Unix系OSにおけるシェルスクリプトのシバンに関する情報 (一般的なLinux/Unixのドキュメント)