diff --git a/internal/taskfile/task.go b/internal/taskfile/task.go index 1afcbfa3..a686f04b 100644 --- a/internal/taskfile/task.go +++ b/internal/taskfile/task.go @@ -1,5 +1,8 @@ package taskfile +import "os" +import "sync" + // Tasks represents a group of tasks type Tasks map[string]*Task @@ -14,6 +17,7 @@ type Task struct { Generates []string Status []string Dir string + mkdirMutex sync.Mutex Vars Vars Env Vars Silent bool @@ -21,3 +25,22 @@ type Task struct { Prefix string IgnoreError bool `yaml:"ignore_error"` } + +// Mkdir creates the directory Task.Dir. +// Safe to be called concurrently. +func (t *Task) Mkdir() error { + if t.Dir == "" { + // No "dir:" attribute, so we do nothing. + return nil + } + + t.mkdirMutex.Lock() + defer t.mkdirMutex.Unlock() + + if _, err := os.Stat(t.Dir); os.IsNotExist(err) { + if err := os.MkdirAll(t.Dir, 0755); err != nil { + return err + } + } + return nil +} diff --git a/task.go b/task.go index 2df08c2c..bca22c4d 100644 --- a/task.go +++ b/task.go @@ -200,6 +200,12 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { } } + // When using the "dir:" attribute it can happen that the directory doesn't exist. + // If so, we create it. + if err := t.Mkdir(); err != nil { + e.Logger.Errf("task: cannot make directory %q: %v", t.Dir, err) + } + for i := range t.Cmds { if err := e.runCommand(ctx, t, call, i); err != nil { if err2 := e.statusOnError(t); err2 != nil { diff --git a/task_test.go b/task_test.go index e2278dc5..bb73b56a 100644 --- a/task_test.go +++ b/task_test.go @@ -236,7 +236,7 @@ func TestDeps(t *testing.T) { for _, f := range files { f = filepath.Join(dir, f) if _, err := os.Stat(f); err != nil { - t.Errorf("File %s should exists", f) + t.Errorf("File %s should exist", f) } } } @@ -248,7 +248,7 @@ func TestStatus(t *testing.T) { _ = os.Remove(file) if _, err := os.Stat(file); err == nil { - t.Errorf("File should not exists: %v", err) + t.Errorf("File should not exist: %v", err) } var buff bytes.Buffer @@ -262,7 +262,7 @@ func TestStatus(t *testing.T) { assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"})) if _, err := os.Stat(file); err != nil { - t.Errorf("File should exists: %v", err) + t.Errorf("File should exist: %v", err) } e.Silent = false @@ -290,7 +290,7 @@ func TestGenerates(t *testing.T) { path := filepath.Join(dir, task) _ = os.Remove(path) if _, err := os.Stat(path); err == nil { - t.Errorf("File should not exists: %v", err) + t.Errorf("File should not exist: %v", err) } } @@ -311,10 +311,10 @@ func TestGenerates(t *testing.T) { assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask})) if _, err := os.Stat(srcFile); err != nil { - t.Errorf("File should exists: %v", err) + t.Errorf("File should exist: %v", err) } if _, err := os.Stat(destFile); err != nil { - t.Errorf("File should exists: %v", err) + t.Errorf("File should exist: %v", err) } // Ensure task was not incorrectly found to be up-to-date on first run. if buff.String() == upToDate { @@ -371,7 +371,7 @@ func TestInit(t *testing.T) { _ = os.Remove(file) if _, err := os.Stat(file); err == nil { - t.Errorf("Taskfile.yml should not exists") + t.Errorf("Taskfile.yml should not exist") } if err := task.InitTaskfile(ioutil.Discard, dir); err != nil { @@ -379,7 +379,7 @@ func TestInit(t *testing.T) { } if _, err := os.Stat(file); err != nil { - t.Errorf("Taskfile.yml should exists") + t.Errorf("Taskfile.yml should exist") } } @@ -575,3 +575,65 @@ func readTestFixture(t *testing.T, dir string, file string) string { assert.NoError(t, err, "error reading text fixture") return string(b) } + +func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) { + const expected = "dir" + const dir = "testdata/" + expected + var out bytes.Buffer + e := &task.Executor{ + Dir: dir, + Stdout: &out, + Stderr: &out, + } + + assert.NoError(t, e.Setup()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"})) + + // got should be the "dir" part of "testdata/dir" + got := strings.TrimSuffix(filepath.Base(out.String()), "\n") + assert.Equal(t, expected, got, "Mismatch in the working directory") +} + +func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) { + const expected = "exists" + const dir = "testdata/dir/explicit_exists" + var out bytes.Buffer + e := &task.Executor{ + Dir: dir, + Stdout: &out, + Stderr: &out, + } + + assert.NoError(t, e.Setup()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"})) + + got := strings.TrimSuffix(filepath.Base(out.String()), "\n") + assert.Equal(t, expected, got, "Mismatch in the working directory") +} + +func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) { + const expected = "createme" + const dir = "testdata/dir/explicit_doesnt_exist/" + const toBeCreated = dir + expected + const target = "whereami" + var out bytes.Buffer + e := &task.Executor{ + Dir: dir, + Stdout: &out, + Stderr: &out, + } + + // Ensure that the directory to be created doesn't actually exist. + _ = os.Remove(toBeCreated) + if _, err := os.Stat(toBeCreated); err == nil { + t.Errorf("Directory should not exist: %v", err) + } + assert.NoError(t, e.Setup()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target})) + + got := strings.TrimSuffix(filepath.Base(out.String()), "\n") + assert.Equal(t, expected, got, "Mismatch in the working directory") + + // Clean-up after ourselves only if no error. + _ = os.Remove(toBeCreated) +} diff --git a/testdata/dir/Taskfile.yml b/testdata/dir/Taskfile.yml new file mode 100644 index 00000000..f17dd9fe --- /dev/null +++ b/testdata/dir/Taskfile.yml @@ -0,0 +1,7 @@ +version: '2' + +tasks: + whereami: + cmds: + - pwd + silent: true diff --git a/testdata/dir/explicit_doesnt_exist/Taskfile.yml b/testdata/dir/explicit_doesnt_exist/Taskfile.yml new file mode 100644 index 00000000..1b6fb7d1 --- /dev/null +++ b/testdata/dir/explicit_doesnt_exist/Taskfile.yml @@ -0,0 +1,8 @@ +version: '2' + +tasks: + whereami: + dir: createme + cmds: + - pwd + silent: true diff --git a/testdata/dir/explicit_exists/Taskfile.yml b/testdata/dir/explicit_exists/Taskfile.yml new file mode 100644 index 00000000..0ab53b26 --- /dev/null +++ b/testdata/dir/explicit_exists/Taskfile.yml @@ -0,0 +1,8 @@ +version: '2' + +tasks: + whereami: + dir: exists + cmds: + - pwd + silent: true diff --git a/testdata/dir/explicit_exists/exists/.keepme b/testdata/dir/explicit_exists/exists/.keepme new file mode 100644 index 00000000..e69de29b