自由帳

とりとめのない学習メモです。主に Web サービスのシステム基盤や運用に関することを書いています

Go の Live Reload を実行してくれる air というツールを試した

はじめに

書籍 詳解Go言語Webアプリケーション開発 を読んでいたら air というLive Reload のツールが登場しました。

github.com

書籍ではハンズオン形式で触ってみただけだったので、もう少し触ってみました。

air を使う

インストール

go instalair のバイナリをインストールします。

% 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 の配置先等に応じて cmdbin あたりを変えたり、除外したいもの、含めたいものに応じて 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 されるようなので、これができる必要があるようでした

参考