mirror of
https://github.com/go-task/task.git
synced 2025-02-03 13:22:11 +02:00
refactor: Create executor struct to get rid of global variables
Maybe eventually help on #17
This commit is contained in:
parent
c40148a52e
commit
f98bf6c4b1
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-task/task"
|
||||
|
||||
@ -9,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
pflag.Usage = func() {
|
||||
fmt.Println(`task [target1 target2 ...]: Runs commands under targets like make.
|
||||
|
||||
@ -25,9 +28,40 @@ hello:
|
||||
`)
|
||||
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")
|
||||
pflag.BoolVarP(&task.Watch, "watch", "w", false, "enables watch of the given task")
|
||||
|
||||
var (
|
||||
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()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package task
|
||||
|
||||
// HasCyclicDep checks if a task tree has any cyclic dependency
|
||||
func HasCyclicDep(m map[string]*Task) bool {
|
||||
visits := make(map[string]struct{}, len(m))
|
||||
func (e *Executor) HasCyclicDep() bool {
|
||||
visits := make(map[string]struct{}, len(e.Tasks))
|
||||
|
||||
var checkCyclicDep func(string, *Task) bool
|
||||
checkCyclicDep = func(name string, t *Task) bool {
|
||||
@ -13,14 +13,14 @@ func HasCyclicDep(m map[string]*Task) bool {
|
||||
defer delete(visits, name)
|
||||
|
||||
for _, d := range t.Deps {
|
||||
if !checkCyclicDep(d, m[d]) {
|
||||
if !checkCyclicDep(d, e.Tasks[d]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
for k, v := range e.Tasks {
|
||||
if !checkCyclicDep(k, v) {
|
||||
return true
|
||||
}
|
||||
|
@ -7,30 +7,34 @@ import (
|
||||
)
|
||||
|
||||
func TestCyclicDepCheck(t *testing.T) {
|
||||
isCyclic := map[string]*task.Task{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-b"},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-a"},
|
||||
isCyclic := &task.Executor{
|
||||
Tasks: map[string]*task.Task{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-b"},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !task.HasCyclicDep(isCyclic) {
|
||||
if !isCyclic.HasCyclicDep() {
|
||||
t.Error("Task should be cyclic")
|
||||
}
|
||||
|
||||
isNotCyclic := map[string]*task.Task{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
isNotCyclic := &task.Executor{
|
||||
Tasks: map[string]*task.Task{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
},
|
||||
"task-c": &task.Task{},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
},
|
||||
"task-c": &task.Task{},
|
||||
}
|
||||
|
||||
if task.HasCyclicDep(isNotCyclic) {
|
||||
if isNotCyclic.HasCyclicDep() {
|
||||
t.Error("Task should not be cyclic")
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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 {
|
||||
taskFile string
|
||||
}
|
||||
|
10
help.go
10
help.go
@ -7,8 +7,8 @@ import (
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
func printExistingTasksHelp() {
|
||||
tasks := tasksWithDesc()
|
||||
func (e *Executor) printExistingTasksHelp() {
|
||||
tasks := e.tasksWithDesc()
|
||||
if len(tasks) == 0 {
|
||||
return
|
||||
}
|
||||
@ -17,13 +17,13 @@ func printExistingTasksHelp() {
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
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()
|
||||
}
|
||||
|
||||
func tasksWithDesc() (tasks []string) {
|
||||
for name, task := range Tasks {
|
||||
func (e *Executor) tasksWithDesc() (tasks []string) {
|
||||
for name, task := range e.Tasks {
|
||||
if task.Desc != "" {
|
||||
tasks = append(tasks, name)
|
||||
}
|
||||
|
10
init.go
10
init.go
@ -13,17 +13,17 @@ default:
|
||||
- echo "Hello, World!"
|
||||
`
|
||||
|
||||
func initTaskfile() {
|
||||
// InitTaskfile Taskfile creates a new Taskfile
|
||||
func InitTaskfile() error {
|
||||
for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} {
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
log.Printf("A Taskfile already exists")
|
||||
os.Exit(1)
|
||||
return
|
||||
return ErrTaskfileAlreadyExists
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
return nil
|
||||
}
|
||||
|
@ -11,27 +11,30 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func readTaskfile() (map[string]*Task, error) {
|
||||
initialTasks, err := readTaskfileData(TaskFilePath)
|
||||
// ReadTaskfile parses Taskfile from the disk
|
||||
func (e *Executor) ReadTaskfile() error {
|
||||
var err error
|
||||
e.Tasks, err = e.readTaskfileData(TaskFilePath)
|
||||
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 {
|
||||
switch err.(type) {
|
||||
default:
|
||||
return nil, err
|
||||
case taskFileNotFound:
|
||||
return initialTasks, nil
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := mergo.MapWithOverwrite(&initialTasks, mergeTasks); err != nil {
|
||||
return nil, err
|
||||
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
|
||||
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 {
|
||||
return tasks, yaml.Unmarshal(b, &tasks)
|
||||
}
|
||||
|
85
task.go
85
task.go
@ -10,24 +10,22 @@ import (
|
||||
|
||||
"github.com/go-task/task/execext"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
const (
|
||||
// TaskFilePath is the default Taskfile
|
||||
TaskFilePath = "Taskfile"
|
||||
)
|
||||
|
||||
// Init (--init or -f flag) creates a Taskfile.yml in the current folder if not exists
|
||||
Init bool
|
||||
// Force (--force or -f flag) forces a task to run even when it's up-to-date
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Tasks map[string]*Task
|
||||
Force bool
|
||||
// Watch (--watch or -w flag) enables watch of a task
|
||||
Watch bool
|
||||
|
||||
// Tasks constains the tasks parsed from Taskfile
|
||||
Tasks = make(map[string]*Task)
|
||||
)
|
||||
watchingFiles map[string]struct{}
|
||||
}
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
@ -44,67 +42,47 @@ type Task struct {
|
||||
}
|
||||
|
||||
// Run runs Task
|
||||
func Run() {
|
||||
log.SetFlags(0)
|
||||
|
||||
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")
|
||||
func (e *Executor) Run(args ...string) error {
|
||||
if e.HasCyclicDep() {
|
||||
return ErrCyclicDependencyDetected
|
||||
}
|
||||
|
||||
// check if given tasks exist
|
||||
for _, a := range args {
|
||||
if _, ok := Tasks[a]; !ok {
|
||||
var err error = &taskNotFoundError{taskName: a}
|
||||
fmt.Println(err)
|
||||
printExistingTasksHelp()
|
||||
return
|
||||
if _, ok := e.Tasks[a]; !ok {
|
||||
// FIXME: move to the main package
|
||||
e.printExistingTasksHelp()
|
||||
return &taskNotFoundError{taskName: a}
|
||||
}
|
||||
}
|
||||
|
||||
if Watch {
|
||||
if err := WatchTasks(args); err != nil {
|
||||
log.Fatal(err)
|
||||
if e.Watch {
|
||||
if err := e.watchTasks(args...); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
if err = RunTask(context.Background(), a); err != nil {
|
||||
log.Fatal(err)
|
||||
if err := e.RunTask(context.Background(), a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunTask runs a task by its name
|
||||
func RunTask(ctx context.Context, name string) error {
|
||||
t, ok := Tasks[name]
|
||||
func (e *Executor) RunTask(ctx context.Context, name string) error {
|
||||
t, ok := e.Tasks[name]
|
||||
if !ok {
|
||||
return &taskNotFoundError{name}
|
||||
}
|
||||
|
||||
if err := t.runDeps(ctx); err != nil {
|
||||
if err := e.runDeps(ctx, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !Force {
|
||||
if !e.Force {
|
||||
upToDate, err := t.isUpToDate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -116,15 +94,16 @@ func RunTask(ctx context.Context, name string) error {
|
||||
}
|
||||
|
||||
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 nil
|
||||
}
|
||||
|
||||
func (t *Task) runDeps(ctx context.Context) error {
|
||||
func (e *Executor) runDeps(ctx context.Context, task string) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
t := e.Tasks[task]
|
||||
|
||||
for _, d := range t.Deps {
|
||||
dep := d
|
||||
@ -135,7 +114,7 @@ func (t *Task) runDeps(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = RunTask(ctx, dep); err != nil {
|
||||
if err = e.RunTask(ctx, dep); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -195,7 +174,9 @@ func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
|
||||
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])
|
||||
if err != nil {
|
||||
return err
|
||||
@ -203,7 +184,7 @@ func (t *Task) runCommand(ctx context.Context, i int) error {
|
||||
|
||||
if strings.HasPrefix(c, "^") {
|
||||
c = strings.TrimPrefix(c, "^")
|
||||
if err = RunTask(ctx, c); err != nil {
|
||||
if err = e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
24
watch.go
24
watch.go
@ -11,13 +11,13 @@ import (
|
||||
"github.com/mattn/go-zglob"
|
||||
)
|
||||
|
||||
// WatchTasks start watching the given tasks
|
||||
func WatchTasks(args []string) error {
|
||||
// watchTasks start watching the given tasks
|
||||
func (e *Executor) watchTasks(args ...string) error {
|
||||
log.Printf("task: Started watching for tasks: %s", strings.Join(args, ", "))
|
||||
|
||||
// run tasks on init
|
||||
for _, a := range args {
|
||||
if err := RunTask(context.Background(), a); err != nil {
|
||||
if err := e.RunTask(context.Background(), a); err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
@ -31,7 +31,7 @@ func WatchTasks(args []string) error {
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if err := registerWatchedFiles(watcher, args); err != nil {
|
||||
if err := e.registerWatchedFiles(watcher, args); err != nil {
|
||||
log.Printf("Error watching files: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
@ -43,7 +43,7 @@ loop:
|
||||
select {
|
||||
case <-watcher.Events:
|
||||
for _, a := range args {
|
||||
if err := RunTask(context.Background(), a); err != nil {
|
||||
if err := e.RunTask(context.Background(), a); err != nil {
|
||||
fmt.Println(err)
|
||||
continue loop
|
||||
}
|
||||
@ -55,11 +55,9 @@ loop:
|
||||
}
|
||||
}
|
||||
|
||||
var watchingFiles map[string]struct{}
|
||||
|
||||
func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
|
||||
oldWatchingFiles := watchingFiles
|
||||
watchingFiles = make(map[string]struct{}, len(oldWatchingFiles))
|
||||
func (e *Executor) registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
|
||||
oldWatchingFiles := e.watchingFiles
|
||||
e.watchingFiles = make(map[string]struct{}, len(oldWatchingFiles))
|
||||
|
||||
for k := range oldWatchingFiles {
|
||||
if err := w.Remove(k); err != nil {
|
||||
@ -68,11 +66,11 @@ func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
task, ok := Tasks[a]
|
||||
task, ok := e.Tasks[a]
|
||||
if !ok {
|
||||
return &taskNotFoundError{a}
|
||||
}
|
||||
if err := registerWatchedFiles(w, task.Deps); err != nil {
|
||||
if err := e.registerWatchedFiles(w, task.Deps); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range task.Sources {
|
||||
@ -84,7 +82,7 @@ func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
|
||||
if err := w.Add(f); err != nil {
|
||||
return err
|
||||
}
|
||||
watchingFiles[f] = struct{}{}
|
||||
e.watchingFiles[f] = struct{}{}
|
||||
|
||||
// run if is new file
|
||||
if oldWatchingFiles != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user