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

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

このコミットでは、go コマンドラインツールの run サブコマンドの動作が変更されました。具体的には、以下の3つのファイルが変更されています。

  • doc/go1.1.html: Go 1.1 のリリースノートに go run コマンドの変更点が追記されました。
  • src/cmd/go/doc.go: go run コマンドのヘルプメッセージが更新され、ファイルが指定されない場合の新しい動作が記述されました。
  • src/cmd/go/run.go: go run コマンドの実際のロジックが修正され、ファイルが指定されない場合に現在のディレクトリ内の非テストGoソースファイルを自動的に実行するようになりました。

コミット

commit 2a99f2fb2a736a9a4575f937cf7af57589beba9b
Author: Jonathan Rudenberg <jonathan@titanous.com>
Date:   Thu Apr 4 12:04:35 2013 +1100

    cmd/go: run main package when no files are listed
    
    Fixes 5164.
    
    R=golang-dev, iant, adg
    CC=golang-dev
    https://golang.org/cl/8119049

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

https://github.com/golang/go/commit/2a99f2fb2a736a9a4575f937cf7af57589beba9b

元コミット内容

cmd/go: run main package when no files are listed

Fixes 5164.

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

変更の背景

この変更は、GoのIssue 5164「cmd/go: run main package when no files are listed」を解決するために行われました。

以前の go run コマンドは、実行するGoソースファイルを明示的に指定する必要がありました。例えば、main.go というファイルを実行するには go run main.go と入力する必要がありました。もしファイルが指定されなかった場合、go run: no go files listed というエラーが発生していました。

この挙動は、特に main パッケージのソースファイルと同じディレクトリにテストファイル(_test.go で終わるファイル)が存在する場合に問題を引き起こしていました。例えば、go run *.go のようにワイルドカードを使って実行しようとすると、テストファイルも含まれてしまい、go run がエラーを返す原因となっていました。これは、go test コマンドが引数なしで実行された場合に現在のディレクトリ内のテストファイルを自動的に見つけて実行するのと対照的で、一貫性に欠けるという指摘がありました。

このコミットは、go run コマンドの使い勝手を向上させ、go test と同様に、引数なしで実行された場合に現在のディレクトリ内の非テストGoソースファイルを自動的に見つけて実行できるようにすることで、この問題を解決することを目的としています。これにより、go run は実質的に go run *.go のような動作を、テストファイルを誤って含めることなく実現できるようになりました。

前提知識の解説

Go言語の main パッケージと main 関数

Go言語の実行可能なプログラムは、必ず package mainfunc main() を含んでいます。

  • package main: これは、そのファイルが実行可能なプログラムのエントリポイントであることを示します。
  • func main(): プログラムが実行される際に最初に呼び出される関数です。

go run コマンドは、この main パッケージと main 関数を含むGoソースファイルをコンパイルし、実行するために使用されます。

go run コマンド

go run コマンドは、指定されたGoソースファイルを一時的にコンパイルし、その結果生成された実行可能ファイルを即座に実行するGoツールチェーンのコマンドです。開発中に小さなプログラムを素早くテストしたり、スクリプトのように実行したりするのに便利です。通常、コンパイルされた実行可能ファイルは一時ディレクトリに作成され、実行後に削除されます。

テストファイル (_test.go)

Go言語では、テストコードは慣習的に _test.go というサフィックスを持つファイルに記述されます。例えば、my_package.go のテストは my_package_test.go に書かれます。go test コマンドは、これらの _test.go ファイルを自動的に認識し、テストを実行します。通常のアプリケーションコードとは分離されており、go rungo build で直接実行されることは意図されていません。

filepath.Glob

Go標準ライブラリの path/filepath パッケージにある Glob 関数は、シェルが使用するパターン(ワイルドカード)に一致するファイル名を検索するために使用されます。例えば、*.go というパターンは、現在のディレクトリにあるすべての .go 拡張子を持つファイルに一致します。

strings.HasSuffix

Go標準ライブラリの strings パッケージにある HasSuffix 関数は、ある文字列が特定のサフィックス(接尾辞)で終わっているかどうかをチェックするために使用されます。このコミットでは、ファイル名が _test.go で終わっているかどうかを判断するために使われています。

技術的詳細

このコミットの主要な変更点は、go run コマンドが引数なしで呼び出された場合の動作です。

  1. 引数なしでの実行: 以前は、go run にファイルが指定されない場合、fatalf("go run: no go files listed") というエラーで即座に終了していました。この変更により、files スライスが空の場合、go run は現在の作業ディレクトリ内のすべての .go ファイルを検索するようになりました。
  2. filepath.Glob("*.go") の使用: path/filepath パッケージの Glob 関数を使用して、現在のディレクトリにあるすべての .go ファイルのリストを取得します。これにより、明示的にファイル名を指定しなくても、Goソースファイルを自動的に検出できるようになります。
  3. テストファイルの除外: 取得した .go ファイルのリストから、_test.go で終わるファイルをフィルタリングして除外します。これは strings.HasSuffix(file, "_test.go") を使用して行われます。これにより、go run が誤ってテストコードをコンパイル・実行してしまうことを防ぎます。
  4. エラーハンドリングの改善: filepath.Glob がエラーを返した場合の処理が追加されました。また、フィルタリングの結果、実行可能なGoファイルが一つも見つからなかった場合に fatalf("go run: no go files found") というエラーメッセージを出すようになりました。
  5. ドキュメントの更新: doc/go1.1.htmlgo run コマンドの変更点がGo 1.1のリリースノートとして追加され、src/cmd/go/doc.go のヘルプメッセージも更新され、新しい動作がユーザーに明確に伝えられるようになりました。

この変更により、go run はより柔軟になり、go run とだけ入力することで、現在のディレクトリにある main パッケージのプログラムを簡単に実行できるようになりました。これは、特に単一のGoファイルで構成されるスクリプトのようなプログラムを頻繁に実行する場合に便利です。

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

src/cmd/go/doc.go

--- a/src/cmd/go/doc.go
+++ b/src/cmd/go/doc.go
@@ -367,9 +367,10 @@ Compile and run Go program
 
 Usage:
 
--	go run [build flags] gofiles... [arguments...]
++	go run [build flags] [gofiles...] [arguments...]
 
 Run compiles and runs the main package comprising the named Go source files.
+If no files are named, it compiles and runs all non-test Go source files.
 
 For more about build flags, see 'go help build'.

src/cmd/go/run.go

--- a/src/cmd/go/run.go
+++ b/src/cmd/go/run.go
@@ -8,14 +8,16 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strings"
 )
 
 var cmdRun = &Command{
-\tUsageLine: "run [build flags] gofiles... [arguments...]\",
+\tUsageLine: "run [build flags] [gofiles...] [arguments...]\",
 	Short:     "compile and run Go program",
 	Long: `
 Run compiles and runs the main package comprising the named Go source files.
+If no files are named, it compiles and runs all non-test Go source files.
 
 For more about build flags, see 'go help build'.
 
@@ -44,7 +46,18 @@ func runRun(cmd *Command, args []string) {
 	}\n\tfiles, cmdArgs := args[:i], args[i:]\n\tif len(files) == 0 {\n-\t\tfatalf(\"go run: no go files listed\")\n+\t\tallFiles, err := filepath.Glob(\"*.go\")\n+\t\tif err != nil {\n+\t\t\tfatalf(\"go run: %s\", err)\n+\t\t}\n+\t\tfor _, file := range allFiles {\n+\t\t\tif !strings.HasSuffix(file, \"_test.go\") {\n+\t\t\t\tfiles = append(files, file)\n+\t\t\t}\n+\t\t}\n+\t\tif len(files) == 0 {\n+\t\t\tfatalf(\"go run: no go files found\")\n+\t\t}\n \t}\n \tfor _, file := range files {\n \t\tif strings.HasSuffix(file, \"_test.go\") {\

コアとなるコードの解説

src/cmd/go/run.gorunRun 関数が go run コマンドの主要なロジックを含んでいます。

変更前のコードでは、files スライス(実行対象のGoファイル名が格納される)が空の場合、即座に fatalf("go run: no go files listed") というエラーを出力して終了していました。

変更後のコードでは、if len(files) == 0 { ... } ブロックが追加され、以下の処理が行われます。

  1. allFiles, err := filepath.Glob("*.go"):
    • filepath.Glob("*.go") を呼び出し、現在のディレクトリにあるすべての .go 拡張子を持つファイル名を検索します。
    • 検索結果は allFiles スライスに格納され、エラーがあれば err に格納されます。
  2. if err != nil { fatalf("go run: %s", err) }:
    • filepath.Glob の実行中にエラーが発生した場合(例: 無効なパターン、パーミッションの問題など)、そのエラーメッセージと共にプログラムを終了します。
  3. for _, file := range allFiles { ... }:
    • allFiles に含まれる各ファイル名に対してループ処理を行います。
  4. if !strings.HasSuffix(file, "_test.go") { files = append(files, file) }:
    • 現在のファイル名 file_test.go で終わっていないかどうかを strings.HasSuffix でチェックします。
    • もし _test.go で終わっていなければ(つまり、テストファイルでなければ)、そのファイル名を files スライスに追加します。これにより、実行対象のファイルリストが構築されます。
  5. if len(files) == 0 { fatalf("go run: no go files found") }:
    • すべての .go ファイルを検索し、テストファイルを除外した結果、files スライスがまだ空の場合(つまり、実行可能な非テストGoファイルが一つも見つからなかった場合)、go run: no go files found というエラーメッセージを出力してプログラムを終了します。

このロジックにより、go run は引数なしで実行された際に、現在のディレクトリから適切な main パッケージのソースファイルを自動的に見つけて実行できるようになりました。

また、src/cmd/go/doc.go では、go runUsageLine[gofiles...] の部分を [gofiles...] と変更し、角括弧で囲むことでオプションであることを示しています。さらに、Long 説明に「If no files are named, it compiles and runs all non-test Go source files.」という新しい行が追加され、この新しい動作が明記されています。

関連リンク

参考にした情報源リンク