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

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

コミット

commit 98257750f483238f2895d6077bf42e7c91cd8f3d
Author: Olivier Duperray <duperray.olivier@gmail.com>
Date:   Mon Feb 6 12:10:49 2012 -0500

    misc/goplay: use go tool "run"
    
    Fixes #2872
    
    R=andybalholm, rsc
    CC=golang-dev
    https://golang.org/cl/5608056

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

https://github.com/golang/go/commit/98257750f483238f2895d6077bf42e7c91cd8f3d

元コミット内容

このコミットは、Go言語のmisc/goplayツールにおいて、プログラムのコンパイルと実行方法を従来のgomake installおよび手動でのコンパイル・リンク(5g/6g/8g5l/6l/8lコマンド)から、go tool "run"コマンドを使用するように変更するものです。これにより、goplayのセットアップと実行が簡素化され、Goツールチェーンの進化に合わせた改善が図られています。

変更の背景

この変更の背景には、Go言語のツールチェーンの成熟と、開発者体験の向上という目的があります。初期のGo言語では、プログラムのコンパイルやリンクには5g(Go 1.5以降はgo tool compile)、6g8gといったアーキテクチャ固有のコンパイラコマンドや、5l(Go 1.5以降はgo tool link)、6l8lといったリンカコマンドを直接使用する必要がありました。しかし、Go 1のリリースに向けて、より統合されたgoコマンドが導入され、そのサブコマンドとしてgo runが提供されるようになりました。

go runコマンドは、Goソースファイルを直接実行するための便利なコマンドであり、内部的にはコンパイルとリンクを自動的に行い、一時的な実行可能ファイルを生成して実行します。これにより、開発者は手動でコンパイルやリンクのステップを踏む必要がなくなり、より迅速にコードのテストや実行ができるようになりました。

goplayは、Go PlaygroundのようなWebベースのGoコード実行環境を提供するツールであり、その性質上、ユーザーが入力したコードを動的にコンパイル・実行する必要があります。このコミットは、goplayが古い手動コンパイル・リンクの仕組みに依存していたのを、よりモダンで簡潔なgo runコマンドに移行することで、コードベースの保守性を高め、将来的なGoツールチェーンの変更にも対応しやすくすることを目的としています。また、Fixes #2872とあるように、特定のバグや問題の解決にも寄与しています。

前提知識の解説

Go言語のツールチェーン

Go言語には、コードのビルド、テスト、実行などを支援する強力なツールチェーンが付属しています。その中心となるのがgoコマンドです。

  • go build: Goソースコードをコンパイルして実行可能ファイルを生成します。
  • go run: Goソースファイルをコンパイルし、生成された実行可能ファイルを一時的に実行します。開発中のスクリプトや簡単なプログラムの実行に便利です。内部的にはgo buildと実行を組み合わせたものです。
  • go install: パッケージをコンパイルし、その結果をGOPATH/bin(またはGOBIN)にインストールします。
  • go tool: Goツールチェーンに含まれる低レベルのツール(コンパイラ、リンカなど)を直接実行するためのコマンドです。例えば、go tool compilego tool linkなどがあります。このコミットで変更される前のgoplayは、これらの低レベルツールを直接呼び出していました。

Go Playground (golang.org/doc/play/)

Go Playgroundは、Go言語の公式ウェブサイトで提供されているオンラインのGoコード実行環境です。ユーザーはブラウザ上でGoコードを記述し、サーバーサイドでコンパイル・実行された結果を即座に確認できます。misc/goplayは、このGo Playgroundのローカル版のような位置づけのツールです。

os/execパッケージ

Go言語の標準ライブラリに含まれるos/execパッケージは、外部コマンドを実行するための機能を提供します。このコミットでは、goplayがユーザーのGoコードをコンパイル・実行するために、このパッケージを使用してgoコマンド(または以前は5g/6g/8g5l/6l/8l)を呼び出しています。

os.TempDir()と一時ファイル

os.TempDir()関数は、システムの一時ディレクトリのパスを返します。goplayは、ユーザーが入力したGoコードを一時ファイルとして保存し、それをコンパイル・実行するための一時的な実行可能ファイルもこのディレクトリに生成します。プログラムの実行後、これらのファイルは削除されます。

技術的詳細

このコミットの主要な技術的変更点は、Goプログラムのコンパイルと実行のワークフローを、手動でのコンパイラ/リンカ呼び出しからgo runコマンドへの移行です。

変更前:

  1. ユーザーのGoコードを一時ファイル(例: /tmp/compileXXXX.go)に保存。
  2. 5g/6g/8g(コンパイラ)を使って.goファイルをコンパイルし、オブジェクトファイル(例: /tmp/compileXXXX.6)を生成。
    • run(archChar+"g", "-o", obj, src)
  3. 5l/6l/8l(リンカ)を使ってオブジェクトファイルをリンクし、実行可能ファイル(例: /tmp/compileXXXX)を生成。
    • run(archChar+"l", "-o", bin, obj)
  4. 生成された実行可能ファイルを実行。
    • run(bin)
  5. 一時ファイル(.go, .6, 実行可能ファイル)を削除。

このアプローチでは、Goの実行環境のアーキテクチャ(GOARCH)に応じて適切なコンパイラ/リンカ(5g, 6g, 8gなど)を選択する必要があり、runtimeパッケージを使用してGOARCHを判定し、archChar変数に格納していました。また、Windows環境では実行可能ファイルに.exe拡張子を付加する処理も必要でした。

変更後:

  1. ユーザーのGoコードを一時ファイル(例: /tmp/goplayXXXX.go)に保存。
  2. go runコマンドを使って、この一時ファイルを直接実行。
    • run("go", "run", x)
  3. go runが内部的にコンパイル、リンク、実行を処理するため、明示的なコンパイラ/リンカの呼び出しは不要になる。
  4. 一時ファイル(.goファイル)を削除。

この変更により、以下のメリットがもたらされます。

  • コードの簡素化: archCharの判定ロジックや、コンパイル・リンクの各ステップを明示的に呼び出すコードが不要になり、goplay.goのコード量が大幅に削減されました。
  • 保守性の向上: Goツールチェーンの内部実装(コンパイラやリンカのコマンド名、引数など)に直接依存しなくなるため、将来的なGoのバージョンアップによるツールチェーンの変更に対して、goplayが影響を受けにくくなります。
  • クロスプラットフォーム対応の簡素化: go runコマンドがプラットフォーム固有の実行可能ファイルの命名規則(例: Windowsの.exe)や、アーキテクチャ固有のコンパイラ/リンカの選択を自動的に処理するため、goplay側でこれらのロジックを記述する必要がなくなります。
  • エラーハンドリングの簡素化: go runがコンパイル、リンク、実行の全てをラップするため、エラーハンドリングのポイントが一つに集約されます。

また、os.Chdir(os.TempDir())の追加により、goplayの実行ディレクトリを一時ディレクトリに変更しています。これは、go runがカレントディレクトリを基準に動作する場合があるため、一時ファイルを生成する場所と実行コンテキストを一致させるための措置と考えられます。

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

misc/goplay/doc.go

--- a/misc/goplay/doc.go
+++ b/misc/goplay/doc.go
@@ -5,11 +5,9 @@
 // Goplay is a web interface for experimenting with Go code.
 // It is similar to the Go Playground: http://golang.org/doc/play/
 // 
-// To use goplay, first build and install it:
+// To use goplay:
 //   $ cd $GOROOT/misc/goplay
-//   $ gomake install
-// Then, run it:
-//   $ goplay
+//   $ go run goplay.go
 // and load http://localhost:3999/ in a web browser.
 // 
 // You should see a Hello World program, which you can compile and run by

doc.goでは、goplayの実行方法に関するドキュメントが更新されています。

  • 変更前: gomake installでビルド・インストールし、goplayを実行。
  • 変更後: go run goplay.goで直接実行。

misc/goplay/goplay.go

--- a/misc/goplay/goplay.go
+++ b/misc/goplay/goplay.go
@@ -12,7 +12,6 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
-\t"runtime"
 	"strconv"
 	"text/template"
 )
@@ -25,25 +24,11 @@ var (
 var (
 	// a source of numbers, for naming temporary files
 	uniq = make(chan int)
-\t// the architecture-identifying character of the tool chain, 5, 6, or 8
-\tarchChar string
 )
 
 func main() {\n 	flag.Parse()\n \n-\t// set archChar
-\tswitch runtime.GOARCH {\n-\tcase "arm":\n-\t\tarchChar = "5"\n-\tcase "amd64":\n-\t\tarchChar = "6"\n-\tcase "386":\n-\t\tarchChar = "8"\n-\tdefault:\n-\t\tlog.Fatalln("unrecognized GOARCH:", runtime.GOARCH)\n-\t}\n-\n \t// source of unique numbers
 \tgo func() {\n \t\tfor i := 0; ; i++ {\n@@ -51,6 +36,12 @@ func main() {\n \t\t}\n \t}()\n \n+\t// go to TempDir
+\terr := os.Chdir(os.TempDir())\n+\tif err != nil {\n+\t\tlog.Fatal(err)\n+\t}\n+\n \thttp.HandleFunc("/", FrontPage)\n \thttp.HandleFunc("/compile", Compile)\n \tlog.Fatal(http.ListenAndServe(*httpListen, nil))\
@@ -69,25 +60,19 @@ func FrontPage(w http.ResponseWriter, req *http.Request) {\n }\n \n // Compile is an HTTP handler that reads Go source code from the request,\n-// compiles and links the code (returning any errors), runs the program, \n+// runs the program (returning any errors),\n // and sends the program\'s output as the HTTP response.\n func Compile(w http.ResponseWriter, req *http.Request) {\n-\t// x is the base name for .go, .6, executable files
-\tx := os.TempDir() + "/compile" + strconv.Itoa(<-uniq)\n-\tsrc := x + ".go"\n-\tobj := x + "." + archChar\n-\tbin := x\n-\tif runtime.GOOS == "windows" {\n-\t\tbin += ".exe"\n-\t}\n+\t// x is the base name for .go files
+\tx := "goplay" + strconv.Itoa(<-uniq) + ".go"\n \n \t// write request Body to x.go
-\tf, err := os.Create(src)\n+\tf, err := os.Create(x)\n \tif err != nil {\n \t\terror_(w, nil, err)\n \t\treturn\n \t}\n-\tdefer os.Remove(src)\n+\tdefer os.Remove(x)\n \tdefer f.Close()\n \t_, err = io.Copy(f, req.Body)\n \tif err != nil {\n@@ -96,26 +81,11 @@ func Compile(w http.ResponseWriter, req *http.Request) {\n \t}\n \tf.Close()\n \n-\t// build x.go, creating x.6
-\tout, err := run(archChar+"g", "-o", obj, src)\n-\tdefer os.Remove(obj)\n-\tif err != nil {\n-\t\terror_(w, out, err)\n-\t\treturn\n-\t}\n-\n-\t// link x.6, creating x (the program binary)
-\tout, err = run(archChar+"l", "-o", bin, obj)\n-\tdefer os.Remove(bin)\n-\tif err != nil {\n-\t\terror_(w, out, err)\n-\t\treturn\n-\t}\n-\n \t// run x
-\tout, err = run(bin)\n+\tout, err := run("go", "run", x)\n \tif err != nil {\n \t\terror_(w, out, err)\n+\t\treturn\n \t}\n \n \t// write the output of x as the http response

コアとなるコードの解説

goplay.goの変更点詳細

  1. runtimeパッケージの削除とarchChar変数の廃止:

    • 変更前は、runtime.GOARCHを使用して現在のアーキテクチャ(arm, amd64, 386)を判定し、それに対応するコンパイラ/リンカのサフィックス(5, 6, 8)をarchCharに設定していました。
    • go runコマンドを使用することで、このアーキテクチャ判定とそれに基づくツール選択のロジックが不要になったため、runtimeパッケージのインポートとarchChar変数の定義、およびその初期化ロジックが削除されました。
  2. main関数でのos.Chdir(os.TempDir())の追加:

    • main関数の冒頭で、カレントディレクトリをシステムの一時ディレクトリ(os.TempDir()が返すパス)に変更しています。
    • これは、go runコマンドが相対パスで指定されたソースファイルを処理する際に、一時ファイルが生成される場所と実行コンテキストを一致させるためと考えられます。これにより、go runが一時ファイルを正しく見つけ、処理できるようになります。
  3. Compile関数内のコンパイル・リンク処理の簡素化:

    • 一時ファイル名の変更: 以前はcompileXXXX.gocompileXXXX.6compileXXXXといった形式で一時ファイルを生成していましたが、go runを使用するにあたり、単にgoplayXXXX.goという形式の一時ファイル名に変更されました。
    • コンパイル・リンクステップの削除:
      • 変更前は、run(archChar+"g", ...)でコンパイル、run(archChar+"l", ...)でリンクを明示的に行っていました。
      • これらのステップが完全に削除され、代わりにrun("go", "run", x)という単一のコマンド呼び出しに置き換えられました。
      • これにより、obj(オブジェクトファイル)やbin(実行可能ファイル)といった中間ファイルに関する処理(作成、削除)も不要になりました。
    • Windows向け.exe拡張子処理の削除: 以前はruntime.GOOS == "windows"の場合に実行可能ファイルに.exe拡張子を付加するロジックがありましたが、go runがこれを自動的に処理するため、このロジックも削除されました。
    • エラーハンドリングの調整: go runの呼び出しでエラーが発生した場合の処理が、以前のコンパイル・リンク・実行の各ステップでのエラーハンドリングと統合されました。

これらの変更により、goplayはGoツールチェーンのより高レベルな抽象化を利用するようになり、コードの可読性、保守性、そして将来的な互換性が向上しました。

関連リンク

参考にした情報源リンク