Skip to content

Commit

Permalink
compile Godofile only when it changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Mario L Gutierrez committed Feb 22, 2015
1 parent 651893c commit 6e123ae
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 74 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ tmp/
_*
node_modules
example/dist
tasks/godobin
tasks/godobin.exe
47 changes: 30 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"})

Expand All @@ -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() {
Expand All @@ -61,19 +68,19 @@ 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

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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion VERSION.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package godo

// Version is the current version
var Version = "1.3.2"
var Version = "1.4.0"
131 changes: 85 additions & 46 deletions cmd/godo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand All @@ -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 := ""
Expand All @@ -57,67 +74,89 @@ 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)
os.Exit(1)
}

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
}
1 change: 0 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,4 @@
//
// func() {} - Simple function handler
// func(c *Context) {} - Handler which accepts the current context

package godo
31 changes: 26 additions & 5 deletions env_test.go
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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))
}
}
18 changes: 14 additions & 4 deletions project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

"github.com/mgutz/str"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -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))
}
})

Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/foo.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@echo FOOBAR

0 comments on commit 6e123ae

Please sign in to comment.