go toolが何をしてるのか知りたかったら-x -workを使うと良い

## はじめに

どうも[ナレッジワーク](https://tenn.in/kw)の[tenntenn](https://tenn.in)です。

この記事では、`go build`や`go run`、`go test`を実行する際のちょっとしたテクニックを紹介します。

## `-x`オプション

Goのツールチェインの内部では、コンパイラやリンカは`$GOROOT/pkg/tool/$GOOS_$GOARCH`にある別コマンドを実行しています。

```
$ ls `go env GOROOT`/pkg/tool/`go env GOOS`_`go env GOARCH`
addr2line	compile		fix		pack		vet
asm		cover		link		pprof
buildid		dist		nm		test2json
cgo		doc		objdump		trace
```

これらのコマンドをどういう風に実行しているのか確かめたい場合は`-x`オプションを使うと良いでしょう。

たとえば、次のようなGoファイルをビルドして実行してみます。

```go
package main

import "fmt"

func main() {
	fmt.Println("hello")
}
```

実行には`go run`コマンドを用います。`-x`オプションをつけてみましょう。

```
$ go run -x main.go
WORK=/var/folders/wg/6bn75hvs4sv6tvd2h67k3z440000gp/T/go-build4263583053
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg << 'EOF' # internal
# import config
packagefile fmt=/usr/local/go/pkg/darwin_arm64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_arm64/runtime.a
EOF
cd /Users/tenntenn/repos/tenntenn/scrap/x-work
/usr/local/go/pkg/tool/darwin_arm64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid ulGFIZNq9utit_V8LK54/ulGFIZNq9utit_V8LK54 -dwarf=false -goversion go1.19.3 -shared -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/usr/local/go/pkg/tool/darwin_arm64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tenntenn/Library/Caches/go-build/b8/b8a3a8e8808474a9885f6d08e80f7b16d3b30d78c3d6a476e678bc6888d506a6-d # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=$WORK/b001/_pkg_.a
packagefile fmt=/usr/local/go/pkg/darwin_arm64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_arm64/runtime.a
packagefile errors=/usr/local/go/pkg/darwin_arm64/errors.a
packagefile internal/fmtsort=/usr/local/go/pkg/darwin_arm64/internal/fmtsort.a
packagefile io=/usr/local/go/pkg/darwin_arm64/io.a
packagefile math=/usr/local/go/pkg/darwin_arm64/math.a
packagefile os=/usr/local/go/pkg/darwin_arm64/os.a
packagefile reflect=/usr/local/go/pkg/darwin_arm64/reflect.a
packagefile strconv=/usr/local/go/pkg/darwin_arm64/strconv.a
packagefile sync=/usr/local/go/pkg/darwin_arm64/sync.a
packagefile unicode/utf8=/usr/local/go/pkg/darwin_arm64/unicode/utf8.a
packagefile internal/abi=/usr/local/go/pkg/darwin_arm64/internal/abi.a
packagefile internal/bytealg=/usr/local/go/pkg/darwin_arm64/internal/bytealg.a
packagefile internal/cpu=/usr/local/go/pkg/darwin_arm64/internal/cpu.a
packagefile internal/goarch=/usr/local/go/pkg/darwin_arm64/internal/goarch.a
packagefile internal/goexperiment=/usr/local/go/pkg/darwin_arm64/internal/goexperiment.a
packagefile internal/goos=/usr/local/go/pkg/darwin_arm64/internal/goos.a
packagefile runtime/internal/atomic=/usr/local/go/pkg/darwin_arm64/runtime/internal/atomic.a
packagefile runtime/internal/math=/usr/local/go/pkg/darwin_arm64/runtime/internal/math.a
packagefile runtime/internal/sys=/usr/local/go/pkg/darwin_arm64/runtime/internal/sys.a
packagefile internal/reflectlite=/usr/local/go/pkg/darwin_arm64/internal/reflectlite.a
packagefile sort=/usr/local/go/pkg/darwin_arm64/sort.a
packagefile math/bits=/usr/local/go/pkg/darwin_arm64/math/bits.a
packagefile internal/itoa=/usr/local/go/pkg/darwin_arm64/internal/itoa.a
packagefile internal/oserror=/usr/local/go/pkg/darwin_arm64/internal/oserror.a
packagefile internal/poll=/usr/local/go/pkg/darwin_arm64/internal/poll.a
packagefile internal/syscall/execenv=/usr/local/go/pkg/darwin_arm64/internal/syscall/execenv.a
packagefile internal/syscall/unix=/usr/local/go/pkg/darwin_arm64/internal/syscall/unix.a
packagefile internal/testlog=/usr/local/go/pkg/darwin_arm64/internal/testlog.a
packagefile internal/unsafeheader=/usr/local/go/pkg/darwin_arm64/internal/unsafeheader.a
packagefile io/fs=/usr/local/go/pkg/darwin_arm64/io/fs.a
packagefile sync/atomic=/usr/local/go/pkg/darwin_arm64/sync/atomic.a
packagefile syscall=/usr/local/go/pkg/darwin_arm64/syscall.a
packagefile time=/usr/local/go/pkg/darwin_arm64/time.a
packagefile unicode=/usr/local/go/pkg/darwin_arm64/unicode.a
packagefile internal/race=/usr/local/go/pkg/darwin_arm64/internal/race.a
packagefile path=/usr/local/go/pkg/darwin_arm64/path.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tcommand-line-arguments\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=arm64\nbuild\tGOOS=darwin\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/darwin_arm64/link -o $WORK/b001/exe/main -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=_mLUCLIz1uIg5o5PxsyQ/ulGFIZNq9utit_V8LK54/QwDWNGpEATLsyzIrn0bj/_mLUCLIz1uIg5o5PxsyQ -extld=clang $WORK/b001/_pkg_.a
$WORK/b001/exe/main
hello
```

`compile`コマンドや`link`コマンドが実行されていることが分かります。
これで各コマンドの引数がどのようなものが渡されているのか調べれます。

## `-work`オプション

Goのツールチェインは、複数のコマンドを組み合わせて動いているため、途中の状態をファイルとして書き出すことが多いです。ツールチェインの挙動を追いたい場合、生成物を見てみるとわかることが多いでしょう。

次のように`-work`オプションをつけると、コマンド実行後に一時ファイルが消されないため、生成物を確認できます。

```
$ go run -work main.go
WORK=/var/folders/wg/6bn75hvs4sv6tvd2h67k3z440000gp/T/go-build1954475371
hello
```

この場合、一時ファイルは`/var/folders/wg/6bn75hvs4sv6tvd2h67k3z440000gp/T/go-build1954475371`にあるため、確認してみましょう。

```
$ tree /var/folders/wg/6bn75hvs4sv6tvd2h67k3z440000gp/T/go-build1954475371
/var/folders/wg/6bn75hvs4sv6tvd2h67k3z440000gp/T/go-build1954475371
└── b001
    ├── _pkg_.a
    ├── exe
    │   └── main
    ├── importcfg
    └── importcfg.link

2 directories, 4 files
```

ビルドされた実行可能ファイルなどが入ってることが分かります。

## キャッシュの削除

ビルドキャッシュを消したい場合は、次のようにコマンドを実行しておくと良いでしょう。
キャッシュが残っていると、生成物が減ったり、実行するコマンドが減ったりするので、挙動を追いづらくなります。

```
$ go clean -cache
```

## おわりに

本記事では、Goのツールチェインの`-x`オプションや`-work`オプションについて解説しました。

他にもオプションはたくさんあるので、一度は`go help build`コマンドを実行してオプションの一覧を眺めてみると良いでしょう。