watch.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package task
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "os/signal"
  7. "path/filepath"
  8. "strings"
  9. "syscall"
  10. "time"
  11. "github.com/radovskyb/watcher"
  12. "github.com/go-task/task/v3/errors"
  13. "github.com/go-task/task/v3/internal/fingerprint"
  14. "github.com/go-task/task/v3/internal/logger"
  15. "github.com/go-task/task/v3/taskfile/ast"
  16. )
  17. const defaultWatchInterval = 5 * time.Second
  18. // watchTasks start watching the given tasks
  19. func (e *Executor) watchTasks(calls ...*ast.Call) error {
  20. tasks := make([]string, len(calls))
  21. for i, c := range calls {
  22. tasks[i] = c.Task
  23. }
  24. e.Logger.Errf(logger.Green, "task: Started watching for tasks: %s\n", strings.Join(tasks, ", "))
  25. ctx, cancel := context.WithCancel(context.Background())
  26. for _, c := range calls {
  27. c := c
  28. go func() {
  29. if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
  30. e.Logger.Errf(logger.Red, "%v\n", err)
  31. }
  32. }()
  33. }
  34. var watchInterval time.Duration
  35. switch {
  36. case e.Interval != 0:
  37. watchInterval = e.Interval
  38. case e.Taskfile.Interval != 0:
  39. watchInterval = e.Taskfile.Interval
  40. default:
  41. watchInterval = defaultWatchInterval
  42. }
  43. e.Logger.VerboseOutf(logger.Green, "task: Watching for changes every %v\n", watchInterval)
  44. w := watcher.New()
  45. defer w.Close()
  46. w.SetMaxEvents(1)
  47. closeOnInterrupt(w)
  48. go func() {
  49. for {
  50. select {
  51. case event := <-w.Event:
  52. e.Logger.VerboseErrf(logger.Magenta, "task: received watch event: %v\n", event)
  53. cancel()
  54. ctx, cancel = context.WithCancel(context.Background())
  55. e.Compiler.ResetCache()
  56. for _, c := range calls {
  57. c := c
  58. go func() {
  59. if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
  60. e.Logger.Errf(logger.Red, "%v\n", err)
  61. }
  62. }()
  63. }
  64. case err := <-w.Error:
  65. switch err {
  66. case watcher.ErrWatchedFileDeleted:
  67. default:
  68. e.Logger.Errf(logger.Red, "%v\n", err)
  69. }
  70. case <-w.Closed:
  71. cancel()
  72. return
  73. }
  74. }
  75. }()
  76. go func() {
  77. // re-register every 5 seconds because we can have new files, but this process is expensive to run
  78. for {
  79. if err := e.registerWatchedFiles(w, calls...); err != nil {
  80. e.Logger.Errf(logger.Red, "%v\n", err)
  81. }
  82. time.Sleep(watchInterval)
  83. }
  84. }()
  85. return w.Start(watchInterval)
  86. }
  87. func isContextError(err error) bool {
  88. if taskRunErr, ok := err.(*errors.TaskRunError); ok {
  89. err = taskRunErr.Err
  90. }
  91. return err == context.Canceled || err == context.DeadlineExceeded
  92. }
  93. func closeOnInterrupt(w *watcher.Watcher) {
  94. ch := make(chan os.Signal, 1)
  95. signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
  96. go func() {
  97. <-ch
  98. w.Close()
  99. }()
  100. }
  101. func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...*ast.Call) error {
  102. watchedFiles := w.WatchedFiles()
  103. var registerTaskFiles func(*ast.Call) error
  104. registerTaskFiles = func(c *ast.Call) error {
  105. task, err := e.CompiledTask(c)
  106. if err != nil {
  107. return err
  108. }
  109. for _, d := range task.Deps {
  110. if err := registerTaskFiles(&ast.Call{Task: d.Task, Vars: d.Vars}); err != nil {
  111. return err
  112. }
  113. }
  114. for _, c := range task.Cmds {
  115. if c.Task != "" {
  116. if err := registerTaskFiles(&ast.Call{Task: c.Task, Vars: c.Vars}); err != nil {
  117. return err
  118. }
  119. }
  120. }
  121. globs, err := fingerprint.Globs(task.Dir, task.Sources)
  122. if err != nil {
  123. return err
  124. }
  125. for _, s := range globs {
  126. files, err := fingerprint.Glob(task.Dir, s)
  127. if err != nil {
  128. return fmt.Errorf("task: %s: %w", s, err)
  129. }
  130. for _, f := range files {
  131. absFile, err := filepath.Abs(f)
  132. if err != nil {
  133. return err
  134. }
  135. if ShouldIgnoreFile(absFile) {
  136. continue
  137. }
  138. if _, ok := watchedFiles[absFile]; ok {
  139. continue
  140. }
  141. if err := w.Add(absFile); err != nil {
  142. return err
  143. }
  144. e.Logger.VerboseOutf(logger.Green, "task: watching new file: %v\n", absFile)
  145. }
  146. }
  147. return nil
  148. }
  149. for _, c := range calls {
  150. if err := registerTaskFiles(c); err != nil {
  151. return err
  152. }
  153. }
  154. return nil
  155. }
  156. func ShouldIgnoreFile(path string) bool {
  157. ignorePaths := []string{
  158. "/.task",
  159. "/.git",
  160. "/.hg",
  161. "/node_modules",
  162. }
  163. for _, p := range ignorePaths {
  164. if strings.Contains(path, fmt.Sprintf("%s/", p)) || strings.HasSuffix(path, p) {
  165. return true
  166. }
  167. }
  168. return false
  169. }