[インデックス 10928] ファイルの概要
このコミットは、Go言語のビルドシステムにおいて、特にWindows環境でのgo install
コマンドの動作を改善するための複数の修正を導入しています。主な目的は、Windows特有のファイルロック問題、実行可能ファイルの命名規則(.exe
拡張子)、およびパスの正規化に関する問題を解決し、go install
がWindows上で正しく機能するようにすることです。
コミット
commit a4628167535086542b40405bbe3d7138816d2e1b
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Wed Dec 21 16:57:44 2011 +1100
build: multiple fixes to make "go install" work on windows
R=rsc
CC=golang-dev
https://golang.org/cl/5502054
---
src/buildscript_windows_386.sh | 2 +-\
src/buildscript_windows_amd64.sh | 2 +-\
src/cmd/go/build.go | 42 +++++++++++++++++++++++++++++++++++++++-
src/cmd/go/main.go | 2 +-\
src/cmd/go/pkg.go | 3 +++
src/pkg/mime/type_unix.go | 2 ++\
src/pkg/runtime/cgo/trigger.go | 2 +-\
7 files changed, 50 insertions(+), 5 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4628167535086542b40405bbe3d7138816d2e1b
元コミット内容
build: multiple fixes to make "go install" work on windows
R=rsc
CC=golang-dev
https://golang.org/cl/5502054
変更の背景
このコミットが行われた背景には、Go言語のビルドツールであるgo install
がWindows環境で安定して動作しないという問題がありました。具体的には、以下の点が課題となっていました。
- Windowsにおけるファイルロックの問題: Windowsでは、実行中のファイルや、他のプロセスによって開かれているファイルを削除または上書きしようとすると、ファイルロックによって操作が失敗することがよくあります。
go install
が既存の実行可能ファイルを更新しようとする際に、この問題に直面し、ビルドが中断されることがありました。 - 実行可能ファイルの命名規則: Windowsの実行可能ファイルは通常
.exe
拡張子を持ちますが、Goのビルドスクリプトやツールがこの拡張子を適切に付与しない場合、システムが実行可能ファイルとして認識できない、またはパスが正しく解決できないといった問題が発生します。 - パスの正規化: WindowsとUnix系OSではパスの区切り文字(
\
vs/
)が異なります。Goのツールチェーン内でパスを扱う際に、OS間の互換性を保つための適切な正規化が不足していると、ファイルが見つからないなどの問題を引き起こす可能性があります。 - Cgoのリンカーフラグ: Cgo(GoとC言語の相互運用機能)を使用する際に、Windows環境でのリンカーフラグが不適切であると、ビルドエラーや実行時エラーが発生する可能性がありました。
これらの問題は、Go開発者がWindows環境でGoプログラムをビルド・インストールする際の大きな障壁となっており、このコミットはそれらの解決を目指しています。
前提知識の解説
このコミットの理解を深めるために、いくつかの前提知識を解説します。
-
go install
コマンド:go install
はGo言語のビルドツールチェーンの一部であり、Goのソースコードをコンパイルし、生成されたバイナリ(実行可能ファイルやライブラリ)をGOBIN
またはGOPATH/pkg
ディレクトリにインストールするコマンドです。これにより、ユーザーはビルドされたプログラムをシステムパスから直接実行できるようになります。 -
GOBIN
環境変数:GOBIN
は、go install
コマンドが生成された実行可能ファイルを配置するディレクトリを指定する環境変数です。このディレクトリがシステムのPATHに含まれていれば、どこからでもGoのプログラムを実行できるようになります。 -
GOOS
環境変数:GOOS
は、Goプログラムをビルドするターゲットオペレーティングシステムを指定する環境変数です。例えば、GOOS=windows
と設定すると、Windows用のバイナリが生成されます。 -
ファイルロック (Windows): Windowsオペレーティングシステムでは、ファイルが別のプロセスによって開かれている場合、そのファイルを削除したり、上書きしたりすることが制限される「ファイルロック」というメカニズムがあります。これは、データの整合性を保護するためのものですが、ビルドプロセスにおいては、古い実行可能ファイルを新しいバージョンで置き換えようとする際に問題となることがあります。
-
ビルドタグ (
+build
directive): Goのソースファイルには、特定のビルド条件(OS、アーキテクチャなど)に基づいてファイルをコンパイルに含めるかどうかを制御するための「ビルドタグ」を記述できます。例えば、// +build windows
はWindows環境でのみこのファイルがコンパイルされることを意味します。 -
Cgo: Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用する際には、Cコンパイラやリンカーのオプションを指定するために
#cgo
ディレクティブが使われます。 -
LDFLAGS
:LDFLAGS
は、リンカーに渡される追加のフラグ(オプション)を指定するための変数です。Cgoのコンテキストでは、リンクするライブラリ(例:-lm
で数学ライブラリ、-lpthread
でPOSIXスレッドライブラリ)やその他のリンカー設定を指定するために使用されます。 -
filepath.ToSlash
: Goのpath/filepath
パッケージは、OS固有のパス操作を提供します。filepath.ToSlash
関数は、OS固有のパス区切り文字(Windowsでは\
)をスラッシュ(/
)に変換し、パスを正規化するために使用されます。これは、Goの内部でパスを一貫して扱うために重要です。 -
os.Rename
とos.Remove
:os.Rename
はファイルまたはディレクトリの名前を変更(移動)する関数、os.Remove
はファイルまたはディレクトリを削除する関数です。Windowsのファイルロック問題に対処するために、これらの関数を組み合わせて一時的な回避策が講じられることがあります。 -
ioutil.TempFile
:io/ioutil
パッケージ(Go 1.16以降はos
パッケージに移行)のTempFile
関数は、一時的なファイルを作成するために使用されます。これは、ファイル操作における中間ステップや、安全なファイル削除のテクニックで利用されます。
技術的詳細
このコミットは、前述の課題に対処するために、以下の技術的詳細な変更を導入しています。
-
Windows実行可能ファイルの
.exe
拡張子付与:src/buildscript_windows_386.sh
とsrc/buildscript_windows_amd64.sh
のビルドスクリプトにおいて、生成されるgo
コマンドのバイナリ名がgo.exe
に変更されました。これにより、Windowsが実行可能ファイルとして正しく認識し、パスからの実行が可能になります。 また、src/cmd/go/pkg.go
では、main
パッケージのターゲットパスを決定する際に、GOOS
がwindows
である場合に.exe
拡張子を明示的に付与するロジックが追加されました。これは、go install
が生成する実行可能ファイルが常に正しい拡張子を持つことを保証します。 -
Windowsファイルロック問題への対処 (
removeByRenaming
関数):src/cmd/go/build.go
にremoveByRenaming
という新しい関数が追加されました。この関数は、Windowsでファイルがロックされているために直接削除できない場合に、一時的な回避策として機能します。- まず、
ioutil.TempFile("", "")
を使って一時ファイルを作成し、その名前を取得します。 - 次に、その一時ファイルをすぐに削除します。これは、
os.Rename
のターゲットとして使用する一時的なファイル名が確実に存在しないようにするためです。 - そして、削除したい元のファイル(
name
)を、先ほど取得した一時ファイル名にリネーム(移動)します。 - 最後に、リネームされた一時ファイルを削除しようと試みます。 この手法は、ファイルがロックされていてもリネームは可能であるというWindowsの特性を利用しています。リネームされたファイルは、元のパスからアクセスできなくなるため、新しいファイルをそのパスに書き込むことが可能になります。削除がすぐに成功しなくても、元のパスは解放されます。
- まず、
-
copyFile
におけるWindows固有のファイル削除ロジック:src/cmd/go/build.go
のcopyFile
関数が修正されました。この関数は、ファイルをコピーする際に、既存の宛先ファイルを削除しようとします。os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
がエラーを返した場合(特にWindowsでファイルロックが原因の場合)、runtime.GOOS != "windows"
のチェックを行い、Windows環境でのみ特別な処理を行います。- Windowsの場合、
removeByRenaming(dst)
を呼び出して、ロックされたファイルを一時的に移動し、元のパスを解放します。 - その後、再度
os.OpenFile
を試みます。これにより、実行中のバイナリを上書きしようとした際に発生するファイルロックエラーを回避し、go install
が既存の実行可能ファイルを正常に更新できるようになります。
-
パスの正規化 (
filepath.ToSlash
):src/cmd/go/main.go
において、パッケージパスを処理する際にfilepath.ToSlash
が導入されました。これにより、Windowsのパス区切り文字(\
)がUnix形式のスラッシュ(/
)に変換され、Goツールチェーン全体でパスの一貫した処理が保証されます。 -
Cgoリンカーフラグの修正:
src/pkg/runtime/cgo/trigger.go
において、Windows向けのCgoリンカーフラグが-lm -lmthreads
から-lm -mthreads
に修正されました。これは、mthreads
が単一のフラグであり、誤ってスペースで区切られていたタイプミスを修正したものです。これにより、Cgoを使用するGoプログラムがWindowsで正しくリンクされるようになります。 -
ビルドタグの追加:
src/pkg/mime/type_unix.go
に// +build darwin freebsd linux openbsd plan9
というビルドタグが追加されました。これにより、このファイルがUnix系OSでのみコンパイルされることが明示され、Windows環境ではコンパイル対象から除外されるようになります。これは、OS固有のコードの分離と、クロスプラットフォームビルドの正確性を保証するために重要です。
これらの変更は、GoのビルドシステムがWindows環境でより堅牢かつ信頼性高く動作するための基盤を築きました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルと関数です。
src/cmd/go/build.go
:removeByRenaming
関数の新規追加copyFile
関数内のWindows固有のファイル削除ロジックの追加
src/cmd/go/pkg.go
:scanPackage
関数内で、Windows向けに.exe
拡張子を付与するロジックの追加
src/buildscript_windows_386.sh
およびsrc/buildscript_windows_amd64.sh
:cp
コマンドのターゲットファイル名をgo
からgo.exe
に変更
コアとなるコードの解説
src/cmd/go/build.go
における removeByRenaming
関数と copyFile
の変更
// removeByRenaming removes file name by moving it to a tmp
// directory and deleting the target if possible.
func removeByRenaming(name string) error {
f, err := ioutil.TempFile("", "")
if err != nil {
return err
}
tmpname := f.Name()
f.Close()
err = os.Remove(tmpname) // 一時ファイルをすぐに削除
if err != nil {
return err
}
err = os.Rename(name, tmpname) // 元のファイルを一時名にリネーム
if err != nil {
// assume name file does not exists,
// otherwise later code will fail.
return nil
}
err = os.Remove(tmpname) // リネームされた一時ファイルを削除
if err != nil {
// TODO(brainman): file is locked and can't be deleted.
// We need to come up with a better way of doing it.
}
return nil
}
// copyFile is like 'cp src dst'.
func (b *builder) copyFile(dst, src string, perm uint32) error {
// ... (既存のコード) ...
os.Remove(dst) // 既存の宛先ファイルを削除しようと試みる
df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
if runtime.GOOS != "windows" { // Windows以外では通常のエラー処理
return err
}
// Windows does not allow to replace binary file
// while it is executing. We will cheat.
err = removeByRenaming(dst) // Windowsでファイルロックが発生した場合の回避策
if err != nil {
return err
}
df, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) // 再度ファイルを開く
if err != nil {
return err
}
}
// ... (既存のコード) ...
}
removeByRenaming
関数は、Windowsで実行中のバイナリを上書きする際に発生するファイルロック問題を回避するための巧妙な手法です。Windowsでは、実行中のファイルを直接削除することはできませんが、リネームすることは可能です。この関数は、まず削除したいファイル(name
)を一時的な名前に変更し、その後その一時ファイルを削除しようとします。これにより、元のパスが解放され、新しいファイルをその場所に書き込むことができるようになります。
copyFile
関数では、ファイルをコピーする際に宛先ファイルを開くことができない場合(特にWindowsでファイルロックが原因の場合)、runtime.GOOS
がwindows
であるかをチェックし、removeByRenaming
を呼び出してこの問題を回避します。これにより、go install
が既存の実行可能ファイルをスムーズに更新できるようになります。
src/cmd/go/pkg.go
における .exe
拡張子付与ロジック
func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string) (*Package, error) {
// ... (既存のコード) ...
if info.Package == "main" {
_, elem := filepath.Split(importPath)
targ = filepath.Join(t.BinDir(), elem)
if ctxt.GOOS == "windows" { // Windowsの場合に.exe拡張子を付与
targ += ".exe"
}
} else {
targ = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// ... (既存のコード) ...
}
このコードスニペットは、go install
が生成する実行可能ファイルの命名規則を修正しています。main
パッケージ(つまり実行可能プログラム)をビルドする際、ターゲットOSがWindowsである場合(ctxt.GOOS == "windows"
)、生成されるバイナリのファイル名に明示的に.exe
拡張子を追加します。これにより、WindowsシステムがGoの実行可能ファイルを正しく認識し、ユーザーがコマンドラインから直接実行できるようになります。
src/buildscript_windows_386.sh
および src/buildscript_windows_amd64.sh
の変更
# ... (既存のコード) ...
mkdir -p $GOBIN/
-cp $WORK/cmd/go/_obj/a.out $GOBIN/go
+cp $WORK/cmd/go/_obj/a.out $GOBIN/go.exe
これらのシェルスクリプトは、Goの初期ビルドプロセスの一部であり、Goツールチェーン自体をビルドする際に使用されます。この変更は、Goコンパイラとツールが生成するgo
コマンドのバイナリが、Windows環境でgo.exe
として正しくコピーされるようにします。これは、Goツールチェーンの自己ホスティング(GoでGoをビルドする)において、Windows上での実行可能性を保証するために重要です。
関連リンク
- Go Change List 5502054: https://golang.org/cl/5502054
参考にした情報源リンク
- Go言語公式ドキュメント (Goコマンド): https://go.dev/cmd/go/
- Go言語公式ドキュメント (パッケージとモジュール): https://go.dev/doc/code
- Go言語公式ドキュメント (Cgo): https://go.dev/blog/cgo
- Windowsにおけるファイルロックの挙動に関する一般的な情報 (Microsoft Learnなど)
- Goの
path/filepath
パッケージに関するドキュメント: https://pkg.go.dev/path/filepath - Goの
os
パッケージに関するドキュメント: https://pkg.go.dev/os - Goの
io/ioutil
パッケージに関するドキュメント (Go 1.16以降はos
パッケージに統合): https://pkg.go.dev/io/ioutil - Goの
runtime
パッケージに関するドキュメント: https://pkg.go.dev/runtime - Goのビルドタグに関するドキュメント: https://go.dev/cmd/go/#hdr-Build_constraints
- Goの環境変数に関するドキュメント: https://go.dev/doc/install/source#environment