diff --git a/air_example.toml b/air_example.toml index 01cec979..2c4e8e83 100644 --- a/air_example.toml +++ b/air_example.toml @@ -6,8 +6,12 @@ root = "." tmp_dir = "tmp" [build] +# Array of commands to run before each build +pre_cmd = ["echo 'hello air' > pre_cmd.txt"] # Just plain old shell command. You could use `make` as well. cmd = "go build -o ./tmp/main ." +# Array of commands to run after ^C +post_cmd = ["echo 'hello air' > post_cmd.txt"] # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary, can setup environment variables when run your app. diff --git a/runner/config.go b/runner/config.go index 47f238ed..3ed4c219 100644 --- a/runner/config.go +++ b/runner/config.go @@ -34,7 +34,9 @@ type Config struct { } type cfgBuild struct { + PreCmd []string `toml:"pre_cmd"` Cmd string `toml:"cmd"` + PostCmd []string `toml:"post_cmd"` Bin string `toml:"bin"` FullBin string `toml:"full_bin"` ArgsBin []string `toml:"args_bin"` diff --git a/runner/config_test.go b/runner/config_test.go index 5f999c94..37a0c6bc 100644 --- a/runner/config_test.go +++ b/runner/config_test.go @@ -15,6 +15,7 @@ const ( func getWindowsConfig() Config { build := cfgBuild{ + PreCmd: []string{"echo Hello Air"}, Cmd: "go build -o ./tmp/main .", Bin: "./tmp/main", Log: "build-errors.log", diff --git a/runner/engine.go b/runner/engine.go index 7d59f5a2..e88f3823 100644 --- a/runner/engine.go +++ b/runner/engine.go @@ -376,6 +376,13 @@ func (e *Engine) buildRun() { default: } var err error + if err = e.runPreCmd(); err != nil { + e.canExit <- true + e.runnerLog("failed to execute pre_cmd: %s", err.Error()) + if e.config.Build.StopOnError { + return + } + } if err = e.building(); err != nil { e.canExit <- true e.buildLog("failed to build, error: %s", err.Error()) @@ -410,10 +417,9 @@ func (e *Engine) flushEvents() { } } -func (e *Engine) building() error { - var err error - e.buildLog("building...") - cmd, stdout, stderr, err := e.startCmd(e.config.Build.Cmd) +// utility to execute commands, such as cmd & pre_cmd +func (e *Engine) runCommand(command string) error { + cmd, stdout, stderr, err := e.startCmd(command) if err != nil { return err } @@ -423,7 +429,7 @@ func (e *Engine) building() error { }() _, _ = io.Copy(os.Stdout, stdout) _, _ = io.Copy(os.Stderr, stderr) - // wait for building + // wait for command to finish err = cmd.Wait() if err != nil { return err @@ -431,6 +437,40 @@ func (e *Engine) building() error { return nil } +// run cmd option in .air.toml +func (e *Engine) building() error { + e.buildLog("building...") + err := e.runCommand(e.config.Build.Cmd) + if err != nil { + return err + } + return nil +} + +// run pre_cmd option in .air.toml +func (e *Engine) runPreCmd() error { + for _, command := range e.config.Build.PreCmd { + e.runnerLog("> %s", command) + err := e.runCommand(command) + if err != nil { + return err + } + } + return nil +} + +// run post_cmd option in .air.toml +func (e *Engine) runPostCmd() error { + for _, command := range e.config.Build.PostCmd { + e.runnerLog("> %s", command) + err := e.runCommand(command) + if err != nil { + return err + } + } + return nil +} + func (e *Engine) runBin() error { // control killFunc should be kill or not killCh := make(chan struct{}) @@ -557,5 +597,8 @@ func (e *Engine) cleanup() { // Stop the air func (e *Engine) Stop() { + if err := e.runPostCmd(); err != nil { + e.runnerLog("failed to execute post_cmd, error: %s", err.Error()) + } close(e.exitCh) } diff --git a/runner/engine_test.go b/runner/engine_test.go index 59b239fc..de348483 100644 --- a/runner/engine_test.go +++ b/runner/engine_test.go @@ -185,6 +185,78 @@ func TestRerunWhenFileChanged(t *testing.T) { } } +func TestRunCommand(t *testing.T) { + // generate a random port + port, f := GetPort() + f() + t.Logf("port: %d", port) + tmpDir := initTestEnv(t, port) + // change dir to tmpDir + chdir(t, tmpDir) + engine, err := NewEngine("", true) + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + err = engine.runCommand("touch test.txt") + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + if _, err := os.Stat("./test.txt"); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Should not be fail: %s.", err) + } + } +} + +func TestRunPreCmd(t *testing.T) { + // generate a random port + port, f := GetPort() + f() + t.Logf("port: %d", port) + tmpDir := initTestEnv(t, port) + // change dir to tmpDir + chdir(t, tmpDir) + engine, err := NewEngine("", true) + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + err = engine.runPreCmd() + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + if _, err := os.Stat("./pre_cmd.txt"); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Should not be fail: %s.", err) + } + } +} + +func TestRunPostCmd(t *testing.T) { + // generate a random port + port, f := GetPort() + f() + t.Logf("port: %d", port) + tmpDir := initTestEnv(t, port) + // change dir to tmpDir + chdir(t, tmpDir) + + engine, err := NewEngine("", true) + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + + err = engine.runPostCmd() + if err != nil { + t.Fatalf("Should not be fail: %s.", err) + } + + if _, err := os.Stat("./post_cmd.txt"); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Should not be fail: %s.", err) + } + } +} + func TestRunBin(t *testing.T) { engine, err := NewEngine("", true) if err != nil {