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

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

このコミットは、Go言語のプレイグラウンドツールである misc/goplay において、プログラムの実行方法を go build から go run に変更するものです。これにより、main パッケージではないプログラムがリンクエラーを適切に報告できるようになります。

コミット

commit 8529d99e1d447e930a1172e5f30fe6c1f46922e6
Author: ChaiShushan <chaishushan@gmail.com>
Date:   Fri Jul 12 09:41:10 2013 +1000

    misc/goplay: use `go run x.go` instead of `go build x.go`
    
    when the program is not main package, `go run x.go` can't return the
    link error message. so use `go run x.go` in instead `go build x.go`.
    
    Fixes #5865.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/11165043

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

https://github.com/golang/go/commit/8529d99e1d447e930a1172e5f30fe6c1f46922e6

元コミット内容

misc/goplay: go build x.go の代わりに go run x.go を使用する。

プログラムが main パッケージではない場合、go run x.go はリンクエラーメッセージを返せません。そのため、go build x.go の代わりに go run x.go を使用します。

Fixes #5865.

R=golang-dev, adg CC=golang-dev https://golang.org/cl/11165043

変更の背景

この変更は、Go言語のIssue #5865 に対応するものです。元の goplay ツールでは、Goプログラムのコンパイルと実行に go build コマンドを使用していました。しかし、go build は実行可能ファイルを生成する際に、main パッケージではないGoファイル(例えば、ライブラリとして設計されたファイルや、単体テストファイルなど)に対しては、リンクエラーを適切に報告しないという問題がありました。

具体的には、go buildmain パッケージを持たないGoファイルをビルドしようとすると、通常はエラーを発生させますが、そのエラーメッセージが goplay の出力に適切に反映されないケースがありました。これにより、ユーザーが goplaymain パッケージではないコードを試した際に、期待するエラーメッセージが得られず、デバッグが困難になるというユーザーエクスペリエンス上の問題が生じていました。

このコミットは、この問題を解決するために、go build の代わりに go run を使用するように変更します。go run は、Goソースファイルをコンパイルして実行するコマンドですが、main パッケージを持たないファイルに対しては、より適切なエラーハンドリングを提供します。特に、リンクエラー(例えば、未定義の関数呼び出しや、依存関係の欠落など)が発生した場合に、そのエラーを goplay の出力に正確に反映させることが可能になります。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念とコマンドについて理解しておく必要があります。

  • main パッケージと main 関数: Go言語の実行可能なプログラムは、必ず main パッケージに属し、main 関数を持っていなければなりません。プログラムのエントリーポイントとなります。main パッケージ以外のパッケージは、通常、ライブラリとして機能し、他のプログラムからインポートされて使用されます。
  • go build コマンド:
    • Goソースコードをコンパイルして実行可能バイナリを生成するコマンドです。
    • go build [パッケージパスまたはファイルパス] の形式で使用します。
    • main パッケージのGoファイルを指定した場合、そのパッケージから実行可能ファイルを生成します。
    • main パッケージではないGoファイルを指定した場合、通常はエラーとなりますが、そのエラーの報告方法が go run とは異なります。
    • -o フラグを使用して、出力される実行可能ファイルの名前を指定できます。
  • go run コマンド:
    • Goソースコードをコンパイルし、その場で実行するコマンドです。
    • go run [ファイルパス] の形式で使用します。
    • 内部的には一時的な実行可能ファイルを生成し、それを実行します。実行後、一時ファイルは削除されます。
    • main パッケージのGoファイルを指定した場合、そのプログラムを実行します。
    • main パッケージではないGoファイルを指定した場合、コンパイルエラーやリンクエラーをより直接的に報告します。これは、go run が単一のファイルまたはパッケージを「実行」することを目的としているため、実行に必要なすべての依存関係が解決されていることを期待し、そうでない場合に明確なエラーを出す傾向があるためです。
  • リンクエラー:
    • プログラムのコンパイル段階ではなく、リンク段階で発生するエラーです。
    • コンパイルは個々のソースファイルを機械語に変換するプロセスですが、リンクはそれらの機械語ファイルやライブラリを結合して最終的な実行可能ファイルを生成するプロセスです。
    • リンクエラーは、例えば、呼び出されている関数がどこにも定義されていない場合(未解決のシンボル)、または必要なライブラリが見つからない場合などに発生します。
  • misc/goplay:
    • Go言語の公式リポジトリに含まれるツールの一つで、Goコードをブラウザ上で実行できるプレイグラウンド機能を提供します。
    • ユーザーが入力したGoコードをサーバーサイドでコンパイル・実行し、その結果をブラウザに返します。

技術的詳細

このコミットの技術的な核心は、Goプログラムのコンパイルと実行における go buildgo run の挙動の違い、特に main パッケージではないGoファイルに対するエラーハンドリングの違いを理解し、それを goplay ツールに適用した点にあります。

go build の問題点: goplay は、ユーザーが入力したGoコードを一時ファイル (x.go) として保存し、それをコンパイル・実行していました。以前は go build -o bin x.go のように go build を使用して実行可能ファイル (bin) を生成し、その後その bin を実行していました。 しかし、ユーザーが main パッケージではないGoコード(例: package mylib; func MyFunc() {})を入力した場合、go build は実行可能ファイルを生成できません。このとき、go build はエラーを返しますが、そのエラーメッセージが goplay の期待する形式でなかったり、あるいは goplay がそのエラーを適切に捕捉・表示できなかったりするケースがありました。特に、リンクエラーのような、実行可能ファイルを生成する上で不可欠な問題が、ユーザーに分かりにくい形でしか伝わらないことが問題でした。

go run への変更の利点: go run x.go は、指定されたGoファイルをコンパイルし、もしそれが main パッケージであり実行可能であれば、その場で実行します。もし main パッケージでなければ、go run はコンパイルエラーまたはリンクエラーをより直接的に、かつ標準エラー出力に明確に出力します。 goplay の文脈では、ユーザーが main パッケージではないコードを入力した場合、それは実行可能ではないため、go run はその事実をエラーとして明確に報告します。このエラーメッセージは、goplay が捕捉しやすく、ユーザーに「これは実行可能なプログラムではありません」という情報を正確に伝えることができます。 また、go run は一時的な実行可能ファイルを生成し、実行後に自動的に削除するため、go build で明示的に os.Remove(bin) を呼び出す必要がなくなり、コードが簡潔になります。

runtime パッケージの削除: 以前のコードでは、runtime.GOOS を使用してWindows環境での実行可能ファイルの拡張子 (.exe) を考慮していましたが、go run を使用する場合、この処理はGoツールチェーンが内部的に処理するため、明示的に runtime パッケージをインポートしてOSを判別する必要がなくなりました。これにより、不要な依存関係が削除され、コードのクリーンアップにも繋がっています。

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

変更は misc/goplay/goplay.go ファイル内で行われています。

--- a/misc/goplay/goplay.go
+++ b/misc/goplay/goplay.go
@@ -14,7 +14,6 @@ import (
 	"os/exec"
 	"path/filepath"
 	"regexp"
-	"runtime"
 	"strconv"
 	"text/template"
 )
@@ -92,10 +91,6 @@ func compile(req *http.Request) (out []byte, err error) {
 	// x is the base name for .go, .6, executable files
 	x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq))
 	src := x + ".go"
-	bin := x
-	if runtime.GOOS == "windows" {
-		bin += ".exe"
-	}
  
 	// rewrite filename in error output
 	defer func() {
@@ -116,16 +111,13 @@ func compile(req *http.Request) (out []byte, err error) {
 \t\treturn
 \t}
  
-\t// build x.go, creating x
-\tout, err = run(dir, "go", "build", "-o", bin, file)
-\tdefer os.Remove(bin)
+\t// go run x.go
+\tout, err = run(dir, "go", "run", file)
 \tif err != nil {\
 \t\treturn
 \t}
-\n-\t// run x
-\treturn run("", bin)
+\treturn out, nil
 }\

コアとなるコードの解説

  1. runtime パッケージの削除:

    -	"runtime"
    

    go run コマンドが内部的にOSに応じた実行可能ファイルの処理を行うため、runtime.GOOS を使用してWindowsの .exe 拡張子を付与するロジックが不要になりました。これに伴い、runtime パッケージのインポートも削除されました。

  2. 実行可能ファイル名 bin の定義とWindows対応ロジックの削除:

    -	bin := x
    -	if runtime.GOOS == "windows" {
    -		bin += ".exe"
    -	}
    

    上記と同様の理由で、bin 変数の定義と、Windows環境での .exe 拡張子を付与する条件分岐が削除されました。go run は直接ソースファイルを指定するため、明示的なバイナリファイル名を管理する必要がなくなります。

  3. go build から go run へのコマンド変更:

    -	// build x.go, creating x
    -	out, err = run(dir, "go", "build", "-o", bin, file)
    -	defer os.Remove(bin)
    +	// go run x.go
    +	out, err = run(dir, "go", "run", file)
    

    これが最も重要な変更点です。

    • 以前は go build -o [バイナリ名] [ソースファイル名] を実行し、指定されたバイナリ名で実行可能ファイルを生成していました。
    • 変更後は go run [ソースファイル名] を実行します。これにより、Goツールチェーンが一時的な実行可能ファイルを生成し、それを実行します。
    • defer os.Remove(bin) も削除されました。これは、go run が実行後に一時ファイルを自動的にクリーンアップするため、手動で削除する必要がなくなったためです。
  4. 実行ロジックの簡素化:

    -	// run x
    -	return run("", bin)
    +	return out, nil
    

    以前は go build で生成したバイナリ (bin) を別途 run("", bin) で実行していました。しかし、go run はコンパイルと実行を一度に行うため、run 関数の呼び出しが一度で済み、その結果を直接返すように変更されました。これにより、コードのフローが大幅に簡素化されています。

これらの変更により、goplaymain パッケージではないGoコードに対しても、より正確で分かりやすいエラーメッセージをユーザーに提供できるようになり、ツールの使い勝手が向上しました。

関連リンク

参考にした情報源リンク