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"
2020-08-19 10:59:58 +02:00
"github.com/go-task/task/v3/taskfile"
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.
2018-02-17 18:22:18 +02:00
func ( e * Executor ) CompiledTask ( call taskfile . Call ) ( * taskfile . Task , error ) {
2021-01-12 17:03:04 +02:00
return e . compiledTask ( call , true )
}
// FastCompiledTask is like CompiledTask, but it skippes dynamic variables.
func ( e * Executor ) FastCompiledTask ( call taskfile . Call ) ( * taskfile . Task , error ) {
return e . compiledTask ( call , false )
}
func ( e * Executor ) compiledTask ( call taskfile . Call , evaluateShVars bool ) ( * taskfile . 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
}
2021-01-12 17:03:04 +02:00
var vars * taskfile . Vars
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
2023-02-08 12:21:43 +02:00
r := templater . Templater { Vars : vars , RemoveNoValue : e . Taskfile . Version . Compare ( taskfile . V3 ) >= 0 }
2017-07-20 09:05:37 +02:00
2018-02-17 18:22:18 +02:00
new := taskfile . Task {
2022-03-19 23:41:03 +02:00
Task : origTask . Task ,
Label : r . Replace ( origTask . Label ) ,
Desc : r . Replace ( origTask . Desc ) ,
2023-06-04 03:33:00 +02:00
Prompt : r . Replace ( origTask . Prompt ) ,
2022-03-19 23:41:03 +02:00
Summary : r . Replace ( origTask . Summary ) ,
2022-10-02 00:39:44 +02:00
Aliases : origTask . Aliases ,
2023-11-30 03:38:12 +02:00
Sources : r . ReplaceGlobs ( origTask . Sources ) ,
Generates : r . ReplaceGlobs ( origTask . Generates ) ,
2022-03-19 23:41:03 +02:00
Dir : r . Replace ( origTask . Dir ) ,
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 ,
2022-12-06 02:25:16 +02:00
Dotenv : r . ReplaceSlice ( origTask . Dotenv ) ,
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 ,
2022-03-19 23:41:03 +02:00
Method : r . Replace ( origTask . Method ) ,
Prefix : r . Replace ( origTask . Prefix ) ,
IgnoreError : origTask . IgnoreError ,
Run : r . Replace ( origTask . Run ) ,
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
2022-12-06 02:25:16 +02:00
dotenvEnvs := & taskfile . 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 {
2023-04-06 13:07:57 +02:00
if ok := dotenvEnvs . Exists ( key ) ; ! ok {
2023-11-28 20:18:28 +02:00
dotenvEnvs . Set ( key , taskfile . Var { Value : value } )
2022-12-06 02:25:16 +02:00
}
}
}
}
2020-03-29 21:54:59 +02:00
new . Env = & taskfile . Vars { }
new . Env . Merge ( r . ReplaceVars ( e . Taskfile . Env ) )
2022-12-06 02:25:16 +02:00
new . Env . Merge ( r . ReplaceVars ( dotenvEnvs ) )
2020-03-29 21:54:59 +02:00
new . Env . Merge ( r . ReplaceVars ( origTask . Env ) )
2021-01-12 17:03:04 +02:00
if evaluateShVars {
err = new . Env . Range ( func ( k string , v taskfile . 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 == "" {
new . Env . Set ( k , taskfile . Var { Value : v . Value } )
return nil
}
2021-01-12 17:03:04 +02:00
static , err := e . Compiler . HandleDynamicVar ( v , new . Dir )
if err != nil {
return err
}
2023-11-28 20:18:28 +02:00
new . Env . Set ( k , taskfile . 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 {
2021-09-14 17:01:33 +02:00
new . Cmds = make ( [ ] * taskfile . Cmd , 0 , len ( origTask . Cmds ) )
for _ , cmd := range origTask . Cmds {
if cmd == nil {
continue
}
2023-06-15 17:04:03 +02:00
if cmd . For != nil {
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 :
return & taskfile . Task { } , errors . TaskfileInvalidError {
URI : origTask . Location . Taskfile ,
Err : errors . New ( "sh is not supported with the 'Any Variables' experiment enabled.\nSee https://taskfile.dev/experiments/any-variables for more information." ) ,
}
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
for _ , loopValue := range list {
extra := map [ string ] any {
as : loopValue ,
}
new . Cmds = append ( new . Cmds , & taskfile . Cmd {
Cmd : r . ReplaceWithExtra ( cmd . Cmd , extra ) ,
Task : r . ReplaceWithExtra ( cmd . Task , extra ) ,
Silent : cmd . Silent ,
Set : cmd . Set ,
Shopt : cmd . Shopt ,
Vars : r . ReplaceVarsWithExtra ( cmd . Vars , extra ) ,
IgnoreError : cmd . IgnoreError ,
Defer : cmd . Defer ,
Platforms : cmd . Platforms ,
} )
}
continue
}
2021-09-14 17:01:33 +02:00
new . Cmds = append ( new . Cmds , & taskfile . Cmd {
2018-07-10 10:44:58 +02:00
Cmd : r . Replace ( cmd . Cmd ) ,
2023-01-14 21:41:56 +02:00
Task : r . Replace ( cmd . Task ) ,
2023-06-15 17:04:03 +02:00
Silent : cmd . Silent ,
2023-01-14 21:41:56 +02:00
Set : cmd . Set ,
Shopt : cmd . Shopt ,
2018-07-10 10:44:58 +02:00
Vars : r . ReplaceVars ( cmd . Vars ) ,
IgnoreError : cmd . IgnoreError ,
2021-12-15 07:03:37 +02:00
Defer : cmd . Defer ,
2023-01-07 02:38:35 +02:00
Platforms : cmd . Platforms ,
2021-09-14 17:01:33 +02:00
} )
2017-07-16 21:09:55 +02:00
}
}
2017-08-16 13:04:58 +02:00
if len ( origTask . Deps ) > 0 {
2021-09-14 17:01:33 +02:00
new . Deps = make ( [ ] * taskfile . Dep , 0 , len ( origTask . Deps ) )
for _ , dep := range origTask . Deps {
if dep == nil {
continue
}
new . Deps = append ( new . Deps , & taskfile . Dep {
2023-04-27 08:23:45 +02:00
Task : r . Replace ( dep . Task ) ,
Vars : r . ReplaceVars ( dep . Vars ) ,
Silent : dep . Silent ,
2021-09-14 17:01:33 +02:00
} )
2017-07-16 21:09:55 +02:00
}
}
2019-05-28 22:02:59 +02:00
if len ( origTask . Preconditions ) > 0 {
2021-09-14 17:01:33 +02:00
new . Preconditions = make ( [ ] * taskfile . Precondition , 0 , len ( origTask . Preconditions ) )
for _ , precond := range origTask . Preconditions {
if precond == nil {
continue
}
new . Preconditions = append ( new . Preconditions , & taskfile . Precondition {
2019-05-28 22:02:59 +02:00
Sh : r . Replace ( precond . Sh ) ,
Msg : r . Replace ( precond . Msg ) ,
2021-09-14 17:01:33 +02:00
} )
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
}
2020-03-29 21:54:59 +02:00
vars . Set ( strings . ToUpper ( checker . Kind ( ) ) , taskfile . 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
2019-09-14 22:54:41 +02:00
r . ResetCache ( )
2019-08-25 19:30:00 +02:00
new . Status = r . ReplaceSlice ( origTask . Status )
}
2018-02-17 20:12:41 +02:00
return & new , r . Err ( )
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
}