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

[インデックス 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環境で安定して動作しないという問題がありました。具体的には、以下の点が課題となっていました。

  1. Windowsにおけるファイルロックの問題: Windowsでは、実行中のファイルや、他のプロセスによって開かれているファイルを削除または上書きしようとすると、ファイルロックによって操作が失敗することがよくあります。go installが既存の実行可能ファイルを更新しようとする際に、この問題に直面し、ビルドが中断されることがありました。
  2. 実行可能ファイルの命名規則: Windowsの実行可能ファイルは通常.exe拡張子を持ちますが、Goのビルドスクリプトやツールがこの拡張子を適切に付与しない場合、システムが実行可能ファイルとして認識できない、またはパスが正しく解決できないといった問題が発生します。
  3. パスの正規化: WindowsとUnix系OSではパスの区切り文字(\ vs /)が異なります。Goのツールチェーン内でパスを扱う際に、OS間の互換性を保つための適切な正規化が不足していると、ファイルが見つからないなどの問題を引き起こす可能性があります。
  4. 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.Renameos.Remove: os.Renameはファイルまたはディレクトリの名前を変更(移動)する関数、os.Removeはファイルまたはディレクトリを削除する関数です。Windowsのファイルロック問題に対処するために、これらの関数を組み合わせて一時的な回避策が講じられることがあります。

  • ioutil.TempFile: io/ioutilパッケージ(Go 1.16以降はosパッケージに移行)のTempFile関数は、一時的なファイルを作成するために使用されます。これは、ファイル操作における中間ステップや、安全なファイル削除のテクニックで利用されます。

技術的詳細

このコミットは、前述の課題に対処するために、以下の技術的詳細な変更を導入しています。

  1. Windows実行可能ファイルの.exe拡張子付与: src/buildscript_windows_386.shsrc/buildscript_windows_amd64.shのビルドスクリプトにおいて、生成されるgoコマンドのバイナリ名がgo.exeに変更されました。これにより、Windowsが実行可能ファイルとして正しく認識し、パスからの実行が可能になります。 また、src/cmd/go/pkg.goでは、mainパッケージのターゲットパスを決定する際に、GOOSwindowsである場合に.exe拡張子を明示的に付与するロジックが追加されました。これは、go installが生成する実行可能ファイルが常に正しい拡張子を持つことを保証します。

  2. Windowsファイルロック問題への対処 (removeByRenaming関数): src/cmd/go/build.goremoveByRenamingという新しい関数が追加されました。この関数は、Windowsでファイルがロックされているために直接削除できない場合に、一時的な回避策として機能します。

    • まず、ioutil.TempFile("", "")を使って一時ファイルを作成し、その名前を取得します。
    • 次に、その一時ファイルをすぐに削除します。これは、os.Renameのターゲットとして使用する一時的なファイル名が確実に存在しないようにするためです。
    • そして、削除したい元のファイル(name)を、先ほど取得した一時ファイル名にリネーム(移動)します。
    • 最後に、リネームされた一時ファイルを削除しようと試みます。 この手法は、ファイルがロックされていてもリネームは可能であるというWindowsの特性を利用しています。リネームされたファイルは、元のパスからアクセスできなくなるため、新しいファイルをそのパスに書き込むことが可能になります。削除がすぐに成功しなくても、元のパスは解放されます。
  3. copyFileにおけるWindows固有のファイル削除ロジック: src/cmd/go/build.gocopyFile関数が修正されました。この関数は、ファイルをコピーする際に、既存の宛先ファイルを削除しようとします。

    • 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が既存の実行可能ファイルを正常に更新できるようになります。
  4. パスの正規化 (filepath.ToSlash): src/cmd/go/main.goにおいて、パッケージパスを処理する際にfilepath.ToSlashが導入されました。これにより、Windowsのパス区切り文字(\)がUnix形式のスラッシュ(/)に変換され、Goツールチェーン全体でパスの一貫した処理が保証されます。

  5. Cgoリンカーフラグの修正: src/pkg/runtime/cgo/trigger.goにおいて、Windows向けのCgoリンカーフラグが-lm -lmthreadsから-lm -mthreadsに修正されました。これは、mthreadsが単一のフラグであり、誤ってスペースで区切られていたタイプミスを修正したものです。これにより、Cgoを使用するGoプログラムがWindowsで正しくリンクされるようになります。

  6. ビルドタグの追加: 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.GOOSwindowsであるかをチェックし、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上での実行可能性を保証するために重要です。

関連リンク

参考にした情報源リンク