task icon indicating copy to clipboard operation
task copied to clipboard

Absolute path bug when using include and dir

Open roycamp opened this issue 3 months ago • 6 comments

Description

When setting an absolute directory (ex. /tmp) from a variable and using that in the dir: directive of an included taskfile, it is treated as a relative path. However, it is dependent on the syntax of the include. Minimal working example included below.

When included using this syntax, the variable is treated as a relative path:

vars:
  TARGET_DIR: '/tmp'
includes:
  broken: 
    taskfile: ./Taskfile.broken.yml

When used like this:

tasks:
  broken:
    dir: "{{.TARGET_DIR}}"
    cmds:
      - |
          echo "PWD DIR: $(pwd)" 

Version

3.45.4

Operating system

OSX 26

Experiments Enabled

No response

Example Taskfile

# Taskfile.yml
version: '3'

vars:
  TARGET_DIR: '/tmp'

includes:
  working: ./Taskfile.working.yml
  broken: 
    taskfile: ./Taskfile.broken.yml
    # this also doesn't fix it
    #vars:
    #  TARGET_DIR: "{{.TARGET_DIR}}"
tasks:
  default:
    cmds:
      - task: working:working
      - task: broken:broken
# Taskfile.working.yml
version: '3'

tasks:
  working:
    dir: "{{.TARGET_DIR}}"
    cmds:
      - |
          echo "TARGET_DIR: {{.TARGET_DIR}}"
          echo "PWD DIR: $(pwd)"          
          cd {{.TARGET_DIR}}
          echo "PWD AFTER CD: $(pwd)"
# Taskfile.broken.yml
version: '3'

tasks:
  broken:
    dir: "{{.TARGET_DIR}}"
    cmds:
      - |
          echo "TARGET_DIR: {{.TARGET_DIR}}"
          echo "PWD DIR: $(pwd)"          
          cd {{.TARGET_DIR}}
          echo "PWD AFTER CD: $(pwd)"

roycamp avatar Sep 26 '25 00:09 roycamp

I have identified a second bug but it feels potentially related to the same code path. When using a variable in the dir of an included task, it does not apply to dynamic vars but does to the cmds, even when using the simple include syntax. Changing to dir: /tmp works as expected.

# Taskfile.yml
version: '3.45.4'

vars:
  TARGET_DIR: /tmp
  
includes:  
  broken: ./Taskfile.broken.yml

tasks:
  default:
    cmds:
      - task: broken:broken
# Taskfile.broken.yml
version: '3.45.4'

tasks:
  broken:
    dir: "{{.TARGET_DIR}}"
    vars:
      TEST_DIR:
        sh: echo $(pwd)        
    cmds:
      - echo {{.TEST_DIR}}
      - echo $(pwd)

roycamp avatar Sep 26 '25 15:09 roycamp

This line is causing the observed behaviour: https://github.com/go-task/task/blob/4e84c6bb766e10a6a9b4b08bfe75519a240ceff8/taskfile/ast/tasks.go#L174

Variable AdvancedImport is set for the expanded style of imports.

However, this is probably as designed (if not expected). @roycamp I think you get the expected behaviour if you do this:

---
version: '3'
vars:
  TARGET_DIR: '/tmp'
includes:
  working: ./Taskfile.working.yml
  broken: 
    taskfile: ./Taskfile.broken.yml
    dir: "{{.TARGET_DIR}}"
tasks:
  default:
    cmds:
      - task: working:working
      - task: broken:broken
---
# Taskfile.working.yml
version: '3'
tasks:
  working:
    dir: "{{.TARGET_DIR}}"
    cmds:
      - |
          echo "PWD DIR: $(pwd)"          
---
# Taskfile.broken.yml
version: '3'
tasks:
  broken:
    cmds:
      - |
          echo "PWD DIR: $(pwd)" 

Produces output:

$ task
task: [working:working] echo "PWD DIR: $(pwd)"          

PWD DIR: /tmp
task: [broken:broken] echo "PWD DIR: $(pwd)"          
PWD DIR: /tmp

However, one might suggest that the above linked code:

if include.AdvancedImport {
    task.Dir = filepathext.SmartJoin(include.Dir, task.Dir)

which is called with parameter task.Dir = "{{.TARGET_DIR}}" is not operating as one might expect. The variable task.Dir should probably be expanded before this function is called; the variable is there (line 179) but I don't think the templating system is available (CompileTask) at that point.

I think this is a bug. The solution might be to calculate the task.Dir later, when template expansion is available.

trulede avatar Sep 27 '25 09:09 trulede

@roycamp I've developed a fix. You might try it ... it probably breaks things somewhere else ...

As an aside, I observed a few other strange things. I would suggest refactoring the "get variables" code out of compiler.go into its own file/module and design something that can handle the complexity a little better. A simple strategy pattern or similar.

trulede avatar Sep 27 '25 11:09 trulede

Thanks, @trulede! I can confirm this fixes both bugs for me.

Defining dir at the include level would not meet my use case of reusing a task across multiple directories. Thank you for the quick turn around!

roycamp avatar Sep 28 '25 00:09 roycamp

Any ETA for that fix? I wanted to refactor my taskfiles and I have the same problem as described by the author.

maciej-lech avatar Oct 21 '25 11:10 maciej-lech

@maciej-lech this is a well known problem that has been around for a long time, everyone encounters it ... and just find a way around it (or give up I guess). You can see here, many of them : https://github.com/go-task/task/issues?q=is%3Aissue%20state%3Aopen%20include%20dir

You can try the PR I wrote. It works, however some unit tests fail, I did not investigate why. Its possible that those tests depend on the changed behaviour, and its also possible the PR breaks something. If you identify the later, I can help resolve it.

trulede avatar Oct 24 '25 21:10 trulede