2022-07-08 20:16:04 +02:00
package task
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
2023-02-08 12:21:43 +02:00
"github.com/Masterminds/semver/v3"
"github.com/sajari/fuzzy"
2022-07-08 20:16:04 +02:00
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
"github.com/go-task/task/v3/internal/execext"
2022-08-06 23:19:07 +02:00
"github.com/go-task/task/v3/internal/filepathext"
2022-07-08 20:16:04 +02:00
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/read"
)
func ( e * Executor ) Setup ( ) error {
2023-09-12 23:42:54 +02:00
e . setupLogger ( )
2022-09-03 23:14:54 +02:00
if err := e . setCurrentDir ( ) ; err != nil {
return err
}
2023-09-14 23:57:46 +02:00
if err := e . setupTempDir ( ) ; err != nil {
2022-07-08 20:16:04 +02:00
return err
}
2023-09-14 23:57:46 +02:00
if err := e . readTaskfile ( ) ; err != nil {
2022-07-08 20:16:04 +02:00
return err
}
2023-09-12 23:42:54 +02:00
e . setupFuzzyModel ( )
2022-07-08 20:16:04 +02:00
e . setupStdFiles ( )
if err := e . setupOutput ( ) ; err != nil {
return err
}
2023-02-08 12:21:43 +02:00
if err := e . setupCompiler ( ) ; err != nil {
2022-07-08 20:16:04 +02:00
return err
}
2023-02-08 12:21:43 +02:00
if err := e . readDotEnvFiles ( ) ; err != nil {
2022-07-08 20:16:04 +02:00
return err
}
2023-02-08 12:21:43 +02:00
if err := e . doVersionChecks ( ) ; err != nil {
2022-07-08 20:16:04 +02:00
return err
}
2023-02-08 12:21:43 +02:00
e . setupDefaults ( )
2022-07-08 20:16:04 +02:00
e . setupConcurrencyState ( )
return nil
}
2022-09-03 23:14:54 +02:00
func ( e * Executor ) setCurrentDir ( ) error {
2023-09-15 00:15:54 +02:00
// If the entrypoint is already set, we don't need to do anything
if e . Entrypoint != "" {
return nil
}
2023-09-14 23:57:46 +02:00
// Default the directory to the current working directory
2022-09-03 23:14:54 +02:00
if e . Dir == "" {
wd , err := os . Getwd ( )
if err != nil {
return err
}
e . Dir = wd
2023-09-14 23:57:46 +02:00
}
2023-09-15 00:15:54 +02:00
// Search for a taskfile
root , err := read . ExistsWalk ( e . Dir )
2023-09-14 23:57:46 +02:00
if err != nil {
return err
}
2023-09-15 00:15:54 +02:00
e . Dir = filepath . Dir ( root )
e . Entrypoint = filepath . Base ( root )
2023-09-14 23:57:46 +02:00
2022-09-03 23:14:54 +02:00
return nil
}
2022-07-08 20:16:04 +02:00
func ( e * Executor ) readTaskfile ( ) error {
2023-09-12 23:42:54 +02:00
uri := filepath . Join ( e . Dir , e . Entrypoint )
node , err := read . NewNode ( uri , e . Insecure )
if err != nil {
return err
}
e . Taskfile , err = read . Taskfile (
node ,
e . Insecure ,
e . Download ,
e . Offline ,
2023-11-17 22:51:10 +02:00
e . Timeout ,
2023-09-12 23:42:54 +02:00
e . TempDir ,
e . Logger ,
)
2023-09-02 22:24:01 +02:00
if err != nil {
return err
}
return nil
2022-07-08 20:16:04 +02:00
}
2022-10-02 17:49:38 +02:00
func ( e * Executor ) setupFuzzyModel ( ) {
if e . Taskfile != nil {
2022-11-02 15:23:19 +02:00
return
}
2022-10-02 17:49:38 +02:00
2022-11-02 15:23:19 +02:00
model := fuzzy . NewModel ( )
model . SetThreshold ( 1 ) // because we want to build grammar based on every task name
2022-10-15 01:08:00 +02:00
2022-11-02 15:23:19 +02:00
var words [ ] string
2023-04-06 13:07:57 +02:00
for _ , taskName := range e . Taskfile . Tasks . Keys ( ) {
2022-11-02 15:23:19 +02:00
words = append ( words , taskName )
2022-10-02 17:49:38 +02:00
2023-04-06 13:07:57 +02:00
for _ , task := range e . Taskfile . Tasks . Values ( ) {
2022-11-02 15:23:19 +02:00
words = append ( words , task . Aliases ... )
}
2022-10-02 17:49:38 +02:00
}
2022-11-02 15:23:19 +02:00
model . Train ( words )
e . fuzzyModel = model
2022-10-02 17:49:38 +02:00
}
2022-07-08 20:16:04 +02:00
func ( e * Executor ) setupTempDir ( ) error {
if e . TempDir != "" {
return nil
}
if os . Getenv ( "TASK_TEMP_DIR" ) == "" {
2022-08-06 23:19:07 +02:00
e . TempDir = filepathext . SmartJoin ( e . Dir , ".task" )
2022-07-08 20:16:04 +02:00
} else if filepath . IsAbs ( os . Getenv ( "TASK_TEMP_DIR" ) ) || strings . HasPrefix ( os . Getenv ( "TASK_TEMP_DIR" ) , "~" ) {
tempDir , err := execext . Expand ( os . Getenv ( "TASK_TEMP_DIR" ) )
if err != nil {
return err
}
projectDir , _ := filepath . Abs ( e . Dir )
projectName := filepath . Base ( projectDir )
2022-08-06 23:19:07 +02:00
e . TempDir = filepathext . SmartJoin ( tempDir , projectName )
2022-07-08 20:16:04 +02:00
} else {
2022-08-06 23:19:07 +02:00
e . TempDir = filepathext . SmartJoin ( e . Dir , os . Getenv ( "TASK_TEMP_DIR" ) )
2022-07-08 20:16:04 +02:00
}
return nil
}
func ( e * Executor ) setupStdFiles ( ) {
if e . Stdin == nil {
e . Stdin = os . Stdin
}
if e . Stdout == nil {
e . Stdout = os . Stdout
}
if e . Stderr == nil {
e . Stderr = os . Stderr
}
}
func ( e * Executor ) setupLogger ( ) {
e . Logger = & logger . Logger {
2023-10-07 23:55:43 +02:00
Stdin : e . Stdin ,
Stdout : e . Stdout ,
Stderr : e . Stderr ,
Verbose : e . Verbose ,
Color : e . Color ,
AssumeYes : e . AssumeYes ,
AssumeTerm : e . AssumeTerm ,
2022-07-08 20:16:04 +02:00
}
}
func ( e * Executor ) setupOutput ( ) error {
if ! e . OutputStyle . IsSet ( ) {
e . OutputStyle = e . Taskfile . Output
}
var err error
e . Output , err = output . BuildFor ( & e . OutputStyle )
return err
}
2023-02-08 12:21:43 +02:00
func ( e * Executor ) setupCompiler ( ) error {
if e . Taskfile . Version . LessThan ( taskfile . V3 ) {
2022-07-08 20:16:04 +02:00
var err error
e . taskvars , err = read . Taskvars ( e . Dir )
if err != nil {
return err
}
e . Compiler = & compilerv2 . CompilerV2 {
Dir : e . Dir ,
Taskvars : e . taskvars ,
TaskfileVars : e . Taskfile . Vars ,
Expansions : e . Taskfile . Expansions ,
Logger : e . Logger ,
}
} else {
2023-08-26 23:06:50 +02:00
if e . UserWorkingDir == "" {
var err error
e . UserWorkingDir , err = os . Getwd ( )
if err != nil {
return err
}
2022-12-06 02:58:20 +02:00
}
2023-08-26 23:06:50 +02:00
2022-07-08 20:16:04 +02:00
e . Compiler = & compilerv3 . CompilerV3 {
2022-12-06 02:58:20 +02:00
Dir : e . Dir ,
2023-08-26 23:06:50 +02:00
UserWorkingDir : e . UserWorkingDir ,
2022-12-06 02:58:20 +02:00
TaskfileEnv : e . Taskfile . Env ,
TaskfileVars : e . Taskfile . Vars ,
Logger : e . Logger ,
2022-07-08 20:16:04 +02:00
}
}
return nil
}
2023-02-08 12:21:43 +02:00
func ( e * Executor ) readDotEnvFiles ( ) error {
if e . Taskfile . Version . LessThan ( taskfile . V3 ) {
2022-07-08 20:16:04 +02:00
return nil
}
env , err := read . Dotenv ( e . Compiler , e . Taskfile , e . Dir )
if err != nil {
return err
}
err = env . Range ( func ( key string , value taskfile . Var ) error {
2023-04-06 13:07:57 +02:00
if ok := e . Taskfile . Env . Exists ( key ) ; ! ok {
2022-07-08 20:16:04 +02:00
e . Taskfile . Env . Set ( key , value )
}
return nil
} )
return err
}
2023-02-08 12:21:43 +02:00
func ( e * Executor ) setupDefaults ( ) {
2022-07-08 20:16:04 +02:00
// Color available only on v3
2023-02-08 12:21:43 +02:00
if e . Taskfile . Version . LessThan ( taskfile . V3 ) {
2022-07-08 20:16:04 +02:00
e . Logger . Color = false
}
if e . Taskfile . Method == "" {
2023-02-08 12:21:43 +02:00
if e . Taskfile . Version . Compare ( taskfile . V3 ) >= 0 {
2022-07-08 20:16:04 +02:00
e . Taskfile . Method = "checksum"
} else {
e . Taskfile . Method = "timestamp"
}
}
if e . Taskfile . Run == "" {
e . Taskfile . Run = "always"
}
}
func ( e * Executor ) setupConcurrencyState ( ) {
e . executionHashes = make ( map [ string ] context . Context )
2023-04-06 13:07:57 +02:00
e . taskCallCount = make ( map [ string ] * int32 , e . Taskfile . Tasks . Len ( ) )
e . mkdirMutexMap = make ( map [ string ] * sync . Mutex , e . Taskfile . Tasks . Len ( ) )
for _ , k := range e . Taskfile . Tasks . Keys ( ) {
2022-07-08 20:16:04 +02:00
e . taskCallCount [ k ] = new ( int32 )
e . mkdirMutexMap [ k ] = & sync . Mutex { }
}
if e . Concurrency > 0 {
e . concurrencySemaphore = make ( chan struct { } , e . Concurrency )
}
}
2023-02-08 12:21:43 +02:00
func ( e * Executor ) doVersionChecks ( ) error {
// Copy the version to avoid modifying the original
v := & semver . Version { }
* v = * e . Taskfile . Version
if v . LessThan ( taskfile . V2 ) {
2023-06-04 02:05:48 +02:00
return fmt . Errorf ( ` task: version 1 schemas are no longer supported ` )
}
if v . LessThan ( taskfile . V3 ) {
e . Logger . Errf ( logger . Yellow , "task: version 2 schemas are deprecated and will be removed in a future release\nSee https://github.com/go-task/task/issues/1197 for more details\n" )
2022-07-08 20:16:04 +02:00
}
// consider as equal to the greater version if round
2023-02-08 12:21:43 +02:00
if v . Equal ( taskfile . V2 ) {
v = semver . MustParse ( "2.6" )
2022-07-08 20:16:04 +02:00
}
2023-02-08 12:21:43 +02:00
if v . Equal ( taskfile . V3 ) {
v = semver . MustParse ( "3.8" )
2022-07-08 20:16:04 +02:00
}
2023-02-08 12:21:43 +02:00
if v . GreaterThan ( semver . MustParse ( "3.8" ) ) {
2022-07-08 20:16:04 +02:00
return fmt . Errorf ( ` task: Taskfile versions greater than v3.8 not implemented in the version of Task ` )
}
2023-02-08 12:21:43 +02:00
if v . LessThan ( semver . MustParse ( "2.1" ) ) && ! e . Taskfile . Output . IsSet ( ) {
2022-07-08 20:16:04 +02:00
return fmt . Errorf ( ` task: Taskfile option "output" is only available starting on Taskfile version v2.1 ` )
}
2023-02-08 12:21:43 +02:00
if v . LessThan ( semver . MustParse ( "2.2" ) ) && e . Taskfile . Includes . Len ( ) > 0 {
2022-07-08 20:16:04 +02:00
return fmt . Errorf ( ` task: Including Taskfiles is only available starting on Taskfile version v2.2 ` )
}
2023-02-08 12:21:43 +02:00
if v . Compare ( taskfile . V3 ) >= 0 && e . Taskfile . Expansions > 2 {
2022-07-08 20:16:04 +02:00
return fmt . Errorf ( ` task: The "expansions" setting is not available anymore on v3.0 ` )
}
2023-02-08 12:21:43 +02:00
if v . LessThan ( semver . MustParse ( "3.8" ) ) && e . Taskfile . Output . Group . IsSet ( ) {
2022-07-08 20:16:04 +02:00
return fmt . Errorf ( ` task: Taskfile option "output.group" is only available starting on Taskfile version v3.8 ` )
}
2023-02-08 12:21:43 +02:00
if v . Compare ( semver . MustParse ( "2.1" ) ) <= 0 {
2022-07-08 20:16:04 +02:00
err := errors . New ( ` task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1 ` )
2023-04-06 13:07:57 +02:00
for _ , task := range e . Taskfile . Tasks . Values ( ) {
2022-07-08 20:16:04 +02:00
if task . IgnoreError {
return err
}
for _ , cmd := range task . Cmds {
if cmd . IgnoreError {
return err
}
}
}
}
2023-02-08 12:21:43 +02:00
if v . LessThan ( semver . MustParse ( "2.6" ) ) {
2023-04-06 13:07:57 +02:00
for _ , task := range e . Taskfile . Tasks . Values ( ) {
2022-07-08 20:16:04 +02:00
if len ( task . Preconditions ) > 0 {
return errors . New ( ` task: Task option "preconditions" is only available starting on Taskfile version v2.6 ` )
}
}
}
2023-02-08 12:21:43 +02:00
if v . LessThan ( taskfile . V3 ) {
2022-07-08 20:16:04 +02:00
err := e . Taskfile . Includes . Range ( func ( _ string , taskfile taskfile . IncludedTaskfile ) error {
if taskfile . AdvancedImport {
return errors . New ( ` task: Import with additional parameters is only available starting on Taskfile version v3 ` )
}
return nil
} )
if err != nil {
return err
}
}
2023-02-08 12:21:43 +02:00
if v . LessThan ( semver . MustParse ( "3.7" ) ) {
2022-07-08 20:16:04 +02:00
if e . Taskfile . Run != "" {
return errors . New ( ` task: Setting the "run" type is only available starting on Taskfile version v3.7 ` )
}
2023-04-06 13:07:57 +02:00
for _ , task := range e . Taskfile . Tasks . Values ( ) {
2022-07-08 20:16:04 +02:00
if task . Run != "" {
return errors . New ( ` task: Setting the "run" type is only available starting on Taskfile version v3.7 ` )
}
}
}
return nil
}