Skip to content

Commit faeae00

Browse files
committed
feat: allow to set dir for command
1 parent 12a26fa commit faeae00

9 files changed

Lines changed: 91 additions & 1 deletion

File tree

task.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,14 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
383383
return nil
384384
}
385385

386+
dir := t.Dir
387+
if cmd.Dir != "" {
388+
dir = cmd.Dir
389+
if err := os.MkdirAll(dir, 0o755); err != nil {
390+
e.Logger.Errf(logger.Red, "task: cannot make command directory %q: %v\n", dir, err)
391+
}
392+
}
393+
386394
if e.Verbose || (!call.Silent && !cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
387395
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd)
388396
}
@@ -404,7 +412,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
404412

405413
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
406414
Command: cmd.Cmd,
407-
Dir: t.Dir,
415+
Dir: dir,
408416
Env: env.Get(t),
409417
PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set, cmd.Set),
410418
BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt, cmd.Shopt),

task_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package task_test
22

33
import (
44
"bytes"
5+
"context"
56
"fmt"
67
"io"
78
"io/fs"
@@ -1527,6 +1528,23 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
15271528
_ = os.RemoveAll(toBeCreated)
15281529
}
15291530

1531+
func TestCommandDirRunsInCommandDir(t *testing.T) {
1532+
t.Parallel()
1533+
const dir = "testdata/command_dir"
1534+
var out bytes.Buffer
1535+
e := &task.Executor{
1536+
Dir: dir,
1537+
Stdout: &out,
1538+
Stderr: &out,
1539+
}
1540+
1541+
require.NoError(t, e.Setup())
1542+
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
1543+
1544+
got := filepath.Base(strings.TrimSpace(out.String()))
1545+
assert.Equal(t, "subdir", got, "Mismatch in the command working directory")
1546+
}
1547+
15301548
func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
15311549
t.Parallel()
15321550

taskfile/ast/cmd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
type Cmd struct {
1212
Cmd string
1313
Task string
14+
Dir string
1415
For *For
1516
If string
1617
Silent bool
@@ -29,6 +30,7 @@ func (c *Cmd) DeepCopy() *Cmd {
2930
return &Cmd{
3031
Cmd: c.Cmd,
3132
Task: c.Task,
33+
Dir: c.Dir,
3234
For: c.For.DeepCopy(),
3335
If: c.If,
3436
Silent: c.Silent,
@@ -56,6 +58,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
5658
var cmdStruct struct {
5759
Cmd string
5860
Task string
61+
Dir string
5962
For *For
6063
If string
6164
Silent bool
@@ -104,6 +107,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
104107
// A command with additional options
105108
if cmdStruct.Cmd != "" {
106109
c.Cmd = cmdStruct.Cmd
110+
c.Dir = cmdStruct.Dir
107111
c.For = cmdStruct.For
108112
c.If = cmdStruct.If
109113
c.Silent = cmdStruct.Silent

testdata/command_dir/Taskfile.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: '3'
2+
3+
tasks:
4+
default:
5+
cmds:
6+
- cmd: pwd
7+
dir: subdir
8+
silent: true

testdata/command_dir/subdir/.keep

Whitespace-only changes.

variables.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,18 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
141141
if new.Prefix == "" {
142142
new.Prefix = new.Task
143143
}
144+
resolveCmdDir := func(dir string) (string, error) {
145+
if dir == "" {
146+
return "", nil
147+
}
148+
149+
dir, err := execext.ExpandLiteral(dir)
150+
if err != nil {
151+
return "", err
152+
}
153+
154+
return filepathext.SmartJoin(new.Dir, dir), nil
155+
}
144156

145157
dotenvEnvs := ast.NewVars()
146158
if len(new.Dotenv) > 0 {
@@ -230,7 +242,14 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
230242
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
231243
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
232244
newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
245+
newCmd.Dir = templater.ReplaceWithExtra(cmd.Dir, cache, extra)
233246
newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
247+
if newCmd.Dir != "" {
248+
newCmd.Dir, err = resolveCmdDir(newCmd.Dir)
249+
if err != nil {
250+
return nil, err
251+
}
252+
}
234253
new.Cmds = append(new.Cmds, newCmd)
235254
}
236255
continue
@@ -245,7 +264,14 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
245264
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
246265
newCmd.Task = templater.Replace(cmd.Task, cache)
247266
newCmd.If = templater.Replace(cmd.If, cache)
267+
newCmd.Dir = templater.Replace(cmd.Dir, cache)
248268
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
269+
if newCmd.Dir != "" {
270+
newCmd.Dir, err = resolveCmdDir(newCmd.Dir)
271+
if err != nil {
272+
return nil, err
273+
}
274+
}
249275
new.Cmds = append(new.Cmds, newCmd)
250276
}
251277
}

website/src/docs/guide.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,22 @@ tasks:
528528

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

531+
You can also change the working directory for a specific command without
532+
affecting the rest of the task by setting `dir` on the command itself. Relative
533+
paths are resolved from the task directory and are created if missing:
534+
535+
```yaml
536+
version: '3'
537+
538+
tasks:
539+
test:
540+
dir: backend
541+
cmds:
542+
- cmd: npm test
543+
dir: frontend
544+
- cmd: go test ./...
545+
```
546+
531547
## Task dependencies
532548

533549
> Dependencies run in parallel, so dependencies of a task should not depend one

website/src/docs/reference/schema.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,13 +736,19 @@ tasks:
736736
example:
737737
cmds:
738738
- cmd: echo "Hello World"
739+
dir: subdir
739740
silent: true
740741
ignore_error: false
741742
platforms: [linux, darwin]
742743
set: [errexit]
743744
shopt: [globstar]
744745
```
745746

747+
748+
#### `dir`
749+
750+
Working directory for the command. Relative paths resolve from the task directory and are created if missing.
751+
746752
### Task References
747753

748754
```yaml

website/src/public/schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@
352352
"description": "Command to run",
353353
"type": "string"
354354
},
355+
"dir": {
356+
"description": "Working directory for the command. Relative paths resolve from the task directory and are created if missing.",
357+
"type": "string"
358+
},
355359
"silent": {
356360
"description": "Silent mode disables echoing of command before Task runs it",
357361
"type": "boolean"

0 commit comments

Comments
 (0)