mirror of
https://github.com/go-task/task.git
synced 2025-01-06 03:53:54 +02:00
Extract some functionality to its own packages
Like variable and template handling, and logging
This commit is contained in:
parent
152fc0ad38
commit
87a200e42c
@ -5,6 +5,10 @@ GO_PACKAGES:
|
|||||||
.
|
.
|
||||||
./cmd/task
|
./cmd/task
|
||||||
./internal/args
|
./internal/args
|
||||||
|
./internal/compiler
|
||||||
|
./internal/compiler/v1
|
||||||
./internal/execext
|
./internal/execext
|
||||||
|
./internal/logger
|
||||||
./internal/status
|
./internal/status
|
||||||
./internal/taskfile
|
./internal/taskfile
|
||||||
|
./internal/templater
|
||||||
|
@ -51,15 +51,6 @@ func (err *cantWatchNoSourcesError) Error() string {
|
|||||||
return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName)
|
return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dynamicVarError struct {
|
|
||||||
cause error
|
|
||||||
cmd string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *dynamicVarError) Error() string {
|
|
||||||
return fmt.Sprintf(`task: Command "%s" in taskvars file failed: %s`, err.cmd, err.cause)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaximumTaskCallExceededError is returned when a task is called too
|
// MaximumTaskCallExceededError is returned when a task is called too
|
||||||
// many times. In this case you probably have a cyclic dependendy or
|
// many times. In this case you probably have a cyclic dependendy or
|
||||||
// infinite loop
|
// infinite loop
|
||||||
|
4
help.go
4
help.go
@ -12,10 +12,10 @@ import (
|
|||||||
func (e *Executor) PrintTasksHelp() {
|
func (e *Executor) PrintTasksHelp() {
|
||||||
tasks := e.tasksWithDesc()
|
tasks := e.tasksWithDesc()
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
e.outf("task: No tasks with description available")
|
e.Logger.Outf("task: No tasks with description available")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.outf("task: Available tasks for this project:")
|
e.Logger.Outf("task: Available tasks for this project:")
|
||||||
|
|
||||||
// Format in tab-separated columns with a tab stop of 8.
|
// Format in tab-separated columns with a tab stop of 8.
|
||||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
||||||
|
12
internal/compiler/compiler.go
Normal file
12
internal/compiler/compiler.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compiler handles compilation of a task before its execution.
|
||||||
|
// E.g. variable merger, template processing, etc.
|
||||||
|
type Compiler interface {
|
||||||
|
GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error)
|
||||||
|
HandleDynamicVar(v taskfile.Var) (string, error)
|
||||||
|
}
|
24
internal/compiler/env.go
Normal file
24
internal/compiler/env.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetEnviron the all return all environment variables encapsulated on a
|
||||||
|
// taskfile.Vars
|
||||||
|
func GetEnviron() taskfile.Vars {
|
||||||
|
var (
|
||||||
|
env = os.Environ()
|
||||||
|
m = make(taskfile.Vars, len(env))
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, e := range env {
|
||||||
|
keyVal := strings.SplitN(e, "=", 2)
|
||||||
|
key, val := keyVal[0], keyVal[1]
|
||||||
|
m[key] = taskfile.Var{Static: val}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
145
internal/compiler/v1/compiler_v1.go
Normal file
145
internal/compiler/v1/compiler_v1.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/compiler"
|
||||||
|
"github.com/go-task/task/internal/execext"
|
||||||
|
"github.com/go-task/task/internal/logger"
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
"github.com/go-task/task/internal/templater"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ compiler.Compiler = &CompilerV1{}
|
||||||
|
|
||||||
|
type CompilerV1 struct {
|
||||||
|
Dir string
|
||||||
|
Vars taskfile.Vars
|
||||||
|
|
||||||
|
Logger *logger.Logger
|
||||||
|
|
||||||
|
dynamicCache map[string]string
|
||||||
|
muDynamicCache sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVariables returns fully resolved variables following the priority order:
|
||||||
|
// 1. Call variables (should already have been resolved)
|
||||||
|
// 2. Environment (should not need to be resolved)
|
||||||
|
// 3. Task variables, resolved with access to:
|
||||||
|
// - call, taskvars and environment variables
|
||||||
|
// 4. Taskvars variables, resolved with access to:
|
||||||
|
// - environment variables
|
||||||
|
func (c *CompilerV1) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
|
||||||
|
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
|
||||||
|
for _, src := range srcs {
|
||||||
|
for k, v := range src {
|
||||||
|
dest[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
varsKeys := func(srcs ...taskfile.Vars) []string {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, src := range srcs {
|
||||||
|
for k := range src {
|
||||||
|
m[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lst := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
lst = append(lst, k)
|
||||||
|
}
|
||||||
|
return lst
|
||||||
|
}
|
||||||
|
replaceVars := func(dest taskfile.Vars, keys []string) error {
|
||||||
|
r := templater.Templater{Vars: dest}
|
||||||
|
for _, k := range keys {
|
||||||
|
v := dest[k]
|
||||||
|
dest[k] = taskfile.Var{
|
||||||
|
Static: r.Replace(v.Static),
|
||||||
|
Sh: r.Replace(v.Sh),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.Err()
|
||||||
|
}
|
||||||
|
resolveShell := func(dest taskfile.Vars, keys []string) error {
|
||||||
|
for _, k := range keys {
|
||||||
|
v := dest[k]
|
||||||
|
static, err := c.HandleDynamicVar(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dest[k] = taskfile.Var{Static: static}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
|
||||||
|
merge(dest, srcs...)
|
||||||
|
// updatedKeys ensures template evaluation is run only once.
|
||||||
|
updatedKeys := varsKeys(srcs...)
|
||||||
|
if err := replaceVars(dest, updatedKeys); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return resolveShell(dest, updatedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve taskvars variables to "result" with environment override variables.
|
||||||
|
override := compiler.GetEnviron()
|
||||||
|
result := make(taskfile.Vars, len(c.Vars)+len(t.Vars)+len(override))
|
||||||
|
if err := update(result, c.Vars, override); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Resolve task variables to "result" with environment and call override variables.
|
||||||
|
merge(override, call.Vars)
|
||||||
|
if err := update(result, t.Vars, override); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompilerV1) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||||
|
if v.Static != "" || v.Sh == "" {
|
||||||
|
return v.Static, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.muDynamicCache.Lock()
|
||||||
|
defer c.muDynamicCache.Unlock()
|
||||||
|
|
||||||
|
if c.dynamicCache == nil {
|
||||||
|
c.dynamicCache = make(map[string]string, 30)
|
||||||
|
}
|
||||||
|
if result, ok := c.dynamicCache[v.Sh]; ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
opts := &execext.RunCommandOptions{
|
||||||
|
Command: v.Sh,
|
||||||
|
Dir: c.Dir,
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: c.Logger.Stderr,
|
||||||
|
}
|
||||||
|
if err := execext.RunCommand(opts); err != nil {
|
||||||
|
return "", &dynamicVarError{cause: err, cmd: opts.Command}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim a single trailing newline from the result to make most command
|
||||||
|
// output easier to use in shell commands.
|
||||||
|
result := strings.TrimSuffix(stdout.String(), "\n")
|
||||||
|
|
||||||
|
c.dynamicCache[v.Sh] = result
|
||||||
|
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicVarError struct {
|
||||||
|
cause error
|
||||||
|
cmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *dynamicVarError) Error() string {
|
||||||
|
return fmt.Sprintf(`task: Command "%s" in taskvars file failed: %s`, err.cmd, err.cause)
|
||||||
|
}
|
38
internal/logger/logger.go
Normal file
38
internal/logger/logger.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Outf(s string, args ...interface{}) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
s, args = "%s", []interface{}{s}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(l.Stdout, s+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
|
||||||
|
if l.Verbose {
|
||||||
|
l.Outf(s, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Errf(s string, args ...interface{}) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
s, args = "%s", []interface{}{s}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(l.Stderr, s+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
|
||||||
|
if l.Verbose {
|
||||||
|
l.Errf(s, args...)
|
||||||
|
}
|
||||||
|
}
|
52
internal/templater/funcs.go
Normal file
52
internal/templater/funcs.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package templater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
templateFuncs template.FuncMap
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
taskFuncs := template.FuncMap{
|
||||||
|
"OS": func() string { return runtime.GOOS },
|
||||||
|
"ARCH": func() string { return runtime.GOARCH },
|
||||||
|
"catLines": func(s string) string {
|
||||||
|
s = strings.Replace(s, "\r\n", " ", -1)
|
||||||
|
return strings.Replace(s, "\n", " ", -1)
|
||||||
|
},
|
||||||
|
"splitLines": func(s string) []string {
|
||||||
|
s = strings.Replace(s, "\r\n", "\n", -1)
|
||||||
|
return strings.Split(s, "\n")
|
||||||
|
},
|
||||||
|
"fromSlash": func(path string) string {
|
||||||
|
return filepath.FromSlash(path)
|
||||||
|
},
|
||||||
|
"toSlash": func(path string) string {
|
||||||
|
return filepath.ToSlash(path)
|
||||||
|
},
|
||||||
|
"exeExt": func() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return ".exe"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
},
|
||||||
|
// IsSH is deprecated.
|
||||||
|
"IsSH": func() bool { return true },
|
||||||
|
}
|
||||||
|
// Deprecated aliases for renamed functions.
|
||||||
|
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
||||||
|
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
||||||
|
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
||||||
|
|
||||||
|
templateFuncs = sprig.TxtFuncMap()
|
||||||
|
for k, v := range taskFuncs {
|
||||||
|
templateFuncs[k] = v
|
||||||
|
}
|
||||||
|
}
|
73
internal/templater/templater.go
Normal file
73
internal/templater/templater.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package templater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
||||||
|
// times, without having to check for error each time. The first error that
|
||||||
|
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||||
|
// return the zero value.
|
||||||
|
type Templater struct {
|
||||||
|
Vars taskfile.Vars
|
||||||
|
|
||||||
|
strMap map[string]string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Templater) Replace(str string) string {
|
||||||
|
if r.err != nil || str == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
r.err = err
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.strMap == nil {
|
||||||
|
r.strMap = r.Vars.ToStringMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err = templ.Execute(&b, r.strMap); err != nil {
|
||||||
|
r.err = err
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Templater) ReplaceSlice(strs []string) []string {
|
||||||
|
if r.err != nil || len(strs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
new := make([]string, len(strs))
|
||||||
|
for i, str := range strs {
|
||||||
|
new[i] = r.Replace(str)
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
|
||||||
|
if r.err != nil || len(vars) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
new := make(taskfile.Vars, len(vars))
|
||||||
|
for k, v := range vars {
|
||||||
|
new[k] = taskfile.Var{
|
||||||
|
Static: r.Replace(v.Static),
|
||||||
|
Sh: r.Replace(v.Sh),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Templater) Err() error {
|
||||||
|
return r.err
|
||||||
|
}
|
31
log.go
31
log.go
@ -1,31 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e *Executor) outf(s string, args ...interface{}) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
s, args = "%s", []interface{}{s}
|
|
||||||
}
|
|
||||||
fmt.Fprintf(e.Stdout, s+"\n", args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) verboseOutf(s string, args ...interface{}) {
|
|
||||||
if e.Verbose {
|
|
||||||
e.outf(s, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) errf(s string, args ...interface{}) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
s, args = "%s", []interface{}{s}
|
|
||||||
}
|
|
||||||
fmt.Fprintf(e.Stderr, s+"\n", args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) verboseErrf(s string, args ...interface{}) {
|
|
||||||
if e.Verbose {
|
|
||||||
e.errf(s, args...)
|
|
||||||
}
|
|
||||||
}
|
|
35
task.go
35
task.go
@ -5,10 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/go-task/task/internal/compiler"
|
||||||
|
compilerv1 "github.com/go-task/task/internal/compiler/v1"
|
||||||
"github.com/go-task/task/internal/execext"
|
"github.com/go-task/task/internal/execext"
|
||||||
|
"github.com/go-task/task/internal/logger"
|
||||||
"github.com/go-task/task/internal/taskfile"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@ -37,12 +39,12 @@ type Executor struct {
|
|||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
|
Logger *logger.Logger
|
||||||
|
Compiler compiler.Compiler
|
||||||
|
|
||||||
taskvars taskfile.Vars
|
taskvars taskfile.Vars
|
||||||
|
|
||||||
taskCallCount map[string]*int32
|
taskCallCount map[string]*int32
|
||||||
|
|
||||||
dynamicCache map[string]string
|
|
||||||
muDynamicCache sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs Task
|
// Run runs Task
|
||||||
@ -59,16 +61,27 @@ func (e *Executor) Run(calls ...taskfile.Call) error {
|
|||||||
if e.Stderr == nil {
|
if e.Stderr == nil {
|
||||||
e.Stderr = os.Stderr
|
e.Stderr = os.Stderr
|
||||||
}
|
}
|
||||||
|
if e.Logger == nil {
|
||||||
|
e.Logger = &logger.Logger{
|
||||||
|
Stdout: e.Stdout,
|
||||||
|
Stderr: e.Stderr,
|
||||||
|
Verbose: e.Verbose,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Add version 2
|
||||||
|
if e.Compiler == nil {
|
||||||
|
e.Compiler = &compilerv1.CompilerV1{
|
||||||
|
Dir: e.Dir,
|
||||||
|
Vars: e.taskvars,
|
||||||
|
Logger: e.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
||||||
for k := range e.Taskfile.Tasks {
|
for k := range e.Taskfile.Tasks {
|
||||||
e.taskCallCount[k] = new(int32)
|
e.taskCallCount[k] = new(int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.dynamicCache == nil {
|
|
||||||
e.dynamicCache = make(map[string]string, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if given tasks exist
|
// check if given tasks exist
|
||||||
for _, c := range calls {
|
for _, c := range calls {
|
||||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||||
@ -111,7 +124,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
if upToDate {
|
if upToDate {
|
||||||
if !e.Silent {
|
if !e.Silent {
|
||||||
e.errf(`task: Task "%s" is up to date`, t.Task)
|
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -120,7 +133,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
for i := range t.Cmds {
|
for i := range t.Cmds {
|
||||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||||
if err2 := statusOnError(t); err2 != nil {
|
if err2 := statusOnError(t); err2 != nil {
|
||||||
e.verboseErrf("task: error cleaning status on error: %v", err2)
|
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
|
||||||
}
|
}
|
||||||
return &taskRunError{t.Task, err}
|
return &taskRunError{t.Task, err}
|
||||||
}
|
}
|
||||||
@ -150,7 +163,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
||||||
e.errf(cmd.Cmd)
|
e.Logger.Errf(cmd.Cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return execext.RunCommand(&execext.RunCommandOptions{
|
return execext.RunCommand(&execext.RunCommandOptions{
|
||||||
|
@ -209,10 +209,12 @@ func TestStatus(t *testing.T) {
|
|||||||
t.Errorf("File should not exists: %v", err)
|
t.Errorf("File should not exists: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
e := &task.Executor{
|
e := &task.Executor{
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Stdout: ioutil.Discard,
|
Stdout: &buff,
|
||||||
Stderr: ioutil.Discard,
|
Stderr: &buff,
|
||||||
|
Silent: true,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.ReadTaskfile())
|
assert.NoError(t, e.ReadTaskfile())
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
||||||
@ -221,8 +223,7 @@ func TestStatus(t *testing.T) {
|
|||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exists: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buff := bytes.NewBuffer(nil)
|
e.Silent = false
|
||||||
e.Stdout, e.Stderr = buff, buff
|
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
||||||
|
|
||||||
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
||||||
|
267
variables.go
267
variables.go
@ -1,17 +1,11 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/go-task/task/internal/execext"
|
|
||||||
"github.com/go-task/task/internal/taskfile"
|
"github.com/go-task/task/internal/taskfile"
|
||||||
|
"github.com/go-task/task/internal/templater"
|
||||||
|
|
||||||
"github.com/Masterminds/sprig"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,131 +14,6 @@ var (
|
|||||||
TaskvarsFilePath = "Taskvars"
|
TaskvarsFilePath = "Taskvars"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getEnvironmentVariables() taskfile.Vars {
|
|
||||||
var (
|
|
||||||
env = os.Environ()
|
|
||||||
m = make(taskfile.Vars, len(env))
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, e := range env {
|
|
||||||
keyVal := strings.SplitN(e, "=", 2)
|
|
||||||
key, val := keyVal[0], keyVal[1]
|
|
||||||
m[key] = taskfile.Var{Static: val}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// getVariables returns fully resolved variables following the priority order:
|
|
||||||
// 1. Call variables (should already have been resolved)
|
|
||||||
// 2. Environment (should not need to be resolved)
|
|
||||||
// 3. Task variables, resolved with access to:
|
|
||||||
// - call, taskvars and environment variables
|
|
||||||
// 4. Taskvars variables, resolved with access to:
|
|
||||||
// - environment variables
|
|
||||||
func (e *Executor) getVariables(call taskfile.Call) (taskfile.Vars, error) {
|
|
||||||
t, ok := e.Taskfile.Tasks[call.Task]
|
|
||||||
if !ok {
|
|
||||||
return nil, &taskNotFoundError{call.Task}
|
|
||||||
}
|
|
||||||
|
|
||||||
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
|
|
||||||
for _, src := range srcs {
|
|
||||||
for k, v := range src {
|
|
||||||
dest[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
varsKeys := func(srcs ...taskfile.Vars) []string {
|
|
||||||
m := make(map[string]struct{})
|
|
||||||
for _, src := range srcs {
|
|
||||||
for k := range src {
|
|
||||||
m[k] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lst := make([]string, 0, len(m))
|
|
||||||
for k := range m {
|
|
||||||
lst = append(lst, k)
|
|
||||||
}
|
|
||||||
return lst
|
|
||||||
}
|
|
||||||
replaceVars := func(dest taskfile.Vars, keys []string) error {
|
|
||||||
r := varReplacer{vars: dest}
|
|
||||||
for _, k := range keys {
|
|
||||||
v := dest[k]
|
|
||||||
dest[k] = taskfile.Var{
|
|
||||||
Static: r.replace(v.Static),
|
|
||||||
Sh: r.replace(v.Sh),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
resolveShell := func(dest taskfile.Vars, keys []string) error {
|
|
||||||
for _, k := range keys {
|
|
||||||
v := dest[k]
|
|
||||||
static, err := e.handleShVar(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dest[k] = taskfile.Var{Static: static}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
|
|
||||||
merge(dest, srcs...)
|
|
||||||
// updatedKeys ensures template evaluation is run only once.
|
|
||||||
updatedKeys := varsKeys(srcs...)
|
|
||||||
if err := replaceVars(dest, updatedKeys); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return resolveShell(dest, updatedKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve taskvars variables to "result" with environment override variables.
|
|
||||||
override := getEnvironmentVariables()
|
|
||||||
result := make(taskfile.Vars, len(e.taskvars)+len(t.Vars)+len(override))
|
|
||||||
if err := update(result, e.taskvars, override); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Resolve task variables to "result" with environment and call override variables.
|
|
||||||
merge(override, call.Vars)
|
|
||||||
if err := update(result, t.Vars, override); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Executor) handleShVar(v taskfile.Var) (string, error) {
|
|
||||||
if v.Static != "" || v.Sh == "" {
|
|
||||||
return v.Static, nil
|
|
||||||
}
|
|
||||||
e.muDynamicCache.Lock()
|
|
||||||
defer e.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
if result, ok := e.dynamicCache[v.Sh]; ok {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
opts := &execext.RunCommandOptions{
|
|
||||||
Command: v.Sh,
|
|
||||||
Dir: e.Dir,
|
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: e.Stderr,
|
|
||||||
}
|
|
||||||
if err := execext.RunCommand(opts); err != nil {
|
|
||||||
return "", &dynamicVarError{cause: err, cmd: opts.Command}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim a single trailing newline from the result to make most command
|
|
||||||
// output easier to use in shell commands.
|
|
||||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
|
||||||
|
|
||||||
e.dynamicCache[v.Sh] = result
|
|
||||||
e.verboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompiledTask returns a copy of a task, but replacing variables in almost all
|
// CompiledTask returns a copy of a task, but replacing variables in almost all
|
||||||
// properties using the Go template package.
|
// properties using the Go template package.
|
||||||
func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
||||||
@ -153,23 +22,23 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
return nil, &taskNotFoundError{call.Task}
|
return nil, &taskNotFoundError{call.Task}
|
||||||
}
|
}
|
||||||
|
|
||||||
vars, err := e.getVariables(call)
|
vars, err := e.Compiler.GetVariables(origTask, call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r := varReplacer{vars: vars}
|
r := templater.Templater{Vars: vars}
|
||||||
|
|
||||||
new := taskfile.Task{
|
new := taskfile.Task{
|
||||||
Task: origTask.Task,
|
Task: origTask.Task,
|
||||||
Desc: r.replace(origTask.Desc),
|
Desc: r.Replace(origTask.Desc),
|
||||||
Sources: r.replaceSlice(origTask.Sources),
|
Sources: r.ReplaceSlice(origTask.Sources),
|
||||||
Generates: r.replaceSlice(origTask.Generates),
|
Generates: r.ReplaceSlice(origTask.Generates),
|
||||||
Status: r.replaceSlice(origTask.Status),
|
Status: r.ReplaceSlice(origTask.Status),
|
||||||
Dir: r.replace(origTask.Dir),
|
Dir: r.Replace(origTask.Dir),
|
||||||
Vars: nil,
|
Vars: nil,
|
||||||
Env: r.replaceVars(origTask.Env),
|
Env: r.ReplaceVars(origTask.Env),
|
||||||
Silent: origTask.Silent,
|
Silent: origTask.Silent,
|
||||||
Method: r.replace(origTask.Method),
|
Method: r.Replace(origTask.Method),
|
||||||
}
|
}
|
||||||
new.Dir, err = homedir.Expand(new.Dir)
|
new.Dir, err = homedir.Expand(new.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -179,7 +48,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
new.Dir = filepath.Join(e.Dir, new.Dir)
|
new.Dir = filepath.Join(e.Dir, new.Dir)
|
||||||
}
|
}
|
||||||
for k, v := range new.Env {
|
for k, v := range new.Env {
|
||||||
static, err := e.handleShVar(v)
|
static, err := e.Compiler.HandleDynamicVar(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -190,10 +59,10 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
new.Cmds = make([]*taskfile.Cmd, len(origTask.Cmds))
|
new.Cmds = make([]*taskfile.Cmd, len(origTask.Cmds))
|
||||||
for i, cmd := range origTask.Cmds {
|
for i, cmd := range origTask.Cmds {
|
||||||
new.Cmds[i] = &taskfile.Cmd{
|
new.Cmds[i] = &taskfile.Cmd{
|
||||||
Task: r.replace(cmd.Task),
|
Task: r.Replace(cmd.Task),
|
||||||
Silent: cmd.Silent,
|
Silent: cmd.Silent,
|
||||||
Cmd: r.replace(cmd.Cmd),
|
Cmd: r.Replace(cmd.Cmd),
|
||||||
Vars: r.replaceVars(cmd.Vars),
|
Vars: r.ReplaceVars(cmd.Vars),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -202,113 +71,11 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
new.Deps = make([]*taskfile.Dep, len(origTask.Deps))
|
new.Deps = make([]*taskfile.Dep, len(origTask.Deps))
|
||||||
for i, dep := range origTask.Deps {
|
for i, dep := range origTask.Deps {
|
||||||
new.Deps[i] = &taskfile.Dep{
|
new.Deps[i] = &taskfile.Dep{
|
||||||
Task: r.replace(dep.Task),
|
Task: r.Replace(dep.Task),
|
||||||
Vars: r.replaceVars(dep.Vars),
|
Vars: r.ReplaceVars(dep.Vars),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &new, r.err
|
return &new, r.Err()
|
||||||
}
|
|
||||||
|
|
||||||
// varReplacer is a help struct that allow us to call "replaceX" funcs multiple
|
|
||||||
// times, without having to check for error each time. The first error that
|
|
||||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
|
||||||
// return the zero value.
|
|
||||||
type varReplacer struct {
|
|
||||||
vars taskfile.Vars
|
|
||||||
strMap map[string]string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *varReplacer) replace(str string) string {
|
|
||||||
if r.err != nil || str == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
r.err = err
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.strMap == nil {
|
|
||||||
r.strMap = r.vars.ToStringMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
if err = templ.Execute(&b, r.strMap); err != nil {
|
|
||||||
r.err = err
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *varReplacer) replaceSlice(strs []string) []string {
|
|
||||||
if r.err != nil || len(strs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
new := make([]string, len(strs))
|
|
||||||
for i, str := range strs {
|
|
||||||
new[i] = r.replace(str)
|
|
||||||
}
|
|
||||||
return new
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *varReplacer) replaceVars(vars taskfile.Vars) taskfile.Vars {
|
|
||||||
if r.err != nil || len(vars) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
new := make(taskfile.Vars, len(vars))
|
|
||||||
for k, v := range vars {
|
|
||||||
new[k] = taskfile.Var{
|
|
||||||
Static: r.replace(v.Static),
|
|
||||||
Sh: r.replace(v.Sh),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
templateFuncs template.FuncMap
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
taskFuncs := template.FuncMap{
|
|
||||||
"OS": func() string { return runtime.GOOS },
|
|
||||||
"ARCH": func() string { return runtime.GOARCH },
|
|
||||||
"catLines": func(s string) string {
|
|
||||||
s = strings.Replace(s, "\r\n", " ", -1)
|
|
||||||
return strings.Replace(s, "\n", " ", -1)
|
|
||||||
},
|
|
||||||
"splitLines": func(s string) []string {
|
|
||||||
s = strings.Replace(s, "\r\n", "\n", -1)
|
|
||||||
return strings.Split(s, "\n")
|
|
||||||
},
|
|
||||||
"fromSlash": func(path string) string {
|
|
||||||
return filepath.FromSlash(path)
|
|
||||||
},
|
|
||||||
"toSlash": func(path string) string {
|
|
||||||
return filepath.ToSlash(path)
|
|
||||||
},
|
|
||||||
"exeExt": func() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return ".exe"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
},
|
|
||||||
// IsSH is deprecated.
|
|
||||||
"IsSH": func() bool { return true },
|
|
||||||
}
|
|
||||||
// Deprecated aliases for renamed functions.
|
|
||||||
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
|
|
||||||
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
|
||||||
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
|
||||||
|
|
||||||
templateFuncs = sprig.TxtFuncMap()
|
|
||||||
for k, v := range taskFuncs {
|
|
||||||
templateFuncs[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
12
watch.go
12
watch.go
@ -21,14 +21,14 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
for i, c := range calls {
|
for i, c := range calls {
|
||||||
tasks[i] = c.Task
|
tasks[i] = c.Task
|
||||||
}
|
}
|
||||||
e.errf("task: Started watching for tasks: %s", strings.Join(tasks, ", "))
|
e.Logger.Errf("task: Started watching for tasks: %s", strings.Join(tasks, ", "))
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
for _, c := range calls {
|
for _, c := range calls {
|
||||||
c := c
|
c := c
|
||||||
go func() {
|
go func() {
|
||||||
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
|
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
|
||||||
e.errf("%v", err)
|
e.Logger.Errf("%v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-w.Event:
|
case event := <-w.Event:
|
||||||
e.verboseErrf("task: received watch event: %v", event)
|
e.Logger.VerboseErrf("task: received watch event: %v", event)
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
ctx, cancel = context.WithCancel(context.Background())
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
@ -52,7 +52,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
c := c
|
c := c
|
||||||
go func() {
|
go func() {
|
||||||
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
|
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
|
||||||
e.errf("%v", err)
|
e.Logger.Errf("%v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
w.TriggerEvent(watcher.Remove, nil)
|
w.TriggerEvent(watcher.Remove, nil)
|
||||||
}()
|
}()
|
||||||
default:
|
default:
|
||||||
e.errf("%v", err)
|
e.Logger.Errf("%v", err)
|
||||||
}
|
}
|
||||||
case <-w.Closed:
|
case <-w.Closed:
|
||||||
return
|
return
|
||||||
@ -75,7 +75,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
|||||||
// re-register each second because we can have new files
|
// re-register each second because we can have new files
|
||||||
for {
|
for {
|
||||||
if err := e.registerWatchedFiles(w, calls...); err != nil {
|
if err := e.registerWatchedFiles(w, calls...); err != nil {
|
||||||
e.errf("%v", err)
|
e.Logger.Errf("%v", err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user