はじめに
書籍 を読んでいたら air というLive Reload のツールが登場しました。
書籍ではハンズオン形式で触ってみただけだったので、もう少し触ってみました。
air を使う
インストール
go instal で air のバイナリをインストールします。
% go install github.com/cosmtrek/air@latest
GOPATH/bin 配下にインストールされました。
% go env GOPATH /home/dshimizu/go % which air /home/dshimizu/go/bin/air
ヘルプを見てみます。
% air -h
Usage of air:
If no command is provided air will start the runner with the provided flags
Commands:
init creates a .air.toml file with default settings to the current directory
Flags:
-build.args_bin string
(default "DEFAULT")
-build.bin string
(default "DEFAULT")
-build.cmd string
(default "DEFAULT")
-build.delay string
(default "DEFAULT")
-build.exclude_dir string
(default "DEFAULT")
-build.exclude_file string
(default "DEFAULT")
-build.exclude_regex string
(default "DEFAULT")
-build.exclude_unchanged string
(default "DEFAULT")
-build.follow_symlink string
(default "DEFAULT")
-build.full_bin string
(default "DEFAULT")
-build.include_dir string
(default "DEFAULT")
-build.include_ext string
(default "DEFAULT")
-build.include_file string
(default "DEFAULT")
-build.kill_delay string
(default "DEFAULT")
-build.log string
(default "DEFAULT")
-build.rerun string
(default "DEFAULT")
-build.rerun_delay string
(default "DEFAULT")
-build.send_interrupt string
(default "DEFAULT")
-build.stop_on_error string
(default "DEFAULT")
-c string
config path
-color.app string
(default "DEFAULT")
-color.build string
(default "DEFAULT")
-color.main string
(default "DEFAULT")
-color.runner string
(default "DEFAULT")
-color.watcher string
(default "DEFAULT")
-d debug mode
-log.main_only string
(default "DEFAULT")
-log.time string
(default "DEFAULT")
-misc.clean_on_exit string
(default "DEFAULT")
-root string
(default "DEFAULT")
-screen.clear_on_rebuild string
(default "DEFAULT")
-screen.keep_scroll string
(default "DEFAULT")
-testdata_dir string
(default "DEFAULT")
-tmp_dir string
(default "DEFAULT")
-v show version
たくさんのオプションがありました。
-v でバージョンが出てくるかと思いましたが出てきませんが一旦スルーします。
% air -v __ _ ___ / /\ | | | |_) /_/--\ |_| |_| \_ , built with Go
起動してみる
適当にプロジェクトディレクトリを作成します。
% mkdir workspace/air-sample % go mod init air-sample
適当に以下のような main.go を作成します。
package main import ( "net/http" ) func main() { server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } server.ListenAndServe() }
air コマンドを実行してみます。
% air __ _ ___ / /\ | | | |_) /_/--\ |_| |_| \_ , built with Go watching . !exclude tmp building... running...
正常に実行できました。
この時に、上述で作成した main.go がビルドされて起動します。
デフォルトだと、プロジェクトディレクトリ配下の tmp/ ディレクトリ(今回だと ari-sample/tmp)にバイナリが配置されて、これが実行されます。
下記のような感じでプロセスが起動されていることが確認できます。
% ps auxf | grep main dshimizu 1467716 0.0 0.0 4504 712 pts/0 S+ 07:36 0:00 | \_ grep --color=auto main dshimizu 1467699 0.0 0.0 2484 516 pts/4 Ss+ 07:36 0:00 \_ /bin/sh -c /home/dshimizu/workspcace/air-sample/tmp/main dshimizu 1467700 0.0 0.1 1084628 4972 pts/4 Sl+ 07:36 0:00 \_ /home/dshimizu/workspcace/air-sample/tmp/main
air コマンドを実行しているターミナルはこのままにして、並行して main.go を、下記のような形で更新してみます。
package main import ( "net/http" ) type HelloHandler struct{} func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World")) } func main() { hello := HelloHandler{} server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } http.Handle("/hello", hello) server.ListenAndServe() }
この時、 air コマンドを実行しているターミナルで、ファイルを変更して保存するたびに、自動でビルドし直されます。
その際、ファイルが更新されるたびに下記のような出力がなされます。ファイルに文法エラー等があるとエラーが出力されますが、正常な状態になれば、 main.go has changed,
building..., running... という出力になり、最新の状態で自動で起動し直されていることが確認できます。
main.go has changed building... # ari-sample ./main.go:8:11: undefined: HelloHandler failed to build, error: exit status 1 : main.go has changed building... running...
プロセスが再起動されていることが確認できます。
% ps auxf | grep main dshimizu 1467716 0.0 0.0 4504 712 pts/0 S+ 07:39 0:00 | \_ grep --color=auto main dshimizu 1467699 0.0 0.0 2484 516 pts/4 Ss+ 07:39 0:00 \_ /bin/sh -c /home/dshimizu/workspcace/air-sample/tmp/main dshimizu 1467700 0.0 0.1 1084628 4972 pts/4 Sl+ 07:39 0:00 \_ /home/dshimizu/workspcace/air-sample/tmp/main
設定ファイルを作成/変更してみる
プロジェクト直下に .air.toml ファイルを配置して、デフォルトの挙動を変更できます。
下記のコマンドで、.air.toml を生成できます。
% air init
v1.42 の air だと、下記のような内容のものが生成されます。
main.go の配置先等に応じて cmd や bin あたりを変えたり、除外したいもの、含めたいものに応じて exclude_xxx, include_xxx あたりを変更することになりそうです。
root = "." testdata_dir = "testdata" tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 0 exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] include_ext = ["go", "tpl", "tmpl", "html"] include_file = [] kill_delay = "0s" log = "build-errors.log" rerun = false rerun_delay = 500 send_interrupt = false stop_on_error = false [color] app = "" build = "yellow" main = "magenta" runner = "green" watcher = "cyan" [log] main_only = false time = false [misc] clean_on_exit = false [screen] clear_on_rebuild = false keep_scroll = true
試しに go build せずに go run で動くかをやってみました。
@@ -4,8 +4,9 @@ [build] args_bin = [] - bin = "./tmp/main" - cmd = "go build -o ./tmp/main ." + #bin = "./tmp/main" + #cmd = "go build -o ./tmp/main ." + cmd = "go run main.go" delay = 0 exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_file = []
running... の出力が出ませんでしたが、プログラムは起動しているようでした。
なお、デフォルトで .air.toml を読み込んでくれるようですが、 air -c ファイル名 のコマンドでファイル名を指定することもできるようです。
% air __ _ ___ / /\ | | | |_) /_/--\ |_| |_| \_ , built with Go watching . !exclude tmp building...
プロセスは下記のような状態でした。
% ps auxf | grep main dshimizu 1468191 0.0 0.0 2484 572 pts/2 Ss+ 08:07 0:00 | \_ /bin/sh -c go run main.go dshimizu 1468192 0.0 0.6 1240428 26156 pts/2 Sl+ 08:07 0:00 | \_ go run main.go dshimizu 1468231 0.0 0.3 1010640 13216 pts/2 Sl+ 08:07 0:00 | \_ /tmp/go-build692725095/b001/exe/main dshimizu 1469281 0.0 0.0 4504 644 pts/3 S+ 08:14 0:00 \_ grep --color=auto main
main.go を変更してみました。
package main import ( "net/http" ) type HelloHandler struct{} func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World!!")) // !! をつけた } func main() { hello := HelloHandler{} server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } http.Handle("/hello", hello) server.ListenAndServe() }
ファイルの更新は検知してくれたようで、ここでは building..., running... と出力はされました。
% air __ _ ___ / /\ | | | |_) /_/--\ |_| |_| \_ , built with Go watching . !exclude tmp building... main.go has changed main.go has changed building... running...
しかしプロセスの状態は変更ないようでした。
% ps auxf | grep main dshimizu 1468191 0.0 0.0 2484 572 pts/2 Ss+ 08:07 0:00 | \_ /bin/sh -c go run main.go dshimizu 1468192 0.0 0.6 1240428 26156 pts/2 Sl+ 08:07 0:00 | \_ go run main.go dshimizu 1468231 0.0 0.3 1010640 13216 pts/2 Sl+ 08:07 0:00 | \_ /tmp/go-build692725095/b001/exe/main dshimizu 1469281 0.0 0.0 4504 644 pts/3 S+ 08:14 0:00 \_ grep --color=auto main
リクエストを投げてみましたが、 Hello World!! とはなっていませんでした。go run の場合は Live Reload はされてないようです。
% curl localhost:8080/hello Hello World%
まとめ
- Go の Live Reload を実行してくれる air を使ってみました
- air コマンド実行時に、アプリケーションをビルドして実行し、ファイルの更新が発生した場合に自動で検知してアプリケーションが再起動されることが確認できました
- 設定ファイルは
.air.tomlで、デフォルトではこのファイルを読み込んでくれるようでした - リロード時には
go buildされるようなので、これができる必要があるようでした