setup.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package task
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "slices"
  8. "strings"
  9. "sync"
  10. "github.com/Masterminds/semver/v3"
  11. "github.com/sajari/fuzzy"
  12. "github.com/go-task/task/v3/errors"
  13. "github.com/go-task/task/v3/internal/compiler"
  14. "github.com/go-task/task/v3/internal/execext"
  15. "github.com/go-task/task/v3/internal/filepathext"
  16. "github.com/go-task/task/v3/internal/logger"
  17. "github.com/go-task/task/v3/internal/output"
  18. "github.com/go-task/task/v3/internal/version"
  19. "github.com/go-task/task/v3/taskfile"
  20. "github.com/go-task/task/v3/taskfile/ast"
  21. )
  22. func (e *Executor) Setup() error {
  23. e.setupLogger()
  24. node, err := e.getRootNode()
  25. if err != nil {
  26. return err
  27. }
  28. if err := e.setupTempDir(); err != nil {
  29. return err
  30. }
  31. if err := e.readTaskfile(node); err != nil {
  32. return err
  33. }
  34. e.setupFuzzyModel()
  35. e.setupStdFiles()
  36. if err := e.setupOutput(); err != nil {
  37. return err
  38. }
  39. if err := e.setupCompiler(); err != nil {
  40. return err
  41. }
  42. if err := e.readDotEnvFiles(); err != nil {
  43. return err
  44. }
  45. if err := e.doVersionChecks(); err != nil {
  46. return err
  47. }
  48. e.setupDefaults()
  49. e.setupConcurrencyState()
  50. return nil
  51. }
  52. func (e *Executor) getRootNode() (taskfile.Node, error) {
  53. node, err := taskfile.NewRootNode(e.Logger, e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
  54. if err != nil {
  55. return nil, err
  56. }
  57. e.Dir = node.Dir()
  58. return node, err
  59. }
  60. func (e *Executor) readTaskfile(node taskfile.Node) error {
  61. reader := taskfile.NewReader(
  62. node,
  63. e.Insecure,
  64. e.Download,
  65. e.Offline,
  66. e.Timeout,
  67. e.TempDir.Remote,
  68. e.Logger,
  69. )
  70. graph, err := reader.Read()
  71. if err != nil {
  72. return err
  73. }
  74. if e.Taskfile, err = graph.Merge(); err != nil {
  75. return err
  76. }
  77. return nil
  78. }
  79. func (e *Executor) setupFuzzyModel() {
  80. if e.Taskfile != nil {
  81. return
  82. }
  83. model := fuzzy.NewModel()
  84. model.SetThreshold(1) // because we want to build grammar based on every task name
  85. var words []string
  86. for _, taskName := range e.Taskfile.Tasks.Keys() {
  87. words = append(words, taskName)
  88. for _, task := range e.Taskfile.Tasks.Values() {
  89. words = slices.Concat(words, task.Aliases)
  90. }
  91. }
  92. model.Train(words)
  93. e.fuzzyModel = model
  94. }
  95. func (e *Executor) setupTempDir() error {
  96. if e.TempDir != (TempDir{}) {
  97. return nil
  98. }
  99. if os.Getenv("TASK_TEMP_DIR") == "" {
  100. e.TempDir = TempDir{
  101. Remote: filepathext.SmartJoin(e.Dir, ".task"),
  102. Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
  103. }
  104. } else if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
  105. tempDir, err := execext.Expand(os.Getenv("TASK_TEMP_DIR"))
  106. if err != nil {
  107. return err
  108. }
  109. projectDir, _ := filepath.Abs(e.Dir)
  110. projectName := filepath.Base(projectDir)
  111. e.TempDir = TempDir{
  112. Remote: tempDir,
  113. Fingerprint: filepathext.SmartJoin(tempDir, projectName),
  114. }
  115. } else {
  116. e.TempDir = TempDir{
  117. Remote: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
  118. Fingerprint: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
  119. }
  120. }
  121. if os.Getenv("TASK_REMOTE_DIR") != "" {
  122. if filepath.IsAbs(os.Getenv("TASK_REMOTE_DIR")) || strings.HasPrefix(os.Getenv("TASK_REMOTE_DIR"), "~") {
  123. remoteTempDir, err := execext.Expand(os.Getenv("TASK_REMOTE_DIR"))
  124. if err != nil {
  125. return err
  126. }
  127. e.TempDir.Remote = remoteTempDir
  128. } else {
  129. e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
  130. }
  131. }
  132. return nil
  133. }
  134. func (e *Executor) setupStdFiles() {
  135. if e.Stdin == nil {
  136. e.Stdin = os.Stdin
  137. }
  138. if e.Stdout == nil {
  139. e.Stdout = os.Stdout
  140. }
  141. if e.Stderr == nil {
  142. e.Stderr = os.Stderr
  143. }
  144. }
  145. func (e *Executor) setupLogger() {
  146. e.Logger = &logger.Logger{
  147. Stdin: e.Stdin,
  148. Stdout: e.Stdout,
  149. Stderr: e.Stderr,
  150. Verbose: e.Verbose,
  151. Color: e.Color,
  152. AssumeYes: e.AssumeYes,
  153. AssumeTerm: e.AssumeTerm,
  154. }
  155. }
  156. func (e *Executor) setupOutput() error {
  157. if !e.OutputStyle.IsSet() {
  158. e.OutputStyle = e.Taskfile.Output
  159. }
  160. var err error
  161. e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
  162. return err
  163. }
  164. func (e *Executor) setupCompiler() error {
  165. if e.UserWorkingDir == "" {
  166. var err error
  167. e.UserWorkingDir, err = os.Getwd()
  168. if err != nil {
  169. return err
  170. }
  171. }
  172. e.Compiler = &compiler.Compiler{
  173. Dir: e.Dir,
  174. Entrypoint: e.Entrypoint,
  175. UserWorkingDir: e.UserWorkingDir,
  176. TaskfileEnv: e.Taskfile.Env,
  177. TaskfileVars: e.Taskfile.Vars,
  178. Logger: e.Logger,
  179. }
  180. return nil
  181. }
  182. func (e *Executor) readDotEnvFiles() error {
  183. if e.Taskfile.Version.LessThan(ast.V3) {
  184. return nil
  185. }
  186. env, err := taskfile.Dotenv(e.Compiler, e.Taskfile, e.Dir)
  187. if err != nil {
  188. return err
  189. }
  190. err = env.Range(func(key string, value ast.Var) error {
  191. if ok := e.Taskfile.Env.Exists(key); !ok {
  192. e.Taskfile.Env.Set(key, value)
  193. }
  194. return nil
  195. })
  196. return err
  197. }
  198. func (e *Executor) setupDefaults() {
  199. if e.Taskfile.Method == "" {
  200. e.Taskfile.Method = "checksum"
  201. }
  202. if e.Taskfile.Run == "" {
  203. e.Taskfile.Run = "always"
  204. }
  205. }
  206. func (e *Executor) setupConcurrencyState() {
  207. e.executionHashes = make(map[string]context.Context)
  208. e.taskCallCount = make(map[string]*int32, e.Taskfile.Tasks.Len())
  209. e.mkdirMutexMap = make(map[string]*sync.Mutex, e.Taskfile.Tasks.Len())
  210. for _, k := range e.Taskfile.Tasks.Keys() {
  211. e.taskCallCount[k] = new(int32)
  212. e.mkdirMutexMap[k] = &sync.Mutex{}
  213. }
  214. if e.Concurrency > 0 {
  215. e.concurrencySemaphore = make(chan struct{}, e.Concurrency)
  216. }
  217. }
  218. func (e *Executor) doVersionChecks() error {
  219. // Copy the version to avoid modifying the original
  220. schemaVersion := &semver.Version{}
  221. *schemaVersion = *e.Taskfile.Version
  222. // Error if the Taskfile uses a schema version below v3
  223. if schemaVersion.LessThan(ast.V3) {
  224. return &errors.TaskfileVersionCheckError{
  225. URI: e.Taskfile.Location,
  226. SchemaVersion: schemaVersion,
  227. Message: `no longer supported. Please use v3 or above`,
  228. }
  229. }
  230. // Get the current version of Task
  231. // If we can't parse the version (e.g. when its "devel"), then ignore the current version checks
  232. currentVersion, err := semver.NewVersion(version.GetVersion())
  233. if err != nil {
  234. return nil
  235. }
  236. // Error if the Taskfile uses a schema version above the current version of Task
  237. if schemaVersion.GreaterThan(currentVersion) {
  238. return &errors.TaskfileVersionCheckError{
  239. URI: e.Taskfile.Location,
  240. SchemaVersion: schemaVersion,
  241. Message: fmt.Sprintf(`is greater than the current version of Task (%s)`, currentVersion.String()),
  242. }
  243. }
  244. return nil
  245. }