タスクランナーgodoをEdison+Gobot開発に導入してみた

> *追記:2017/03/06*
> いろいろあってソースを読んだところ、GobotのEdison用ドライバーはsysfsを直接叩きに行ってることがわかりました
> 特にPWM周りで不満が大きくて、Gobotからはduty比しかいじくれないけど、Edisonの仕様でduty比0でも僅かに出力が出るので
> 結局最後はライブラリのソースを改変しながら使っていました(それでも不具合が出る、sysfsをプログラムから叩くのが原因ぽい？)
> あまりそれらがよろしくないと思ったので、今度はgolangでmraaのCライブラリを直接触ってみたいところです

golangのクロスコンパイルを何かに使ってみたかった矢先、Gobotというものを知ったので、
Edison開発にgolangで挑戦してみることにした
それにあたって、開発環境作りに精を出した話

# できたこと
ソースを保存すると、
1. ビルドして
2. Edisonに転送され
3. 今までのプロセスを殺して
4. 新たなプログラムが走ってくれます
ソースの監視を切って、手動で上記を実行するのもできます

# godoとは
https://github.com/go-godo/godo
>godo is a task runner and file watcher for golang in the spirit of rake, gulp.

golang製のタスクランナー
gulpみたいなもの

# やりたいこと
1. ソースからEdison向けにビルド
2. Edisonへ転送
3. Edison上のプロセスのkill
4. Edison上でプログラムの実行
5. 上記の集約

## 下準備
### Edisonの設定
Edisonにはsshでアクセスします。
事前にEdisonにsshの鍵を渡しておいてください
Edisonにscreenで接続して、

`# mkdir .ssh`
`# echo "公開鍵をコピペ" >> .ssh/authorized_keys`

### godoの導入
`go get`最高ありがとう
`go get -u gopkg.in/godo.v2/cmd/godo`

### godo用の領域の作成
godoはワーキングディレクトリ下にGododirというディレクトリを作って
その下にmain.goを作って設定をします
`mkdir Gododir`

### Edison開発用の枠組み
Edison開発のための枠組みを設定しておきます

まず、自分のEdisonのドメイン(`[エジソンの名前].local`)を確認しておきます
僕の場合は初期設定どおりの`edison.local`です
また、ユーザーは`root`で動かします

godoのTaskは、標準ライブラリの`text/template`のように、
Taskに渡すコマンド内の"{{.option}}"みたいな領域にテンプレート処理ができます
`options do.M`は、そのためのmapになっています

```go:Gododir/main.go
package main

import (
	"strings"

	do "gopkg.in/godo.v2"
)

// EdisonHost is host name of edison
var EdisonHost = "edison.local"

// EdisonUser is user name of edison
var EdisonUser = "root"

// AppName is Application Name
var AppName = "EdisonProgram"

var options = do.M{
	"app_name":    AppName,
	"edison_host": EdisonUser + "@" + EdisonHost,
	"edison_user": EdisonUser,
}

func tasks(p *do.Project) {

	// ここにTaskを並べていく

}

func main() {
	do.Godo(tasks)
}
```

## ソースからEdison向けにビルド
念願のクロスコンパイルを実行します

```go

p.Task("build", nil, func(c *do.Context) {
	c.Run("GOOS=linux GOARCH=386 go build")
})
```

## Edisonへ転送
scpを使います

```go

p.Task("copy_to_edison", nil, func(c *do.Context) {
	c.Run("scp {{.app_name}} {{.edison_host}}:/home/{{.edison_user}}", options)
})
```

## Edison上のプロセスのkill
トリッキーなことをしています
psをgrepしてgrep以外のプロセスが走っていたらのidを抜き出してkillします
killallで殺すとプロセスが走ってないときにステータスコードが1で帰ってきてエラーで止まってしまった
エラーも標準入力に逃がすようにすれば動くのかもしれない

```go

p.Task("killall_process", nil, func(c *do.Context) {
	// もしかしてこれで動く?
	// c.Run("ssh {{.edison_host}} killall -q {{.app_name}} 2>&1", options)

	ps := c.RunOutput("ssh {{.edison_host}} 'ps | grep {{.app_name}}'", options)

	rows := strings.Split(ps, "\n")

	ids := []string{}
	for _, row := range rows {
		if strings.Contains(row, AppName) && !strings.Contains(row, "grep") {
			ids = append(ids, strings.Split(strings.Trim(row, " \n"), " ")[0])
		}
	}

	if len(ids) != 0 {
		addOptions := options
		addOptions["ps_ids"] = strings.Join(ids, " ")

		c.Run("ssh {{.edison_host}} 'kill {{.ps_ids}}'", addOptions)
	}
})
```

## Edison上でプログラムの実行
sshでコマンドを送信するのに、適当に&をつけてもバックグラウンド実行できなくてハマりまくった
いろいろ試した結果これで動いた

```go

p.Task("exec_process", nil, func(c *do.Context) {
	c.Run("ssh {{.edison_host}} 'nohup /home/{{.edison_user}}/{{.app_name}} > /home/{{.edison_user}}/output.log 2>&1 &'", options)
})
```

## 上記の集約
デフォルトのTaskを設定します
do.SでTaskを渡すと、SerialにTaskを実行してくれます
do.Pだと、Prallelです
`do.S{"task1", do.P{"task2-1", "task2-2"}, "task3"}`
とかもできるっぽい 
今回は並列に動かす必要はないので、do.Sだけです(buildとkillall_processは並列でもいいかな)

Taskに対してSrc()を設定しておくと、監視対象を設定してくれる

```go

defaultTask := do.S{
	"build",
	"killall_process",
	"copy_to_edison",
	"exec_process",
}

p.Task("default", defaultTask, nil).Src("**/*.go")
```

# 実行

`godo`

で、デフォルトのタスクが実行される

`godo --watch`

で起動しておけば、ソースを保存する毎に実行してくれる