spf13/cobra をソースリーディングしていてメモっておきたかったので、
自分用のメモです。
パッケージ構成
$GOPATH
└── src
└── myapp
├── main.go
└── mymodule
├── aaa.go
├── root.go
└── zzz.go
- main.goは単純にmymoduleのExecute関数を実行しているだけ
- mymoduleには、myListというパッケージローカルな変数があり、それはroot.goで初期化されている
- パッケージには、aaa.go, root.go, zzz.go という3つのファイルがある。
ソース
main.go
package main
import (
"fmt"
"myapp/mymodule"
)
func main() {
fmt.Println("main >")
mymodule.Execute()
fmt.Println("main <")
}
mymodule/root.go
package mymodule
import "fmt"
var myList = makeList()
func makeList() []string {
fmt.Println("root/makeList")
return []string{}
}
func init() {
fmt.Println("root/init >")
myList = append(myList, "root/init")
fmt.Println("root/init <")
}
func Execute() {
myList = append(myList, "root/Execute")
fmt.Println("root/Execute")
}
mymodule/aaa.go
package mymodule
import "fmt"
func init() {
fmt.Println("aaa/init >")
myList = append(myList, "aaa/init")
fmt.Println("aaa/init <")
}
mymodule/zzz.go
package mymodule
import "fmt"
func init() {
fmt.Println("zzz/init >")
myList = append(myList, "zzz/init")
fmt.Println("zzz/init <")
}
Result ( ゚д゚)
$ go run main.go
root/makeList
aaa/init >
aaa/init <
root/init >
root/init <
zzz/init >
zzz/init <
main >
root/Execute
main <
パッケージのインポート
↓
パッケージ中の各種変数の初期化 (ファイル名がAから順番、だけど処理系依存かもしれない)
↓
パッケージ中の各種init()関数の呼び出し (ファイル名がAから順番、だけど処理系依存かもしれない)
↓
main関数の呼び出し
spf13/cobra はどうやって「各コマンドは別ファイルで定義する」というスタイルを確立したか
- main関数からは、root.Execute()を叩いているだけ。
- root.Execute() は、それまでに作成されたコマンドパーサ(rootCmd変数に格納されている)のExecuteを叩くだけ。
- rootCmdは cmd/root.go で初期化される。
- 各種cmd/xxx.goのinit()で、rootCmdを拡張していく。
- cmd/root.goのinit()で OnInitializeで、cobraがパッケージローカルで持ってるinitializerを拡張する
- 各メソッド(cobra.Command#AddCommandやcobra#OnInitializeなど)はどういう順番で叩かれても困らないように設計されていると思われる
- AddCommandとかOnInitializeは、単純に、いろいろなコマンドデータをリストにデータを突っ込んでいるだけ
- Execute()やCommands()とか、いざ使われる際に、リストを整形(ソートとか)して使ったりしている
まとめ
- パッケージ中の変数の初期化が一通り終わってから、パッケージ中のinit()が呼ばれるよ!
- 順番はファイル名とかに依存するかもしれないし、ランダムかもしれないので、どういう順番でinitされてもバグらないようにしようね!