// Expands glob patterns. func (task *Task) expandGlobs() { // runs once lazily if len(task.SrcFiles) > 0 { return } files, regexps, err := glob.Glob(task.SrcGlobs) if err != nil { util.Error(task.Name, "%v", err) return } task.SrcRegexps = regexps task.SrcFiles = files if len(task.DestGlobs) > 0 { files, regexps, err := glob.Glob(task.DestGlobs) if err != nil { util.Error(task.Name, "%v", err) return } task.DestRegexps = regexps task.DestFiles = files } }
func main() { // v2 ONLY uses Gododir/main.go godoFiles := []string{"Gododir/main.go", "Gododir/Godofile.go", "tasks/Godofile.go"} src := "" for _, filename := range godoFiles { src = util.FindUp(".", filename) if src != "" { break } } if src == "" { fmt.Printf("\n\n%s not found\n", src) os.Exit(1) } wd, err := os.Getwd() if err != nil { util.Error("godo", "Could not get working directory: %s\n", err.Error()) } // parent of Gododir/main.go absParentDir, err := filepath.Abs(filepath.Dir(filepath.Dir(src))) if err != nil { util.Error("godo", "Could not get absolute parent of %s: %s\n", src, err.Error()) } if wd != absParentDir { relDir, _ := filepath.Rel(wd, src) os.Chdir(absParentDir) util.Info("godo", "Using %s\n", relDir) } os.Setenv("GODOFILE", src) argm := minimist.Parse() isRebuild = argm.AsBool("rebuild") isWatch = argm.AsBool("w", "watch") isVerbose = argm.AsBool("v", "verbose") hasTasks = len(argm.NonFlags()) > 0 run(src) }
func runAndWatch(godoFile string) { done := make(chan bool, 1) run := func(forceBuild bool) (*exec.Cmd, string) { cmd, exe := buildCommand(godoFile, forceBuild) cmd.Start() go func() { err := cmd.Wait() done <- true if err != nil { if isVerbose { util.Debug("godo", "godo process killed\n") } } }() return cmd, exe } bufferSize := 2048 watchr, err := watcher.NewWatcher(bufferSize) if err != nil { util.Panic("project", "%v\n", err) } godoDir := filepath.Dir(godoFile) watchr.WatchRecursive(godoDir) watchr.ErrorHandler = func(err error) { util.Error("godo", "Watcher error %v\n", err) } cmd, exe := run(false) // this function will block forever, Ctrl+C to quit app // var lastHappenedTime int64 watchr.Start() util.Info("godo", "watching %s ...\n", godoDir) <-time.After(godo.GetWatchDelay() + (300 * time.Millisecond)) // forloop: for { select { case event := <-watchr.Event: if event.Path == exe { continue } util.Debug("watchmain", "%+v\n", event) syscall.Kill(cmd.Process.Pid, syscall.SIGQUIT) cmd.Process.Kill() <-done cmd, _ = run(true) } } }
func killSpawned(command string) { process := Processes[command] if process == nil { return } err := process.Kill() delete(Processes, command) if err != nil && !strings.Contains(err.Error(), "process already finished") { util.Error("Start", "Could not kill existing process %+v\n%s\n", process, err.Error()) return } if verbose { util.Debug("#", "Processes[%q] killed\n", command) } }
func (project *Project) watchTask(task *Task, root string, logName string, handler func(e *watcher.FileEvent)) { ignorePathFn := func(p string) bool { return watcher.DefaultIgnorePathFn(p) || !task.isWatchedFile(p) } // if len(task.EffectiveWatchRegexps) == 0 { // util.Error("godo", "EffectiveWatchRegexps should not be zero") // } else { // ignorePathFn = func(p string) bool { // return watcher.DefaultIgnorePathFn(p) || !task.isWatchedFile(p) // } // } bufferSize := 2048 watchr, err := watcher.NewWatcher(bufferSize) if err != nil { util.Panic("project", "%v\n", err) } watchr.IgnorePathFn = ignorePathFn watchr.ErrorHandler = func(err error) { util.Error("project", "Watcher error %v\n", err) } watchr.WatchRecursive(root) // this function will block forever, Ctrl+C to quit app // var lastHappenedTime int64 util.Info(logName, "watching %s ...\n", root) // not sure why this need to be unbuffered, but it was blocking // on cquit <- true cquit := make(chan bool, 1) project.Lock() project.cwatchTasks[cquit] = true project.Unlock() watchr.Start() forloop: for { select { case event := <-watchr.Event: //util.Debug("DBG", "handling watchr.Event %+v\n", event) handler(event) case <-cquit: watchr.Stop() break forloop } } }
// Deps are task dependencies and must specify how to run tasks in series or in parallel. func (task *Task) Deps(names ...interface{}) { for _, name := range names { switch dep := name.(type) { default: util.Error(task.Name, "Dependency types must be (string | P | Parallel | S | Series)") case string: task.dependencies = append(task.dependencies, dep) case P: task.dependencies = append(task.dependencies, Parallel(dep)) case Parallel: task.dependencies = append(task.dependencies, dep) case S: task.dependencies = append(task.dependencies, Series(dep)) case Series: task.dependencies = append(task.dependencies, dep) } } }
// Watch watches the Files of a task and reruns the task on a watch event. Any // direct dependency is also watched. Returns true if watching. // // // TODO: // 1. Only the parent task watches, but it gathers wath info from all dependencies. // // 2. Anything without src files always run when a dependency is triggered by a glob match. // // build [generate{*.go} compile] => go file changes => build, generate and compile // // 3. Tasks with src only run if it matches a src // // build [generate{*.go} css{*.scss} compile] => go file changes => build, generate and compile // css does not need to run since no SCSS files ran // // X depends on [A:txt, B] => txt changes A runs, X runs without deps // X:txt on [A, B] => txt changes A, B, X runs // func (project *Project) Watch(names []string, isParent bool) bool { // fixes a bug where the first debounce prevents the task from running because // all tasks are run once before Watch() is called project.reset() funcs := []func(){} taskClosure := func(project *Project, task *Task, taskname string, logName string) func() { paths := calculateWatchPaths(task.EffectiveWatchGlobs) return func() { if len(paths) == 0 { return } for _, pth := range paths { go func(path string) { project.watchTask(task, path, logName, func(e *watcher.FileEvent) { err := project.run(taskname, taskname, e) if err != nil { util.Error("ERR", "%s\n", err.Error()) } }) }(pth) } } } for _, taskname := range names { proj, task, _ := project.mustTask(taskname) // updates effectiveWatchGlobs proj.gatherWatchInfo(task) if len(task.EffectiveWatchGlobs) > 0 { funcs = append(funcs, taskClosure(project, task, taskname, taskname)) } } if len(funcs) > 0 { <-all(funcs) return true } return false }
func pgTasks(p *Project) { Env = ` DAT_DRIVER=postgres DAT_DSN="dbname=dbr_test user=dbr password=!test host=localhost sslmode=disable" ` p.Task("file", nil, func(c *Context) { filename := c.Args.Leftover()[0] if !util.FileExists(filename) { util.Error("ERR", "file not found %s", filename) return } b, err := ioutil.ReadFile(filename) if err != nil { panic(err) } parts := strings.Split(string(b), "---\n") if len(parts) != 2 { panic("sql file must have frontmatter") } var args []interface{} err = json.Unmarshal([]byte(parts[0]), &args) if err != nil { panic(err) } sql := parts[1] sql, args, _ = dat.Interpolate(sql, args) querySQL(sql, args) }).Desc("Executes a SQL file with placeholders") p.Task("query", nil, func(c *Context) { if len(c.Args.Leftover()) != 1 { fmt.Println(`usage: godo query -- "SELECT * ..." `) return } sql := c.Args.Leftover()[0] querySQL(sql, nil) }).Desc("Executes a query against the database") }
func querySQL(sql string, args []interface{}) { fmt.Printf("%s: %s\n", cyan("sql"), sql) if len(args) > 0 { fmt.Printf("%s: %#v\n", cyan("args"), args) } conn := getConnection() if conn != nil { rows, err := conn.DB.Queryx(sql, args...) if err != nil { util.Error("pg", "Error executing SQL: %s", err.Error()) } for rows.Next() { m := map[string]interface{}{} rows.MapScan(m) mapBytesToString(m) b, _ := json.Marshal(m) json.Unmarshal(b, &m) printMap(m) } } util.Info("pg", "OK\n") }
func checkError(err error, format string, args ...interface{}) { if err != nil { util.Error("ERR", format, args...) os.Exit(1) } }
// RunWithEvent runs this task when triggered from a watch. // *e* FileEvent contains information about the file/directory which changed // in watch mode. func (task *Task) RunWithEvent(logName string, e *watcher.FileEvent) (err error) { if task.RunOnce && task.Complete { util.Debug(task.Name, "Already ran\n") return nil } task.expandGlobs() if !task.shouldRun(e) { util.Info(logName, "up-to-date 0ms\n") return nil } start := time.Now() if len(task.SrcGlobs) > 0 && len(task.SrcFiles) == 0 { util.Error("task", "\""+task.Name+"\" '%v' did not match any files\n", task.SrcGlobs) } // Run this task only if the file matches watch Regexps rebuilt := "" if e != nil { rebuilt = "rebuilt " if !task.isWatchedFile(e.Path) && len(task.SrcGlobs) > 0 { return nil } if verbose { util.Debug(logName, "%s\n", e.String()) } } log := true if task.Handler != nil { context := Context{Task: task, Args: task.argm} defer func() { if p := recover(); p != nil { sp, ok := p.(*softPanic) if !ok { panic(p) } err = fmt.Errorf("%q: %s", logName, sp) } }() task.Handler.Handle(&context) if context.Error != nil { return fmt.Errorf("%q: %s", logName, context.Error.Error()) } } else if len(task.dependencies) > 0 { // no need to log if just dependency log = false } else { util.Info(task.Name, "Ignored. Task does not have a handler or dependencies.\n") return nil } if log { util.Info(logName, "%s%vms\n", rebuilt, time.Since(start).Nanoseconds()/1e6) } task.Complete = true return nil }
// used for testing to switch out exitFn func godoExit(tasksFunc func(*Project), argv []string, exitFn func(int)) { if argv == nil { argm = minimist.Parse() } else { argm = minimist.ParseArgv(argv) } dump := argm.AsBool("dump") help = argm.AsBool("help", "h", "?") verbose = argm.AsBool("verbose", "v") version = argm.AsBool("version", "V") watching = argm.AsBool("watch", "w") deprecatedWarnings = argm.AsBool("D") contextArgm := minimist.ParseArgv(argm.Unparsed()) project := NewProject(tasksFunc, exitFn, contextArgm) if help { Usage(project.usage()) exitFn(0) } if version { fmt.Printf("godo %s\n", Version) exitFn(0) } if dump { project.dump(os.Stdout, "", " ") exitFn(0) } // env vars are any nonflag key=value pair addToOSEnviron(argm.NonFlags()) // Run each task including their dependencies. args := []string{} for _, s := range argm.NonFlags() { // skip env vars if !strings.Contains(s, "=") { args = append(args, s) } } if len(args) == 0 { if project.Tasks["default"] != nil { args = append(args, "default") } else { Usage(project.usage()) exitFn(0) } } for _, name := range args { err := project.Run(name) if err != nil { util.Error("ERR", "%s\n", err.Error()) exitFn(1) } } if watching { if project.Watch(args, true) { runnerWaitGroup.Add(1) waitExit = true } else { fmt.Println("Nothing to watch. Use Task#Src() to specify watch patterns") exitFn(0) } } if waitExit { // Ctrl+C handler csig := make(chan os.Signal, 1) signal.Notify(csig, syscall.SIGQUIT) go func() { for sig := range csig { fmt.Println("SIG caught") if sig == syscall.SIGQUIT { fmt.Println("SIG caught B") project.Exit(0) break } } }() runnerWaitGroup.Wait() } exitFn(0) }