パッケージ名を指定してgo buildしよう

## はじめに

[フューチャー Advent Calendar 2021](https://qiita.com/advent-calendar/2021/future)の7日目です。

本記事では `go build main.go` と `go build {パッケージ名}` としてビルドしたときの微妙な違いについて説明します。Goのバージョンは `go1.18beta1` で確認しています。

Go 1.18から `runtime/debug` パッケージの `ReadBuildInfo()` を使って、ビルドしたときのVCSのハッシュを取得できるようになりますが、このビルドしたときの微妙な違いが影響するようになります。[^1]
https://tip.golang.org/doc/go1.18#go-command

[^1]: `go version -m` でも確認できます。https://pkg.go.dev/cmd/go#hdr-Print_Go_version

## `go build` の引数による違い

`go build` でGoのソースをビルドして、実行バイナリが生成されることはよく知られています。

https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies

Usageにあるように

> go build [-o output] [build flags] [packages]

パッケージ名 (`packages`) を指定してビルドしますが、Goの1つあるいは複数のソースファイルを `go build` の引数に渡すこともできます。`go build a.go b.go c.go` といったものです。

簡単のために以下のようなディレクトリ、ファイル構成を考えてみます。

```text:ディレクトリ構成
.
├── go.mod
└── main.go
```

```text:go.mod
module sample

go 1.18
```

```go:main.go
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, 世界")
}
```

### `go build ${パッケージ名}` としてビルドしたとき

`go build ${パッケージ名}` としてビルドします。本例ではパッケージ名は `sample` です。

`go version -m sample` で確認すると以下のようになります。`path` の値が `sample` のパッケージ名になっており、また `mod` の値として `sample` のパッケージ名が含まれていることが分かります。VCS(ここではGit)のハッシュが含まれていることも分かります。便利ですね。

```sh
$ go build sample
$ go version -m sample
sample: go1.18beta1
        path    sample
        mod     sample  (devel)
        build   -compiler=gc
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=linux
        build   GOAMD64=v1
        build   vcs=git
        build   vcs.revision=sample0123456789012345678901234567sample
        build   vcs.time=2021-12-27T04:24:55Z
        build   vcs.modified=true
```

### `go build main.go` としてビルドしたとき

`go build main.go` としてGoのソースファイルを指定してビルドするとコマンドライン引数(`command-line-arguments`)としてビルドされます。

`go version -m main` で確認すると以下のようになります。`path` が `command-line-arguments` となっていることが分かります。`mod` の値はありません。VCSのハッシュも含まれていません。

```sh
$ go build main.go
$ go version -m main
main: go1.18beta1
        path    command-line-arguments
        build   -compiler=gc
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=linux
        build   GOAMD64=v1
```

### より詳細を知りたい方へ

Goの標準ライブラリも多くはGoで書かれています。上記の `go build` コマンドへの引数による違いは `cmd/go/internal/work/build.go` や `cmd/go/internal/load/pkg.go` のソースを読んでみると参考になります。`cmd/go/internal/load/pkg.go` の中で `.go` のファイルが引数として渡されたときは引数がコマンドライン引数として渡されたものとしてフラグ(`pkg.Internal.CmdlineFiles`)がセットされます。

```go:cmd/go/internal/load/pkg.go
func GoFilesPackage(ctx context.Context, opts PackageOpts, gofiles []string) *Package {

	// ...

	bp, err := ctxt.ImportDir(dir, 0)
	pkg := new(Package)
	pkg.Internal.Local = true
	pkg.Internal.CmdlineFiles = true
	pkg.load(ctx, opts, "command-line-arguments", &stk, nil, bp, err)
	if !cfg.ModulesEnabled {
		pkg.Internal.LocalPrefix = dirToImportPath(dir)
	}
	pkg.ImportPath = "command-line-arguments"
	pkg.Target = ""
	pkg.Match = gofiles

	// ...
}
```

https://github.com/golang/go/blob/becaeea1199b875bc24800fa88f2f4fea119bf78/src/cmd/go/internal/load/pkg.go#L2937-L2947
 
## まとめ

* `go build` の引数にファイル名を渡すか、パッケージ名を渡すかで、ビルドの方法が異なります。
* ファイル名を渡してビルドした場合は、VCSのハッシュがビルドに含まれません。
  * 本挙動は2021/12/30現在[Go 1.18のドラフトリリースノート](https://tip.golang.org/doc/go1.18)からは確認できませんでした。もしかしたら今後追記されるかも知れません。
* ビルドするときはパッケージ名を指定してビルドしましょう。