Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion task.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,14 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
return nil
}

dir := t.Dir
if cmd.Dir != "" {
dir = cmd.Dir
if err := os.MkdirAll(dir, 0o755); err != nil {
e.Logger.Errf(logger.Red, "task: cannot make command directory %q: %v\n", dir, err)
}
}

if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) {
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd)
}
Expand All @@ -413,7 +421,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in

err = execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: cmd.Cmd,
Dir: t.Dir,
Dir: dir,
Env: env.Get(t),
PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set, cmd.Set),
BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt, cmd.Shopt),
Expand Down
18 changes: 18 additions & 0 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package task_test

import (
"bytes"
"context"
"fmt"
"io"
"io/fs"
Expand Down Expand Up @@ -1527,6 +1528,23 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
_ = os.RemoveAll(toBeCreated)
}

func TestCommandDirRunsInCommandDir(t *testing.T) {
t.Parallel()
const dir = "testdata/command_dir"
var out bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &out,
Stderr: &out,
}

require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))

got := filepath.Base(strings.TrimSpace(out.String()))
assert.Equal(t, "subdir", got, "Mismatch in the command working directory")
}

func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 4 additions & 0 deletions taskfile/ast/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type Cmd struct {
Cmd string
Task string
Dir string
For *For
If string
Silent bool
Expand All @@ -29,6 +30,7 @@ func (c *Cmd) DeepCopy() *Cmd {
return &Cmd{
Cmd: c.Cmd,
Task: c.Task,
Dir: c.Dir,
For: c.For.DeepCopy(),
If: c.If,
Silent: c.Silent,
Expand Down Expand Up @@ -56,6 +58,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
var cmdStruct struct {
Cmd string
Task string
Dir string
For *For
If string
Silent bool
Expand Down Expand Up @@ -104,6 +107,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
// A command with additional options
if cmdStruct.Cmd != "" {
c.Cmd = cmdStruct.Cmd
c.Dir = cmdStruct.Dir
c.For = cmdStruct.For
c.If = cmdStruct.If
c.Silent = cmdStruct.Silent
Expand Down
8 changes: 8 additions & 0 deletions testdata/command_dir/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: '3'

tasks:
default:
cmds:
- cmd: pwd
dir: subdir
silent: true
Empty file.
26 changes: 26 additions & 0 deletions variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
if new.Prefix == "" {
new.Prefix = new.Task
}
resolveCmdDir := func(dir string) (string, error) {
if dir == "" {
return "", nil
}

dir, err := execext.ExpandLiteral(dir)
if err != nil {
return "", err
}

return filepathext.SmartJoin(new.Dir, dir), nil
}

dotenvEnvs := ast.NewVars()
if len(new.Dotenv) > 0 {
Expand Down Expand Up @@ -231,7 +243,14 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
newCmd.Dir = templater.ReplaceWithExtra(cmd.Dir, cache, extra)
newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
if newCmd.Dir != "" {
newCmd.Dir, err = resolveCmdDir(newCmd.Dir)
if err != nil {
return nil, err
}
}
new.Cmds = append(new.Cmds, newCmd)
}
continue
Expand All @@ -246,7 +265,14 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
newCmd.Task = templater.Replace(cmd.Task, cache)
newCmd.If = templater.Replace(cmd.If, cache)
newCmd.Dir = templater.Replace(cmd.Dir, cache)
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
if newCmd.Dir != "" {
newCmd.Dir, err = resolveCmdDir(newCmd.Dir)
if err != nil {
return nil, err
}
}
new.Cmds = append(new.Cmds, newCmd)
}
}
Expand Down
16 changes: 16 additions & 0 deletions website/src/docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,22 @@ tasks:

If the directory does not exist, `task` creates it.

You can also change the working directory for a specific command without
affecting the rest of the task by setting `dir` on the command itself. Relative
paths are resolved from the task directory and are created if missing:

```yaml
version: '3'

tasks:
test:
dir: backend
cmds:
- cmd: npm test
dir: frontend
- cmd: go test ./...
```

## Task dependencies

> Dependencies run in parallel, so dependencies of a task should not depend one
Expand Down
6 changes: 6 additions & 0 deletions website/src/docs/reference/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,13 +736,19 @@ tasks:
example:
cmds:
- cmd: echo "Hello World"
dir: subdir
silent: true
ignore_error: false
platforms: [linux, darwin]
set: [errexit]
shopt: [globstar]
```


#### `dir`

Working directory for the command. Relative paths resolve from the task directory and are created if missing.

### Task References

```yaml
Expand Down
4 changes: 4 additions & 0 deletions website/src/public/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@
"description": "Command to run",
"type": "string"
},
"dir": {
"description": "Working directory for the command. Relative paths resolve from the task directory and are created if missing.",
"type": "string"
},
"silent": {
"description": "Silent mode disables echoing of command before Task runs it",
"type": "boolean"
Expand Down
Loading