Go (Golang) コマンドラインツールの作り方

Golang バージョン 1.16 以上をを想定しています
環境の構築方法については次の記事を参照してください:
[Docker とVisual Studio Code を使った Go (Golang) 開発環境構築 - Qiita](https://qiita.com/y_shinoda/items/e6e2fef46408c0bae83b)

## 基本

まずは Golang プロジェクトを初期化します

プロジェクト用フォルダーを作成し、そのフォルダーの直下で次のコマンドを実行します:

```console
go mod init <リポジトリーのパス>
```

例:

```console
go mod init github.com/yukihiko-shinoda/go-commandline-tool
```

※ リポジトリーのパスは後から変えることもできます

すると、ファイル: `go.mod` が作成されます

```text
project/
+---go.mod
```

次に `.go` ファイルを作成します
例:

```text
project/
+---commandline_tool.go
+---go.mod
```

```golang:commandline_tool.go
package main

func main() {
	println("Hello world!")
}
```

このとき、次のことに気をつけます:

- `package` は `main` とします
- コマンドラインツールを実行したときの処理を `main()` に定義します

このコードをコマンドラインツールとしてインストールします
インストールには 2 通りの方法があります

### go install

`go install` とだけ実行すると、
最初に実行した `go mod init <リポジトリーのパス>` のリポジトリー名の部分が
コマンド名になります:

```console
$ go install
$ go-commandline-tool
Hello world!
```

### go install <.go ファイル名>

`go install` に続けて `.go` ファイル名を入力すると、
`.go` ファイル名の拡張子を除いた部分の文字列がコマンド名になります:

```console
$ go install commandline_tool.go
$ commandline_tool
Hello world!
```

## 複数のコマンドラインツールを作成する場合

1 つのパッケージ内に `main()` を複数定義することはできないので、
リポジトリーを分けるか、サブフォルダーを作成します

サブフォルダーを作成する場合は、`cmd` フォルダーを作成し、
さらにコマンドごとにサブフォルダーを作成するのが一般的であるようです:

```text
project/
+----cmd/
    +----command1/
    |    +----command1.go
    +----command2/
         +----command2.go
```

ちなみに、Golang 公式のフォルダー構成の推奨は特になく、
`cmd` フォルダーについても公式の言及はありませんが、
多くの Golang プロジェクトが
次の非公式ベストプラクティスに倣って `cmd` フォルダーを作成しているようです:

参考: [golang-standards/project-layout: Standard Go Project Layout](https://github.com/golang-standards/project-layout)


各 `.go` ファイルの package は `main` にし、
それぞれにコマンド実行時の処理を `main()` として定義します
次のコマンドで go ファイル名のコマンドが実行できるようになります:

```console
$ go install cmd/command1/command1.go
$ go install cmd/command2/command2.go
$ command1
Hello world 1!
$ command2
Hello world 2!
```

## リポジトリーからコマンドラインツールをインストール

以下の説明は Git などバージョン管理システムの知識を前提としています
バージョン管理システムの基礎については次の記事を参照してください:
[Linux 知識の要らない Git 講座 目次 | ultra code](https://futureys.tokyo/table-of-contents-git-lesson-without-linux-knowledge/)

作成したコマンドラインツールを Git などのリポジトリーに push して、
バージョンを示すタグを付けておくことで、
Golang のインストールされている環境にいつでもコマンドラインツールをインストールできます:

```console
go install <リポジトリーの main パッケージのフォルダーまでのパス>@<バージョンを示すタグ>
```

例 1:
ルートパッケージがコマンドラインツールの、タグ: `v1.0.0` が示すバージョンをインストールする場合:

```console
go install github.com/yukihiko-shinoda/go-commandline-tool@v1.0.0
```

例 2:
`cmd/command1/command1.go` にコマンドラインツールを実装しており、
タグ: `v1.0.0` が示すバージョンをインストールする場合:

```console
go install github.com/yukihiko-shinoda/go-commandline-tool/cmd/command1@v1.0.0
```

この場合、コマンド名は `main()` が定義されている `.go` ファイル名……ではなく、
その `.go` ファイルの親フォルダー名になります

## インストール方法ごとのコマンド名まとめ

今まで見てきたように、インストール方法によってコマンド名が変わってしまいます
まとめると、次のようになります:

インストール方法|コマンド名
---|---
ローカルの `go.mod` を含むフォルダーで `go install`|最初に実行した `go mod init <リポジトリーのパス>` のリポジトリー名の部分
`go install <.go ファイル名>`|`.go` ファイル名の拡張子を除いた部分の文字列
リポジトリーからコマンドラインツールをインストール|リポジトリーの main パッケージのフォルダー名

## 開発時とリリース時でコマンド名が変わらないようにする方法

次のようにすると、開発時とリリース時でコマンド名が変わらないようにできます

### プロジェクト直下にコマンドラインツールを作成するとき

開発時にテストのためコマンドラインツールをインストールするときは、
ファイル名を指定せずに `go install` するようにします

### サブフォルダーを用意してコマンドラインツールを作成するとき

フォルダー名と `main()` を定義する `.go` ファイル名を同じ名前で作成するようにします

例:

```text
project/
+----cmd/
    +----command1/
    |    +----command1.go
    +----command2/
         +----command2.go
```