123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- package task
- import (
- "os"
- "path/filepath"
- "strings"
- "github.com/joho/godotenv"
- "github.com/go-task/task/v3/errors"
- "github.com/go-task/task/v3/internal/execext"
- "github.com/go-task/task/v3/internal/filepathext"
- "github.com/go-task/task/v3/internal/fingerprint"
- "github.com/go-task/task/v3/internal/omap"
- "github.com/go-task/task/v3/internal/templater"
- "github.com/go-task/task/v3/taskfile/ast"
- )
- // CompiledTask returns a copy of a task, but replacing variables in almost all
- // properties using the Go template package.
- func (e *Executor) CompiledTask(call *ast.Call) (*ast.Task, error) {
- return e.compiledTask(call, true)
- }
- // FastCompiledTask is like CompiledTask, but it skippes dynamic variables.
- func (e *Executor) FastCompiledTask(call *ast.Call) (*ast.Task, error) {
- return e.compiledTask(call, false)
- }
- func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task, error) {
- origTask, err := e.GetTask(call)
- if err != nil {
- return nil, err
- }
- var vars *ast.Vars
- if evaluateShVars {
- vars, err = e.Compiler.GetVariables(origTask, call)
- } else {
- vars, err = e.Compiler.FastGetVariables(origTask, call)
- }
- if err != nil {
- return nil, err
- }
- cache := &templater.Cache{Vars: vars}
- new := ast.Task{
- Task: origTask.Task,
- Label: templater.Replace(origTask.Label, cache),
- Desc: templater.Replace(origTask.Desc, cache),
- Prompt: templater.Replace(origTask.Prompt, cache),
- Summary: templater.Replace(origTask.Summary, cache),
- Aliases: origTask.Aliases,
- Sources: templater.ReplaceGlobs(origTask.Sources, cache),
- Generates: templater.ReplaceGlobs(origTask.Generates, cache),
- Dir: templater.Replace(origTask.Dir, cache),
- Set: origTask.Set,
- Shopt: origTask.Shopt,
- Vars: nil,
- Env: nil,
- Dotenv: templater.Replace(origTask.Dotenv, cache),
- Silent: origTask.Silent,
- Interactive: origTask.Interactive,
- Internal: origTask.Internal,
- Method: templater.Replace(origTask.Method, cache),
- Prefix: templater.Replace(origTask.Prefix, cache),
- IgnoreError: origTask.IgnoreError,
- Run: templater.Replace(origTask.Run, cache),
- IncludeVars: origTask.IncludeVars,
- IncludedTaskfileVars: origTask.IncludedTaskfileVars,
- Platforms: origTask.Platforms,
- Location: origTask.Location,
- Requires: origTask.Requires,
- Watch: origTask.Watch,
- Namespace: origTask.Namespace,
- }
- new.Dir, err = execext.Expand(new.Dir)
- if err != nil {
- return nil, err
- }
- if e.Dir != "" {
- new.Dir = filepathext.SmartJoin(e.Dir, new.Dir)
- }
- if new.Prefix == "" {
- new.Prefix = new.Task
- }
- dotenvEnvs := &ast.Vars{}
- 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 {
- if ok := dotenvEnvs.Exists(key); !ok {
- dotenvEnvs.Set(key, ast.Var{Value: value})
- }
- }
- }
- }
- new.Env = &ast.Vars{}
- new.Env.Merge(templater.ReplaceVars(e.Taskfile.Env, cache), nil)
- new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache), nil)
- new.Env.Merge(templater.ReplaceVars(origTask.Env, cache), nil)
- if evaluateShVars {
- err = new.Env.Range(func(k string, v ast.Var) error {
- // If the variable is not dynamic, we can set it and return
- if v.Value != nil || v.Sh == nil {
- new.Env.Set(k, ast.Var{Value: v.Value})
- return nil
- }
- static, err := e.Compiler.HandleDynamicVar(v, new.Dir)
- if err != nil {
- return err
- }
- new.Env.Set(k, ast.Var{Value: static})
- return nil
- })
- if err != nil {
- return nil, err
- }
- }
- if len(origTask.Cmds) > 0 {
- new.Cmds = make([]*ast.Cmd, 0, len(origTask.Cmds))
- for _, cmd := range origTask.Cmds {
- if cmd == nil {
- continue
- }
- if cmd.For != nil {
- list, keys, err := itemsFromFor(cmd.For, new.Dir, new.Sources, vars, origTask.Location)
- if err != nil {
- return nil, err
- }
- // 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
- for i, loopValue := range list {
- extra := map[string]any{
- as: loopValue,
- }
- if len(keys) > 0 {
- extra["KEY"] = keys[i]
- }
- newCmd := cmd.DeepCopy()
- newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
- newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
- newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
- new.Cmds = append(new.Cmds, newCmd)
- }
- continue
- }
- // Defer commands are replaced in a lazy manner because
- // we need to include EXIT_CODE.
- if cmd.Defer {
- new.Cmds = append(new.Cmds, cmd.DeepCopy())
- continue
- }
- newCmd := cmd.DeepCopy()
- newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
- newCmd.Task = templater.Replace(cmd.Task, cache)
- newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
- new.Cmds = append(new.Cmds, newCmd)
- }
- }
- if len(origTask.Deps) > 0 {
- new.Deps = make([]*ast.Dep, 0, len(origTask.Deps))
- for _, dep := range origTask.Deps {
- if dep == nil {
- continue
- }
- if dep.For != nil {
- list, keys, err := itemsFromFor(dep.For, new.Dir, new.Sources, vars, origTask.Location)
- if err != nil {
- return nil, err
- }
- // Name the iterator variable
- var as string
- if dep.For.As != "" {
- as = dep.For.As
- } else {
- as = "ITEM"
- }
- // Create a new command for each item in the list
- for i, loopValue := range list {
- extra := map[string]any{
- as: loopValue,
- }
- if len(keys) > 0 {
- extra["KEY"] = keys[i]
- }
- newDep := dep.DeepCopy()
- newDep.Task = templater.ReplaceWithExtra(dep.Task, cache, extra)
- newDep.Vars = templater.ReplaceVarsWithExtra(dep.Vars, cache, extra)
- new.Deps = append(new.Deps, newDep)
- }
- continue
- }
- newDep := dep.DeepCopy()
- newDep.Task = templater.Replace(dep.Task, cache)
- newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
- new.Deps = append(new.Deps, newDep)
- }
- }
- if len(origTask.Preconditions) > 0 {
- new.Preconditions = make([]*ast.Precondition, 0, len(origTask.Preconditions))
- for _, precondition := range origTask.Preconditions {
- if precondition == nil {
- continue
- }
- newPrecondition := precondition.DeepCopy()
- newPrecondition.Sh = templater.Replace(precondition.Sh, cache)
- newPrecondition.Msg = templater.Replace(precondition.Msg, cache)
- new.Preconditions = append(new.Preconditions, newPrecondition)
- }
- }
- if len(origTask.Status) > 0 {
- timestampChecker := fingerprint.NewTimestampChecker(e.TempDir.Fingerprint, e.Dry)
- checksumChecker := fingerprint.NewChecksumChecker(e.TempDir.Fingerprint, e.Dry)
- for _, checker := range []fingerprint.SourcesCheckable{timestampChecker, checksumChecker} {
- value, err := checker.Value(&new)
- if err != nil {
- return nil, err
- }
- vars.Set(strings.ToUpper(checker.Kind()), ast.Var{Live: value})
- }
- // Adding new variables, requires us to refresh the templaters
- // cache of the the values manually
- cache.ResetCache()
- new.Status = templater.Replace(origTask.Status, cache)
- }
- // We only care about templater errors if we are evaluating shell variables
- if evaluateShVars && cache.Err() != nil {
- return &new, cache.Err()
- }
- return &new, nil
- }
- func asAnySlice[T any](slice []T) []any {
- ret := make([]any, len(slice))
- for i, v := range slice {
- ret[i] = v
- }
- return ret
- }
- func itemsFromFor(
- f *ast.For,
- dir string,
- sources []*ast.Glob,
- vars *ast.Vars,
- location *ast.Location,
- ) ([]any, []string, error) {
- var keys []string // The list of keys to loop over (only if looping over a map)
- var values []any // The list of values to loop over
- // Get the list from a matrix
- if f.Matrix.Len() != 0 {
- return asAnySlice(product(f.Matrix)), nil, nil
- }
- // Get the list from the explicit for list
- if len(f.List) > 0 {
- return f.List, nil, nil
- }
- // Get the list from the task sources
- if f.From == "sources" {
- glist, err := fingerprint.Globs(dir, sources)
- if err != nil {
- return nil, nil, err
- }
- // Make the paths relative to the task dir
- for i, v := range glist {
- if glist[i], err = filepath.Rel(dir, v); err != nil {
- return nil, nil, err
- }
- }
- values = asAnySlice(glist)
- }
- // Get the list from a variable and split it up
- if f.Var != "" {
- if vars != nil {
- v := vars.Get(f.Var)
- // 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 == nil {
- switch value := v.Value.(type) {
- case string:
- if f.Split != "" {
- values = asAnySlice(strings.Split(value, f.Split))
- } else {
- values = asAnySlice(strings.Fields(value))
- }
- case []string:
- values = asAnySlice(value)
- case []int:
- values = asAnySlice(value)
- case []any:
- values = value
- case map[string]any:
- for k, v := range value {
- keys = append(keys, k)
- values = append(values, v)
- }
- default:
- return nil, nil, errors.TaskfileInvalidError{
- URI: location.Taskfile,
- Err: errors.New("loop var must be a delimiter-separated string, list or a map"),
- }
- }
- }
- }
- }
- return values, keys, nil
- }
- // product generates the cartesian product of the input map of slices.
- func product(inputMap omap.OrderedMap[string, []any]) []map[string]any {
- if inputMap.Len() == 0 {
- return nil
- }
- // Start with an empty product result
- result := []map[string]any{{}}
- // Iterate over each slice in the slices
- _ = inputMap.Range(func(key string, slice []any) error {
- var newResult []map[string]any
- // For each combination in the current result
- for _, combination := range result {
- // Append each element from the current slice to the combinations
- for _, item := range slice {
- newComb := make(map[string]any, len(combination))
- // Copy the existing combination
- for k, v := range combination {
- newComb[k] = v
- }
- // Add the current item with the corresponding key
- newComb[key] = item
- newResult = append(newResult, newComb)
- }
- }
- // Update result with the new combinations
- result = newResult
- return nil
- })
- return result
- }
|