数年ぶりに戻ってきたGoですが、環境が大きく様変わりしていて劇的に使いやすくなっていました。
とくにいいなと思ったのが go mod
でした。これは Go の 1.11 に実験で入ったあとから利用できる機能のようです。
ところで、go mod init
というコマンドがあります。これは Go プロジェクトを新規に始める際に、go.mod
という設定ファイルを作成してくれるものです。Go はプロジェクトの内容をここから色々読み取って機能するようになっています。
このコマンドは、一度ディレクトリを作成してからそのディレクトリに入って叩く必要があります。たとえば、github.com/yuk1ty/startgo
というパッケージパスでプロジェクトを始めたい際には、一度 startgo
というディレクトリを作成し、cd
し、その中で go mod init
を叩く必要があります。
mkdir startgo cd startgo go mod init github.com/yuk1ty/startgo
さらに、git
を使えるようにしたい場合、git init
も中で打つ必要があります。加えて .gitignore
も用意したいことも多いでしょう。Hello, world のために main.go
もファイルを作成してエディタで用意して…といった具合にです。
git init gibo dump go >> .gitignore touch main.go vim main.go # vim で Go を編集する
最初 go mod init
を触った際、Rust の cargo new
のように一通りプロジェクトに必要な内容物を一気に生成してくれるといろいろ手間が省けて嬉しいなと感じたのを覚えています。git も gitignore も Hello, world 用の簡単なファイルも用意された状態でプロジェクトが始まり、go run main.go
するだけで開発をスタートできると嬉しいはずです。
というわけで cargo にインスパイアされてこのニーズを満たす CLI ツールを作ってみました。
この手のツールはすでに存在していそうですが、とくに調べずにとりあえず好きに作ってみました。知り合いの Gopher に聞いてみたところとくに心当たりがなさそうでしたので[*1、この手のツールは本当に現時点ではないのかもしれません。心当たりのいる Gopher の方がいたら教えていただきたいです。
使い方と仕様
前提
コマンド
readygo
コマンドには次のオプションが用意されています。
--module-path
(-p
):go mod init
する際に使用するモジュールパス。--dir-name
(-n
): 作成するディレクトリに使用する名前。省略可能。省略した場合は、--module-path
を参考にディレクトリ名は設定される。--layout
(-l
): Go Standard Layout か、そうではなく空っぽのディレクトリを作成するかを選べる。default
かstandard
を設定可能。省略可能。省略した場合の値はdefault
。
たとえば、次のように使用することができます。
readygo --module-path github.com/yuk1ty/startgo --dir-name startgo --layout default
この結果生成されるファイル等は次のようになります。
ls -a --tree --level 1 startgo startgo ├── .git ├── .gitignore ├── go.mod └── main.go
もちろん短いコマンドも用意されています。
readygo -p github.com/yuk1ty/startgo -n startgo -s default
また、--dir-name
オプションは省略可能です。
readygo --module-path github.com/yuk1ty/startgo
この場合、--dir-name
には startgo
が自動で挿入されます。これは非常にシンプルなロジックで決定されています。具体的には、スラッシュで一度 split した後に、配列の一番うしろを取るという簡単なロジックです。だいたいのケースはこれで対応できるのではないかと思い採用しています。
最後に --layout
に standard
を設定すると、いわゆる Standard Go Project Layout のうち、cmd
, pkg
, internal
の3つのディレクトリを一旦生成します。この Standard Go Project Layout はいろいろと議論の余地があるようですが[*2]、OSS をいくつか眺めていると意外と利用のユースケースが見受けられたので、最小限用意するようにしています。
readygo --module-path github.com/yuk1ty/startgo --layout standard
この結果生成されるファイル等は次のようになります。
ls -a --tree --level 1 startgo startgo ├── .git ├── .gitignore ├── cmd ├── go.mod ├── internal ├── main.go └── pkg
なお、この --layout
には所定のフォーマットの YAML ファイルを読み込む機能を導入しようかと考えています。ご自身のよく作られるパッケージの型に合わせてカスタマイズできるとよいのではないかとは考えています。他にも Go のコミュニティでよく利用されるディレクトリレイアウトなどあればご教授ください。一番多いのはフラットディレクトリではないかと思っているので、基本的には default
の生成する空のディレクトリで事足りるのではないかとは思っています。
生成されるファイル
readygo
コマンドを実行すると、go.mod
以外にもいくつか開発に必要なファイルを用意します。
コマンド実行後用意されるのは、具体的には次のファイルやディレクトリです。パッケージ作成後すぐに git にコミットしたり、あるいは go run
して動作確認できることを目指してこのファイルやディレクトリを選んでいます。
.git
:git init
した結果生成されるディレクトリ。.gitignore
: Go パッケージで使用できる .gitignore が生成される。main.go
: Hello, world できるコードが記述された Go ファイル。
内部実装
内部実装はだいたい300行前後の比較的簡単なロジックになっています。Go で CLI ツールを作ったのは初めてでしたので、知見を少し残しておきたいと思います。
Cobra
Go で CLI ツールを使う際に使えるライブラリのようです。
とくに cobra-cli が強力で、この CLI ツールにいろいろと指示を出すと雛形を用意してくれます。この上で開発をすれば好みの CLI ツールを作成可能なので、非常に開発しやすく体験がよかったです。
今回作成した CLI ツールもこの Cobra を使い倒しています。コマンド処理の本体実装は root.go に記述されています。このディレクトリのレイアウトなどは cobra-cli の生成するものに従っています。
git や go コマンド周りの実装
git については最初 git 専用のライブラリがありそうだったので使用しようかと思いましたが、結局普通にシェルを Go から実行することにしました。これが一般的な方法なのかはよくわかりませんが、やることは git init くらいでその結果をアプリケーション側で利用することはなかったため、この形で間に合ったかなと思っています。git コマンドがお使いの環境にない場合はエラーになりそこで処理が終了します。
git := exec.Command("git", "init") err = git.Run() if err != nil { return err }
ただ、cargo などの実装を見ていると VCS にはさまざまな種類を選択できるようです。一旦自分が使いたいために git での初期化のみに対応しましたが、今後他の VCS 対応も追加していこうかなと思っています。cargo では git 以外の VCS を使用する場合は追加でオプションをつけることで専用の初期化が走るように作られていますが、readygo もこの方式に習って新しいオプションをつけるようにしようかと考えています。
go xxx
コマンドを Go から実行する際に特別なパッケージがあるかもよくわからなかったので、やはり同様に go mod init
コマンドを Go から直接コマンドを実行しています。同様にとくにコマンドを実行した結果を使用したかったわけではなく、副作用だけ発生させて結果はそのまま破棄で構わなかったので、この形で間に合ったかなと思います。
cmd := exec.Command("go", "mod", "init", *pkgName) err = cmd.Run() if err != nil { return err }
readygo -p [パッケージパス]
ではじめられるので、ぜひ試してみてください。
今後のプラン
- Go 1.18 から workspace モードというものが導入されたらしく、仕様を確認してそれもできるように試してみようかと思っています。
*1:というか、たった数コマンドなのでそこまで手間じゃないというのはありそうです。
*2:Russ Cox がコメントしている(https://github.com/golang-standards/project-layout/issues/117)。そもそも Go がオフィシャルに推進しているものではないことや、「多くの Go のエコシステムで使われてきた」という言説自体が誤りであること、また pkg ディレクトリがとくに余計な複雑性を持ち込むことになりよくないなどといった話が書かれている。