1
0
mirror of https://github.com/go-task/task.git synced 2025-06-08 23:56:21 +02:00

refactor: Create executor struct to get rid of global variables

Maybe eventually help on #17
This commit is contained in:
Andrey Nering 2017-06-04 16:02:04 -03:00
parent c40148a52e
commit f98bf6c4b1
9 changed files with 137 additions and 109 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"log"
"github.com/go-task/task" "github.com/go-task/task"
@ -9,6 +10,8 @@ import (
) )
func main() { func main() {
log.SetFlags(0)
pflag.Usage = func() { pflag.Usage = func() {
fmt.Println(`task [target1 target2 ...]: Runs commands under targets like make. fmt.Println(`task [target1 target2 ...]: Runs commands under targets like make.
@ -25,9 +28,40 @@ hello:
`) `)
pflag.PrintDefaults() pflag.PrintDefaults()
} }
pflag.BoolVarP(&task.Init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
pflag.BoolVarP(&task.Force, "force", "f", false, "forces execution even when the task is up-to-date") var (
pflag.BoolVarP(&task.Watch, "watch", "w", false, "enables watch of the given task") init bool
force bool
watch bool
)
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
pflag.Parse() pflag.Parse()
task.Run()
if init {
if err := task.InitTaskfile(); err != nil {
log.Fatal(err)
}
return
}
e := task.Executor{
Force: force,
Watch: watch,
}
if err := e.ReadTaskfile(); err != nil {
log.Fatal(err)
}
args := pflag.Args()
if len(args) == 0 {
log.Println("task: No argument given, trying default task")
args = []string{"default"}
}
if err := e.Run(args...); err != nil {
log.Fatal(err)
}
} }

View File

@ -1,8 +1,8 @@
package task package task
// HasCyclicDep checks if a task tree has any cyclic dependency // HasCyclicDep checks if a task tree has any cyclic dependency
func HasCyclicDep(m map[string]*Task) bool { func (e *Executor) HasCyclicDep() bool {
visits := make(map[string]struct{}, len(m)) visits := make(map[string]struct{}, len(e.Tasks))
var checkCyclicDep func(string, *Task) bool var checkCyclicDep func(string, *Task) bool
checkCyclicDep = func(name string, t *Task) bool { checkCyclicDep = func(name string, t *Task) bool {
@ -13,14 +13,14 @@ func HasCyclicDep(m map[string]*Task) bool {
defer delete(visits, name) defer delete(visits, name)
for _, d := range t.Deps { for _, d := range t.Deps {
if !checkCyclicDep(d, m[d]) { if !checkCyclicDep(d, e.Tasks[d]) {
return false return false
} }
} }
return true return true
} }
for k, v := range m { for k, v := range e.Tasks {
if !checkCyclicDep(k, v) { if !checkCyclicDep(k, v) {
return true return true
} }

View File

@ -7,20 +7,23 @@ import (
) )
func TestCyclicDepCheck(t *testing.T) { func TestCyclicDepCheck(t *testing.T) {
isCyclic := map[string]*task.Task{ isCyclic := &task.Executor{
Tasks: map[string]*task.Task{
"task-a": &task.Task{ "task-a": &task.Task{
Deps: []string{"task-b"}, Deps: []string{"task-b"},
}, },
"task-b": &task.Task{ "task-b": &task.Task{
Deps: []string{"task-a"}, Deps: []string{"task-a"},
}, },
},
} }
if !task.HasCyclicDep(isCyclic) { if !isCyclic.HasCyclicDep() {
t.Error("Task should be cyclic") t.Error("Task should be cyclic")
} }
isNotCyclic := map[string]*task.Task{ isNotCyclic := &task.Executor{
Tasks: map[string]*task.Task{
"task-a": &task.Task{ "task-a": &task.Task{
Deps: []string{"task-c"}, Deps: []string{"task-c"},
}, },
@ -28,9 +31,10 @@ func TestCyclicDepCheck(t *testing.T) {
Deps: []string{"task-c"}, Deps: []string{"task-c"},
}, },
"task-c": &task.Task{}, "task-c": &task.Task{},
},
} }
if task.HasCyclicDep(isNotCyclic) { if isNotCyclic.HasCyclicDep() {
t.Error("Task should not be cyclic") t.Error("Task should not be cyclic")
} }
} }

View File

@ -1,9 +1,17 @@
package task package task
import ( import (
"errors"
"fmt" "fmt"
) )
var (
// ErrCyclicDependencyDetected is returned when a cyclic dependency was found in the Taskfile
ErrCyclicDependencyDetected = errors.New("task: cyclic dependency detected")
// ErrTaskfileAlreadyExists is returned on creating a Taskfile if one already exists
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
)
type taskFileNotFound struct { type taskFileNotFound struct {
taskFile string taskFile string
} }

10
help.go
View File

@ -7,8 +7,8 @@ import (
"text/tabwriter" "text/tabwriter"
) )
func printExistingTasksHelp() { func (e *Executor) printExistingTasksHelp() {
tasks := tasksWithDesc() tasks := e.tasksWithDesc()
if len(tasks) == 0 { if len(tasks) == 0 {
return return
} }
@ -17,13 +17,13 @@ func printExistingTasksHelp() {
// Format in tab-separated columns with a tab stop of 8. // Format in tab-separated columns with a tab stop of 8.
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0) w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
for _, task := range tasks { for _, task := range tasks {
fmt.Fprintln(w, fmt.Sprintf("- %s:\t%s", task, Tasks[task].Desc)) fmt.Fprintln(w, fmt.Sprintf("- %s:\t%s", task, e.Tasks[task].Desc))
} }
w.Flush() w.Flush()
} }
func tasksWithDesc() (tasks []string) { func (e *Executor) tasksWithDesc() (tasks []string) {
for name, task := range Tasks { for name, task := range e.Tasks {
if task.Desc != "" { if task.Desc != "" {
tasks = append(tasks, name) tasks = append(tasks, name)
} }

10
init.go
View File

@ -13,17 +13,17 @@ default:
- echo "Hello, World!" - echo "Hello, World!"
` `
func initTaskfile() { // InitTaskfile Taskfile creates a new Taskfile
func InitTaskfile() error {
for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} { for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} {
if _, err := os.Stat(f); err == nil { if _, err := os.Stat(f); err == nil {
log.Printf("A Taskfile already exists") return ErrTaskfileAlreadyExists
os.Exit(1)
return
} }
} }
if err := ioutil.WriteFile("Taskfile.yml", []byte(defaultTaskfile), 0666); err != nil { if err := ioutil.WriteFile("Taskfile.yml", []byte(defaultTaskfile), 0666); err != nil {
log.Fatalf("%v", err) return err
} }
log.Printf("Taskfile.yml created in the current directory") log.Printf("Taskfile.yml created in the current directory")
return nil
} }

View File

@ -11,27 +11,30 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
func readTaskfile() (map[string]*Task, error) { // ReadTaskfile parses Taskfile from the disk
initialTasks, err := readTaskfileData(TaskFilePath) func (e *Executor) ReadTaskfile() error {
var err error
e.Tasks, err = e.readTaskfileData(TaskFilePath)
if err != nil { if err != nil {
return nil, err return err
} }
mergeTasks, err := readTaskfileData(fmt.Sprintf("%s_%s", TaskFilePath, runtime.GOOS))
osTasks, err := e.readTaskfileData(fmt.Sprintf("%s_%s", TaskFilePath, runtime.GOOS))
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
default:
return nil, err
case taskFileNotFound: case taskFileNotFound:
return initialTasks, nil return nil
default:
return err
} }
} }
if err := mergo.MapWithOverwrite(&initialTasks, mergeTasks); err != nil { if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
return nil, err return err
} }
return initialTasks, nil return nil
} }
func readTaskfileData(path string) (tasks map[string]*Task, err error) { func (e *Executor) readTaskfileData(path string) (tasks map[string]*Task, err error) {
if b, err := ioutil.ReadFile(path + ".yml"); err == nil { if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
return tasks, yaml.Unmarshal(b, &tasks) return tasks, yaml.Unmarshal(b, &tasks)
} }

85
task.go
View File

@ -10,24 +10,22 @@ import (
"github.com/go-task/task/execext" "github.com/go-task/task/execext"
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
var ( const (
// TaskFilePath is the default Taskfile // TaskFilePath is the default Taskfile
TaskFilePath = "Taskfile" TaskFilePath = "Taskfile"
)
// Init (--init or -f flag) creates a Taskfile.yml in the current folder if not exists // Executor executes a Taskfile
Init bool type Executor struct {
// Force (--force or -f flag) forces a task to run even when it's up-to-date Tasks map[string]*Task
Force bool Force bool
// Watch (--watch or -w flag) enables watch of a task
Watch bool Watch bool
// Tasks constains the tasks parsed from Taskfile watchingFiles map[string]struct{}
Tasks = make(map[string]*Task) }
)
// Task represents a task // Task represents a task
type Task struct { type Task struct {
@ -44,67 +42,47 @@ type Task struct {
} }
// Run runs Task // Run runs Task
func Run() { func (e *Executor) Run(args ...string) error {
log.SetFlags(0) if e.HasCyclicDep() {
return ErrCyclicDependencyDetected
args := pflag.Args()
if Init {
initTaskfile()
return
}
if len(args) == 0 {
log.Println("task: No argument given, trying default task")
args = []string{"default"}
}
var err error
Tasks, err = readTaskfile()
if err != nil {
log.Fatal(err)
}
if HasCyclicDep(Tasks) {
log.Fatal("Cyclic dependency detected")
} }
// check if given tasks exist // check if given tasks exist
for _, a := range args { for _, a := range args {
if _, ok := Tasks[a]; !ok { if _, ok := e.Tasks[a]; !ok {
var err error = &taskNotFoundError{taskName: a} // FIXME: move to the main package
fmt.Println(err) e.printExistingTasksHelp()
printExistingTasksHelp() return &taskNotFoundError{taskName: a}
return
} }
} }
if Watch { if e.Watch {
if err := WatchTasks(args); err != nil { if err := e.watchTasks(args...); err != nil {
log.Fatal(err) return err
} }
return return nil
} }
for _, a := range args { for _, a := range args {
if err = RunTask(context.Background(), a); err != nil { if err := e.RunTask(context.Background(), a); err != nil {
log.Fatal(err) return err
} }
} }
return nil
} }
// RunTask runs a task by its name // RunTask runs a task by its name
func RunTask(ctx context.Context, name string) error { func (e *Executor) RunTask(ctx context.Context, name string) error {
t, ok := Tasks[name] t, ok := e.Tasks[name]
if !ok { if !ok {
return &taskNotFoundError{name} return &taskNotFoundError{name}
} }
if err := t.runDeps(ctx); err != nil { if err := e.runDeps(ctx, name); err != nil {
return err return err
} }
if !Force { if !e.Force {
upToDate, err := t.isUpToDate(ctx) upToDate, err := t.isUpToDate(ctx)
if err != nil { if err != nil {
return err return err
@ -116,15 +94,16 @@ func RunTask(ctx context.Context, name string) error {
} }
for i := range t.Cmds { for i := range t.Cmds {
if err := t.runCommand(ctx, i); err != nil { if err := e.runCommand(ctx, name, i); err != nil {
return &taskRunError{name, err} return &taskRunError{name, err}
} }
} }
return nil return nil
} }
func (t *Task) runDeps(ctx context.Context) error { func (e *Executor) runDeps(ctx context.Context, task string) error {
g, ctx := errgroup.WithContext(ctx) g, ctx := errgroup.WithContext(ctx)
t := e.Tasks[task]
for _, d := range t.Deps { for _, d := range t.Deps {
dep := d dep := d
@ -135,7 +114,7 @@ func (t *Task) runDeps(ctx context.Context) error {
return err return err
} }
if err = RunTask(ctx, dep); err != nil { if err = e.RunTask(ctx, dep); err != nil {
return err return err
} }
return nil return nil
@ -195,7 +174,9 @@ func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
return generatesMinTime.After(sourcesMaxTime), nil return generatesMinTime.After(sourcesMaxTime), nil
} }
func (t *Task) runCommand(ctx context.Context, i int) error { func (e *Executor) runCommand(ctx context.Context, task string, i int) error {
t := e.Tasks[task]
c, err := t.ReplaceVariables(t.Cmds[i]) c, err := t.ReplaceVariables(t.Cmds[i])
if err != nil { if err != nil {
return err return err
@ -203,7 +184,7 @@ func (t *Task) runCommand(ctx context.Context, i int) error {
if strings.HasPrefix(c, "^") { if strings.HasPrefix(c, "^") {
c = strings.TrimPrefix(c, "^") c = strings.TrimPrefix(c, "^")
if err = RunTask(ctx, c); err != nil { if err = e.RunTask(ctx, c); err != nil {
return err return err
} }
return nil return nil

View File

@ -11,13 +11,13 @@ import (
"github.com/mattn/go-zglob" "github.com/mattn/go-zglob"
) )
// WatchTasks start watching the given tasks // watchTasks start watching the given tasks
func WatchTasks(args []string) error { func (e *Executor) watchTasks(args ...string) error {
log.Printf("task: Started watching for tasks: %s", strings.Join(args, ", ")) log.Printf("task: Started watching for tasks: %s", strings.Join(args, ", "))
// run tasks on init // run tasks on init
for _, a := range args { for _, a := range args {
if err := RunTask(context.Background(), a); err != nil { if err := e.RunTask(context.Background(), a); err != nil {
fmt.Println(err) fmt.Println(err)
break break
} }
@ -31,7 +31,7 @@ func WatchTasks(args []string) error {
go func() { go func() {
for { for {
if err := registerWatchedFiles(watcher, args); err != nil { if err := e.registerWatchedFiles(watcher, args); err != nil {
log.Printf("Error watching files: %v", err) log.Printf("Error watching files: %v", err)
} }
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
@ -43,7 +43,7 @@ loop:
select { select {
case <-watcher.Events: case <-watcher.Events:
for _, a := range args { for _, a := range args {
if err := RunTask(context.Background(), a); err != nil { if err := e.RunTask(context.Background(), a); err != nil {
fmt.Println(err) fmt.Println(err)
continue loop continue loop
} }
@ -55,11 +55,9 @@ loop:
} }
} }
var watchingFiles map[string]struct{} func (e *Executor) registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
oldWatchingFiles := e.watchingFiles
func registerWatchedFiles(w *fsnotify.Watcher, args []string) error { e.watchingFiles = make(map[string]struct{}, len(oldWatchingFiles))
oldWatchingFiles := watchingFiles
watchingFiles = make(map[string]struct{}, len(oldWatchingFiles))
for k := range oldWatchingFiles { for k := range oldWatchingFiles {
if err := w.Remove(k); err != nil { if err := w.Remove(k); err != nil {
@ -68,11 +66,11 @@ func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
} }
for _, a := range args { for _, a := range args {
task, ok := Tasks[a] task, ok := e.Tasks[a]
if !ok { if !ok {
return &taskNotFoundError{a} return &taskNotFoundError{a}
} }
if err := registerWatchedFiles(w, task.Deps); err != nil { if err := e.registerWatchedFiles(w, task.Deps); err != nil {
return err return err
} }
for _, s := range task.Sources { for _, s := range task.Sources {
@ -84,7 +82,7 @@ func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
if err := w.Add(f); err != nil { if err := w.Add(f); err != nil {
return err return err
} }
watchingFiles[f] = struct{}{} e.watchingFiles[f] = struct{}{}
// run if is new file // run if is new file
if oldWatchingFiles != nil { if oldWatchingFiles != nil {