Compare commits

...

40 Commits

Author SHA1 Message Date
Andrey Nering
e6c6cc7811 v3.7.1 2021-09-04 21:52:46 -03:00
Andrey Nering
fa0e72bd69 Documentation typo
Fixes #555
2021-09-04 21:30:28 -03:00
Andrey Nering
18decac44d Homebrew: Automate with Goreleaser and start publishing M1 binaries 2021-09-04 21:26:17 -03:00
Andrey Nering
1012a0cf2b Upgrade to Go 1.17. Set 1.16 as the minimal version 2021-09-04 21:04:40 -03:00
Andrey Nering
7e4de945cf Merge pull request #564 from JulienBreux/feat/upgrade-goreleaser-action
Upgrade Goreleaser action to v2 - To ensure mac/m1 mac/arm release
2021-09-04 20:56:16 -03:00
Andrey Nering
a468272726 Merge pull request #567 from LukeCarrier/go-1.16
Build with Go 1.16
2021-09-04 20:52:24 -03:00
Luke Carrier
039d8f000d Build with Go 1.16
This enables darwin-arm64 builds to support Apple Silicon-based Macs.
2021-09-04 22:56:39 +01:00
Julien Breux
d35f960a8a Upgrade Goreleaser action to v2 2021-09-02 17:45:01 +02:00
Andrey Nering
634f8ed574 Merge pull request #562 from notnmeyer/fix-fish-shell-completions
Fix fish completions
2021-09-01 13:34:10 -03:00
Nate Meyer
d369451308 keep the sed, we do want to skip the first line 2021-09-01 08:54:48 -07:00
Nate Meyer
8f1202424d properly format each line 2021-09-01 08:33:08 -07:00
Andrey Nering
50e5813222 Merge pull request #544 from AlexanderBeyn/fix-bash-completion
Fix bash completions when the current argument begins with a dash
2021-08-07 13:54:39 -03:00
Andrey Nering
7bc268aeaa Merge pull request #545 from go-task/dependabot/go_modules/mvdan.cc/sh/v3-3.3.1
Bump mvdan.cc/sh/v3 from 3.3.0 to 3.3.1
2021-08-07 13:53:22 -03:00
dependabot[bot]
537b5b1e25 Bump mvdan.cc/sh/v3 from 3.3.0 to 3.3.1
Bumps [mvdan.cc/sh/v3](https://github.com/mvdan/sh) from 3.3.0 to 3.3.1.
- [Release notes](https://github.com/mvdan/sh/releases)
- [Changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdan/sh/compare/v3.3.0...v3.3.1)

---
updated-dependencies:
- dependency-name: mvdan.cc/sh/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-07 11:03:11 +00:00
Alexander Beyn
aae38f8ce7 Don't let grep treat the search pattern as an option 2021-08-07 02:28:52 -07:00
Andrey Nering
ad05432bcf v3.7.0 2021-07-31 21:36:03 -03:00
Andrey Nering
8aa983257d Small adjustments and CHANGELOG for #359 2021-07-31 20:29:59 -03:00
Andrey Nering
046a97d1e5 go.mod: Upgrade minimal Go version to v1.15 2021-07-31 20:29:19 -03:00
Ross Hammermeister
d28649b13d Refactoring startExecution 2021-07-31 19:36:40 -03:00
Ross Hammermeister
3e16ca37bc Updating version checking 2021-07-31 19:36:40 -03:00
Ross Hammermeister
2da38a5bdc Cleaning up the run code 2021-07-31 19:36:40 -03:00
Ross Hammermeister
bbe1d8b52e Adding proper version checking for taskfile when using run 2021-07-31 19:36:40 -03:00
Ross Hammermeister
97c85e39c3 Only run task once for #53 2021-07-31 19:36:40 -03:00
Andrey Nering
a7b59e5b12 community.md: Update title level 2021-07-23 18:32:13 -03:00
Andrey Nering
9eb1252ce9 Merge pull request #531 from biozz/patch-2
Update community.md about Sublime Text 4 package
2021-07-23 18:30:11 -03:00
Ivan Elfimov
0e01e13670 Update community.md about Sublime Text 4 package
Added a simple abstract about Sublime Text 4 package with the links to the source code and package control.
2021-07-23 23:45:22 +03:00
Andrey Nering
239e61e718 v3.6.0 2021-07-10 23:13:03 -03:00
Andrey Nering
22549e9fd8 CHANGELOG: Fix typo 2021-07-10 22:04:15 -03:00
Andrey Nering
1f9fd24064 Small improvement and CHANGELOG for #477 2021-07-10 21:58:03 -03:00
Andrey Nering
a7594740e3 Merge pull request #477 from mrngsht/sources_evaluation
Evaluate sources also if status is up-to-date
2021-07-10 21:54:47 -03:00
Andrey Nering
945c72cf6c Small optimization and bug fix: don't compute variables if not needed for dotenv:
Closes #517
2021-07-10 19:46:53 -03:00
Andrey Nering
824b0c0132 Improve error message 2021-07-10 19:33:36 -03:00
Andrey Nering
51e9f2f579 Site: Enable search 2021-07-10 19:13:04 -03:00
Andrey Nering
e8ec33d9d0 Site: Return to light theme 2021-07-10 19:07:17 -03:00
Andrey Nering
3bbbaf12fd v3.5.0 2021-07-04 21:53:22 -03:00
Andrey Nering
30ffacd879 Update currency 2021-07-04 21:47:36 -03:00
Andrey Nering
75e9b7791c Remove Patreon
Also, do a few more updates.
2021-07-04 21:45:07 -03:00
Andrey Nering
4b665ab19a Add CHANGELOG entry for 08265ed1d7 2021-06-05 16:04:01 -03:00
Nicolas Fouché
08265ed1d7 Allow vars in dotenv paths, including environment variables
Closes #453
Closes #434
Ref #433

Co-authored-by: Andrey Nering <andrey@nering.com.br>
2021-06-05 16:00:19 -03:00
mrngsht
58c69e36a1 Evaluate sources also if status is up-to-date 2021-04-21 21:56:25 +09:00
36 changed files with 543 additions and 120 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,2 @@
open_collective: task
patreon: andreynering
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A&currency_code=BRL&source=url'
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A&currency_code=USD&source=url'

View File

@@ -15,10 +15,10 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15.x
go-version: 1.17.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v1
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist

View File

@@ -5,7 +5,7 @@ jobs:
name: Test
strategy:
matrix:
go-version: [1.14.x, 1.15.x]
go-version: [1.16.x, 1.17.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:

View File

@@ -1,6 +1,6 @@
build:
binary: task
main: cmd/task
main: ./cmd/task
goos:
- windows
- darwin
@@ -50,3 +50,17 @@ nfpms:
- deb
- rpm
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
brews:
- name: go-task
description: Task runner / simpler Make alternative written in Go
license: MIT
homepage: https://taskfile.dev
folder: Formula
tap:
owner: go-task
name: homebrew-tap
test:
system "#{bin}/task", "--help"
url_template: https://github.com/go-task/task/releases/download/{{.Tag}}/{{.ArtifactName}}
commit_msg_template: Bump {{.Tag}}

View File

@@ -1,5 +1,31 @@
# Changelog
## 3.7.1 - 2021-09-04
- Add official support to Apple M1 ([#564](https://github.com/go-task/task/pull/564), [#567](https://github.com/go-task/task/pull/567)).
- Our [official Homebrew tap](https://github.com/go-task/homebrew-tap) will
support more platforms, including Apple M1
## v3.7.0 - 2021-07-31
- Add `run:` setting to control if tasks should run multiple times or not.
Available options are `always` (the default), `when_changed` (if a variable
modified the task) and `once` (run only once no matter what).
This is a long time requested feature. Enjoy!
([#53](https://github.com/go-task/task/issues/53), [#359](https://github.com/go-task/task/pull/359)).
## v3.6.0 - 2021-07-10
- Allow using both `sources:` and `status:` in the same task
([#411](https://github.com/go-task/task/issues/411), [#427](https://github.com/go-task/task/issues/427), [#477](https://github.com/go-task/task/pull/477)).
- Small optimization and bug fix: don't compute variables if not needed for
`dotenv:` ([#517](https://github.com/go-task/task/issues/517)).
## v3.5.0 - 2021-07-04
- Add support for interpolation in `dotenv:`
([#433](https://github.com/go-task/task/discussions/433), [#434](https://github.com/go-task/task/issues/434), [#453](https://github.com/go-task/task/pull/453)).
## v3.4.3 - 2021-05-30
- Add support for the `NO_COLOR` environment variable.

View File

@@ -15,7 +15,7 @@ _task_completion()
return
fi
COMPREPLY=($(compgen -c | echo "$scripts" | grep $curr_arg));
COMPREPLY=($(compgen -c | echo "$scripts" | grep -- $curr_arg));
}
complete -F _task_completion task

View File

@@ -1,5 +1,5 @@
function __task_get_tasks --description "Prints all available tasks with their description"
task -l | sed '1d' | awk '{ $1=""; print $0 }' | tr ': ', '\t' | string trim
task -l | sed '1d' | awk '{ $1=""; print $0 }' | sed 's/:\ /\t/g' | string trim
end
complete -c task -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was

View File

@@ -23,6 +23,12 @@ Additionally, there's also some work done by
extension, which has its code [here](https://github.com/paulvarache/vscode-taskfile)
and is published [here](https://marketplace.visualstudio.com/items?itemName=paulvarache.vscode-taskfile).
### Sublime Text 4 package
There is a convenience wrapper for initializing and running tasks from Sublime Text's command palette. The package is
developed by [@biozz](https://github.com/biozz), the source code is available [here](https://github.com/biozz/sublime-taskfile)
and it is published on Package Control [here](https://packagecontrol.io/packages/Taskfile).
## Installation methods
Some installation methods are maintained by third party:

View File

@@ -14,22 +14,14 @@ these options to donate:
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
- [$20 per month](https://opencollective.com/task/contribute/sponsor-4035/checkout)
- [$50 per month](https://opencollective.com/task/contribute/sponsor-28775/checkout)
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
## Patreon
I'm also on [Patreon](https://www.patreon.com/andreynering) if
you prefer:
- [$5 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229277)
- [$10 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229276)
- [$15 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229275)
You can choose a custom value on any of the links above.
Patreon does not support one-time donation. As a workaround you can fire a
subscription and cancel it once the donation was succeded.
## PayPal
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A&currency_code=BRL&source=url)
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A&currency_code=USD&source=url)
## PIX (Brazil only)
If you're Brazilian, you can donate any value by
<a href="/pix.png" target="_blank">using this QR Code</a>.

View File

@@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="A task runner / simpler Make alternative written in Go">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple-dark.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
<style>
#logo {
@@ -43,6 +43,7 @@
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-themeable/dist/js/docsify-themeable.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-tabs"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-yaml.min.js"></script>
</body>

BIN
docs/pix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -78,18 +78,25 @@ setting:
KEYNAME=VALUE
```
```
# testing/.env
ENDPOINT=testing.com
```
```yaml
# Taskfile.yml
version: '3'
dotenv: ['.env']
env:
ENV: testing
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
tasks:
greet:
cmds:
- echo "Using $KEYNAME"
- echo "Using $KEYNAME and endpoint $ENDPOINT"
```
## Including other Taskfiles
@@ -446,6 +453,47 @@ tasks:
- echo "I will not run"
```
### Limiting when tasks run
If a task executed by multiple `cmds` or multiple `deps` you can control
when it is executed using `run`. `run` can also be set at the root
of the Taskfile to change the behavior of all the tasks unless explicitly
overridden.
Supported values for `run`:
* `always` (default) always attempt to invoke the task regardless of the
number of previous executions
* `once` only invoke this task once regardless of the number of references
* `when_changed` only invokes the task once for each unique set of variables
passed into the task
```yaml
version: '3'
tasks:
default:
cmds:
- task: generate-file
vars: { CONTENT: '1' }
- task: generate-file
vars: { CONTENT: '2' }
- task: generate-file
vars: { CONTENT: '2' }
generate-file:
run: when_changed
deps:
- install-deps
cmds:
- echo {{.CONTENT}}
install-deps:
run: once
cmds:
- sleep 5 # long operation like installing packages
```
## Variables
When doing interpolation of variables, Task will look for the below.
@@ -569,7 +617,7 @@ Task also adds the following functions:
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
path format to `/`.
- `fromSlash`: Opposite of `toSlash`. Does nothing on Unix, but on Windows
converts a string from `\` path format to `/`.
converts a string from `/` path format to `\`.
- `exeExt`: Returns the right executable extension for the current OS
(`".exe"` for Windows, `""` for others).

5
go.mod
View File

@@ -5,12 +5,13 @@ require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
github.com/joho/godotenv v1.3.0
github.com/mattn/go-zglob v0.0.3
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/radovskyb/watcher v1.0.7
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
mvdan.cc/sh/v3 v3.3.0
mvdan.cc/sh/v3 v3.3.1
)
go 1.13
go 1.16

24
go.sum
View File

@@ -1,6 +1,6 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.13 h1:rTPnd/xocYRjutMfqide2zle1u96upp1gm6eUHKi7us=
github.com/creack/pty v1.1.13/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@@ -11,7 +11,7 @@ github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/google/renameio v1.0.1-0.20210406141108-81588dbe0453/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -27,12 +27,14 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/go-internal v1.7.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -43,11 +45,11 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324 h1:pAwJxDByZctfPwzlNGrDN2BQLsdPb9NkhoTJtUkAO28=
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -56,5 +58,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
mvdan.cc/sh/v3 v3.3.0 h1:ujzElMnry63f4I5sjPFxzo6xia+gwsHZM0yyauuyZ6k=
mvdan.cc/sh/v3 v3.3.0/go.mod h1:dh3avhLDhJJ/MJKzbak6FYn+DJKUWk7Fb6Dh5mGdv6Y=
mvdan.cc/sh/v3 v3.3.1 h1:aA0i7NZOc1oV5jfAH20FCz+QsmI/TX7FiAquC5Rdo5o=
mvdan.cc/sh/v3 v3.3.1/go.mod h1:DpbFT2B4fXpKiq69fEoMe+71JrmUn5aUekYy9fNKnQw=

28
hash.go Normal file
View File

@@ -0,0 +1,28 @@
package task
import (
"fmt"
"github.com/go-task/task/v3/internal/hash"
"github.com/go-task/task/v3/taskfile"
)
func (e *Executor) GetHash(t *taskfile.Task) (string, error) {
r := t.Run
if r == "" {
r = e.Taskfile.Run
}
var h hash.HashFunc
switch r {
case "always":
h = hash.Empty
case "once":
h = hash.Name
case "when_changed":
h = hash.Hash
default:
return "", fmt.Errorf(`task: invalid run "%s"`, r)
}
return h(t)
}

View File

@@ -7,6 +7,7 @@ import (
// Compiler handles compilation of a task before its execution.
// E.g. variable merger, template processing, etc.
type Compiler interface {
GetTaskfileVariables() (*taskfile.Vars, error)
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
HandleDynamicVar(v taskfile.Var, dir string) (string, error)

View File

@@ -30,6 +30,10 @@ type CompilerV2 struct {
muDynamicCache sync.Mutex
}
func (c *CompilerV2) GetTaskfileVariables() (*taskfile.Vars, error) {
return &taskfile.Vars{}, nil
}
// FastGetVariables is a no-op on v2
func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
return c.GetVariables(t, call)
@@ -105,7 +109,7 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error)
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)
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command

View File

@@ -29,17 +29,23 @@ type CompilerV3 struct {
muDynamicCache sync.Mutex
}
func (c *CompilerV3) GetTaskfileVariables() (*taskfile.Vars, error) {
return c.getVariables(nil, nil, true)
}
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
return c.getVariables(t, call, true)
return c.getVariables(t, &call, true)
}
func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
return c.getVariables(t, call, false)
return c.getVariables(t, &call, false)
}
func (c *CompilerV3) getVariables(t *taskfile.Task, call taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
result := compiler.GetEnviron()
result.Set("TASK", taskfile.Var{Static: t.Task})
if t != nil {
result.Set("TASK", taskfile.Var{Static: t.Task})
}
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
return func(k string, v taskfile.Var) error {
@@ -74,6 +80,11 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call taskfile.Call, evaluate
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
return nil, err
}
if t == nil || call == nil {
return result, nil
}
if err := call.Vars.Range(rangeFunc); err != nil {
return nil, err
}
@@ -125,7 +136,7 @@ func (c *CompilerV3) HandleDynamicVar(v taskfile.Var, dir string) (string, error
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)
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command

24
internal/hash/hash.go Normal file
View File

@@ -0,0 +1,24 @@
package hash
import (
"fmt"
"github.com/mitchellh/hashstructure/v2"
"github.com/go-task/task/v3/taskfile"
)
type HashFunc func(*taskfile.Task) (string, error)
func Empty(*taskfile.Task) (string, error) {
return "", nil
}
func Name(t *taskfile.Task) (string, error) {
return t.Task, nil
}
func Hash(t *taskfile.Task) (string, error) {
h, err := hashstructure.Hash(t, hashstructure.FormatV2, nil)
return fmt.Sprintf("%s:%d", t.Task, h), err
}

View File

@@ -29,16 +29,35 @@ func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
}
func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
if len(t.Status) == 0 && len(t.Sources) == 0 {
return false, nil
}
if len(t.Status) > 0 {
return e.isTaskUpToDateStatus(ctx, t)
isUpToDate, err := e.isTaskUpToDateStatus(ctx, t)
if err != nil {
return false, err
}
if !isUpToDate {
return false, nil
}
}
checker, err := e.getStatusChecker(t)
if err != nil {
return false, err
if len(t.Sources) > 0 {
checker, err := e.getStatusChecker(t)
if err != nil {
return false, err
}
isUpToDate, err := checker.IsUpToDate()
if err != nil {
return false, err
}
if !isUpToDate {
return false, nil
}
}
return checker.IsUpToDate()
return true, nil
}
func (e *Executor) statusOnError(t *taskfile.Task) error {

143
task.go
View File

@@ -58,6 +58,8 @@ type Executor struct {
concurrencySemaphore chan struct{}
taskCallCount map[string]*int32
mkdirMutexMap map[string]*sync.Mutex
executionHashes map[string]context.Context
executionHashesMutex sync.Mutex
}
// Run runs Task
@@ -149,9 +151,12 @@ func (e *Executor) Setup() error {
if v == 2.0 {
v = 2.6
}
if v == 3.0 {
v = 3.7
}
if v > 3.0 {
return fmt.Errorf(`task: Taskfile versions greater than v3.0 not implemented in the version of Task`)
if v > 3.7 {
return fmt.Errorf(`task: Taskfile versions greater than v3.7 not implemented in the version of Task`)
}
// Color available only on v3
@@ -176,6 +181,23 @@ func (e *Executor) Setup() error {
}
}
if v >= 3.0 {
env, err := read.Dotenv(e.Compiler, e.Taskfile, e.Dir)
if err != nil {
return err
}
err = env.Range(func(key string, value taskfile.Var) error {
if _, ok := e.Taskfile.Env.Mapping[key]; !ok {
e.Taskfile.Env.Set(key, value)
}
return nil
})
if err != nil {
return err
}
}
if v < 2.1 && e.Taskfile.Output != "" {
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
}
@@ -243,6 +265,24 @@ func (e *Executor) Setup() error {
}
}
if v < 3.7 {
if e.Taskfile.Run != "" {
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
}
for _, task := range e.Taskfile.Tasks {
if task.Run != "" {
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
}
}
}
if e.Taskfile.Run == "" {
e.Taskfile.Run = "always"
}
e.executionHashes = make(map[string]context.Context)
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
for k := range e.Taskfile.Tasks {
@@ -269,48 +309,50 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
release := e.acquireConcurrencyLimit()
defer release()
if err := e.runDeps(ctx, t); err != nil {
return err
}
if !e.Force {
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
if err != nil {
return e.startExecution(ctx, t, func(ctx context.Context) error {
if err := e.runDeps(ctx, t); err != nil {
return err
}
upToDate, err := e.isTaskUpToDate(ctx, t)
if err != nil {
return err
}
if upToDate && preCondMet {
if !e.Silent {
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Name())
}
return nil
}
}
if err := e.mkdir(t); err != nil {
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(logger.Yellow, "task: error cleaning status on error: %v", err2)
if !e.Force {
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
if err != nil {
return err
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
continue
upToDate, err := e.isTaskUpToDate(ctx, t)
if err != nil {
return err
}
return &taskRunError{t.Task, err}
if upToDate && preCondMet {
if !e.Silent {
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Name())
}
return nil
}
}
}
return nil
if err := e.mkdir(t); err != nil {
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(logger.Yellow, "task: error cleaning status on error: %v", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v", err)
continue
}
return &taskRunError{t.Task, err}
}
}
return nil
})
}
func (e *Executor) mkdir(t *taskfile.Task) error {
@@ -428,3 +470,32 @@ func getEnviron(t *taskfile.Task) []string {
return environ
}
func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute func(ctx context.Context) error) error {
h, err := e.GetHash(t)
if err != nil {
return err
}
if h == "" {
return execute(ctx)
}
e.executionHashesMutex.Lock()
otherExecutionCtx, ok := e.executionHashes[h]
if ok {
e.executionHashesMutex.Unlock()
e.Logger.VerboseErrf(logger.Magenta, "task: skipping execution of task: %s", h)
<-otherExecutionCtx.Done()
return nil
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
e.executionHashes[h] = ctx
e.executionHashesMutex.Unlock()
return execute(ctx)
}

View File

@@ -252,12 +252,18 @@ func TestDeps(t *testing.T) {
func TestStatus(t *testing.T) {
const dir = "testdata/status"
var file = filepath.Join(dir, "foo.txt")
_ = os.Remove(file)
files := []string{
"foo.txt",
"bar.txt",
}
if _, err := os.Stat(file); err == nil {
t.Errorf("File should not exist: %v", err)
for _, f := range files {
path := filepath.Join(dir, f)
_ = os.Remove(path)
if _, err := os.Stat(path); err == nil {
t.Errorf("File should not exist: %v", err)
}
}
var buff bytes.Buffer
@@ -269,17 +275,33 @@ func TestStatus(t *testing.T) {
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
if _, err := os.Stat(file); err != nil {
t.Errorf("File should exist: %v", err)
for _, f := range files {
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
t.Errorf("File should exist: %v", err)
}
}
e.Silent = false
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
t.Errorf("Wrong output message: %s", buff.String())
}
// all: not up-to-date
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
buff.Reset()
// status: not up-to-date
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
buff.Reset()
// sources: not up-to-date
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
assert.Equal(t, "task: [gen-bar] touch bar.txt", strings.TrimSpace(buff.String()))
buff.Reset()
// all: up-to-date
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
assert.Equal(t, `task: Task "gen-bar" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
}
func TestPrecondition(t *testing.T) {
@@ -904,6 +926,44 @@ func TestDotenvShouldAllowMissingEnv(t *testing.T) {
tt.Run(t)
}
func TestDotenvHasLocalEnvInPath(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dotenv/local_env_in_path",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"var.txt": "VAR='var_in_dot_env_1'\n",
},
}
tt.Run(t)
}
func TestDotenvHasLocalVarInPath(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dotenv/local_var_in_path",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"var.txt": "VAR='var_in_dot_env_3'\n",
},
}
tt.Run(t)
}
func TestDotenvHasEnvVarInPath(t *testing.T) {
os.Setenv("ENV_VAR", "testing")
tt := fileContentTest{
Dir: "testdata/dotenv/env_var_in_path",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"var.txt": "VAR='var_in_dot_env_2'\n",
},
}
tt.Run(t)
}
func TestExitImmediately(t *testing.T) {
const dir = "testdata/exit_immediately"
@@ -919,3 +979,14 @@ func TestExitImmediately(t *testing.T) {
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
}
func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/run",
Target: "generate-hash",
Files: map[string]string{
"hash.txt": "starting 1\n1\n2\n",
},
}
tt.Run(t)
}

50
taskfile/read/dotenv.go Normal file
View File

@@ -0,0 +1,50 @@
package read
import (
"os"
"path/filepath"
"github.com/joho/godotenv"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.Vars, error) {
if len(tf.Dotenv) == 0 {
return nil, nil
}
vars, err := c.GetTaskfileVariables()
if err != nil {
return nil, err
}
env := &taskfile.Vars{}
tr := templater.Templater{Vars: vars, RemoveNoValue: true}
for _, dotEnvPath := range tf.Dotenv {
dotEnvPath = tr.Replace(dotEnvPath)
if !filepath.IsAbs(dotEnvPath) {
dotEnvPath = filepath.Join(dir, dotEnvPath)
}
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
continue
}
envs, err := godotenv.Read(dotEnvPath)
if err != nil {
return nil, err
}
for key, value := range envs {
if _, ok := env.Mapping[key]; !ok {
env.Set(key, taskfile.Var{Static: value})
}
}
}
return env, nil
}

View File

@@ -7,7 +7,6 @@ import (
"path/filepath"
"runtime"
"github.com/joho/godotenv"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/templater"
@@ -37,27 +36,6 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return nil, err
}
if v >= 3.0 {
for _, dotEnvPath := range t.Dotenv {
if !filepath.IsAbs(dotEnvPath) {
dotEnvPath = filepath.Join(dir, dotEnvPath)
}
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
continue
}
envs, err := godotenv.Read(dotEnvPath)
if err != nil {
return nil, err
}
for key, value := range envs {
if _, ok := t.Env.Mapping[key]; !ok {
t.Env.Set(key, taskfile.Var{Static: value})
}
}
}
}
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
if v >= 3.0 {
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}

View File

@@ -22,6 +22,7 @@ type Task struct {
Method string
Prefix string
IgnoreError bool
Run string
}
func (t *Task) Name() string {
@@ -61,6 +62,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
Run string
}
if err := unmarshal(&task); err != nil {
return err
@@ -81,5 +83,6 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
t.Method = task.Method
t.Prefix = task.Prefix
t.IgnoreError = task.IgnoreError
t.Run = task.Run
return nil
}

View File

@@ -17,6 +17,7 @@ type Taskfile struct {
Tasks Tasks
Silent bool
Dotenv []string
Run string
}
// UnmarshalYAML implements yaml.Unmarshaler interface
@@ -32,6 +33,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
Tasks Tasks
Silent bool
Dotenv []string
Run string
}
if err := unmarshal(&taskfile); err != nil {
return err
@@ -46,6 +48,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Tasks = taskfile.Tasks
tf.Silent = taskfile.Silent
tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run
if tf.Expansions <= 0 {
tf.Expansions = 2
}

View File

@@ -0,0 +1 @@
VAR_IN_DOTENV=var_in_dot_env_2

View File

@@ -0,0 +1,8 @@
version: "3"
dotenv: [".env.{{.ENV_VAR}}"]
tasks:
default:
cmds:
- echo "VAR='$VAR_IN_DOTENV'" > var.txt

View File

@@ -0,0 +1 @@
VAR_IN_DOTENV=var_in_dot_env_1

View File

@@ -0,0 +1,11 @@
version: "3"
env:
LOCAL_ENV: testing
dotenv: [".env.{{.LOCAL_ENV}}"]
tasks:
default:
cmds:
- echo "VAR='$VAR_IN_DOTENV'" > var.txt

View File

@@ -0,0 +1 @@
VAR_IN_DOTENV=var_in_dot_env_3

View File

@@ -0,0 +1,13 @@
version: "3"
vars:
PART_1: test
PART_2: ing
LOCAL_VAR: "{{.PART_1}}{{.PART_2}}"
dotenv: [".env.{{.LOCAL_VAR}}"]
tasks:
default:
cmds:
- echo "VAR='$VAR_IN_DOTENV'" > var.txt

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

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

24
testdata/run/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
version: '3.7'
run: when_changed
tasks:
generate-hash:
- rm -f hash.txt
- task: input-content
vars: { CONTENT: '1' }
- task: input-content
vars: { CONTENT: '2' }
- task: input-content
vars: { CONTENT: '2' }
input-content:
deps:
- task: create-output
vars: { CONTENT: '1' }
cmds:
- echo {{.CONTENT}} >> hash.txt
create-output:
run: once
cmds:
- echo starting {{.CONTENT}} >> hash.txt

View File

@@ -4,5 +4,15 @@ tasks:
gen-foo:
cmds:
- touch foo.txt
sources:
- ./foo.txt
status:
- test -f foo.txt
- test 1 = 0
gen-bar:
cmds:
- touch bar.txt
sources:
- ./bar.txt
status:
- test 1 = 1

View File

@@ -59,6 +59,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
Method: r.Replace(origTask.Method),
Prefix: r.Replace(origTask.Prefix),
IgnoreError: origTask.IgnoreError,
Run: r.Replace(origTask.Run),
}
new.Dir, err = execext.Expand(new.Dir)
if err != nil {