2018-07-22 21:05:47 +02:00
package read
import (
2018-10-13 22:52:09 +02:00
"errors"
2018-07-22 21:05:47 +02:00
"fmt"
"os"
"path/filepath"
"runtime"
2020-03-28 16:27:49 +02:00
"gopkg.in/yaml.v3"
2021-01-07 16:48:33 +02:00
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
2018-07-22 21:05:47 +02:00
)
2019-01-21 11:56:14 +02:00
var (
2020-08-16 00:12:39 +02:00
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
2020-08-04 00:18:38 +02:00
ErrIncludedTaskfilesCantHaveDotenvs = errors . New ( "task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile" )
2021-12-04 17:37:52 +02:00
2022-02-19 23:24:43 +02:00
defaultTaskfiles = [ ] string {
"Taskfile.yml" ,
"Taskfile.yaml" ,
"Taskfile.dist.yml" ,
"Taskfile.dist.yaml" ,
}
2019-01-21 11:56:14 +02:00
)
2018-10-13 22:52:09 +02:00
2022-01-15 05:38:37 +02:00
type ReaderNode struct {
Dir string
Entrypoint string
Optional bool
Parent * ReaderNode
}
2018-07-22 21:05:47 +02:00
// Taskfile reads a Taskfile for a given directory
2021-12-04 17:37:52 +02:00
// Uses current dir when dir is left empty. Uses Taskfile.yml
// or Taskfile.yaml when entrypoint is left empty
2022-01-15 05:38:37 +02:00
func Taskfile ( readerNode * ReaderNode ) ( * taskfile . Taskfile , error ) {
if readerNode . Dir == "" {
2021-12-04 17:37:52 +02:00
d , err := os . Getwd ( )
if err != nil {
return nil , err
}
2022-01-15 05:38:37 +02:00
readerNode . Dir = d
2018-09-22 22:29:18 +02:00
}
2022-07-26 00:10:16 +02:00
2022-01-15 05:38:37 +02:00
path , err := exists ( filepath . Join ( readerNode . Dir , readerNode . Entrypoint ) )
2021-12-04 17:37:52 +02:00
if err != nil {
return nil , err
}
2022-01-15 05:38:37 +02:00
readerNode . Entrypoint = filepath . Base ( path )
2021-12-04 17:37:52 +02:00
2018-07-22 21:05:47 +02:00
t , err := readTaskfile ( path )
if err != nil {
2018-09-22 22:29:18 +02:00
return nil , err
2018-07-22 21:05:47 +02:00
}
2020-05-17 21:03:03 +02:00
v , err := t . ParsedVersion ( )
if err != nil {
return nil , err
}
2022-07-26 00:10:16 +02:00
// Annotate any included Taskfile reference with a base directory for resolving relative paths
_ = t . Includes . Range ( func ( key string , includedFile taskfile . IncludedTaskfile ) error {
// Set the base directory for resolving relative paths, but only if not already set
if includedFile . BaseDir == "" {
includedFile . BaseDir = readerNode . Dir
t . Includes . Set ( key , includedFile )
}
return nil
} )
2021-01-01 23:27:50 +02:00
err = t . Includes . Range ( func ( namespace string , includedTask taskfile . IncludedTaskfile ) error {
2020-05-17 21:03:03 +02:00
if v >= 3.0 {
tr := templater . Templater { Vars : & taskfile . Vars { } , RemoveNoValue : true }
includedTask = taskfile . IncludedTaskfile {
Taskfile : tr . Replace ( includedTask . Taskfile ) ,
Dir : tr . Replace ( includedTask . Dir ) ,
2021-08-11 18:28:44 +02:00
Optional : includedTask . Optional ,
2020-05-17 21:03:03 +02:00
AdvancedImport : includedTask . AdvancedImport ,
2022-02-24 00:53:46 +02:00
Vars : includedTask . Vars ,
2022-07-26 00:10:16 +02:00
BaseDir : includedTask . BaseDir ,
2020-05-17 21:03:03 +02:00
}
if err := tr . Err ( ) ; err != nil {
2021-01-01 23:27:50 +02:00
return err
2020-05-17 21:03:03 +02:00
}
}
2022-07-26 00:10:16 +02:00
path , err := includedTask . FullTaskfilePath ( )
2021-09-05 16:57:49 +02:00
if err != nil {
return err
}
2022-07-26 00:10:16 +02:00
2021-12-04 17:37:52 +02:00
path , err = exists ( path )
2018-09-10 03:29:29 +02:00
if err != nil {
2021-09-25 14:40:03 +02:00
if includedTask . Optional {
return nil
}
2021-01-01 23:27:50 +02:00
return err
2018-09-10 03:29:29 +02:00
}
2021-12-04 17:37:52 +02:00
2022-01-16 06:34:59 +02:00
includeReaderNode := & ReaderNode {
2022-01-15 05:38:37 +02:00
Dir : filepath . Dir ( path ) ,
Entrypoint : filepath . Base ( path ) ,
Parent : readerNode ,
Optional : includedTask . Optional ,
}
2022-01-16 06:34:59 +02:00
if err := checkCircularIncludes ( includeReaderNode ) ; err != nil {
2021-01-01 23:27:50 +02:00
return err
2018-09-10 03:29:29 +02:00
}
2022-01-16 06:34:59 +02:00
includedTaskfile , err := Taskfile ( includeReaderNode )
2018-09-10 03:29:29 +02:00
if err != nil {
2022-01-15 05:38:37 +02:00
if includedTask . Optional {
return nil
}
2021-01-01 23:27:50 +02:00
return err
2018-10-13 22:52:09 +02:00
}
2020-01-29 09:03:06 +02:00
2020-08-16 00:12:39 +02:00
if v >= 3.0 && len ( includedTaskfile . Dotenv ) > 0 {
2021-01-01 23:27:50 +02:00
return ErrIncludedTaskfilesCantHaveDotenvs
2020-08-04 00:18:38 +02:00
}
2020-02-15 16:24:06 +02:00
if includedTask . AdvancedImport {
2022-07-26 00:10:16 +02:00
dir , err := includedTask . FullDirPath ( )
if err != nil {
return err
}
2021-01-09 17:09:23 +02:00
for k , v := range includedTaskfile . Vars . Mapping {
o := v
2022-07-26 00:10:16 +02:00
o . Dir = dir
2021-01-09 17:09:23 +02:00
includedTaskfile . Vars . Mapping [ k ] = o
}
for k , v := range includedTaskfile . Env . Mapping {
o := v
2022-07-26 00:10:16 +02:00
o . Dir = dir
2021-01-09 17:09:23 +02:00
includedTaskfile . Env . Mapping [ k ] = o
}
2020-02-15 16:24:06 +02:00
for _ , task := range includedTaskfile . Tasks {
if ! filepath . IsAbs ( task . Dir ) {
2022-07-26 00:10:16 +02:00
task . Dir = filepath . Join ( dir , task . Dir )
2020-02-15 16:24:06 +02:00
}
2022-07-26 00:10:16 +02:00
2022-03-19 23:41:03 +02:00
task . IncludeVars = includedTask . Vars
task . IncludedTaskfileVars = includedTaskfile . Vars
2020-02-15 15:40:42 +02:00
}
2020-01-29 09:03:06 +02:00
}
2018-09-10 03:29:29 +02:00
if err = taskfile . Merge ( t , includedTaskfile , namespace ) ; err != nil {
2021-01-01 23:27:50 +02:00
return err
2018-09-10 03:29:29 +02:00
}
2021-01-01 23:27:50 +02:00
return nil
} )
if err != nil {
return nil , err
2018-09-10 03:29:29 +02:00
}
2020-05-17 20:42:27 +02:00
if v < 3.0 {
2022-01-15 05:38:37 +02:00
path = filepath . Join ( readerNode . Dir , fmt . Sprintf ( "Taskfile_%s.yml" , runtime . GOOS ) )
2020-05-17 20:42:27 +02:00
if _ , err = os . Stat ( path ) ; err == nil {
osTaskfile , err := readTaskfile ( path )
if err != nil {
return nil , err
}
if err = taskfile . Merge ( t , osTaskfile ) ; err != nil {
return nil , err
}
2018-07-22 21:05:47 +02:00
}
}
for name , task := range t . Tasks {
2020-10-13 02:03:13 +02:00
if task == nil {
task = & taskfile . Task { }
t . Tasks [ name ] = task
}
2018-07-22 21:05:47 +02:00
task . Task = name
}
return t , nil
}
func readTaskfile ( file string ) ( * taskfile . Taskfile , error ) {
f , err := os . Open ( file )
if err != nil {
return nil , err
}
var t taskfile . Taskfile
return & t , yaml . NewDecoder ( f ) . Decode ( & t )
}
2021-12-04 17:37:52 +02:00
2022-07-26 00:10:16 +02:00
// exists finds a Taskfile at the stated location, returning a fully qualified path to the file
2021-12-04 17:37:52 +02:00
func exists ( path string ) ( string , error ) {
fi , err := os . Stat ( path )
if err != nil {
return "" , err
}
if fi . Mode ( ) . IsRegular ( ) {
2022-07-26 00:10:16 +02:00
// File exists, return a fully qualified path
result , err := filepath . Abs ( path )
if err != nil {
return "" , err
}
return result , nil
2021-12-04 17:37:52 +02:00
}
for _ , n := range defaultTaskfiles {
fpath := filepath . Join ( path , n )
if _ , err := os . Stat ( fpath ) ; err == nil {
2022-07-26 00:10:16 +02:00
result , err := filepath . Abs ( fpath )
if err != nil {
return "" , err
}
return result , nil
2021-12-04 17:37:52 +02:00
}
}
return "" , fmt . Errorf ( ` task: No Taskfile found in "%s". Use "task --init" to create a new one ` , path )
}
2022-01-16 06:34:59 +02:00
func checkCircularIncludes ( node * ReaderNode ) error {
if node == nil {
2022-02-04 05:12:58 +02:00
return errors . New ( "task: failed to check for include cycle: node was nil" )
2022-01-16 06:34:59 +02:00
}
if node . Parent == nil {
2022-02-04 05:12:58 +02:00
return errors . New ( "task: failed to check for include cycle: node.Parent was nil" )
2022-01-16 06:34:59 +02:00
}
var curNode = node
var basePath = filepath . Join ( node . Dir , node . Entrypoint )
for curNode . Parent != nil {
curNode = curNode . Parent
curPath := filepath . Join ( curNode . Dir , curNode . Entrypoint )
if curPath == basePath {
2022-02-04 05:12:58 +02:00
return fmt . Errorf ( "task: include cycle detected between %s <--> %s" ,
2022-01-16 06:34:59 +02:00
curPath ,
filepath . Join ( node . Parent . Dir , node . Parent . Entrypoint ) ,
)
}
}
return nil
}