Edited at
Go3Day 19

Realize が Go 1.11 の Modules で使えない

去年、 [Go] Realizeが便利なので、もう少し仲良くなってみる という記事を公開してから今でもたまにいいねしていただけるのですが、ホットリロードツールとしては本当に realize は使いやすいですよね。

※ もうこの時点で 「いや、こっちのツールのほうがいいよ」 などありましたら、ぜひコメントで教えていただけると泣いて喜びます :pray:

しかし、 Go 1.11 から組み込まれた Modules を使おうとすると realize は動かなくなってしまいます。

本記事では、この原因を探しにいった旅行記になります。


発生した問題

realize init コマンドを打つと、 .realize.yaml というファイルが生成されます。

このファイルを元に、監視するファイルの絞り込みや、変更されたときに Before/After で行うコマンドの指定など、さまざまなオプションを指定することができます。

例えば、.go のファイルを監視して変更がされた場合に、再度 go run ... をさせたい場合は、以下のように設定します(一部抜粋)。

...

schema:
watcher:
extensions:
- go
commands:
run:
status: true
...

ところが、 GO111MODULE=on では realize の起動ができなくなってしまいます。

$ realize start

[09:38:37][MYAPP] : Watching 0 file/s 0 folder/s
[09:38:37][MYAPP] : Build started
[09:38:37][MYAPP] : Build
exec: not started

この原因を探っていきます。


Issue はあるのか


Go module support


とりあえず、Gopkg.toml を go.mod と go.sum に置き換えたら?


んーそうじゃないんだよな…


Cannot do --run with go1.11 using go mod under windows.

Windows に限った話ではなく、 Mac / Linux でも起きてるよー、とのこと。

ただ、根本的な原因が見えなかったので、直接コードを見ることにしました。


コードを読む


realize.go

とりあえず main パッケージから読み始めます。

まずは realize start してる部分を探します。

CLI パッケージは gopkg.in/urfave/cli.v2 を使ってるみたいですね。

Commands: []*cli.Command{

{
Name: "start",
...
Action: start,
},

プライベート関数 start を呼んでるので、次はそちら。

いろいろ書かれていますが、最後の return r.Start() の先にありそうですね。


cli.go

見る限り、goroutine で複数のプロジェクトを監視できるようにしてるみたいですね。

複数プロジェクトをまたいだ開発や、そもそも設定でプロジェクトのパスを設定できるため、リポジトリ内には .realize.yaml を含めない、みたいなことも可能だそうです。

// Start realize workflow

func (r *Realize) Start() error {
if len(r.Schema.Projects) > 0 {
var wg sync.WaitGroup
wg.Add(len(r.Schema.Projects))
for k := range r.Schema.Projects {
r.Schema.Projects[k].exit = make(chan os.Signal, 1)
signal.Notify(r.Schema.Projects[k].exit, os.Interrupt)
r.Schema.Projects[k].parent = r
go r.Schema.Projects[k].Watch(&wg)
}
wg.Wait()
} else {
return errors.New("there are no projects")
}
return nil
}

次は、 r.Schema.Projects[k].Watch(&wg) 関数を辿ってみます。


projects.go

// Watch a project

func (p *Project) Watch(wg *sync.WaitGroup) {
...
// before start checks
p.Before()
// start watcher
go p.Reload("", p.stop)
...

コメントがありがたいですね。 p.Before() この辺が怪しそうです。

func (p *Project) Before() {

...
if hasGoMod(Wdir()) {
p.Tools.vgo = true
}
...
// setup go tools
p.Tools.Setup()
// global commands before
p.cmd(p.stop, "before", true)
// indexing files and dirs
for _, dir := range p.Watcher.Paths {
base, _ := filepath.Abs(p.Path)
...

hasGoMod(Wdir())p.Tools.vgo が True になっている。

Go1.11 なら go mod ... で良いんじゃなかったっけ…?

Wdir() は Working ディレクトリを返します。

では、 hasGoMod() は?


utils.go

func hasGoMod(dir string) bool {

filename := path.Join(dir, "go.mod")
if _, err := os.Stat(filename); os.IsNotExist(err) {
return false
}

return true
}

ははーん。 go.mod があると、 vgo は True になるんですね。

もしや…


tools.go

ありました。

projects.goBefore() 内にある Setup() で以下のように書かれていました。

// Setup go tools

func (t *Tools) Setup() {
var gocmd string
if t.vgo {
gocmd = "vgo"
} else {
gocmd = "go"
}
...

t.Install.name = "Install"
t.Install.cmd = replace([]string{gocmd, "install"}, t.Install.Method)
t.Install.Args = split([]string{}, t.Install.Args)
...

つまり Go Modules には対応していない みたいです。


まとめ

「お、これは PR チャンスなのでは?」

「いや、そもそもここまで書いてるんだったら、PR 出してマージされましたーぐらいまで話もってこいや」

と思われる方もいらっしゃるでしょう。

しかし、Cannot do --run with go1.11 using go mod under windows. や、他のいくつかの Issue をご覧になった方はすでに把握していると思うんですが、数日前のコメントで


thanks for the support and for the reports, in the last months we had some company changes but in the first trimester 2019 we will release a new version

レポーティングありがとー。2019年の第一期に新しいバージョン出すね。


とのこと。

1.12 から GO111MODULE=on の挙動が標準になるらしい点も踏まえると、もうちょっと待ってみてもいいかな…それでもだめなら他のツールか、もしくは自作してみるのもありかもしれない。

明らかに間違っている情報などありましたら、コメント等でいただけますと幸いです。

[2019/05/12 追記]

@taiba さんより、 Cannot do --run with go1.11 using go mod under windows. #217コメント で解決策が提示されていると教えていただきました。