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

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

このコミットは、Go言語のツールチェインにおける重要な再編成の第一歩を示しています。具体的には、go tool コマンドの導入、Goツールが配置される新しいディレクトリ go-tool の作成、ビルドスクリプト make.bash における古いバイナリのクリーンアップ、そして goyacc ツールを yacc にリネームし、新しい go tool コマンドの最初のテストケースとして統合する変更が含まれています。これにより、Goのツール管理がより体系化され、将来的なツール拡張のための基盤が築かれました。

コミット

  • コミットハッシュ: 79dc34413e4ad93cc8c590e9f3cc97538c7f8266
  • Author: Rob Pike r@golang.org
  • Date: Sun Jan 29 09:19:05 2012 -0800

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

https://github.com/golang/go/commit/79dc34413e4ad93cc8c590e9f3cc97538c7f8266

元コミット内容

cmd/go: first piece of tool rearrangement
1) create go-tool dir in make.bash
2) clean up stale binaries in make.bash
3) add 'tool' command to go
4) convert goyacc->yacc as a first test tool
Since goyacc stands alone, it's a safe trial.

R=rsc
CC=golang-dev
https://golang.org/cl/5576061

変更の背景

このコミットが行われた2012年当時、Go言語はまだ比較的新しい言語であり、そのツールチェインは進化の途上にありました。初期のGoツールは、gofmtgoinstall のように、それぞれが独立したコマンドとして提供されていました。しかし、ツールが増えるにつれて、それらを一元的に管理し、ユーザーが発見しやすく、かつ実行しやすいメカニズムが必要になってきました。

この変更の主な背景は以下の通りです。

  1. ツール管理の体系化: 多数のGoツールが個別のバイナリとして GOROOT/bin に散在している状態から、go tool という単一のエントリポイントを通じてアクセスできるようにすることで、ツールの発見性と管理性を向上させる必要がありました。
  2. ビルドプロセスの改善: make.bash スクリプトはGoのビルドシステムの中核であり、新しいツール管理構造に合わせて、ツールのビルドと配置、そして古いバイナリのクリーンアップを適切に行う必要がありました。
  3. goyacc の統合: goyacc はGo言語で書かれたYaccパーサジェネレータであり、Goツールチェインの一部として提供されていました。このツールは比較的独立性が高いため、新しい go tool コマンドの最初のテストケースとして統合するのに適していました。これにより、goyaccgo tool yacc として実行できるようになり、Goツールチェイン内での位置付けが明確化されました。
  4. 将来的な拡張性: go tool コマンドの導入は、将来的にGo言語の標準ツールセットに新しいツールを追加する際の基盤となります。これにより、Goエコシステム全体の成長を促進することが期待されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびビルドシステムに関する基本的な知識が必要です。

  • Go言語のツールチェイン: Go言語は、コンパイラ (gc8g/6g/5g など)、リンカ (ld8l/6l/5l など)、アセンブラ (as8a/6a/5a など)、パッケージアーカイバ (packgopack など) など、多くのツールで構成されています。これらはGoプログラムのビルド、テスト、解析などを支援します。
  • GOROOT: Goのインストールディレクトリを指す環境変数です。Goの標準ライブラリ、ツール、ソースコードなどがこのディレクトリ以下に配置されます。
  • GOBIN: Goの実行可能バイナリが配置されるディレクトリを指す環境変数です。通常は $GOROOT/bin またはユーザーの $HOME/go/bin などに設定されます。
  • make.bash: Go言語のソースコードからGoツールチェイン全体をビルドするためのシェルスクリプトです。Goの初期のビルドシステムの中核をなしていました。
  • Makefile: make コマンドによって実行されるビルド設定ファイルです。Goのプロジェクトでは、各パッケージやツールのビルド手順が Makefile に記述されていました。
  • go tool コマンド: Go 1.0以降で導入された、Go言語の内部ツールを実行するためのコマンドです。例えば、go tool vet はコードの静的解析ツール vet を実行します。これにより、Goのツールが go コマンドの下に統合され、一貫したインターフェースで利用できるようになりました。
  • goyacc / yacc: yacc (Yet Another Compiler Compiler) は、文法定義からパーサ(構文解析器)を生成するツールです。goyacc はGo言語で書かれた yacc の実装であり、Go言語のコードを生成します。このコミットでは、goyaccyacc にリネームされ、go tool yacc として利用できるようになりました。
  • CL (Change List): Google社内で使用されるコードレビューシステムにおける変更の単位です。GoプロジェクトはGoogleの内部システムで開発されていたため、コミットメッセージに CL 番号が含まれることがあります。https://golang.org/cl/5576061 は、このコミットに対応するGoのコードレビューページへのリンクです。

技術的詳細

このコミットは、Goツールチェインの構造を根本的に変更するものであり、複数のファイルにわたる広範な変更が含まれています。

  1. go-tool ディレクトリの導入:

    • src/make.bashmkdir -p "$GOROOT/bin/go-tool" が追加され、$GOROOT/bin/go-tool という新しいディレクトリが作成されるようになりました。これは、go tool コマンドを通じて実行されるすべてのGoツールが配置される場所となります。
    • src/Make.tool という新しい Makefile が追加されました。この Makefile は、go-tool ディレクトリにツールをインストールするための共通のルールを定義しています。具体的には、TOOLDIR=$(QUOTED_GOROOT)/bin/go-tool と定義され、install ターゲットが $(TOOLDIR)/$(TARG) にバイナリをコピーするように設定されています。
  2. 古いバイナリのクリーンアップ:

    • src/make.bash に、$GOROOT/bin および GOBIN から古いGoツールチェインのバイナリ(例: 5g, 6g, 8g, goyacc など)を削除する rm -f コマンドが追加されました。これは、新しい go-tool 構造への移行に伴い、古い形式のツールが残存しないようにするための措置です。
  3. go tool コマンドの追加:

    • src/cmd/go/main.gocmdToolcommands スライスに追加され、go コマンドのサブコマンドとして tool が認識されるようになりました。
    • src/cmd/go/tool.go という新しいファイルが作成されました。このファイルは go tool コマンドのロジックを実装しています。
      • runTool 関数は、go tool <command> [args...] の形式で呼び出された際に、指定されたツールを実行します。
      • ツール名は小文字の英数字のみを許可し、不正なツール名が指定された場合にはエラーを返します。
      • ツールは $GOROOT/bin/go-tool ディレクトリから検索され、Windows環境では .exe 拡張子が考慮されます。
      • 指定されたツールが見つからない場合や、ツールの実行に失敗した場合には、適切なエラーメッセージが表示されます。
      • 引数なしで go tool が実行された場合 (len(args) == 0)、listTools 関数が呼び出され、go-tool ディレクトリ内の利用可能なツールの一覧が表示されます。
      • listTools 関数は go-tool ディレクトリを読み込み、ファイル名をソートして表示します。Windows環境では .exe 拡張子を非表示にします。
  4. goyacc から yacc への変換と統合:

    • src/cmd/goyacc ディレクトリが src/cmd/yacc にリネームされました。これに伴い、関連するファイル (Makefile, doc.go, units.txt, units.y, goyacc.go) も新しいディレクトリ構造に合わせて移動・リネームされました。
    • src/cmd/goyacc/Makefilesrc/cmd/yacc/Makefile にリネームされ、TARG=goyaccTARG=yacc に変更されました。また、include ../../Make.cmdinclude ../../Make.tool に変更され、新しい Make.tool のビルドルールを利用するように更新されました。
    • src/cmd/goyacc/doc.gosrc/cmd/yacc/doc.go にリネームされ、ドキュメント内の Goyaccgoyacc の記述が Yaccyacc に変更されました。また、go tool yacc args... という新しい実行方法が明記されました。
    • src/cmd/goyacc/units.y および src/cmd/goyacc/goyacc.go 内の goyacc への参照も yacc に更新されました。
    • src/cmd/Makefile および src/pkg/Makefile における goyacc への参照が yacc に変更されました。
    • 各プラットフォームの src/buildscript/*.sh ファイルにおいて、src/cmd/go のコンパイル時に tool.go が含まれるように変更されました。

これらの変更により、Goのツールチェインはよりモジュール化され、go コマンドの下に統一されたインターフェースでツールが提供されるようになりました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集約されます。

  1. src/Make.tool (新規ファイル):

    • go-tool ディレクトリへのツールのインストール方法を定義する新しい Makefile
    • TOOLDIR=$(QUOTED_GOROOT)/bin/go-tool でツールのインストール先を定義。
    • install ターゲットが $(TOOLDIR)/$(TARG) にバイナリをコピーする。
  2. src/cmd/go/tool.go (新規ファイル):

    • go tool コマンドの主要なロジックを実装。
    • runTool 関数: 指定されたツールを実行。
    • listTools 関数: 利用可能なツールの一覧を表示。
    • ツールのパス解決、エラーハンドリング、Windows環境での .exe 拡張子の処理など。
  3. src/make.bash:

    • $GOROOT/bin/go-tool ディレクトリの作成。
    • 古いGoツールチェインのバイナリを削除するクリーンアップロジック。
  4. src/cmd/{goyacc => yacc}/Makefile (リネームと変更):

    • TARG=goyacc から TARG=yacc への変更。
    • include ../../Make.cmd から include ../../Make.tool への変更。
  5. src/cmd/{goyacc => yacc}/doc.go (リネームと変更):

    • ドキュメント内の goyacc 参照を yacc に更新。
    • go tool yacc args... という新しい実行方法の記述。

コアとなるコードの解説

src/Make.tool

# Copyright 2009 The Go Authors.  All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

ifeq ($(GOOS),windows)
TARG:=$(TARG).exe
endif

# Tools always go into $GOROOT/bin/go-tool
TOOLDIR=$(QUOTED_GOROOT)/bin/go-tool

all: $(TARG)

include $(QUOTED_GOROOT)/src/Make.common

PREREQ+=$(patsubst %,%.make,$(DEPS))

$(TARG): _go_.$O
	$(LD) $(LDIMPORTS) -o $@ _go_.$O

_go_.$O: $(GOFILES) $(PREREQ)
	$(GC) $(GCFLAGS) $(GCIMPORTS) -o $@ $(GOFILES)

install: $(TOOLDIR)/$(TARG)

$(TOOLDIR)/$(TARG): $(TARG)
	mkdir -p $(TOOLDIR) && cp -f $(TARG) $(TOOLDIR)

CLEANFILES+=$(TARG) _test _testmain.go test.out build.out

nuke: clean
	rm -f $(TOOLDIR)/$(TARG)

# for gotest
testpackage: _test/main.a

testpackage-clean:
	rm -f _test/main.a _gotest_.$O

_test/main.a: _gotest_.$O
	@mkdir -p _test
	rm -f $@
	gopack grc $@ _gotest_.$O

_gotest_.$O: $(GOFILES) $(GOTESTFILES)
	$(GC) $(GCFLAGS) $(GCIMPORTS) -o $@ $(GOFILES) $(GOTESTFILES)

importpath:
	echo main

この Makefile は、Goツールをビルドし、$GOROOT/bin/go-tool ディレクトリにインストールするための共通のテンプレートを提供します。

  • TOOLDIR 変数でツールのインストール先を定義しています。
  • install ターゲットは、ビルドされたツールバイナリ ($(TARG)) を $(TOOLDIR) にコピーします。mkdir -p $(TOOLDIR) により、必要に応じてディレクトリが作成されます。
  • nuke ターゲットは、インストールされたツールを削除します。
  • testpackage 関連のターゲットは、Goのテストパッケージをビルドするためのルールです。

src/cmd/go/tool.go

// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"go/build"
	"os"
	"os/exec"
	"sort"
	"strings"
)

var cmdTool = &Command{
	Run:       runTool,
	UsageLine: "tool command [args...]",
	Short:     "run specified go tool",
	Long: `
Tool runs the go tool command identified by the arguments.
With no arguments it prints the list of known tools.

For more about each tool command, see 'go tool command -h'.
`,
}

var (
	toolGoos       = build.DefaultContext.GOOS
	toolIsWindows  = toolGoos == "windows"
	toolBinToolDir = build.Path[0].BinDir() + "/go-tool"
)

const toolWindowsExtension = ".exe"

func runTool(cmd *Command, args []string) {
	if len(args) == 0 {
		listTools()
		return
	}
	tool := args[0]
	// The tool name must be lower-case letters and numbers.
	for _, c := range tool {
		switch {
		case 'a' <= c && c <= 'z', '0' <= c && c <= '9':
		default:
			fmt.Fprintf(os.Stderr, "go tool: bad tool name %q\n", tool)
			exitStatus = 2
			return
		}
	}
	toolPath := toolBinToolDir + "/" + tool
	if toolIsWindows {
		toolPath += toolWindowsExtension
	}
	// Give a nice message if there is no tool with that name.
	if _, err := os.Stat(toolPath); err != nil {
		fmt.Fprintf(os.Stderr, "go tool: no such tool %q\n", tool)
		exitStatus = 3
		return
	}
	toolCmd := &exec.Cmd{
		Path:   toolPath,
		Args:   args,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
	err := toolCmd.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool %s failed: %s\n", tool, err)
		exitStatus = 1
		return
	}
}

// listTools prints a list of the available tools in the go-tools directory.
func listTools() {
	toolDir, err := os.Open(toolBinToolDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool: no tool directory: %s\n", err)
		exitStatus = 2
		return
	}
	names, err := toolDir.Readdirnames(-1)
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool: can't read directory: %s\n", err)
		exitStatus = 2
		return
	}
	sort.StringSlice(names).Sort()
	for _, name := range names {
		// Unify presentation by going to lower case.
		name = strings.ToLower(name)
		// If it's windows, don't show the .exe suffix.
		if toolIsWindows && strings.HasSuffix(name, toolWindowsExtension) {
			name = name[:len(name)-len(toolWindowsExtension)]
		}
		fmt.Println(name)
	}
}

このファイルは go tool コマンドの心臓部です。

  • cmdTool 変数は go コマンドに tool サブコマンドを登録します。Run フィールドには runTool 関数が割り当てられています。
  • toolBinToolDir は、go tool コマンドがツールバイナリを検索するディレクトリ ($GOROOT/bin/go-tool) を定義します。
  • runTool 関数は、ユーザーが go tool <tool_name> [args...] と入力したときに実行されます。
    • 引数がない場合は listTools() を呼び出して利用可能なツールの一覧を表示します。
    • 指定されたツール名が有効な形式(小文字の英数字)であるかを検証します。
    • toolBinToolDir からツールバイナリのパスを構築し、Windowsの場合は .exe 拡張子を追加します。
    • os.Stat でツールバイナリの存在を確認し、存在しない場合はエラーを返します。
    • os/exec.Cmd を使用してツールバイナリを実行し、標準出力と標準エラーを親プロセスにリダイレクトします。
    • ツールの実行に失敗した場合、エラーメッセージを表示し、exitStatus を設定します。
  • listTools 関数は、toolBinToolDir ディレクトリの内容を読み込み、利用可能なツールの一覧をアルファベット順に表示します。Windows環境では .exe 拡張子を削除して表示します。

関連リンク

  • Go Change-Id: I2222222222222222222222222222222222222222 (これはコミットメッセージに記載されているCLのIDですが、実際のGoのCLページは https://go.dev/cl/ の形式でアクセスできます。このコミットのCLは https://go.dev/cl/5576061 です。)
  • Go issue: https://go.dev/issue/2820 (このコミットに関連する可能性のあるGoのissue。go tool コマンドの導入に関する議論が含まれている可能性があります。)

参考にした情報源リンク