diff --git a/internal/filepathext/filepathext.go b/internal/filepathext/filepathext.go index f2a1ba15a0..947d52b927 100644 --- a/internal/filepathext/filepathext.go +++ b/internal/filepathext/filepathext.go @@ -32,12 +32,22 @@ var knownAbsDirs = []string{ } func isSpecialDir(dir string) bool { - for _, d := range knownAbsDirs { - if strings.Contains(dir, d) { - return true + for { + _, action, ok := strings.Cut(dir, "{{") + if !ok { + return false + } + action, dir, ok = strings.Cut(action, "}}") + if !ok { + return false + } + // Only inspect template actions, not literal path components. + for _, d := range knownAbsDirs { + if strings.Contains(action, d) { + return true + } } } - return false } // TryAbsToRel tries to convert an absolute path to relative based on the diff --git a/internal/filepathext/filepathext_test.go b/internal/filepathext/filepathext_test.go new file mode 100644 index 0000000000..9de492fb14 --- /dev/null +++ b/internal/filepathext/filepathext_test.go @@ -0,0 +1,45 @@ +package filepathext + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSmartJoinRelativePathContainingSpecialVariableName(t *testing.T) { + t.Parallel() + + base := t.TempDir() + for _, variable := range []string{".ROOT_DIR", ".TASKFILE_DIR", ".USER_WORKING_DIR"} { + t.Run(variable, func(t *testing.T) { + t.Parallel() + for _, relative := range []string{ + filepath.Join("project"+variable, "file.txt"), + filepath.Join(variable, "file.txt"), + filepath.Join("{{.PROJECT}}"+variable, "file.txt"), + } { + require.False(t, IsAbs(relative), relative) + require.Equal(t, filepath.Join(base, relative), SmartJoin(base, relative)) + } + }) + } +} + +func TestSmartJoinAbsoluteAndTemplatePaths(t *testing.T) { + t.Parallel() + + base := t.TempDir() + absolute := filepath.Join(t.TempDir(), "file.txt") + for _, path := range []string{ + absolute, + "{{.ROOT_DIR}}/file.txt", + "{{ .TASKFILE_DIR }}/file.txt", + "{{- .USER_WORKING_DIR -}}/file.txt", + "{{.ROOT_DIR | toSlash}}/file.txt", + "{{.PROJECT}}/{{.ROOT_DIR}}/file.txt", + } { + require.True(t, IsAbs(path), path) + require.Equal(t, path, SmartJoin(base, path)) + } +} diff --git a/taskfile/node_file_test.go b/taskfile/node_file_test.go new file mode 100644 index 0000000000..8f8552d550 --- /dev/null +++ b/taskfile/node_file_test.go @@ -0,0 +1,33 @@ +package taskfile + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFileNodeResolveLiteralSpecialDir(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + entrypoint := filepath.Join(dir, "Taskfile.yml") + require.NoError(t, os.WriteFile(entrypoint, []byte("version: '3'\n"), 0o600)) + node, err := NewFileNode(entrypoint, "") + require.NoError(t, err) + + for _, name := range []string{"project.ROOT_DIR", "project.TASKFILE_DIR", "project.USER_WORKING_DIR"} { + t.Run(name, func(t *testing.T) { + t.Parallel() + + resolvedDir, err := node.ResolveDir(name) + require.NoError(t, err) + require.Equal(t, filepath.Join(dir, name), resolvedDir) + + resolvedEntrypoint, err := node.ResolveEntrypoint(filepath.Join(name, "Taskfile.yml")) + require.NoError(t, err) + require.Equal(t, filepath.Join(dir, name, "Taskfile.yml"), resolvedEntrypoint) + }) + } +}