Skip to content

Commit

Permalink
feat: support run custom command after build succeed
Browse files Browse the repository at this point in the history
  • Loading branch information
YYCoder committed Feb 7, 2021
1 parent 2725a3e commit a381477
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 7 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,30 +58,42 @@ Create a `gowatch.yml` file in the execution directory:
# The name of the executable file generated under the current directory execution. The default is the current directory name.
appname: "test"
# Specify the command to run after builds done (only support linux platform currently)
run_cmd: "./run.sh"
# Specify the directory where the compiled object files are stored
output: /bin/demo
# The file name suffix that needs to be monitored. By default, there is only a '.go' file.
watch_exts:
- .yml
# The directory that needs to listen for file changes. By default, only the current directory.
watch_paths:
- ../pk
# Additional parameters that need to be added when running the application
cmd_args:
- arg1=val1
# Additional parameters that need to be added when building the application
build_args:
- -race
# Need to increase environment variables, the current environment variables are loaded by default
envs:
- a=b
# Whether to listen to file changes in the 'vendor' folder
vendor_watch: false
# Directory that do not need to listen for file changes
excluded_paths:
- path
# main package path, can also be a single file, multiple files separated by commas
build_pkg: ""
# build tags
build_tags: ""
Expand Down
13 changes: 13 additions & 0 deletions README_ZH_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,43 @@ go get github.com/silenceper/gowatch
# 当前目录执行下生成的可执行文件的名字,默认是当前目录名
appname: "test"
# 指定编译完成后执行的命令,可指定自定义脚本(目前只支持 linux 系统)
run_cmd: "./run.sh"
# 指定编译后的目标文件目录
output: /bin/demo
# 需要追加监听的文件名后缀,默认只有'.go'文件
watch_exts:
- .yml
# 需要监听的目录,默认只有当前目录
watch_paths:
- ../pk
# 在执行命令时,需要增加的其他参数
cmd_args:
- arg1=val1
# 在构建命令时,需要增加的其他参数
build_args:
- -race
# 需要增加环境变量,默认已加载当前环境变量
envs:
- a=b
# 是否监听 ‘vendor’ 文件夹下的文件改变
vendor_watch: false
# 不需要监听的目录名字
excluded_paths:
- path
# main 包路径,也可以是单个文件,多个文件使用逗号分隔
build_pkg: ""
# build tags
build_tags: ""
Expand Down
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type config struct {
BuildTags string `yaml:"build_tags"`
// Specify whether the program runs automatically
DisableRun bool `yaml:"disable_run"`
// commands when build finished to run
RunCmd string `yaml:"run_cmd"`
}

func parseConfig() *config {
Expand Down
3 changes: 3 additions & 0 deletions example/gowatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
# 程序名称
appname: "example"

# running command after builds done
# run_cmd: "./dev-run.sh"

# Output
output: ./example

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.14
require (
github.com/howeyc/fsnotify v0.9.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/sbinet/pstree v0.3.0
github.com/silenceper/log v0.0.0-20171204144354-e5ac7fa8a76a
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/sbinet/pstree v0.3.0 h1:XZLdGCbAg/wg8TzoW/060WtRoAQdECnsTrn+RYgyJoQ=
github.com/sbinet/pstree v0.3.0/go.mod h1:G208WfJOi4oxq4++w97Y4AeuydVuoOz7tPKCEm8y1oE=
github.com/silenceper/log v0.0.0-20171204144354-e5ac7fa8a76a h1:COf2KvPmardI1M8p2fhHsXlFS2EXSQygbGgcDYBI9Wc=
github.com/silenceper/log v0.0.0-20171204144354-e5ac7fa8a76a/go.mod h1:nyN/YUSK3CgJjtNzm6dVTkcou+RYXNMP+XLSlzQu0m0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
Expand Down
106 changes: 99 additions & 7 deletions gowatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"os/exec"
path "path/filepath"
"regexp"
"sort"
"strings"
"sync"
"time"

"github.com/howeyc/fsnotify"
"github.com/sbinet/pstree"
"github.com/silenceper/log"
)

Expand Down Expand Up @@ -155,36 +157,126 @@ func Autobuild(files []string) {
}
log.Infof("Build was successful\n")
if !cfg.DisableRun {
Restart(cfg.Output)
if len(cfg.RunCmd) != 0 {
Restart(cfg.RunCmd, true)
} else {
Restart(cfg.Output, false)
}
}
}

// Kill kill process
// Kill
func Kill() {
defer func() {
if e := recover(); e != nil {
fmt.Println("Kill.recover -> ", e)
}
}()
if cmd != nil && cmd.Process != nil {
err := cmd.Process.Kill()
// err := cmd.Process.Kill()
err := killAllProcesses(cmd.Process.Pid)
if err != nil {
fmt.Println("Kill -> ", err)
}
}
}

// kill main process and all its children
func killAllProcesses(pid int) (err error) {
hasAllKilled := make(chan bool)
go func() {
pids, err := getAllProcesses(pid)
if err != nil {
log.Fatalf("getting all sub processes error: %v\n", err)
return
}
log.Debugf("main pid: %d", pid)
log.Debugf("pids: %+v", pids)

for _, subPid := range pids {
killProcess(subPid)
}

waitForProcess(pid, hasAllKilled)
}()

// finally kill the main process
<-hasAllKilled
log.Debugf("killing MAIN process pid: %d", pid)
err = cmd.Process.Kill()
if err != nil {
return
}
log.Debugf("kill MAIN process succeed")

return
}

func killProcess(pid int) (err error) {
log.Debugf("killing process pid: %d", pid)
ps, err := os.FindProcess(pid)
if err != nil {
log.Errorf("find process %d error: %v\n", pid, err)
return
}
err = ps.Kill()
if err != nil {
log.Errorf("killing process %d error: %v\n", pid, err)
// retry
time.AfterFunc(2*time.Second, func() {
log.Debugf("retry killing process pid: %d", pid)
killProcess(pid)
})
return
}
return
}

func getAllProcesses(pid int) (pids []int, err error) {
tree, err := pstree.New()
if err != nil {
log.Fatalf("error: %v\n", err)
return
}

traverseChildren(pid, tree, &pids)
// we must kill the sub processes from smallest to largest
sort.Ints(pids)
return
}

func traverseChildren(pid int, tree *pstree.Tree, curPids *[]int) {
for _, cid := range tree.Procs[pid].Children {
*curPids = append(*curPids, cid)
traverseChildren(cid, tree, curPids)
}
}

func waitForProcess(pid int, hasAllKilled chan bool) (err error) {
pids, _ := getAllProcesses(pid)
if len(pids) == 0 {
hasAllKilled <- true
return
}

log.Infof("still waiting for %d processes %+v to exit", len(pids), pids)
time.AfterFunc(time.Second, func() {
waitForProcess(pid, hasAllKilled)
})
return
}

// Restart restart app
func Restart(appname string) {
func Restart(appname string, isCmd bool) {
// log.Debugf("kill running process")
Kill()
go Start(appname)
go Start(appname, isCmd)
}

// Start start app
func Start(appname string) {
func Start(appname string, isCmd bool) {
log.Infof("Restarting %s ...\n", appname)
if !strings.HasPrefix(appname, "./") {
if !strings.HasPrefix(appname, "./") && !isCmd {
appname = "./" + appname
}

Expand Down

0 comments on commit a381477

Please sign in to comment.