diff --git a/.gitignore b/.gitignore index 2436660..b01b8af 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ tmp/ _* node_modules example/dist +tasks/godobin +tasks/godobin.exe \ No newline at end of file diff --git a/README.md b/README.md index 3957d59..2f1b668 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ import ( ) func tasks(p *Project) { - Env = "GOPATH=.vendor::$GOPATH PG_PASSWORD=dev" + Env = `GOPATH=.vendor::$GOPATH` p.Task("default", D{"hello", "build"}) @@ -37,19 +37,26 @@ func tasks(p *Project) { } }) - p.Task("build", func() { - Run("GOOS=linux GOARCH=amd64 go build", In{"cmd/server"}) - }).Watch("**/*.go") + p.Task("assets?", func() { + // The "?" tells Godo to run this task ONLY ONCE regardless of + // how many tasks depend on it. In this case watchify watches + // on its own. + Run("watchify public/js/index.js d -o dist/js/app.bundle.js") + }).Watch("public/**/*.{css,js,html}") - p.Task("views", func() { - Run("razor templates") - }).Watch("templates/**/*.go.html") + p.Task("build", D{"views", "assets"}, func() error { + return Run("GOOS=linux GOARCH=amd64 go build", In{"cmd/server"}) + }).Watch("**/*.go") - p.Task("server", D{"views"}, func() { - // rebuilds and restarts the process when a watched file changes + p.Task("server", D{"views", "assets"}, func() { + // rebuilds and restarts when a watched file changes Start("main.go", In{"cmd/server"}) }).Watch("server/**/*.go", "cmd/server/*.{go,json}"). Debounce(3000) + + p.Task("views", func() error { + return Run("razor templates") + }).Watch("templates/**/*.go.html") } func main() { @@ -61,11 +68,11 @@ To run "server" task from parent dir of `tasks/` godo server -To rerun "server" and its dependencies whenever any `*.go.html`, `*.go` or `*.json` file changes +To rerun "server" and its dependencies whenever any of their watched files change godo server --watch -To run the "default" task which runs "hello" and "views" +To run the "default" task which runs "hello" and "build" godo @@ -73,7 +80,7 @@ Task names may add a "?" suffix to execute only once even when watching ```go // build once regardless of number of dependents -p.Task("build?", func() {}) +p.Task("assets?", func() {}) ``` Task options @@ -91,13 +98,13 @@ Task options Task handlers - func() - Simple function handler - func(c *Context) - Handler which accepts the current context - func() error - func(c *Context) error + func() - Simple function handler, don't care about return + func() error - Simple function handler + func(c *Context) - Task with context, don't care about return + func(c *Context) error - Task with context Any error return in task or its dependencies stops the pipeline and -`godo` exits with 1 except in watch mode. +`godo` exits with status code of 1, except in watch mode. ### Task Arguments @@ -149,6 +156,12 @@ c.Args.MustInt("number", "n") c.Args.ZeroInt("number", "n") ``` +## godobin + +`godo` compiles `Godofile.go` to `godobin` (`godobin.exe` on Windows) whenever +`Godofile.go` changes. The binary file is built into the same directory as +`Godofile.go` and should be ignored by adding the path to `.gitignore`. + ## Exec functions ### Bash diff --git a/VERSION.go b/VERSION.go index a4a8108..d612e9e 100644 --- a/VERSION.go +++ b/VERSION.go @@ -1,4 +1,4 @@ package godo // Version is the current version -var Version = "1.3.2" +var Version = "1.4.0" diff --git a/cmd/godo/main.go b/cmd/godo/main.go index 8ec3d52..45afa68 100644 --- a/cmd/godo/main.go +++ b/cmd/godo/main.go @@ -7,13 +7,18 @@ import ( "os" "path/filepath" "regexp" + "runtime" "strings" + "time" + "github.com/mgutz/minimist" "github.com/mgutz/str" "gopkg.in/godo.v1" "gopkg.in/godo.v1/util" ) +var isWindows = runtime.GOOS == "windows" + func checkError(err error, format string, args ...interface{}) { if err != nil { util.Error("ERR", format, args...) @@ -34,6 +39,18 @@ func isPackageMain(data []byte) bool { } func main() { + // cfg := profile.Config{ + // BlockProfile: true, + // CPUProfile: true, + // MemProfile: true, + // NoShutdownHook: true, // do not hook SIGINT + // } + + // // p.Stop() must be called before the program exits to + // // ensure profiling information is written to disk. + // p := profile.Start(&cfg) + // defer p.Stop() + // legacy version used tasks/ godoFiles := []string{"Gododir/Godofile.go", "tasks/Godofile.go"} src := "" @@ -57,18 +74,20 @@ func main() { os.Exit(1) } - mainFile := buildMain(rel) - if mainFile != "" { - src = mainFile - defer os.RemoveAll(filepath.Dir(mainFile)) - } - cmd := "go run " + src + " " + strings.Join(os.Args[1:], " ") + exe := buildMain(rel) + cmd := exe + " " + strings.Join(os.Args[1:], " ") + cmd = str.Clean(cmd) // errors are displayed by tasks + godo.Run(cmd) } -func buildMain(src string) string { - tempFile := "" +type godorc struct { + ModTime time.Time + Size int64 +} + +func mustBeMain(src string) { data, err := ioutil.ReadFile(src) if err != nil { fmt.Fprintln(os.Stderr, err) @@ -76,48 +95,68 @@ func buildMain(src string) string { } if !hasMain(data) { - if isPackageMain(data) { - msg := `%s is not runnable. Rename package OR make it runnable by adding - - func main() { - godo.Godo(Tasks) - } -` - fmt.Printf(msg, src) - os.Exit(1) - } + msg := `%s is not runnable. Rename package OR make it runnable by adding + + func main() { + godo.Godo(tasks) + } + ` + fmt.Printf(msg, src) + os.Exit(1) + } + + if !isPackageMain(data) { + msg := `%s is not runnable. It must be package main` + fmt.Printf(msg, src) + os.Exit(1) + } +} + +func buildMain(src string) string { + mustBeMain(src) + + exeFile := "godobin" + if isWindows { + exeFile = "godobin.exe" + } + + dir := filepath.Dir(src) + exe := filepath.Join(dir, exeFile) + reasonFormat := "Godofile changed. Rebuilding %s...\n" + + argm := minimist.Parse() + rebuild := argm.ZeroBool("rebuild") + if rebuild { + os.Remove(exe) + } + + // see if last run exists .godoinfo + fiExe, err := os.Stat(exe) + build := os.IsNotExist(err) + if build { + reasonFormat = "Building %s...\n" + } + + fiGodofile, err := os.Stat(src) + if os.IsNotExist(err) { + log.Fatalln(err) + os.Exit(1) + } + build = build || fiExe.ModTime().Before(fiGodofile.ModTime()) + + if build { + util.Debug("godo", reasonFormat, exe) - template := ` - package main - import ( - "gopkg.in/godo.v1" - pkg "{{package}}" - ) - func main() { - godo.Godo(pkg.Tasks) - } - ` - packageName, err := util.PackageName(src) + err := godo.Run("go build -a -o "+exeFile, godo.In{dir}) if err != nil { panic(err) } - code := str.Template(template, map[string]interface{}{ - "package": filepath.ToSlash(packageName), - }) - //log.Println("DBG template", code) - tempDir, err := ioutil.TempDir("", "godo") - if err != nil { - panic("Could not create temp directory") - } - //log.Printf("code\n %s\n", code) - tempFile = filepath.Join(tempDir, "Godofile_main.go") - err = ioutil.WriteFile(tempFile, []byte(code), 0644) - if err != nil { - log.Panicf("Could not write temp file %s\n", tempFile) - } + } - src = tempFile - return src + if rebuild { + util.Info("godo", "ok") + os.Exit(0) } - return "" + + return exe } diff --git a/doc.go b/doc.go index 8b89a3d..aada8da 100644 --- a/doc.go +++ b/doc.go @@ -70,5 +70,4 @@ // // func() {} - Simple function handler // func(c *Context) {} - Handler which accepts the current context - package godo diff --git a/env_test.go b/env_test.go index 52f80d7..9e9f718 100644 --- a/env_test.go +++ b/env_test.go @@ -1,14 +1,26 @@ package godo import ( + "fmt" "os" + "runtime" "testing" + + "github.com/mgutz/str" ) +var isWindows = runtime.GOOS == "windows" + func TestEnvironment(t *testing.T) { - SetEnviron("USER=$USER:godo", true) + var user string + if isWindows { + user = os.Getenv("USERNAME") + os.Setenv("USER", user) + } else { + user = os.Getenv("USER") + } - user := os.Getenv("USER") + SetEnviron("USER=$USER:godo", true) env := effectiveEnv(nil) if !sliceContains(env, "USER="+user+":godo") { t.Error("Environment interpolation failed", env) @@ -92,8 +104,17 @@ func TestExpansion(t *testing.T) { func TestInheritedRunEnv(t *testing.T) { os.Setenv("TEST_RUN_ENV", "fubar") SetEnviron("", true) - output, _ := RunOutput(`FOO=bar BAH=baz bash -c "echo -n $TEST_RUN_ENV $FOO"`) - if output != "fubar bar" { - t.Error("Environment was not inherited! Got", output) + + var output string + + if isWindows { + output, _ = RunOutput(`FOO=bar BAH=baz cmd /C "echo %TEST_RUN_ENV% %FOO%"`) + } else { + output, _ = RunOutput(`FOO=bar BAH=baz bash -c "echo -n $TEST_RUN_ENV $FOO"`) + } + + + if str.Clean(output) != "fubar bar" { + t.Error("Environment was not inherited! Got", fmt.Sprintf("%q", output)) } } diff --git a/project_test.go b/project_test.go index 676bd97..94846a8 100644 --- a/project_test.go +++ b/project_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/mgutz/str" "github.com/stretchr/testify/assert" ) @@ -168,10 +169,16 @@ func TestCalculateWatchPaths(t *testing.T) { } func TestInside(t *testing.T) { - Inside("./test", func() { - out, _ := RunOutput("bash foo.sh") - if out != "FOOBAR" { - t.Error("Inside failed") + Inside("test", func() { + var out string + if isWindows { + out, _ = RunOutput("foo.cmd") + } else { + out, _ = RunOutput("bash foo.sh") + } + + if str.Clean(out) != "FOOBAR" { + t.Error("Inside failed. Got", fmt.Sprintf("%q", out)) } }) @@ -182,6 +189,9 @@ func TestInside(t *testing.T) { } func TestBash(t *testing.T) { + if isWindows { + return + } out, _ := BashOutput(`echo -n foobar`) if out != "foobar" { t.Error("Simple bash failed. Got", out) diff --git a/test/foo.cmd b/test/foo.cmd new file mode 100644 index 0000000..9ea4e1c --- /dev/null +++ b/test/foo.cmd @@ -0,0 +1 @@ +@echo FOOBAR \ No newline at end of file