Compare commits

...

45 Commits

Author SHA1 Message Date
Andrey Nering
bc99509395 Merge branch 'master' into v3 2019-09-22 18:43:45 -03:00
Andrey Nering
d3060b0060 Add CHANGELOG for #216 2019-09-14 18:09:34 -03:00
Andrey Nering
14d7f04a81 Always expode .TIMESTAMP and .STATUS when using status: 2019-09-14 18:04:41 -03:00
Andrey Nering
1a28e5e0d4 Few code improvements on #216 2019-09-14 17:54:41 -03:00
Andrey Nering
884cd0d636 Merge branch 'CypherpunkArmory-report-timestamp-to-status' into v3 2019-09-14 17:18:42 -03:00
Andrey Nering
6a7a3c0ae8 Merge pull request #246 from go-task/method-on-v3
Add global "method:" option to allow setting a default for all tasks. Change default from "timestamp" to "checksum"
2019-09-08 23:02:40 -03:00
Andrey Nering
948e6bd57c Update v3 CHANGELOG 2019-09-08 22:59:27 -03:00
Andrey Nering
78595fba0b Make "checksum" the default method in v3 2019-09-08 22:51:56 -03:00
Andrey Nering
8020284b12 Add global method: option to set default method 2019-09-08 22:51:14 -03:00
Andrey Nering
d6a49da870 Merge branch 'master' into v3 2019-09-08 22:12:02 -03:00
Andrey Nering
bae1e1ee9f Update .gitignore 2019-09-07 14:46:46 -03:00
Andrey Nering
7138785500 Merge branch 'master' into v3 2019-09-07 14:44:21 -03:00
Andrey Nering
1a33f9168b Merge branch 'report-timestamp-to-status' of https://github.com/CypherpunkArmory/task into CypherpunkArmory-report-timestamp-to-status 2019-09-01 21:44:23 -03:00
Andrey Nering
ccae3d7383 Update CHANGELOG 2019-08-25 18:17:33 -03:00
Andrey Nering
847651a90a Improve note wording a bit 2019-08-25 18:15:26 -03:00
Andrey Nering
1b8998e7a2 Merge pull request #237 from jaedle/v3
Remove all code related support of version 1
2019-08-25 18:11:47 -03:00
Andrey Nering
dc8fb79759 Merge branch 'master' into v3 2019-08-25 18:05:40 -03:00
Stephen Prater
6b0935d6cf Fix tests 2019-08-25 13:47:29 -07:00
Stephen Prater
d1183ce272 Merge branch 'master' into report-timestamp-to-status
* master:
  Update CHANGELOG
  Small improvements to #228
  Fix a typo
  Fix Checksum.IsUpToDate
  Remove directory check
  Update glob.go
  Separate error handlings for readability
  Re-run the task if generated files do not exist
2019-08-25 13:46:02 -07:00
Stephen Prater
a1aec8178a Export Time Struct to Template 2019-08-25 13:36:48 -07:00
Stephen Prater
cb6fe4bb59 Merge remote-tracking branch 'upstream/v3' into report-timestamp-to-status
* upstream/v3:
  v3.0.0-preview1
  Update v3 changelog
  Only have colored output on v3
  Add --color=false flag to disable colored output
  Update documentation about sprig
  Update CHANGELOG
  Migrate from sprig to slim-sprig
  Fix build after merging master
  Use colors for some output messages
2019-08-25 10:33:13 -07:00
Stephen Prater
db36bc67f1 Changes per feedback 2019-08-25 10:30:00 -07:00
Stephen Prater
e0f72a6193 Apply suggestions from code review
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-25 09:39:39 -07:00
Stephen Prater
1ee684b7c0 Expose timestamp and checksum to status 2019-08-25 09:39:39 -07:00
jaedle
93005512b4 cleanp taskfile reader 2019-08-24 06:28:12 +02:00
jaedle
b8094fd771 bump version of auto generated taskfile 2019-08-19 21:01:36 +02:00
jaedle
af5d9c952d assert error message 2019-08-19 21:01:01 +02:00
jaedle
ce4e187cbc show documentation of version 1, add deprecation notice 2019-08-19 20:59:02 +02:00
jaedle
821c80b61e Fix error message
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-19 20:53:35 +02:00
jaedle
5a6fb7c973 Bump version of taskfile
Co-Authored-By: Andrey Nering <andrey.nering@gmail.com>
2019-08-19 20:53:20 +02:00
jaedle
0f385f9f4e remove v1 2019-08-18 17:37:21 +02:00
Andrey Nering
c47c15ee47 v3.0.0-preview1 2019-08-11 23:43:27 -03:00
Andrey Nering
6803ad2e59 Update v3 changelog 2019-08-11 23:10:12 -03:00
Andrey Nering
d5a791b470 Merge branch 'master' into v3 2019-08-11 23:03:58 -03:00
Andrey Nering
e6255081a8 Merge pull request #207 from go-task/colored-output
Use colors for some output messages
2019-07-07 14:41:25 -03:00
Andrey Nering
623db0ed94 Only have colored output on v3 2019-07-07 14:18:02 -03:00
Andrey Nering
0e575e9c25 Add --color=false flag to disable colored output 2019-07-07 14:13:53 -03:00
Andrey Nering
fb23ba9878 Merge branch 'v3' into colored-output 2019-07-07 14:04:12 -03:00
Andrey Nering
4e09fc7f43 Update documentation about sprig 2019-06-22 22:48:53 -03:00
Andrey Nering
64cfdd815f Update CHANGELOG 2019-06-22 22:47:23 -03:00
Andrey Nering
f6f31e0a8d Merge pull request #219 from go-task/migrate-from-sprig-to-slim-sprig
Migrate from sprig to slim-sprig
2019-06-22 22:37:42 -03:00
Andrey Nering
bd5fb9be03 Migrate from sprig to slim-sprig 2019-06-22 22:17:24 -03:00
Andrey Nering
dd9cdb0ec9 Fix build after merging master 2019-06-15 22:47:15 -03:00
Andrey Nering
7f082a821d Merge branch 'v3' into colored-output 2019-06-15 22:42:15 -03:00
Andrey Nering
8efc38ad82 Use colors for some output messages 2019-05-26 18:36:39 -03:00
44 changed files with 526 additions and 549 deletions

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@ dist/
tags
/bin
/testdata/vars/v1

View File

@@ -1,5 +1,32 @@
# Changelog
# v3.0.0 - Preview 2
- Taskfiles in version 1 are not supported anymore
([#237](https://github.com/go-task/task/pull/237)).
- Added global `method:` option. With this option, you can set a default
method to all tasks in a Taskfile
([#246](https://github.com/go-task/task/issues/246)).
- Changed default method from `timestamp` to `checksum`
([#246](https://github.com/go-task/task/issues/246)).
- New magic variables are now available when using `status:`:
`.TIMESTAMP` which contains the greatest modification date
from the files listed in `sources:`, and `.CHECKSUM`, which
contains a checksum of all files listed in `status:`.
This is useful for manual checking when using external, or even remote,
artifacts when using `status:`
([#216](https://github.com/go-task/task/pull/216)).
## v3.0.0 - Preview 1
- We're now using [slim-sprig](https://github.com/go-task/slim-sprig) instead of
[sprig](https://github.com/Masterminds/sprig), which allowed a file size
reduction of about 22%
([#219](https://github.com/go-task/task/pull/219)).
- We now use some colors on Task output to better distinguish message types -
commands are green, errors are red, etc
([#207](https://github.com/go-task/task/pull/207)).
## v2.7.0 - 2019-09-22
- Fixed panic bug when assigning a global variable

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
includes:
docs: ./docs

View File

@@ -10,6 +10,7 @@ import (
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/args"
"github.com/go-task/task/v2/internal/logger"
"github.com/spf13/pflag"
)
@@ -27,12 +28,14 @@ Example: 'task hello' with the following 'Taskfile.yml' file will generate an
'output.txt' file with the content "hello".
'''
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
version: '3'
tasks:
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
'''
Options:
@@ -61,6 +64,7 @@ func main() {
dir string
entrypoint string
output string
color bool
)
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
@@ -76,6 +80,7 @@ func main() {
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
pflag.BoolVarP(&color, "color", "c", true, "colored output")
pflag.Parse()
if versionFlag {
@@ -114,6 +119,7 @@ func main() {
Dry: dry,
Entrypoint: entrypoint,
Summary: summary,
Color: color,
Stdin: os.Stdin,
Stdout: os.Stdout,
@@ -132,7 +138,7 @@ func main() {
arguments := pflag.Args()
if len(arguments) == 0 {
log.Println("task: No argument given, trying default task")
e.Logger.Errf(logger.Yellow, "task: No argument given, trying default task")
arguments = []string{"default"}
}
@@ -154,7 +160,8 @@ func main() {
}
if err := e.Run(ctx, calls...); err != nil {
log.Fatal(err)
e.Logger.Errf(logger.Red, "%v", err)
os.Exit(1)
}
}

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
tasks:
install:

View File

@@ -15,6 +15,8 @@ available, but not `3.0.0+`.
## Version 1
> NOTE: Taskfiles in version 1 are not supported on Task >= v3.0.0 anymore.
In the first version of the `Taskfile`, the `version:` key was not available,
because the tasks was in the root of the YAML document. Like this:

View File

@@ -266,6 +266,8 @@ The above syntax is also supported in `deps`.
## Prevent unnecessary work
### By fingerprinting locally generated files and their sources
If a task generates something, you can inform Task the source and generated
files, so Task will prevent to run them if not necessary.
@@ -321,6 +323,8 @@ tasks:
> TIP: method `none` skips any validation and always run the task.
### Using programmatic checks to indicate a task is up to date.
Alternatively, you can inform a sequence of tests as `status`. If no error
is returned (exit status 0), the task is considered up-to-date:
@@ -340,15 +344,34 @@ tasks:
- test -f directory/file2.txt
```
Normally, you would use `sources` in combination with
`generates` - but for tasks that generate remote artifacts (Docker images,
deploys, CD releases) the checksum source and timestamps require either
access to the artifact or for an out-of-band refresh of the `.checksum`
fingerprint file.
Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available
for interpolation within `status` commands, depending on the method assigned
to fingerprint the sources. Only `source` globs are fingerprinted.
Note that the `{{.TIMESTAMP}}` variable is a "live" Go `time.Time` struct, and
can be formatted using any of the methods that `time.Time` responds to.
See [the Go Time documentation](https://golang.org/pkg/time/) for more information.
You can use `--force` or `-f` if you want to force a task to run even when
up-to-date.
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
the tasks are not up-to-date.
If you need a certain set of conditions to be _true_ you can use the
`preconditions` stanza. `preconditions` are very similar to `status`
lines except they support `sh` expansion and they SHOULD all return 0.
### Using programmatic checks to cancel execution of an task and it's dependencies
In addition to `status` checks, there are also `preconditions` checks, which are
the logical inverse of `status` checks. That is, if you need a certain set of
conditions to be _true_ you can use the `preconditions` stanza.
`preconditions` are similar to `status` lines except they support `sh`
expansion and they SHOULD all return 0.
```yaml
version: '2'
@@ -512,7 +535,7 @@ This works for all types of variables.
Task parse commands as [Go's template engine][gotemplate] before executing
them. Variables are accessible through dot syntax (`.VARNAME`).
All functions by the Go's [sprig lib](http://masterminds.github.io/sprig/)
All functions by the Go's [slim-sprig lib](https://go-task.github.io/slim-sprig/)
are available. The following example gets the current date in a given format:
```yaml

10
go.mod
View File

@@ -1,13 +1,9 @@
module github.com/go-task/task/v2
require (
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.16.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.0.0 // indirect
github.com/huandu/xstrings v1.1.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/fatih/color v1.7.0
github.com/go-task/slim-sprig v0.0.0-20190623010546-24867827a98b
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-zglob v0.0.1
github.com/radovskyb/watcher v1.0.5
github.com/spf13/pflag v1.0.3

22
go.sum
View File

@@ -1,23 +1,19 @@
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.1.0 h1:9oZY6Z/H3A1gytJxzuicbmV5QoR8M1TAPcn9WTg7vqg=
github.com/huandu/xstrings v1.1.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-task/slim-sprig v0.0.0-20190623010546-24867827a98b h1:z6iCP1USASmEZtKTzynd/rP4vOtBLlsD3v24wItbJIs=
github.com/go-task/slim-sprig v0.0.0-20190623010546-24867827a98b/go.mod h1:XLIiFDBy2M8pA/fEL5rx9xr2EAzrDEO0S5brm5iekOE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -28,6 +24,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20180319223459-c679ae2cc0cb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -37,6 +34,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -5,6 +5,7 @@ import (
"sort"
"text/tabwriter"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
)
@@ -12,10 +13,10 @@ import (
func (e *Executor) PrintTasksHelp() {
tasks := e.tasksWithDesc()
if len(tasks) == 0 {
e.Logger.Outf("task: No tasks with description available")
e.Logger.Outf(logger.Yellow, "task: No tasks with description available")
return
}
e.Logger.Outf("task: Available tasks for this project:")
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
// Format in tab-separated columns with a tab stop of 8.
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)

View File

@@ -10,7 +10,7 @@ import (
const defaultTaskfile = `# https://taskfile.dev
version: '2'
version: '3'
vars:
GREETING: Hello, World!

View File

@@ -1,137 +0,0 @@
package v1
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"github.com/go-task/task/v2/internal/compiler"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/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(context.Background(), opts); err != nil {
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
}
// 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
}

View File

@@ -103,7 +103,7 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -1,38 +1,65 @@
package logger
import (
"fmt"
"io"
"github.com/fatih/color"
)
type PrintFunc func(io.Writer, string, ...interface{})
var (
Default PrintFunc = color.New(color.Reset).FprintfFunc()
Bold PrintFunc = color.New(color.Bold).FprintfFunc()
Blue PrintFunc = color.New(color.FgBlue, color.Bold).FprintfFunc()
Green PrintFunc = color.New(color.FgGreen, color.Bold).FprintfFunc()
Cyan PrintFunc = color.New(color.FgCyan, color.Bold).FprintfFunc()
Yellow PrintFunc = color.New(color.FgYellow, color.Bold).FprintfFunc()
Magenta PrintFunc = color.New(color.FgMagenta, color.Bold).FprintfFunc()
Red PrintFunc = color.New(color.FgRed, color.Bold).FprintfFunc()
)
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
// with optional color.
type Logger struct {
Stdout io.Writer
Stderr io.Writer
Verbose bool
Color bool
}
func (l *Logger) Outf(s string, args ...interface{}) {
// Outf prints stuff to STDOUT.
func (l *Logger) Outf(print PrintFunc, s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stdout, s+"\n", args...)
if !l.Color {
print = Default
}
print(l.Stdout, s+"\n", args...)
}
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
func (l *Logger) VerboseOutf(print PrintFunc, s string, args ...interface{}) {
if l.Verbose {
l.Outf(s, args...)
l.Outf(print, s, args...)
}
}
func (l *Logger) Errf(s string, args ...interface{}) {
// Errf prints stuff to STDERR.
func (l *Logger) Errf(print PrintFunc, s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stderr, s+"\n", args...)
if !l.Color {
print = Default
}
print(l.Stderr, s+"\n", args...)
}
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
// VerboseErrf prints stuff to STDERR if verbose mode is enabled.
func (l *Logger) VerboseErrf(print PrintFunc, s string, args ...interface{}) {
if l.Verbose {
l.Errf(s, args...)
l.Errf(print, s, args...)
}
}

View File

@@ -23,6 +23,10 @@ type Checksum struct {
// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
if len(c.Sources) == 0 {
return false, nil
}
checksumFile := c.checksumFilePath()
data, _ := ioutil.ReadFile(checksumFile)
@@ -84,11 +88,21 @@ func (c *Checksum) checksum(files ...string) (string, error) {
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
// Value implements the Checker Interface
func (c *Checksum) Value() (interface{}, error) {
return c.checksum()
}
// OnError implements the Checker interface
func (c *Checksum) OnError() error {
return os.Remove(c.checksumFilePath())
}
// Kind implements the Checker Interface
func (*Checksum) Kind() string {
return "checksum"
}
func (c *Checksum) checksumFilePath() string {
return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
}

View File

@@ -8,6 +8,15 @@ func (None) IsUpToDate() (bool, error) {
return false, nil
}
// Value implements the Checker interface
func (None) Value() (interface{}, error) {
return "", nil
}
func (None) Kind() string {
return "none"
}
// OnError implements the Checker interface
func (None) OnError() error {
return nil

View File

@@ -9,5 +9,7 @@ var (
// Checker is an interface that checks if the status is up-to-date
type Checker interface {
IsUpToDate() (bool, error)
Value() (interface{}, error)
OnError() error
Kind() string
}

View File

@@ -41,6 +41,29 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
return !generatesMinTime.Before(sourcesMaxTime), nil
}
func (t *Timestamp) Kind() string {
return "timestamp"
}
// Value implements the Checker Interface
func (t *Timestamp) Value() (interface{}, error) {
sources, err := globs(t.Dir, t.Sources)
if err != nil {
return time.Now(), err
}
sourcesMaxTime, err := getMaxTime(sources...)
if err != nil {
return time.Now(), err
}
if sourcesMaxTime.IsZero() {
return time.Unix(0, 0), nil
}
return sourcesMaxTime, nil
}
func getMinTime(files ...string) (time.Time, error) {
var t time.Time
for _, f := range files {

View File

@@ -20,8 +20,8 @@ func printSpaceBetweenSummaries(l *logger.Logger, i int) {
return
}
l.Outf("")
l.Outf("")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "")
}
func PrintTask(l *logger.Logger, t *taskfile.Task) {
@@ -50,14 +50,14 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
for i, line := range lines {
notLastLine := i+1 < len(lines)
if notLastLine || line != "" {
l.Outf(line)
l.Outf(logger.Default, line)
}
}
}
func printTaskName(l *logger.Logger, t *taskfile.Task) {
l.Outf("task: %s", t.Task)
l.Outf("")
l.Outf(logger.Default, "task: %s", t.Task)
l.Outf(logger.Default, "")
}
func hasDescription(t *taskfile.Task) bool {
@@ -65,11 +65,11 @@ func hasDescription(t *taskfile.Task) bool {
}
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
l.Outf(t.Desc)
l.Outf(logger.Default, t.Desc)
}
func printNoDescriptionOrSummary(l *logger.Logger) {
l.Outf("(task does not have description or summary)")
l.Outf(logger.Default, "(task does not have description or summary)")
}
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
@@ -77,11 +77,11 @@ func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
return
}
l.Outf("")
l.Outf("dependencies:")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "dependencies:")
for _, d := range t.Deps {
l.Outf(" - %s", d.Task)
l.Outf(logger.Default, " - %s", d.Task)
}
}
@@ -90,14 +90,14 @@ func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
return
}
l.Outf("")
l.Outf("commands:")
l.Outf(logger.Default, "")
l.Outf(logger.Default, "commands:")
for _, c := range t.Cmds {
isCommand := c.Cmd != ""
if isCommand {
l.Outf(" - %s", c.Cmd)
l.Outf(logger.Default, " - %s", c.Cmd)
} else {
l.Outf(" - Task: %s", c.Task)
l.Outf(logger.Default, " - Task: %s", c.Task)
}
}
}

View File

@@ -5,6 +5,7 @@ type Taskfile struct {
Version string
Expansions int
Output string
Method string
Includes map[string]string
Vars Vars
Env Vars
@@ -13,15 +14,11 @@ type Taskfile struct {
// UnmarshalYAML implements yaml.Unmarshaler interface
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&tf.Tasks); err == nil {
tf.Version = "1"
return nil
}
var taskfile struct {
Version string
Expansions int
Output string
Method string
Includes map[string]string
Vars Vars
Env Vars
@@ -33,6 +30,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Method = taskfile.Method
tf.Includes = taskfile.Includes
tf.Vars = taskfile.Vars
tf.Env = taskfile.Env

View File

@@ -13,17 +13,22 @@ var (
// Vars is a string[string] variables map.
type Vars map[string]Var
// ToStringMap converts Vars to a string map containing only the static
// ToCacheMap converts Vars to a map containing only the static
// variables
func (vs Vars) ToStringMap() (m map[string]string) {
m = make(map[string]string, len(vs))
func (vs Vars) ToCacheMap() (m map[string]interface{}) {
m = make(map[string]interface{}, len(vs))
for k, v := range vs {
if v.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
continue
}
m[k] = v.Static
if v.Live != nil {
m[k] = v.Live
} else {
m[k] = v.Static
}
}
return
}
@@ -31,6 +36,7 @@ func (vs Vars) ToStringMap() (m map[string]string) {
// Var represents either a static or dynamic variable.
type Var struct {
Static string
Live interface{}
Sh string
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/go-task/slim-sprig"
)
var (

View File

@@ -14,8 +14,12 @@ import (
type Templater struct {
Vars taskfile.Vars
strMap map[string]string
err error
cacheMap map[string]interface{}
err error
}
func (r *Templater) ResetCache() {
r.cacheMap = r.Vars.ToCacheMap()
}
func (r *Templater) Replace(str string) string {
@@ -29,12 +33,12 @@ func (r *Templater) Replace(str string) string {
return ""
}
if r.strMap == nil {
r.strMap = r.Vars.ToStringMap()
if r.cacheMap == nil {
r.cacheMap = r.Vars.ToCacheMap()
}
var b bytes.Buffer
if err = templ.Execute(&b, r.strMap); err != nil {
if err = templ.Execute(&b, r.cacheMap); err != nil {
r.err = err
return ""
}
@@ -62,6 +66,7 @@ func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
for k, v := range vars {
new[k] = taskfile.Var{
Static: r.Replace(v.Static),
Live: v.Live,
Sh: r.Replace(v.Sh),
}
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
)
@@ -22,7 +23,7 @@ func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task
})
if err != nil {
e.Logger.Errf("task: %s", p.Msg)
e.Logger.Errf(logger.Magenta, "task: %s", p.Msg)
return false, ErrPreconditionFailed
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
)
@@ -49,25 +50,37 @@ func (e *Executor) statusOnError(t *taskfile.Task) error {
}
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
switch t.Method {
case "", "timestamp":
return &status.Timestamp{
Dir: t.Dir,
Sources: t.Sources,
Generates: t.Generates,
}, nil
method := t.Method
if method == "" {
method = e.Taskfile.Method
}
switch method {
case "timestamp":
return e.timestampChecker(t), nil
case "checksum":
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Generates: t.Generates,
Dry: e.Dry,
}, nil
return e.checksumChecker(t), nil
case "none":
return status.None{}, nil
default:
return nil, fmt.Errorf(`task: invalid method "%s"`, t.Method)
return nil, fmt.Errorf(`task: invalid method "%s"`, method)
}
}
func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
return &status.Timestamp{
Dir: t.Dir,
Sources: t.Sources,
Generates: t.Generates,
}
}
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Generates: t.Generates,
Dry: e.Dry,
}
}
@@ -79,10 +92,10 @@ func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (
Env: getEnviron(t),
})
if err != nil {
e.Logger.VerboseOutf("task: status command %s exited non-zero: %s", s, err)
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err)
return false, nil
}
e.Logger.VerboseOutf("task: status command %s exited zero", s)
e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s)
}
return true, nil
}

66
task.go
View File

@@ -11,7 +11,6 @@ import (
"sync/atomic"
"github.com/go-task/task/v2/internal/compiler"
compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
@@ -41,6 +40,7 @@ type Executor struct {
Silent bool
Dry bool
Summary bool
Color bool
Stdin io.Reader
Stdout io.Writer
@@ -114,38 +114,38 @@ func (e *Executor) Setup() error {
Stdout: e.Stdout,
Stderr: e.Stderr,
Verbose: e.Verbose,
Color: e.Color,
}
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
if err != nil {
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
}
if v < 2 {
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
}
// consider as equal to the greater version if round
if v == 2.0 {
v = 2.6
}
if v < 1 {
return fmt.Errorf(`task: Taskfile version should be greater or equal to v1`)
}
if v > 2.6 {
return fmt.Errorf(`task: Taskfile versions greater than v2.6 not implemented in the version of Task`)
if v > 3.0 {
return fmt.Errorf(`task: Taskfile versions greater than v3.0 not implemented in the version of Task`)
}
if v < 2 {
e.Compiler = &compilerv1.CompilerV1{
Dir: e.Dir,
Vars: e.taskvars,
Logger: e.Logger,
}
} else { // v >= 2
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
TaskfileVars: e.Taskfile.Vars,
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
// Color available only on v3
if v < 3 {
e.Logger.Color = false
}
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
TaskfileVars: e.Taskfile.Vars,
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
if v < 2.1 && e.Taskfile.Output != "" {
@@ -169,6 +169,14 @@ func (e *Executor) Setup() error {
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
}
if e.Taskfile.Method == "" {
if v >= 3 {
e.Taskfile.Method = "checksum"
} else {
e.Taskfile.Method = "timestamp"
}
}
if v <= 2.1 {
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
@@ -228,24 +236,24 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
if upToDate && preCondMet {
if !e.Silent {
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Task)
}
return nil
}
}
if err := e.mkdir(t); err != nil {
e.Logger.Errf("task: cannot make directory %q: %v", t.Dir, err)
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v", t.Dir, err)
}
for i := range t.Cmds {
if err := e.runCommand(ctx, t, call, i); err != nil {
if err2 := e.statusOnError(t); err2 != nil {
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf("task: task error ignored: %v", err)
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
continue
}
@@ -302,7 +310,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
return nil
case cmd.Cmd != "":
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
e.Logger.Errf(cmd.Cmd)
e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd)
}
if e.Dry {
@@ -333,7 +341,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
Stderr: stdErr,
})
if execext.IsExitError(err) && cmd.IgnoreError {
e.Logger.VerboseErrf("task: command error ignored: %v", err)
e.Logger.VerboseErrf(logger.Yellow, "task: command error ignored: %v", err)
return nil
}
return err
@@ -348,8 +356,10 @@ func getEnviron(t *taskfile.Task) []string {
}
environ := os.Environ()
for k, v := range t.Env.ToStringMap() {
environ = append(environ, fmt.Sprintf("%s=%s", k, v))
for k, v := range t.Env.ToCacheMap() {
if s, ok := v.(string); ok {
environ = append(environ, fmt.Sprintf("%s=%s", k, s))
}
}
return environ
}

View File

@@ -69,43 +69,6 @@ func TestEnv(t *testing.T) {
tt.Run(t)
}
func TestVarsV1(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v1",
Target: "default",
TrimSpace: true,
Files: map[string]string{
// hello task:
"foo.txt": "foo",
"bar.txt": "bar",
"baz.txt": "baz",
"tmpl_foo.txt": "foo",
"tmpl_bar.txt": "<no value>",
"tmpl_foo2.txt": "foo2",
"tmpl_bar2.txt": "bar2",
"shtmpl_foo.txt": "foo",
"shtmpl_foo2.txt": "foo2",
"nestedtmpl_foo.txt": "{{.FOO}}",
"nestedtmpl_foo2.txt": "foo2",
"foo2.txt": "foo2",
"bar2.txt": "bar2",
"baz2.txt": "baz2",
"tmpl2_foo.txt": "<no value>",
"tmpl2_foo2.txt": "foo2",
"tmpl2_bar.txt": "<no value>",
"tmpl2_bar2.txt": "<no value>",
"shtmpl2_foo.txt": "<no value>",
"shtmpl2_foo2.txt": "foo2",
"nestedtmpl2_foo2.txt": "{{.FOO2}}",
"override.txt": "bar",
},
}
tt.Run(t)
// Ensure identical results when running hello task directly.
tt.Target = "hello"
tt.Run(t)
}
func TestVarsV2(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/v2",
@@ -144,7 +107,7 @@ func TestVarsV2(t *testing.T) {
}
func TestMultilineVars(t *testing.T) {
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
for _, dir := range []string{"testdata/vars/v2/multiline"} {
tt := fileContentTest{
Dir: dir,
Target: "default",
@@ -168,7 +131,7 @@ func TestMultilineVars(t *testing.T) {
func TestVarsInvalidTmpl(t *testing.T) {
const (
dir = "testdata/vars/v1"
dir = "testdata/vars/v2"
target = "invalid-var-tmpl"
expectError = "template: :1: unexpected EOF"
)
@@ -406,6 +369,34 @@ func TestStatusChecksum(t *testing.T) {
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
}
func TestStatusVariables(t *testing.T) {
const dir = "testdata/status_vars"
_ = os.RemoveAll(filepath.Join(dir, ".task"))
_ = os.Remove(filepath.Join(dir, "generated.txt"))
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: false,
Verbose: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
assert.NoError(t, err)
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
tf := fmt.Sprintf("%s", inf.ModTime())
assert.Contains(t, buff.String(), ts)
assert.Contains(t, buff.String(), tf)
}
func TestInit(t *testing.T) {
const dir = "testdata/init"
var file = filepath.Join(dir, "Taskfile.yml")
@@ -441,7 +432,6 @@ func TestTaskVersion(t *testing.T) {
Dir string
Version string
}{
{"testdata/version/v1", "1"},
{"testdata/version/v2", "2"},
}
@@ -511,7 +501,7 @@ func TestDry(t *testing.T) {
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
assert.Equal(t, "task: touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
t.Errorf("File should not exist %s", file)
}
@@ -668,7 +658,7 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
}
// Ensure that the directory to be created doesn't actually exist.
_ = os.Remove(toBeCreated)
_ = os.RemoveAll(toBeCreated)
if _, err := os.Stat(toBeCreated); err == nil {
t.Errorf("Directory should not exist: %v", err)
}
@@ -679,5 +669,17 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
assert.Equal(t, expected, got, "Mismatch in the working directory")
// Clean-up after ourselves only if no error.
_ = os.Remove(toBeCreated)
_ = os.RemoveAll(toBeCreated)
}
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
e := task.Executor{
Dir: "testdata/version/v1",
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
err := e.Setup()
assert.Error(t, err)
assert.Equal(t, "task: Taskfile versions prior to v2 are not supported anymore", err.Error())
}

View File

@@ -1,9 +1,12 @@
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt
method: checksum
version: '2'
tasks:
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt
method: checksum

View File

@@ -1,7 +1,10 @@
task-1:
deps:
- task: task-2
version: '2'
task-2:
deps:
- task: task-1
tasks:
task-1:
deps:
- task: task-2
task-2:
deps:
- task: task-1

View File

@@ -1,53 +1,56 @@
default:
deps: [d1, d2, d3]
version: '2'
d1:
deps: [d11, d12, d13]
cmds:
- echo 'Text' > d1.txt
tasks:
default:
deps: [d1, d2, d3]
d2:
deps: [d21, d22, d23]
cmds:
- echo 'Text' > d2.txt
d1:
deps: [d11, d12, d13]
cmds:
- echo 'Text' > d1.txt
d3:
deps: [d31, d32, d33]
cmds:
- echo 'Text' > d3.txt
d2:
deps: [d21, d22, d23]
cmds:
- echo 'Text' > d2.txt
d11:
cmds:
- echo 'Text' > d11.txt
d3:
deps: [d31, d32, d33]
cmds:
- echo 'Text' > d3.txt
d12:
cmds:
- echo 'Text' > d12.txt
d11:
cmds:
- echo 'Text' > d11.txt
d13:
cmds:
- echo 'Text' > d13.txt
d12:
cmds:
- echo 'Text' > d12.txt
d21:
cmds:
- echo 'Text' > d21.txt
d13:
cmds:
- echo 'Text' > d13.txt
d22:
cmds:
- echo 'Text' > d22.txt
d21:
cmds:
- echo 'Text' > d21.txt
d23:
cmds:
- echo 'Text' > d23.txt
d22:
cmds:
- echo 'Text' > d22.txt
d31:
cmds:
- echo 'Text' > d31.txt
d23:
cmds:
- echo 'Text' > d23.txt
d32:
cmds:
- echo 'Text' > d32.txt
d31:
cmds:
- echo 'Text' > d31.txt
d33:
cmds:
- echo 'Text' > d33.txt
d32:
cmds:
- echo 'Text' > d32.txt
d33:
cmds:
- echo 'Text' > d33.txt

View File

@@ -1,41 +1,48 @@
abs.txt:
desc: generates dest file based on absolute paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '{{.BUILD_DIR}}/abs.txt'
sources:
- src.txt
generates:
- "{{.BUILD_DIR}}/abs.txt"
version: '2'
rel.txt:
desc: generates dest file based on relative paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '../rel.txt'
sources:
- src.txt
generates:
- "../rel.txt"
tasks:
abs.txt:
desc: generates dest file based on absolute paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '{{.BUILD_DIR}}/abs.txt'
method: timestamp
sources:
- src.txt
generates:
- "{{.BUILD_DIR}}/abs.txt"
sub/src.txt:
desc: generate source file
cmds:
- mkdir -p sub
- echo "hello world" > sub/src.txt
status:
- test -f sub/src.txt
rel.txt:
desc: generates dest file based on relative paths
deps:
- sub/src.txt
dir: sub
cmds:
- cat src.txt > '../rel.txt'
method: timestamp
sources:
- src.txt
generates:
- "../rel.txt"
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
sources:
- sub/src.txt
generates:
- 'my text file.txt'
sub/src.txt:
desc: generate source file
cmds:
- mkdir -p sub
- echo "hello world" > sub/src.txt
method: timestamp
status:
- test -f sub/src.txt
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
method: timestamp
sources:
- sub/src.txt
generates:
- 'my text file.txt'

View File

@@ -1,37 +1,40 @@
default:
vars:
SPANISH: ¡Holla mundo!
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
GERMAN: "Welt!"
deps:
- task: write-file
vars: {CONTENT: Dependence1, FILE: dep1.txt}
- task: write-file
vars: {CONTENT: Dependence2, FILE: dep2.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH|replace \"mundo\" \"dependencia\"}}", FILE: spanish-dep.txt}
cmds:
- task: write-file
vars: {CONTENT: Hello, FILE: hello.txt}
- task: write-file
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
- task: write-file
vars: {CONTENT: "!", FILE: exclamation.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH}}", FILE: spanish.txt}
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese.txt}
- task: write-file
vars: {CONTENT: "{{.GERMAN}}", FILE: german.txt}
- task: non-default
version: '2'
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}
tasks:
default:
vars:
SPANISH: ¡Holla mundo!
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
GERMAN: "Welt!"
deps:
- task: write-file
vars: {CONTENT: Dependence1, FILE: dep1.txt}
- task: write-file
vars: {CONTENT: Dependence2, FILE: dep2.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH|replace \"mundo\" \"dependencia\"}}", FILE: spanish-dep.txt}
cmds:
- task: write-file
vars: {CONTENT: Hello, FILE: hello.txt}
- task: write-file
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
- task: write-file
vars: {CONTENT: "!", FILE: exclamation.txt}
- task: write-file
vars: {CONTENT: "{{.SPANISH}}", FILE: spanish.txt}
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese.txt}
- task: write-file
vars: {CONTENT: "{{.GERMAN}}", FILE: german.txt}
- task: non-default
non-default:
vars:
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
cmds:
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese2.txt}
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}
non-default:
vars:
PORTUGUESE: "{{.PORTUGUESE_HELLO_WORLD}}"
cmds:
- task: write-file
vars: {CONTENT: "{{.PORTUGUESE}}", FILE: portuguese2.txt}

View File

@@ -1,5 +1,8 @@
gen-foo:
cmds:
- touch foo.txt
status:
- test -f foo.txt
version: '2'
tasks:
gen-foo:
cmds:
- touch foo.txt
status:
- test -f foo.txt

1
testdata/status_vars/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
generated.txt

10
testdata/status_vars/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
tasks:
build:
sources:
- ./source.txt
status:
- echo "{{.CHECKSUM}}"
- echo '{{.TIMESTAMP.Unix}}'
- echo '{{.TIMESTAMP}}'

1
testdata/status_vars/source.txt vendored Normal file
View File

@@ -0,0 +1 @@
Hello, World!

View File

@@ -1,4 +1,4 @@
version: 2
version: '2'
tasks:
task-with-summary:

View File

@@ -1 +0,0 @@
*.txt

View File

@@ -1,48 +0,0 @@
default:
deps: [hello]
hello:
cmds:
- echo {{.FOO}} > foo.txt
- echo {{.BAR}} > bar.txt
- echo {{.BAZ}} > baz.txt
- echo '{{.TMPL_FOO}}' > tmpl_foo.txt
- echo '{{.TMPL_BAR}}' > tmpl_bar.txt
- echo '{{.TMPL_FOO2}}' > tmpl_foo2.txt
- echo '{{.TMPL_BAR2}}' > tmpl_bar2.txt
- echo '{{.SHTMPL_FOO}}' > shtmpl_foo.txt
- echo '{{.SHTMPL_FOO2}}' > shtmpl_foo2.txt
- echo '{{.NESTEDTMPL_FOO}}' > nestedtmpl_foo.txt
- echo '{{.NESTEDTMPL_FOO2}}' > nestedtmpl_foo2.txt
- echo {{.FOO2}} > foo2.txt
- echo {{.BAR2}} > bar2.txt
- echo {{.BAZ2}} > baz2.txt
- echo '{{.TMPL2_FOO}}' > tmpl2_foo.txt
- echo '{{.TMPL2_BAR}}' > tmpl2_bar.txt
- echo '{{.TMPL2_FOO2}}' > tmpl2_foo2.txt
- echo '{{.TMPL2_BAR2}}' > tmpl2_bar2.txt
- echo '{{.SHTMPL2_FOO}}' > shtmpl2_foo.txt
- echo '{{.SHTMPL2_FOO2}}' > shtmpl2_foo2.txt
- echo '{{.NESTEDTMPL2_FOO2}}' > nestedtmpl2_foo2.txt
- echo {{.OVERRIDE}} > override.txt
vars:
FOO: foo
BAR: $echo bar
BAZ:
sh: echo baz
TMPL_FOO: "{{.FOO}}"
TMPL_BAR: "{{.BAR}}"
TMPL_FOO2: "{{.FOO2}}"
TMPL_BAR2: "{{.BAR2}}"
SHTMPL_FOO:
sh: "echo '{{.FOO}}'"
SHTMPL_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL_FOO: "{{.TMPL_FOO}}"
NESTEDTMPL_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "bar"
invalid-var-tmpl:
vars:
CHARS: "abcd"
INVALID: "{{range .CHARS}}no end"

View File

@@ -1,12 +0,0 @@
FOO2: foo2
BAR2: $echo bar2
BAZ2:
sh: echo baz2
TMPL2_FOO: "{{.FOO}}"
TMPL2_BAR: "{{.BAR}}"
TMPL2_FOO2: "{{.FOO2}}"
TMPL2_BAR2: "{{.BAR2}}"
SHTMPL2_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL2_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "foo"

View File

@@ -1,43 +0,0 @@
default:
vars:
MULTILINE: "\n\nfoo\n bar\nfoobar\n\nbaz\n\n"
cmds:
- task: file
vars:
CONTENT:
sh: "echo 'foo\nbar'"
FILE: "echo_foobar.txt"
- task: file
vars:
CONTENT:
sh: "echo -n 'foo\nbar'"
FILE: "echo_n_foobar.txt"
- task: file
vars:
CONTENT:
sh: echo -n "{{.MULTILINE}}"
FILE: "echo_n_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE}}"
FILE: "var_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE | catLines}}"
FILE: "var_catlines.txt"
- task: enumfile
vars:
LINES: "{{.MULTILINE}}"
FILE: "var_enumfile.txt"
file:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{.CONTENT}}
EOF
enumfile:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{range $i, $line := .LINES| splitLines}}{{$i}}:{{$line}}
{{end}}EOF

View File

@@ -1,9 +1,9 @@
version: 2
version: '2'
tasks:
foo:
cmds:
- echo "Foo"
bar:
cmds:
- echo "Bar"

View File

@@ -2,8 +2,10 @@ package task
import (
"path/filepath"
"strings"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
)
@@ -20,6 +22,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
if err != nil {
return nil, err
}
r := templater.Templater{Vars: vars}
new := taskfile.Task{
@@ -27,7 +30,6 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
Desc: r.Replace(origTask.Desc),
Sources: r.ReplaceSlice(origTask.Sources),
Generates: r.ReplaceSlice(origTask.Generates),
Status: r.ReplaceSlice(origTask.Status),
Dir: r.Replace(origTask.Dir),
Vars: nil,
Env: nil,
@@ -94,5 +96,21 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
}
}
if len(origTask.Status) > 0 {
for _, checker := range []status.Checker{e.timestampChecker(&new), e.checksumChecker(&new)} {
value, err := checker.Value()
if err != nil {
return nil, err
}
vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Live: value}
}
// Adding new variables, requires us to refresh the templaters
// cache of the the values manually
r.ResetCache()
new.Status = r.ReplaceSlice(origTask.Status)
}
return &new, r.Err()
}

View File

@@ -8,6 +8,7 @@ import (
"syscall"
"time"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/mattn/go-zglob"
"github.com/radovskyb/watcher"
@@ -24,14 +25,14 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
for i, c := range calls {
tasks[i] = c.Task
}
e.Logger.Errf("task: Started watching for tasks: %s", strings.Join(tasks, ", "))
e.Logger.Errf(logger.Green, "task: Started watching for tasks: %s", strings.Join(tasks, ", "))
ctx, cancel := context.WithCancel(context.Background())
for _, c := range calls {
c := c
go func() {
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
}()
}
@@ -49,7 +50,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
for {
select {
case event := <-w.Event:
e.Logger.VerboseErrf("task: received watch event: %v", event)
e.Logger.VerboseErrf(logger.Magenta, "task: received watch event: %v", event)
cancel()
ctx, cancel = context.WithCancel(context.Background())
@@ -57,7 +58,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
c := c
go func() {
if err := e.RunTask(ctx, c); err != nil && !isContextError(err) {
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
}()
}
@@ -68,7 +69,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
w.TriggerEvent(watcher.Remove, nil)
}()
default:
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
case <-w.Closed:
cancel()
@@ -81,7 +82,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
// re-register each second because we can have new files
for {
if err := e.registerWatchedFiles(w, calls...); err != nil {
e.Logger.Errf("%v", err)
e.Logger.Errf(logger.Red, "%v", err)
}
time.Sleep(time.Second)
}