From 051745abfa168d2b367bf50622566a83bc392a7b Mon Sep 17 00:00:00 2001 From: Mario Valentin Ochoa Mota Date: Wed, 13 Nov 2019 10:11:03 -0600 Subject: [PATCH 1/4] Add go modules --- .gitignore | 3 +++ README.md | 32 ++++++++++++++++---------------- go.mod | 11 +++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 .gitignore create mode 100644 go.mod diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7bab06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +go.sum +CompileDaemon \ No newline at end of file diff --git a/README.md b/README.md index 0586199..98e7d91 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,22 @@ You can use the `go` tool to install `CompileDaemon`: ## Command Line Options -|Option | Default | Description| -|--------- | ----------- | -----------| -| | | **actions** | -|`-build=…` | go build | Specify the command to run when rebuilding is required.| -|`-command=…` | *none* | Specify the command to run after a succesful build. The default is to run nothing. This command is issued with the working directory set to -directory.| -| | | **file selection** | -|`-directory=…` | . | Which directory to watch.| -|`-recursive=…` | true | Recurse down the specified directory| -|`-exclude-dir=…` | none | Do not watch directories matching this glob pattern, e.g. ".git". You may have multiples of this flag.| -|`-exclude=…` | none | Exclude files matching this glob pattern, e.g. ".#*" ignores emacs temporary files. You may have multiples of this flag.| -|`-include=…` | none | Include files whose last path component matches this glob pattern. You may have multiples of this flag.| -|`-pattern=…` | (.+\\.go|.+\\.c)$ | A regular expression which matches the files to watch. The default watches *.go* and *.c* files.| -| | | **misc** | -|`-color=_` | false | Colorize the output of the daemon's status messages. | -|`-log-prefix=_` | true | Prefix all child process output with stdout/stderr labels and log timestamps. | -|`-graceful-kill=_`| false | On supported platforms, send the child process a SIGTERM to allow it to exit gracefully if possible. | +| Option | Default | Description | +| ------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| | | **actions** | +| `-build=…` | go build | Specify the command to run when rebuilding is required. | +| `-command=…` | *none* | Specify the command to run after a succesful build. The default is to run nothing. This command is issued with the working directory set to -directory. | +| | | **file selection** | +| `-directory=…` | . | Which directory to watch. | +| `-recursive=…` | true | Recurse down the specified directory | +| `-exclude-dir=…` | none | Do not watch directories matching this glob pattern, e.g. ".git". You may have multiples of this flag. | +| `-exclude=…` | none | Exclude files matching this glob pattern, e.g. ".#*" ignores emacs temporary files. You may have multiples of this flag. | +| `-include=…` | none | Include files whose last path component matches this glob pattern. You may have multiples of this flag. | +| `-pattern=…` | (.+\\.go|.+\\.c)$ | A regular expression which matches the files to watch. The default watches *.go* and *.c* files. | +| | | **misc** | +| `-color=_` | false | Colorize the output of the daemon's status messages. | +| `-log-prefix=_` | true | Prefix all child process output with stdout/stderr labels and log timestamps. | +| `-graceful-kill=_` | false | On supported platforms, send the child process a SIGTERM to allow it to exit gracefully if possible. | ## Examples diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8be0c81 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com:mvochoa/CompileDaemon + +go 1.12 + +require ( + github.com/fatih/color v1.7.0 + github.com/fsnotify/fsnotify v1.4.7 + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.10 // indirect + golang.org/x/sys v0.0.0-20191113150313-8ad342257130 // indirect +) From 913a0550e60a44192c1afee449a9f4d9fa456169 Mon Sep 17 00:00:00 2001 From: Mario Valentin Ochoa Mota Date: Wed, 13 Nov 2019 10:12:55 -0600 Subject: [PATCH 2/4] Add environment variable from the file path that I change --- daemon.go | 120 +++++++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/daemon.go b/daemon.go index fa519ac..3286a37 100644 --- a/daemon.go +++ b/daemon.go @@ -80,10 +80,10 @@ import ( "github.com/fsnotify/fsnotify" ) -// Milliseconds to wait for the next job to begin after a file change +// WorkDelay Milliseconds to wait for the next job to begin after a file change const WorkDelay = 900 -// Default pattern to match files which trigger a build +// FilePattern Default pattern to match files which trigger a build const FilePattern = `(.+\.go|.+\.c)$` type globList []string @@ -107,47 +107,50 @@ func (g *globList) Matches(value string) bool { } var ( - flag_directory = flag.String("directory", ".", "Directory to watch for changes") - flag_pattern = flag.String("pattern", FilePattern, "Pattern of watched files") - flag_command = flag.String("command", "", "Command to run and restart after build") - flag_command_stop = flag.Bool("command-stop", false, "Stop command before building") - flag_recursive = flag.Bool("recursive", true, "Watch all dirs. recursively") - flag_build = flag.String("build", "go build", "Command to rebuild after changes") - flag_build_dir = flag.String("build-dir", "", "Directory to run build command in. Defaults to directory") - flag_run_dir = flag.String("run-dir", "", "Directory to run command in. Defaults to directory") - flag_color = flag.Bool("color", false, "Colorize output for CompileDaemon status messages") - flag_logprefix = flag.Bool("log-prefix", true, "Print log timestamps and subprocess stderr/stdout output") - flag_gracefulkill = flag.Bool("graceful-kill", false, "Gracefully attempt to kill the child process by sending a SIGTERM first") - flag_gracefultimeout = flag.Uint("graceful-timeout", 3, "Duration (in seconds) to wait for graceful kill to complete") - flag_verbose = flag.Bool("verbose", false, "Be verbose about which directories are watched.") + flagDirectory = flag.String("directory", ".", "Directory to watch for changes") + flagPattern = flag.String("pattern", FilePattern, "Pattern of watched files") + flagCommand = flag.String("command", "", "Command to run and restart after build") + flagCommandStop = flag.Bool("command-stop", false, "Stop command before building") + flagRecursive = flag.Bool("recursive", true, "Watch all dirs. recursively") + flagBuild = flag.String("build", "go build", "Command to rebuild after changes") + flagBuildDir = flag.String("build-dir", "", "Directory to run build command in. Defaults to directory") + flagRunDir = flag.String("run-dir", "", "Directory to run command in. Defaults to directory") + flagColor = flag.Bool("color", false, "Colorize output for CompileDaemon status messages") + flagLogPrefix = flag.Bool("log-prefix", true, "Print log timestamps and subprocess stderr/stdout output") + flagGracefulkill = flag.Bool("graceful-kill", false, "Gracefully attempt to kill the child process by sending a SIGTERM first") + flagGracefultimeout = flag.Uint("graceful-timeout", 3, "Duration (in seconds) to wait for graceful kill to complete") + flagVerbose = flag.Bool("verbose", false, "Be verbose about which directories are watched.") // initialized in main() due to custom type. - flag_excludedDirs globList - flag_excludedFiles globList - flag_includedFiles globList + flagExcludedDirs globList + flagExcludedFiles globList + flagIncludedFiles globList ) func okColor(format string, args ...interface{}) string { - if *flag_color { + if *flagColor { return color.GreenString(format, args...) - } else { - return fmt.Sprintf(format, args...) } + + return fmt.Sprintf(format, args...) } func failColor(format string, args ...interface{}) string { - if *flag_color { + if *flagColor { return color.RedString(format, args...) - } else { - return fmt.Sprintf(format, args...) } + + return fmt.Sprintf(format, args...) } // Run `go build` and print the output if something's gone wrong. func build() bool { log.Println(okColor("Running build command!")) + if os.Getenv("FILE") != "" { + log.Println(okColor("File changed: %s", os.Getenv("FILE"))) + } - args := strings.Split(*flag_build, " ") + args := strings.Split(strings.Replace(*flagBuild, "$FILE", os.Getenv("FILE"), -1), " ") if len(args) == 0 { // If the user has specified and empty then we are done. return true @@ -155,10 +158,10 @@ func build() bool { cmd := exec.Command(args[0], args[1:]...) - if *flag_build_dir != "" { - cmd.Dir = *flag_build_dir + if *flagBuildDir != "" { + cmd.Dir = *flagBuildDir } else { - cmd.Dir = *flag_directory + cmd.Dir = *flagDirectory } output, err := cmd.CombinedOutput() @@ -192,6 +195,7 @@ func builder(jobs <-chan string, buildStarted chan<- string, buildDone chan<- bo case eventPath = <-jobs: threshold = createThreshold() case <-threshold: + os.Setenv("FILE", eventPath) buildStarted <- eventPath buildDone <- build() } @@ -210,12 +214,13 @@ func logger(pipeChan <-chan io.ReadCloser) { break readloop } - if *flag_logprefix { + if *flagLogPrefix { log.Print(prefix, " ", line) } else { log.Print(line) } } + fmt.Print("\n") } for { @@ -229,11 +234,11 @@ func logger(pipeChan <-chan io.ReadCloser) { // Start the supplied command and return stdout and stderr pipes for logging. func startCommand(command string) (cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, err error) { - args := strings.Split(command, " ") + args := strings.Split(strings.Replace(command, "$FILE", os.Getenv("FILE"), -1), " ") cmd = exec.Command(args[0], args[1:]...) - - if *flag_run_dir != "" { - cmd.Dir = *flag_run_dir + + if *flagRunDir != "" { + cmd.Dir = *flagRunDir } if stdout, err = cmd.StdoutPipe(); err != nil { @@ -280,7 +285,7 @@ func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-c // to suppress warning in returned string. command := fmt.Sprintf("%0.s"+commandTemplate, eventPath) - if !*flag_command_stop { + if !*flagCommandStop { if !<-buildSuccess { continue } @@ -289,7 +294,7 @@ func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-c if currentProcess != nil { killProcess(currentProcess) } - if *flag_command_stop { + if *flagCommandStop { log.Println(okColor("Command stopped. Waiting for build to complete.")) if !<-buildSuccess { continue @@ -311,7 +316,7 @@ func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-c } func killProcess(process *os.Process) { - if *flag_gracefulkill { + if *flagGracefulkill { killProcessGracefully(process) } else { killProcessHard(process) @@ -343,7 +348,7 @@ func killProcessGracefully(process *os.Process) { }() select { - case <-time.After(time.Duration(*flag_gracefultimeout) * time.Second): + case <-time.After(time.Duration(*flagGracefultimeout) * time.Second): log.Println(failColor("Could not gracefully stop the current process, proceeding to hard stop.")) killProcessHard(process) <-done @@ -362,22 +367,27 @@ func flusher(buildStarted <-chan string, buildSuccess <-chan bool) { } func main() { - flag.Var(&flag_excludedDirs, "exclude-dir", " Don't watch directories matching this name") - flag.Var(&flag_excludedFiles, "exclude", " Don't watch files matching this name") - flag.Var(&flag_includedFiles, "include", " Watch files matching this name") + flag.Var(&flagExcludedDirs, "exclude-dir", " Don't watch directories matching this name") + flag.Var(&flagExcludedFiles, "exclude", " Don't watch files matching this name") + flag.Var(&flagIncludedFiles, "include", " Watch files matching this name") + flag.Usage = func() { + fmt.Print("Copyright (c) 2013, Marian Tietz\n\n") + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + } flag.Parse() - if !*flag_logprefix { + if !*flagLogPrefix { log.SetFlags(0) } - if *flag_directory == "" { + if *flagDirectory == "" { fmt.Fprintf(os.Stderr, "-directory=... is required.\n") os.Exit(1) } - if *flag_gracefulkill && !gracefulTerminationPossible() { + if *flagGracefulkill && !gracefulTerminationPossible() { log.Fatal("Graceful termination is not supported on your platform.") } @@ -389,13 +399,13 @@ func main() { defer watcher.Close() - if *flag_recursive == true { - err = filepath.Walk(*flag_directory, func(path string, info os.FileInfo, err error) error { + if *flagRecursive == true { + err = filepath.Walk(*flagDirectory, func(path string, info os.FileInfo, err error) error { if err == nil && info.IsDir() { - if flag_excludedDirs.Matches(path) { + if flagExcludedDirs.Matches(path) { return filepath.SkipDir } else { - if *flag_verbose { + if *flagVerbose { log.Printf("Watching directory '%s' for changes.\n", path) } return watcher.Add(path) @@ -408,25 +418,25 @@ func main() { log.Fatal("filepath.Walk():", err) } - if err := watcher.Add(*flag_directory); err != nil { + if err := watcher.Add(*flagDirectory); err != nil { log.Fatal("watcher.Add():", err) } } else { - if err := watcher.Add(*flag_directory); err != nil { + if err := watcher.Add(*flagDirectory); err != nil { log.Fatal("watcher.Add():", err) } } - pattern := regexp.MustCompile(*flag_pattern) + pattern := regexp.MustCompile(*flagPattern) jobs := make(chan string) buildSuccess := make(chan bool) buildStarted := make(chan string) go builder(jobs, buildStarted, buildSuccess) - if *flag_command != "" { - go runner(*flag_command, buildStarted, buildSuccess) + if *flagCommand != "" { + go runner(*flagCommand, buildStarted, buildSuccess) } else { go flusher(buildStarted, buildSuccess) } @@ -438,12 +448,12 @@ func main() { base := filepath.Base(ev.Name) // Assume it is a directory and track it. - if *flag_recursive == true && !flag_excludedDirs.Matches(ev.Name) { + if *flagRecursive == true && !flagExcludedDirs.Matches(ev.Name) { watcher.Add(ev.Name) } - if flag_includedFiles.Matches(base) || matchesPattern(pattern, ev.Name) { - if !flag_excludedFiles.Matches(base) { + if flagIncludedFiles.Matches(base) || matchesPattern(pattern, ev.Name) { + if !flagExcludedFiles.Matches(base) { jobs <- ev.Name } } From 902c8760d28c2bae1a66a909de8060a6c2623a4b Mon Sep 17 00:00:00 2001 From: Mario Valentin Ochoa Mota Date: Wed, 13 Nov 2019 10:29:48 -0600 Subject: [PATCH 3/4] Change help message --- daemon.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/daemon.go b/daemon.go index 3286a37..67c717b 100644 --- a/daemon.go +++ b/daemon.go @@ -34,6 +34,10 @@ If you want to monitor files other than .go and .c files you might… $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl" +If you only need to work with the file that changes, the $FILE variable has the modified file path + + $ CompileDaemon --build="go build \$FILE" + Options There are command line options. @@ -42,7 +46,7 @@ There are command line options. -directory=XXX – Which directory to monitor for changes -recursive=XXX – Look into subdirectories -exclude-dir=XXX – Exclude directories matching glob pattern XXX - -exlude=XXX – Exclude files whose basename matches glob pattern XXX + -exclude=XXX – Exclude files whose basename matches glob pattern XXX -include=XXX – Include files whose basename matches glob pattern XXX -pattern=XXX – Include files whose path matches regexp XXX @@ -127,6 +131,48 @@ var ( flagIncludedFiles globList ) +func helpMsg(compilerName string) string { + return strings.Replace(`Copyright (c) 2013, Marian Tietz + +Examples + +In its simplest form, the defaults will do. With the current working directory set +to the source directory you can simply… + + $ CompileDaemon + +… and it will recompile your code whenever you save a source file. + +If you want it to also run your program each time it builds you might add… + + $ CompileDaemon -command="./MyProgram -my-options" + +… and it will also keep a copy of your program running. Killing the old one and +starting a new one each time you build. For advanced usage you can also supply +the changed file to the command by doing… + + $ CompileDaemon -command="./MyProgram -my-options %%[1]s" + +…but note that this will not be set on the first start. + +You may find that you need to exclude some directories and files from +monitoring, such as a .git repository or emacs temporary files… + + $ CompileDaemon -exclude-dir=.git -exclude=".#*" + +If you want to monitor files other than .go and .c files you might… + + $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl" + +If you only need to work with the file that changes, the $FILE variable has the modified file path + + $ CompileDaemon --build="go build \$FILE" + +Usage of CompileDaemon: + +`, "CompileDaemon", compilerName, -1) +} + func okColor(format string, args ...interface{}) string { if *flagColor { return color.GreenString(format, args...) @@ -371,8 +417,7 @@ func main() { flag.Var(&flagExcludedFiles, "exclude", " Don't watch files matching this name") flag.Var(&flagIncludedFiles, "include", " Watch files matching this name") flag.Usage = func() { - fmt.Print("Copyright (c) 2013, Marian Tietz\n\n") - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, helpMsg(os.Args[0])) flag.PrintDefaults() } From 5c8f3a8404963a8052e1fe4394061dda7361c927 Mon Sep 17 00:00:00 2001 From: Mario Valentin Ochoa Mota Date: Wed, 13 Nov 2019 10:33:21 -0600 Subject: [PATCH 4/4] Documentation Improvements --- README.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 98e7d91..ae66bff 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,17 @@ a file changed. Nothing more. Usage: - $ ./CompileDaemon -directory=yourproject/ +```bash +$ ./CompileDaemon -directory=yourproject/ +``` ## Installation You can use the `go` tool to install `CompileDaemon`: +```bash go get github.com/githubnemo/CompileDaemon +``` ## Command Line Options @@ -37,13 +41,17 @@ You can use the `go` tool to install `CompileDaemon`: In its simplest form, the defaults will do. With the current working directory set to the source directory you can simply… - $ CompileDaemon +```bash +$ CompileDaemon +``` … and it will recompile your code whenever you save a source file. If you want it to also run your program each time it builds you might add… - $ CompileDaemon -command="./MyProgram -my-options" +```bash +$ CompileDaemon -command="./MyProgram -my-options" +``` … and it will also keep a copy of your program running. Killing the old one and starting a new one each time you build. @@ -51,11 +59,21 @@ starting a new one each time you build. You may find that you need to exclude some directories and files from monitoring, such as a .git repository or emacs temporary files… - $ CompileDaemon -exclude-dir=.git -exclude=".#*" … +```bash +$ CompileDaemon -exclude-dir=.git -exclude=".#*" … +``` If you want to monitor files other than .go and .c files you might… - $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl" +```bash +$ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl" +``` + +If you only need to work with the file that changes, the $FILE variable has the modified file path + +```bash +$ CompileDaemon --build="go build \$FILE" +``` ## Notes