mirror of
https://github.com/go-task/task.git
synced 2025-12-16 19:57:43 +01:00
185 lines
4.3 KiB
Go
185 lines
4.3 KiB
Go
package task
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"github.com/go-task/task/v3/errors"
|
|
"github.com/go-task/task/v3/internal/prompt"
|
|
"github.com/go-task/task/v3/internal/term"
|
|
"github.com/go-task/task/v3/taskfile/ast"
|
|
)
|
|
|
|
// collectAllRequiredVars traverses the dependency tree of all calls and collects
|
|
// all required variables that are missing. Returns a deduplicated list.
|
|
func (e *Executor) collectAllRequiredVars(calls []*Call) ([]*ast.VarsWithValidation, error) {
|
|
visited := make(map[string]bool)
|
|
varsMap := make(map[string]*ast.VarsWithValidation)
|
|
|
|
var collect func(call *Call) error
|
|
collect = func(call *Call) error {
|
|
// Always compile to resolve variables (also fetches the task)
|
|
compiledTask, err := e.FastCompiledTask(call)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Always collect required vars from this task
|
|
if compiledTask.Requires != nil {
|
|
for _, v := range compiledTask.Requires.Vars {
|
|
// Check if var is already set
|
|
if _, ok := compiledTask.Vars.Get(v.Name); !ok {
|
|
// Add to map if not already there
|
|
if _, exists := varsMap[v.Name]; !exists {
|
|
varsMap[v.Name] = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only skip recursion if already visited (to avoid infinite loops)
|
|
// We already collected the vars above, so we're good
|
|
if visited[call.Task] {
|
|
return nil
|
|
}
|
|
visited[call.Task] = true
|
|
|
|
// Recurse into deps
|
|
for _, dep := range compiledTask.Deps {
|
|
depCall := &Call{
|
|
Task: dep.Task,
|
|
Vars: dep.Vars,
|
|
Silent: dep.Silent,
|
|
}
|
|
if err := collect(depCall); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Collect from all initial calls
|
|
for _, call := range calls {
|
|
if err := collect(call); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Convert map to slice
|
|
result := make([]*ast.VarsWithValidation, 0, len(varsMap))
|
|
for _, v := range varsMap {
|
|
result = append(result, v)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// promptForAllVars prompts for all the given variables at once and returns
|
|
// a Vars object with all the values.
|
|
func (e *Executor) promptForAllVars(vars []*ast.VarsWithValidation) (*ast.Vars, error) {
|
|
if len(vars) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Don't prompt if interactive mode is disabled
|
|
if !e.Interactive {
|
|
return nil, nil
|
|
}
|
|
|
|
// Don't prompt if NoTTY is set or we're not in a terminal
|
|
if e.NoTTY || (!e.AssumeTerm && !term.IsTerminal()) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Lock to prevent any output during prompting
|
|
e.promptMutex.Lock()
|
|
defer e.promptMutex.Unlock()
|
|
|
|
// Use raw stderr for prompts to avoid deadlock with SyncWriter
|
|
prompter := &prompt.Prompter{
|
|
Stdin: e.Stdin,
|
|
Stdout: e.rawStdout,
|
|
Stderr: e.rawStderr,
|
|
}
|
|
|
|
result := ast.NewVars()
|
|
|
|
for _, v := range vars {
|
|
var value string
|
|
var err error
|
|
|
|
if len(v.Enum) > 0 {
|
|
value, err = prompter.Select(v.Name, v.Enum)
|
|
} else {
|
|
value, err = prompter.Text(v.Name)
|
|
}
|
|
|
|
if err != nil {
|
|
if errors.Is(err, prompt.ErrCancelled) {
|
|
return nil, &errors.TaskCancelledByUserError{TaskName: "interactive prompt"}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
result.Set(v.Name, ast.Var{Value: value})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
|
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var missingVars []errors.MissingVar
|
|
for _, requiredVar := range t.Requires.Vars {
|
|
_, ok := t.Vars.Get(requiredVar.Name)
|
|
if !ok {
|
|
missingVars = append(missingVars, errors.MissingVar{
|
|
Name: requiredVar.Name,
|
|
AllowedValues: requiredVar.Enum,
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(missingVars) > 0 {
|
|
return &errors.TaskMissingRequiredVarsError{
|
|
TaskName: t.Name(),
|
|
MissingVars: missingVars,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Executor) areTaskRequiredVarsAllowedValuesSet(t *ast.Task) error {
|
|
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var notAllowedValuesVars []errors.NotAllowedVar
|
|
for _, requiredVar := range t.Requires.Vars {
|
|
varValue, _ := t.Vars.Get(requiredVar.Name)
|
|
|
|
value, isString := varValue.Value.(string)
|
|
if isString && requiredVar.Enum != nil && !slices.Contains(requiredVar.Enum, value) {
|
|
notAllowedValuesVars = append(notAllowedValuesVars, errors.NotAllowedVar{
|
|
Value: value,
|
|
Enum: requiredVar.Enum,
|
|
Name: requiredVar.Name,
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
if len(notAllowedValuesVars) > 0 {
|
|
return &errors.TaskNotAllowedVarsError{
|
|
TaskName: t.Name(),
|
|
NotAllowedVars: notAllowedValuesVars,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|