2017-03-02 11:46:20 +02:00
|
|
|
package task
|
|
|
|
|
|
|
|
import (
|
2022-12-06 02:25:16 +02:00
|
|
|
"os"
|
2023-06-27 18:51:54 +02:00
|
|
|
"path/filepath"
|
2019-06-11 20:49:37 +02:00
|
|
|
"strings"
|
2017-03-02 12:28:34 +02:00
|
|
|
|
2022-12-06 02:25:16 +02:00
|
|
|
"github.com/joho/godotenv"
|
|
|
|
|
2023-11-30 13:32:53 +02:00
|
|
|
"github.com/go-task/task/v3/errors"
|
2020-08-16 20:48:19 +02:00
|
|
|
"github.com/go-task/task/v3/internal/execext"
|
2022-08-06 23:19:07 +02:00
|
|
|
"github.com/go-task/task/v3/internal/filepathext"
|
2023-03-10 20:27:30 +02:00
|
|
|
"github.com/go-task/task/v3/internal/fingerprint"
|
2020-08-16 20:48:19 +02:00
|
|
|
"github.com/go-task/task/v3/internal/templater"
|
2023-12-29 22:32:03 +02:00
|
|
|
"github.com/go-task/task/v3/taskfile/ast"
|
2017-03-02 11:46:20 +02:00
|
|
|
)
|
|
|
|
|
2017-09-03 12:48:06 +02:00
|
|
|
// CompiledTask returns a copy of a task, but replacing variables in almost all
|
|
|
|
// properties using the Go template package.
|
2024-01-26 16:34:18 +02:00
|
|
|
func (e *Executor) CompiledTask(call *ast.Call) (*ast.Task, error) {
|
2021-01-12 17:03:04 +02:00
|
|
|
return e.compiledTask(call, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FastCompiledTask is like CompiledTask, but it skippes dynamic variables.
|
2024-01-26 16:34:18 +02:00
|
|
|
func (e *Executor) FastCompiledTask(call *ast.Call) (*ast.Task, error) {
|
2021-01-12 17:03:04 +02:00
|
|
|
return e.compiledTask(call, false)
|
|
|
|
}
|
|
|
|
|
2024-01-26 16:34:18 +02:00
|
|
|
func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task, error) {
|
2022-10-02 00:39:44 +02:00
|
|
|
origTask, err := e.GetTask(call)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-08-16 13:04:58 +02:00
|
|
|
}
|
|
|
|
|
2023-12-29 22:32:03 +02:00
|
|
|
var vars *ast.Vars
|
2021-01-12 17:03:04 +02:00
|
|
|
if evaluateShVars {
|
|
|
|
vars, err = e.Compiler.GetVariables(origTask, call)
|
|
|
|
} else {
|
|
|
|
vars, err = e.Compiler.FastGetVariables(origTask, call)
|
|
|
|
}
|
2017-11-02 14:25:50 +02:00
|
|
|
if err != nil {
|
2017-08-16 13:04:58 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-11 20:49:37 +02:00
|
|
|
|
2024-03-10 19:11:07 +02:00
|
|
|
cache := &templater.Cache{Vars: vars}
|
2017-07-20 09:05:37 +02:00
|
|
|
|
2023-12-29 22:32:03 +02:00
|
|
|
new := ast.Task{
|
2022-03-19 23:41:03 +02:00
|
|
|
Task: origTask.Task,
|
2024-03-10 19:11:07 +02:00
|
|
|
Label: templater.Replace(origTask.Label, cache),
|
|
|
|
Desc: templater.Replace(origTask.Desc, cache),
|
|
|
|
Prompt: templater.Replace(origTask.Prompt, cache),
|
|
|
|
Summary: templater.Replace(origTask.Summary, cache),
|
2022-10-02 00:39:44 +02:00
|
|
|
Aliases: origTask.Aliases,
|
2024-03-10 19:11:07 +02:00
|
|
|
Sources: templater.ReplaceGlobs(origTask.Sources, cache),
|
|
|
|
Generates: templater.ReplaceGlobs(origTask.Generates, cache),
|
|
|
|
Dir: templater.Replace(origTask.Dir, cache),
|
2023-01-14 21:41:56 +02:00
|
|
|
Set: origTask.Set,
|
|
|
|
Shopt: origTask.Shopt,
|
2022-03-19 23:41:03 +02:00
|
|
|
Vars: nil,
|
|
|
|
Env: nil,
|
2024-03-10 19:11:07 +02:00
|
|
|
Dotenv: templater.Replace(origTask.Dotenv, cache),
|
2022-03-19 23:41:03 +02:00
|
|
|
Silent: origTask.Silent,
|
|
|
|
Interactive: origTask.Interactive,
|
2022-07-22 04:15:35 +02:00
|
|
|
Internal: origTask.Internal,
|
2024-03-10 19:11:07 +02:00
|
|
|
Method: templater.Replace(origTask.Method, cache),
|
|
|
|
Prefix: templater.Replace(origTask.Prefix, cache),
|
2022-03-19 23:41:03 +02:00
|
|
|
IgnoreError: origTask.IgnoreError,
|
2024-03-10 19:11:07 +02:00
|
|
|
Run: templater.Replace(origTask.Run, cache),
|
2022-03-19 23:41:03 +02:00
|
|
|
IncludeVars: origTask.IncludeVars,
|
|
|
|
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
2023-01-07 02:38:35 +02:00
|
|
|
Platforms: origTask.Platforms,
|
2023-03-17 14:34:06 +02:00
|
|
|
Location: origTask.Location,
|
2023-06-30 03:13:41 +02:00
|
|
|
Requires: origTask.Requires,
|
2023-10-07 23:06:43 +02:00
|
|
|
Watch: origTask.Watch,
|
2017-07-16 21:09:55 +02:00
|
|
|
}
|
2018-12-24 19:19:53 +02:00
|
|
|
new.Dir, err = execext.Expand(new.Dir)
|
2017-11-02 14:25:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-08-06 23:19:07 +02:00
|
|
|
if e.Dir != "" {
|
|
|
|
new.Dir = filepathext.SmartJoin(e.Dir, new.Dir)
|
2017-08-16 13:04:58 +02:00
|
|
|
}
|
2018-04-22 20:41:53 +02:00
|
|
|
if new.Prefix == "" {
|
|
|
|
new.Prefix = new.Task
|
|
|
|
}
|
2019-01-02 16:05:40 +02:00
|
|
|
|
2023-12-29 22:32:03 +02:00
|
|
|
dotenvEnvs := &ast.Vars{}
|
2022-12-06 02:25:16 +02:00
|
|
|
if len(new.Dotenv) > 0 {
|
|
|
|
for _, dotEnvPath := range new.Dotenv {
|
|
|
|
dotEnvPath = filepathext.SmartJoin(new.Dir, dotEnvPath)
|
|
|
|
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
envs, err := godotenv.Read(dotEnvPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for key, value := range envs {
|
2023-04-06 13:07:57 +02:00
|
|
|
if ok := dotenvEnvs.Exists(key); !ok {
|
2023-12-29 22:32:03 +02:00
|
|
|
dotenvEnvs.Set(key, ast.Var{Value: value})
|
2022-12-06 02:25:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-29 22:32:03 +02:00
|
|
|
new.Env = &ast.Vars{}
|
2024-03-10 19:11:07 +02:00
|
|
|
new.Env.Merge(templater.ReplaceVars(e.Taskfile.Env, cache))
|
|
|
|
new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache))
|
|
|
|
new.Env.Merge(templater.ReplaceVars(origTask.Env, cache))
|
2021-01-12 17:03:04 +02:00
|
|
|
if evaluateShVars {
|
2023-12-29 22:32:03 +02:00
|
|
|
err = new.Env.Range(func(k string, v ast.Var) error {
|
2023-11-29 18:21:21 +02:00
|
|
|
// If the variable is not dynamic, we can set it and return
|
|
|
|
if v.Value != nil || v.Sh == "" {
|
2023-12-29 22:32:03 +02:00
|
|
|
new.Env.Set(k, ast.Var{Value: v.Value})
|
2023-11-29 18:21:21 +02:00
|
|
|
return nil
|
|
|
|
}
|
2021-01-12 17:03:04 +02:00
|
|
|
static, err := e.Compiler.HandleDynamicVar(v, new.Dir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-12-29 22:32:03 +02:00
|
|
|
new.Env.Set(k, ast.Var{Value: static})
|
2021-01-12 17:03:04 +02:00
|
|
|
return nil
|
|
|
|
})
|
2017-08-16 13:04:58 +02:00
|
|
|
if err != nil {
|
2021-01-12 17:03:04 +02:00
|
|
|
return nil, err
|
2017-08-16 13:04:58 +02:00
|
|
|
}
|
2017-07-31 00:45:01 +02:00
|
|
|
}
|
2017-07-16 21:09:55 +02:00
|
|
|
|
2017-08-16 13:04:58 +02:00
|
|
|
if len(origTask.Cmds) > 0 {
|
2023-12-29 22:32:03 +02:00
|
|
|
new.Cmds = make([]*ast.Cmd, 0, len(origTask.Cmds))
|
2021-09-14 17:01:33 +02:00
|
|
|
for _, cmd := range origTask.Cmds {
|
|
|
|
if cmd == nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-06-15 17:04:03 +02:00
|
|
|
if cmd.For != nil {
|
2023-12-21 17:43:56 +02:00
|
|
|
var keys []string
|
2023-11-30 13:32:53 +02:00
|
|
|
var list []any
|
2023-06-27 16:14:29 +02:00
|
|
|
// Get the list from the explicit for list
|
2023-06-15 17:04:03 +02:00
|
|
|
if cmd.For.List != nil && len(cmd.For.List) > 0 {
|
|
|
|
list = cmd.For.List
|
|
|
|
}
|
|
|
|
// Get the list from the task sources
|
2023-06-27 16:14:29 +02:00
|
|
|
if cmd.For.From == "sources" {
|
2023-11-30 13:32:53 +02:00
|
|
|
glist, err := fingerprint.Globs(new.Dir, new.Sources)
|
2023-06-15 17:04:03 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-27 18:51:54 +02:00
|
|
|
// Make the paths relative to the task dir
|
2023-11-30 13:32:53 +02:00
|
|
|
for i, v := range glist {
|
|
|
|
if glist[i], err = filepath.Rel(new.Dir, v); err != nil {
|
2023-06-27 18:51:54 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-11-30 13:32:53 +02:00
|
|
|
list = asAnySlice(glist)
|
2023-06-15 17:04:03 +02:00
|
|
|
}
|
|
|
|
// Get the list from a variable and split it up
|
|
|
|
if cmd.For.Var != "" {
|
|
|
|
if vars != nil {
|
|
|
|
v := vars.Get(cmd.For.Var)
|
2023-12-02 03:00:32 +02:00
|
|
|
// If the variable is dynamic, then it hasn't been resolved yet
|
|
|
|
// and we can't use it as a list. This happens when fast compiling a task
|
|
|
|
// for use in --list or --list-all etc.
|
|
|
|
if v.Value != nil && v.Sh == "" {
|
|
|
|
switch value := v.Value.(type) {
|
|
|
|
case string:
|
|
|
|
if cmd.For.Split != "" {
|
|
|
|
list = asAnySlice(strings.Split(value, cmd.For.Split))
|
|
|
|
} else {
|
|
|
|
list = asAnySlice(strings.Fields(value))
|
|
|
|
}
|
|
|
|
case []any:
|
|
|
|
list = value
|
2023-12-02 04:26:08 +02:00
|
|
|
case map[string]any:
|
2023-12-21 17:43:56 +02:00
|
|
|
for k, v := range value {
|
|
|
|
keys = append(keys, k)
|
|
|
|
list = append(list, v)
|
2023-12-02 04:26:08 +02:00
|
|
|
}
|
2023-12-02 03:00:32 +02:00
|
|
|
default:
|
|
|
|
return nil, errors.TaskfileInvalidError{
|
|
|
|
URI: origTask.Location.Taskfile,
|
|
|
|
Err: errors.New("var must be a delimiter-separated string or a list"),
|
|
|
|
}
|
2023-11-29 18:21:21 +02:00
|
|
|
}
|
2023-06-15 17:04:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Name the iterator variable
|
|
|
|
var as string
|
|
|
|
if cmd.For.As != "" {
|
|
|
|
as = cmd.For.As
|
|
|
|
} else {
|
|
|
|
as = "ITEM"
|
|
|
|
}
|
|
|
|
// Create a new command for each item in the list
|
2023-12-21 17:43:56 +02:00
|
|
|
for i, loopValue := range list {
|
2023-06-15 17:04:03 +02:00
|
|
|
extra := map[string]any{
|
|
|
|
as: loopValue,
|
|
|
|
}
|
2023-12-21 17:43:56 +02:00
|
|
|
if len(keys) > 0 {
|
|
|
|
extra["KEY"] = keys[i]
|
|
|
|
}
|
2023-12-23 06:41:59 +02:00
|
|
|
newCmd := cmd.DeepCopy()
|
2024-03-10 19:11:07 +02:00
|
|
|
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
|
|
|
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
|
|
|
newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
2023-12-23 06:41:59 +02:00
|
|
|
new.Cmds = append(new.Cmds, newCmd)
|
2023-06-15 17:04:03 +02:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2023-12-23 06:41:59 +02:00
|
|
|
newCmd := cmd.DeepCopy()
|
2024-03-10 19:11:07 +02:00
|
|
|
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
|
|
|
newCmd.Task = templater.Replace(cmd.Task, cache)
|
|
|
|
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
2023-12-29 05:49:12 +02:00
|
|
|
// Loop over the command's variables and resolve any references to other variables
|
|
|
|
err := cmd.Vars.Range(func(k string, v ast.Var) error {
|
|
|
|
if v.Ref != "" {
|
|
|
|
refVal := vars.Get(v.Ref)
|
|
|
|
newCmd.Vars.Set(k, refVal)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-23 06:41:59 +02:00
|
|
|
new.Cmds = append(new.Cmds, newCmd)
|
2017-07-16 21:09:55 +02:00
|
|
|
}
|
|
|
|
}
|
2017-08-16 13:04:58 +02:00
|
|
|
if len(origTask.Deps) > 0 {
|
2023-12-29 22:32:03 +02:00
|
|
|
new.Deps = make([]*ast.Dep, 0, len(origTask.Deps))
|
2021-09-14 17:01:33 +02:00
|
|
|
for _, dep := range origTask.Deps {
|
|
|
|
if dep == nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-12-23 06:41:59 +02:00
|
|
|
newDep := dep.DeepCopy()
|
2024-03-10 19:11:07 +02:00
|
|
|
newDep.Task = templater.Replace(dep.Task, cache)
|
|
|
|
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
|
2023-12-30 20:10:20 +02:00
|
|
|
// Loop over the dep's variables and resolve any references to other variables
|
|
|
|
err := dep.Vars.Range(func(k string, v ast.Var) error {
|
|
|
|
if v.Ref != "" {
|
|
|
|
refVal := vars.Get(v.Ref)
|
|
|
|
newDep.Vars.Set(k, refVal)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-23 06:41:59 +02:00
|
|
|
new.Deps = append(new.Deps, newDep)
|
2017-07-16 21:09:55 +02:00
|
|
|
}
|
|
|
|
}
|
2019-05-28 22:02:59 +02:00
|
|
|
|
|
|
|
if len(origTask.Preconditions) > 0 {
|
2023-12-29 22:32:03 +02:00
|
|
|
new.Preconditions = make([]*ast.Precondition, 0, len(origTask.Preconditions))
|
2023-12-23 06:41:59 +02:00
|
|
|
for _, precondition := range origTask.Preconditions {
|
|
|
|
if precondition == nil {
|
2021-09-14 17:01:33 +02:00
|
|
|
continue
|
|
|
|
}
|
2023-12-23 06:41:59 +02:00
|
|
|
newPrecondition := precondition.DeepCopy()
|
2024-03-10 19:11:07 +02:00
|
|
|
newPrecondition.Sh = templater.Replace(precondition.Sh, cache)
|
|
|
|
newPrecondition.Msg = templater.Replace(precondition.Msg, cache)
|
2023-12-23 06:41:59 +02:00
|
|
|
new.Preconditions = append(new.Preconditions, newPrecondition)
|
2019-05-17 22:13:47 +02:00
|
|
|
}
|
|
|
|
}
|
2017-07-16 21:09:55 +02:00
|
|
|
|
2019-08-25 19:30:00 +02:00
|
|
|
if len(origTask.Status) > 0 {
|
2023-03-10 20:27:30 +02:00
|
|
|
timestampChecker := fingerprint.NewTimestampChecker(e.TempDir, e.Dry)
|
|
|
|
checksumChecker := fingerprint.NewChecksumChecker(e.TempDir, e.Dry)
|
|
|
|
|
|
|
|
for _, checker := range []fingerprint.SourcesCheckable{timestampChecker, checksumChecker} {
|
|
|
|
value, err := checker.Value(&new)
|
2019-09-14 23:04:41 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-29 22:32:03 +02:00
|
|
|
vars.Set(strings.ToUpper(checker.Kind()), ast.Var{Live: value})
|
2019-08-25 19:30:00 +02:00
|
|
|
}
|
|
|
|
|
2019-08-25 22:16:59 +02:00
|
|
|
// Adding new variables, requires us to refresh the templaters
|
|
|
|
// cache of the the values manually
|
2024-03-10 19:11:07 +02:00
|
|
|
cache.ResetCache()
|
2019-08-25 19:30:00 +02:00
|
|
|
|
2024-03-10 19:11:07 +02:00
|
|
|
new.Status = templater.Replace(origTask.Status, cache)
|
2019-08-25 19:30:00 +02:00
|
|
|
}
|
|
|
|
|
2023-12-23 06:41:59 +02:00
|
|
|
// We only care about templater errors if we are evaluating shell variables
|
2024-03-10 19:11:07 +02:00
|
|
|
if evaluateShVars && cache.Err() != nil {
|
|
|
|
return &new, cache.Err()
|
2023-12-23 06:41:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return &new, nil
|
2017-09-03 12:48:06 +02:00
|
|
|
}
|
2023-11-30 13:32:53 +02:00
|
|
|
|
|
|
|
func asAnySlice[T any](slice []T) []any {
|
|
|
|
ret := make([]any, len(slice))
|
|
|
|
for i, v := range slice {
|
|
|
|
ret[i] = v
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|