gomソースコードリーディング

More than 3 years have passed since last update.

最近Goを使い始めたので、勉強も兼ねてgomのコードを読んでいきます。誤読してれば指摘貰えると嬉しいです。

読んだ時点でのmasterのHEADのハッシュはdf50cf4b41ab0e1deb907246e3622f33f80a5146になります。


main.go

main.goにmain関数があるのでそこから読み始めます。

始めにflagパッケージを使用して引数のパースを行い、-groupsオプションで指定されたグループの初期化や$GOPATHを通すディレクトリの名前の初期化を行います。


main.go

var productionEnv = flag.Bool("production", false, "production environment")

var developmentEnv = flag.Bool("development", false, "development environment")
var testEnv = flag.Bool("test", false, "test environment")
var customGroups = flag.String("groups", "", "comma-separated list of Gomfile groups")
var customGroupList []string
var vendorFolder string

func main() {


customGroupList = strings.Split(*customGroups, ",")

if len(os.Getenv("GOM_VENDOR_NAME")) > 0 {
vendorFolder = os.Getenv("GOM_VENDOR_NAME")
} else {
vendorFolder = "_vendor"
}



あとは引数で指定されたタスクに応じて処理を分岐させて、main.goでの仕事はほとんど終わりになります。


main.go#L57

    var err error

subArgs := flag.Args()[1:]
switch flag.Arg(0) {
case "install", "i":
err = install(subArgs)
case "build", "b":
err = run(append([]string{"go", "build"}, subArgs...), None)
case "test", "t":
err = run(append([]string{"go", "test"}, subArgs...), None)
case "run", "r":
err = run(append([]string{"go", "run"}, subArgs...), None)



タスクがinstallである場合はinstall関数を、それ以外のほとんどの場合ではrun関数を呼び出します。

run関数はinstall関数の中でも使用されるので、先にrun関数を読むことにしましょう。


run関数

run関数はexec.goに記述されています。

execパッケージのCommand関数で外部コマンドを呼び出すシンプルな関数です。


exec.go#L79

func run(args []string, c Color) error {

if err := ready(); err != nil {
return err
}
if len(args) == 0 {
usage()
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Stdin = stdin
ct.ChangeColor(ct.Color(c), true, ct.None, false)
err := cmd.Run()
ct.ResetColor()
return err
}

出力される文字色の変更はgithub.com/daviddengcn/go-colortextを使用しているようです。

main.goで見たように、run関数には引数として"go", "build"等が渡されます。

これをそのまま実行してしまうと自分でgo buildを実行するのと変わらない結果になってしまうため、gomでは$GOPATHを実行時に書き換えて"_vendor"ディレクトリを利用したインストールやビルドを行うようになっているようです。

$GOPATHの書き換えは、run関数の先頭で呼び出しているready関数にて行われます。


exec.go#L31

func ready() error {



vendor, err := filepath.Abs(vendorFolder)
if err != nil {
return err
}


vendor = strings.Join(
[]string{vendor, dir, os.Getenv("GOPATH")},
string(filepath.ListSeparator),
)
err = os.Setenv("GOPATH", vendor)
if err != nil {
return err
}

return nil
}


installタスクを指定した場合に呼び出されるinstall関数も、この"$GOPATHを実行時に書き換える"というアイデアでgo get等を行います。


install関数

タスクに応じた処理の大部分はrun関数にて処理されますが、installタスクの場合はinstall.goに記述されているinstall関数が処理します。実装を順に読んでいきます。


install.go#L239

func install(args []string) error {

allGoms, err := parseGomfile("Gomfile")
if err != nil {
return err
}



gomfile.goに記述されているparseGomfile関数で"Gemfile"もしくは"Gemfile.lock"をパースしてGom構造体を取得します。Gemfileライクな記法をサポートするため正規表現でゴリゴリパースしてます。

次にrun関数同様$GOPATHを置き換えます。


install.go

    vendor, err := filepath.Abs(vendorFolder)

if err != nil {
return err
}


err = os.Setenv("GOPATH", vendor)
if err != nil {
return err
}

その後Gom構造体をgroupやgoosでフィルタし、Gom構造体それぞれについてClone, Checkout, Buildメソッドを呼び出します。


Cloneメソッド

Cloneメソッドでは以下のフローで最新のコードを取得します。いずれの場合でもコマンドはrun関数に渡され実行されます。


  1. Gomfileにcommandオプションがある場合はそのコマンドを実行

  2. Gomfileにprivateが真で設定されているならgit pull若しくはgit cloneを実行

  3. 上記以外の場合はgo getを実行


Checkoutメソッド

Gomfileにbranch, tag, commitオプションのいずれかが設定されている場合のみ処理を行います。

VCS(Git, Mercurial, Bazzar)に応じた方法で指定のブランチ, リビジョンのコードをチェックアウトします。


Buildメソッド

go installを実行します。


以上がinstall関数の処理になります。

run, install関数周りの処理を抑えておけば大体の処理は追えそうですね。


その他

他にはGomfile, Gomfile.lockを生成するためのgenGomfileLock関数がgen.goに記述されてたりします。