diff --git a/.editorconfig b/.editorconfig index 3cf19174..f60a8b79 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,6 @@ trim_trailing_whitespace = true indent_style = tab indent_size = 8 -[*.{md,yml,yaml,json,toml}] +[*.{md,yml,yaml,json,toml,htm,html}] indent_style = space indent_size = 2 diff --git a/README.md b/README.md index f370f5db..8e01e183 100644 --- a/README.md +++ b/README.md @@ -1,789 +1,12 @@ [![Build Status](https://travis-ci.org/go-task/task.svg?branch=master)](https://travis-ci.org/go-task/task) -# Task - A task runner / simpler Make alternative written in Go +# Task -> We recently released version 2.0.0 of Task. The Taskfile changed a bit. -Please, check the [Taskfile versions](TASKFILE_VERSIONS.md) document to see -what changed and how to upgrade. +Task is a task runner / build tool that aims to be simpler and easier to use +than, for example, [GNU Make][make]. -Task is a simple tool that allows you to easily run development and build -tasks. Task is written in Golang, but can be used to develop any language. -It aims to be simpler and easier to use then [GNU Make][make]. +--- -- [Installation](#installation) - - [Go](#go) - - [Homebrew](#homebrew) - - [Snap](#snap) - - [Binary](#binary) -- [Usage](#usage) - - [Environment](#environment) - - [OS specific task](#os-specific-task) - - [Task directory](#task-directory) - - [Task dependencies](#task-dependencies) - - [Calling another task](#calling-another-task) - - [Prevent unnecessary work](#prevent-unnecessary-work) - - [Variables](#variables) - - [Dynamic variables](#dynamic-variables) - - [Go's template engine](#gos-template-engine) - - [Help](#help) - - [Silent mode](#silent-mode) - - [Dry run mode](#dry-run-mode) - - [Ignore errors](#ignore-errors) - - [Output syntax](#output-syntax) - - [Watch tasks](#watch-tasks-experimental) -- [Examples](#examples) -- [Alternative task runners](#alternative-task-runners) - -## Installation - -### Go - -If you have a [Golang][golang] environment setup, you can simply run: - -```bash -go get -u -v github.com/go-task/task/cmd/task -``` - -### Homebrew - -If you're on macOS and have [Homebrew][homebrew] installed, getting Task is -as simple as running: - -```bash -brew install go-task/tap/go-task -``` - -### Snap - -Task is available for [Snapcraft][snapcraft], but keep in mind that your -Linux distribution should allow classic confinement for Snaps to Task work -right: - -```bash -sudo snap install task -``` - -### Install script - -We also have a [install script][installscript], which is very useful on -scanarios like CIs. Many thanks to [godownloader][godownloader] for easily -generating this script. - -```bash -curl -s https://raw.githubusercontent.com/go-task/task/master/install-task.sh | sh -``` - -### Binary - -Or you can download the binary from the [releases][releases] page and add to -your `PATH`. DEB and RPM packages are also available. -The `task_checksums.txt` file contains the sha256 checksum for each file. - -## Usage - -Create a file called `Taskfile.yml` in the root of your project. -The `cmds` attribute should contain the commands of a task. -The example below allows compiling a Go app and uses [Minify][minify] to concat -and minify multiple CSS files into a single one. - -```yml -version: '2' - -tasks: - build: - cmds: - - go build -v -i main.go - - assets: - cmds: - - minify -o public/style.css src/css -``` - -Running the tasks is as simple as running: - -```bash -task assets build -``` - -Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh -interpreter. So you can write sh/bash commands and it will work even on -Windows, where `sh` or `bash` are usually not available. Just remember any -executable called must be available by the OS or in PATH. - -If you ommit a task name, "default" will be assumed. - -### Environment - -You can specify environment variables that are added when running a command: - -```yml -version: '2' - -tasks: - build: - cmds: - - echo $hallo - env: - hallo: welt -``` - -### OS specific task - -If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile -based on the operating system. - -Example: - -Taskfile.yml: - -```yml -version: '2' - -tasks: - build: - cmds: - - echo "default" -``` - -Taskfile_linux.yml: - -```yml -version: '2' - -tasks: - build: - cmds: - - echo "linux" -``` - -Will print out `linux` and not `default`. - -Keep in mind that the version of the files should match. Also, when redefining -a task the whole task is replaced, properties of the task are not merged. - -It's also possible to have an OS specific `Taskvars.yml` file, like -`Taskvars_windows.yml`, `Taskfile_linux.yml`, or `Taskvars_darwin.yml`. See the -[variables section](#variables) below. - -### Task directory - -By default, tasks will be executed in the directory where the Taskfile is -located. But you can easily make the task run in another folder informing -`dir`: - -```yml -version: '2' - -tasks: - serve: - dir: public/www - cmds: - # run http server - - caddy -``` - -### Task dependencies - -You may have tasks that depend on others. Just pointing them on `deps` will -make them run automatically before running the parent task: - -```yml -version: '2' - -tasks: - build: - deps: [assets] - cmds: - - go build -v -i main.go - - assets: - cmds: - - minify -o public/style.css src/css -``` - -In the above example, `assets` will always run right before `build` if you run -`task build`. - -A task can have only dependencies and no commands to group tasks together: - -```yml -version: '2' - -tasks: - assets: - deps: [js, css] - - js: - cmds: - - minify -o public/script.js src/js - - css: - cmds: - - minify -o public/style.css src/css -``` - -If there is more than one dependency, they always run in parallel for better -performance. - -If you want to pass information to dependencies, you can do that the same -manner as you would to [call another task](#calling-another-task): - -```yml -version: '2' - -tasks: - default: - deps: - - task: echo_sth - vars: {TEXT: "before 1"} - - task: echo_sth - vars: {TEXT: "before 2"} - cmds: - - echo "after" - - echo_sth: - cmds: - - echo {{.TEXT}} -``` - -### Calling another task - -When a task has many dependencies, they are executed concurrently. This will -often result in a faster build pipeline. But in some situations you may need -to call other tasks serially. In this case, just use the following syntax: - -```yml -version: '2' - -tasks: - main-task: - cmds: - - task: task-to-be-called - - task: another-task - - echo "Both done" - - task-to-be-called: - cmds: - - echo "Task to be called" - - another-task: - cmds: - - echo "Another task" -``` - -Overriding variables in the called task is as simple as informing `vars` -attribute: - -```yml -version: '2' - -tasks: - main-task: - cmds: - - task: write-file - vars: {FILE: "hello.txt", CONTENT: "Hello!"} - - task: write-file - vars: {FILE: "world.txt", CONTENT: "World!"} - - write-file: - cmds: - - echo "{{.CONTENT}}" > {{.FILE}} -``` - -The above syntax is also supported in `deps`. - -### Prevent unnecessary work - -If a task generates something, you can inform Task the source and generated -files, so Task will prevent to run them if not necessary. - -```yml -version: '2' - -tasks: - build: - deps: [js, css] - cmds: - - go build -v -i main.go - - js: - cmds: - - minify -o public/script.js src/js - sources: - - src/js/**/*.js - generates: - - public/script.js - - css: - cmds: - - minify -o public/style.css src/css - sources: - - src/css/**/*.css - generates: - - public/style.css -``` - -`sources` and `generates` can be files or file patterns. When both are given, -Task will compare the modification date/time of the files to determine if it's -necessary to run the task. If not, it will just print a message like -`Task "js" is up to date`. - -If you prefer this check to be made by the content of the files, instead of -its timestamp, just set the `method` property to `checksum`. -You will probably want to ignore the `.task` folder in your `.gitignore` file -(It's there that Task stores the last checksum). - -```yml -version: '2' - -tasks: - build: - cmds: - - go build . - sources: - - ./*.go - generates: - - app{{exeExt}} - method: checksum -``` - -> TIP: method `none` skips any validation and always run the task. - -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: - -```yml -version: '2' - -tasks: - generate-files: - cmds: - - mkdir directory - - touch directory/file1.txt - - touch directory/file2.txt - # test existence of files - status: - - test -d directory - - test -f directory/file1.txt - - test -f directory/file2.txt -``` - -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. - -### Variables - -When doing interpolation of variables, Task will look for the below. -They are listed below in order of importance (e.g. most important first): - -- Variables declared locally in the task -- Variables given while calling a task from another. - (See [Calling another task](#calling-another-task) above) -- Variables declared in the `vars:` option in the `Taskfile` -- Variables available in the `Taskvars.yml` file -- Environment variables - -Example of sending parameters with environment variables: - -```bash -$ TASK_VARIABLE=a-value task do-something -``` - -Since some shells don't support above syntax to set environment variables -(Windows) tasks also accepts a similar style when not in the beginning of -the command. Variables given in this form are only visible to the task called -right before. - -```bash -$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!" -``` - -Example of locally declared vars: - -```yml -version: '2' - -tasks: - print-var: - cmds: - echo "{{.VAR}}" - vars: - VAR: Hello! -``` - -Example of global vars in a `Taskfile.yml`: - -```yml -version: '2' - -vars: - GREETING: Hello from Taskfile! - -tasks: - greet: - cmds: - - echo "{{.GREETING}}" -``` - -Example of `Taskvars.yml` file: - -```yml -PROJECT_NAME: My Project -DEV_MODE: production -GIT_COMMIT: {sh: git log -n 1 --format=%h} -``` - -#### Variables expansion - -Variables are expanded 2 times by default. You can change that by setting the -`expansions:` option. Change that will be necessary if you compose many -variables together: - -```yml -version: '2' - -expansions: 3 - -vars: - FOO: foo - BAR: bar - BAZ: baz - FOOBAR: "{{.FOO}}{{.BAR}}" - FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}" - -tasks: - default: - cmds: - - echo "{{.FOOBARBAZ}}" -``` - -#### Dynamic variables - -The below syntax (`sh:` prop in a variable) is considered a dynamic variable. -The value will be treated as a command and the output assigned. If there is one -or more trailing newlines, the last newline will be trimmed. - -```yml -version: '2' - -tasks: - build: - cmds: - - go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go - vars: - GIT_COMMIT: - sh: git log -n 1 --format=%h -``` - -This works for all types of variables. - -### Go's template engine - -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/) -are available. The following example gets the current date in a given format: - -```yml -version: '2' - -tasks: - print-date: - cmds: - - echo {{now | date "2006-01-02"}} -``` - -Task also adds the following functions: - -- `OS`: Returns operating system. Possible values are "windows", "linux", - "darwin" (macOS) and "freebsd". -- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm" - or "s390x". -- `splitLines`: Splits Unix (\n) and Windows (\r\n) styled newlines. -- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space. -- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\` - path format to `/`. -- `fromSlash`: Oposite of `toSlash`. Does nothing on Unix, but on Windows - converts a string from `\` path format to `/`. -- `exeExt`: Returns the right executable extension for the current OS - (`".exe"` for Windows, `""` for others). - -Example: - -```yml -version: '2' - -tasks: - print-os: - cmds: - - echo '{{OS}} {{ARCH}}' - - echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}' - # This will be path/to/file on Unix but path\to\file on Windows - - echo '{{fromSlash "path/to/file"}}' - enumerated-file: - vars: - CONTENT: | - foo - bar - cmds: - - | - cat << EOF > output.txt - {{range $i, $line := .CONTENT | splitLines -}} - {{printf "%3d" $i}}: {{$line}} - {{end}}EOF -``` - -### Help - -Running `task --list` (or `task -l`) lists all tasks with a description. -The following taskfile: - -```yml -version: '2' - -tasks: - build: - desc: Build the go binary. - cmds: - - go build -v -i main.go - - test: - desc: Run all the go tests. - cmds: - - go test -race ./... - - js: - cmds: - - minify -o public/script.js src/js - - css: - cmds: - - minify -o public/style.css src/css -``` - -would print the following output: - -```bash -* build: Build the go binary. -* test: Run all the go tests. -``` - -## Silent mode - -Silent mode disables echoing of commands before Task runs it. -For the following Taskfile: - -```yml -version: '2' - -tasks: - echo: - cmds: - - echo "Print something" -``` - -Normally this will be print: - -```sh -echo "Print something" -Print something -``` - -With silent mode on, the below will be print instead: - -```sh -Print something -``` - -There's three ways to enable silent mode: - -* At command level: - -```yml -version: '2' - -tasks: - echo: - cmds: - - cmd: echo "Print something" - silent: true -``` - -* At task level: - -```yml -version: '2' - -tasks: - echo: - cmds: - - echo "Print something" - silent: true -``` - -* Or globally with `--silent` or `-s` flag - -If you want to suppress stdout instead, just redirect a command to `/dev/null`: - -```yml -version: '2' - -tasks: - echo: - cmds: - - echo "This will print nothing" > /dev/null -``` - -## Dry run mode - -Dry run mode (`--dry`) compiles and steps through each task, printing the commands -that would be run without executing them. This is useful for debugging your Taskfiles. - -## Ignore errors - -You have the option to ignore errors during command execution. -Given the following Taskfile: - -```yml -version: '2' - -tasks: - echo: - cmds: - - exit 1 - - echo "Hello World" -``` - -Task will abort the execution after running `exit 1` because the status code `1` stands for `EXIT_FAILURE`. -However it is possible to continue with execution using `ignore_error`: - -```yml -version: '2' - -tasks: - echo: - cmds: - - cmd: exit 1 - ignore_error: true - - echo "Hello World" -``` - -`ignore_error` can also be set for a task, which mean errors will be supressed -for all commands. But keep in mind this option won't propagate to other tasks -called either by `deps` or `cmds`! - -## Output syntax - -By default, Task just redirect the STDOUT and STDERR of the running commands -to the shell in real time. This is good for having live feedback for log -printed by commands, but the output can become messy if you have multiple -commands running at the same time and printing lots of stuff. - -To make this more customizable, there are currently three different output -options you can choose: - -- `interleaved` (default) -- `group` -- `prefixed` - - To choose another one, just set it to root in the Taskfile: - - ```yml -version: '2' - -output: 'group' - -tasks: - # ... - ``` - - The `group` output will print the entire output of a command once, after it - finishes, so you won't have live feedback for commands that take a long time - to run. - - The `prefix` output will prefix every line printed by a command with - `[task-name] ` as the prefix, but you can customize the prefix for a command - with the `prefix:` attribute: - - ```yml -version: '2' - -output: prefixed - -tasks: - default: - deps: - - task: print - vars: {TEXT: foo} - - task: print - vars: {TEXT: bar} - - task: print - vars: {TEXT: baz} - - print: - cmds: - - echo "{{.TEXT}}" - prefix: "print-{{.TEXT}}" - silent: true -``` - -```bash -$ task default -[print-foo] foo -[print-bar] bar -[print-baz] baz -``` - -## Watch tasks - -If you give a `--watch` or `-w` argument, task will watch for file changes -and run the task again. This requires the `sources` attribute to be given, -so task know which files to watch. - -## Examples - -The [go-task/examples][examples] intends to be a collection of Taskfiles for -various use cases. -(It still lacks many examples, though. Contributions are welcome). - -## Alternative task runners - -- YAML based: - - [rliebz/tusk][tusk] -- Go based: - - [magefile/mage][mage] -- Make based or similar: - - [casey/just][just] - -### Sponsors - -[![Sponsors](https://opencollective.com/task/sponsors.svg?width=890)][opencollective] - -### Backers - -[![Backers](https://opencollective.com/task/backers.svg?width=890)][opencollective] - -### Contributors - -[![Contributors](https://opencollective.com/task/contributors.svg?width=890)][contributors] +See [taskfile.org](https://taskfile.org) for documentation. [make]: https://www.gnu.org/software/make/ -[releases]: https://github.com/go-task/task/releases -[golang]: https://golang.org/ -[gotemplate]: https://golang.org/pkg/text/template/ -[tusk]: https://github.com/rliebz/tusk -[mage]: https://github.com/magefile/mage -[just]: https://github.com/casey/just -[sh]: https://github.com/mvdan/sh -[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify -[examples]: https://github.com/go-task/examples -[snapcraft]: https://snapcraft.io/ -[homebrew]: https://brew.sh/ -[installscript]: https://github.com/go-task/task/blob/master/install-task.sh -[godownloader]: https://github.com/goreleaser/godownloader -[opencollective]: https://opencollective.com/task -[contributors]: https://github.com/go-task/task/graphs/contributors diff --git a/Taskfile.yml b/Taskfile.yml index 90fa4869..91621a54 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -60,9 +60,10 @@ tasks: - goreleaser --snapshot --rm-dist generate-install-script: - desc: Generate install script using https://githbub.com/goreleaser/godownloader + desc: Generate install script using https://github.com/goreleaser/godownloader cmds: - godownloader --repo go-task/task -o install-task.sh + - cp ./install-task.sh ./docs/install.sh ci: cmds: @@ -79,3 +80,13 @@ tasks: cmds: - echo '{{.GO_PACKAGES}}' silent: true + + docs:install: + desc: Installs docsify to work the on the documentation site + cmds: + - npm install docsify-cli -g + + docs:serve: + desc: Serves the documentation site locally + cmds: + - docsify serve docs diff --git a/cmd/task/task.go b/cmd/task/task.go index 74c54697..40950ffe 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -87,6 +87,11 @@ func main() { return } + ctx := context.Background() + if !watch { + ctx = getSignalContext() + } + e := task.Executor{ Force: force, Watch: watch, @@ -95,7 +100,7 @@ func main() { Dir: dir, Dry: dry, - Context: getSignalContext(), + Context: ctx, Stdin: os.Stdin, Stdout: os.Stdout, diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 00000000..8354e11f --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +taskfile.org \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..ed06d7d1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,46 @@ +# Task + +Task is a task runner / build tool that aims to be simpler and easier to use +than, for example, [GNU Make][make]. + +Since it's written in [Go][go], Task is just a single binary and has no other +dependencies, which means you don't need to mess with any complicated install +setups just to use a build tool. + +Once [installed](installation), you just need to describe your build tasks +using a simple [YAML][yaml] schema in a file called `Taskfile.yml`: + +```yaml +version: '2' + +tasks: + hello: + cmds: + - echo 'Hello World from Task!' + silent: true +``` + +And call it by running `task hello` from you terminal. + +The above example is just the start, you can take a look at the [usage](usage) +guide to check the full schema documentation and Task features. + +## Features + +- [Easy installation](installation): just download a single binary, add to + $PATH and you're done! Or you can also install using [Homebrew][homebrew] or + [Snapcraft][snapcraft] if you want; +- Available on CIs: by adding [this simple command](installation#install-script) + to install on your CI script and you're done to use Task as part of your CI pipeline; +- Truly cross-platform: while most build tools only work well on Linux or macOS, + Task also supports Windows thanks to [this awesome shell interpreter for Go][sh]; +- Great for code generation: you can easily [prevent a task from running](usage#prevent-unnecessary-work) + if a given set of files haven't changed since last run (based either on its + timestamp or content). + +[make]: https://www.gnu.org/software/make/ +[go]: https://golang.org/ +[yaml]: http://yaml.org/ +[homebrew]: https://brew.sh/ +[snapcraft]: https://snapcraft.io/ +[sh]: https://mvdan.cc/sh diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000..f2426430 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,8 @@ +- [Installation](installation) +- [Usage](usage) +- [Taskfile Versions](taskfile_versions) +- [Examples](examples) +- [Releasing Task](releasing_task) +- [Alternative Task Runners](alternative_task_runners) +- [Sponsors and Backers](sponsors_and_backers) +- [![Github](https://icongram.jgog.in/simple/github.svg?color=808080&size=16)Github](https://github.com/go-task/task) diff --git a/docs/alternative_task_runners.md b/docs/alternative_task_runners.md new file mode 100644 index 00000000..750bebb7 --- /dev/null +++ b/docs/alternative_task_runners.md @@ -0,0 +1,17 @@ +# Alternative task runners + +## YAML based + +- [rliebz/tusk][tusk] + +## Go based + +- [magefile/mage][mage] + +## Make similar + +- [casey/just][just] + +[tusk]: https://github.com/rliebz/tusk +[mage]: https://github.com/magefile/mage +[just]: https://github.com/casey/just diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 00000000..f4bda2b5 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,7 @@ +# Examples + +The [go-task/examples][examples] intends to be a collection of Taskfiles for +various use cases. +(It still lacks many examples, though. Contributions are welcome). + +[examples]: https://github.com/go-task/examples diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 00000000..25d2a12e Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..3042fe74 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,31 @@ + + + + + Task + + + + + + +
+ + + + + + + + diff --git a/docs/install.sh b/docs/install.sh new file mode 100755 index 00000000..c101ee5d --- /dev/null +++ b/docs/install.sh @@ -0,0 +1,390 @@ +#!/bin/sh +set -e +# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT. +# + +usage() { + this=$1 + cat </dev/null +} +echoerr() { + echo "$@" 1>&2 +} +log_prefix() { + echo "$0" +} +_logp=6 +log_set_priority() { + _logp="$1" +} +log_priority() { + if test -z "$1"; then + echo "$_logp" + return + fi + [ "$1" -le "$_logp" ] +} +log_tag() { + case $1 in + 0) echo "emerg" ;; + 1) echo "alert" ;; + 2) echo "crit" ;; + 3) echo "err" ;; + 4) echo "warning" ;; + 5) echo "notice" ;; + 6) echo "info" ;; + 7) echo "debug" ;; + *) echo "$1" ;; + esac +} +log_debug() { + log_priority 7 || return 0 + echoerr "$(log_prefix)" "$(log_tag 7)" "$@" +} +log_info() { + log_priority 6 || return 0 + echoerr "$(log_prefix)" "$(log_tag 6)" "$@" +} +log_err() { + log_priority 3 || return 0 + echoerr "$(log_prefix)" "$(log_tag 3)" "$@" +} +log_crit() { + log_priority 2 || return 0 + echoerr "$(log_prefix)" "$(log_tag 2)" "$@" +} +uname_os() { + os=$(uname -s | tr '[:upper:]' '[:lower:]') + case "$os" in + msys_nt) os="windows" ;; + esac + echo "$os" +} +uname_arch() { + arch=$(uname -m) + case $arch in + x86_64) arch="amd64" ;; + x86) arch="386" ;; + i686) arch="386" ;; + i386) arch="386" ;; + aarch64) arch="arm64" ;; + armv5*) arch="arm5" ;; + armv6*) arch="arm6" ;; + armv7*) arch="arm7" ;; + esac + echo ${arch} +} +uname_os_check() { + os=$(uname_os) + case "$os" in + darwin) return 0 ;; + dragonfly) return 0 ;; + freebsd) return 0 ;; + linux) return 0 ;; + android) return 0 ;; + nacl) return 0 ;; + netbsd) return 0 ;; + openbsd) return 0 ;; + plan9) return 0 ;; + solaris) return 0 ;; + windows) return 0 ;; + esac + log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" + return 1 +} +uname_arch_check() { + arch=$(uname_arch) + case "$arch" in + 386) return 0 ;; + amd64) return 0 ;; + arm64) return 0 ;; + armv5) return 0 ;; + armv6) return 0 ;; + armv7) return 0 ;; + ppc64) return 0 ;; + ppc64le) return 0 ;; + mips) return 0 ;; + mipsle) return 0 ;; + mips64) return 0 ;; + mips64le) return 0 ;; + s390x) return 0 ;; + amd64p32) return 0 ;; + esac + log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" + return 1 +} +untar() { + tarball=$1 + case "${tarball}" in + *.tar.gz | *.tgz) tar -xzf "${tarball}" ;; + *.tar) tar -xf "${tarball}" ;; + *.zip) unzip "${tarball}" ;; + *) + log_err "untar unknown archive format for ${tarball}" + return 1 + ;; + esac +} +mktmpdir() { + test -z "$TMPDIR" && TMPDIR="$(mktemp -d)" + mkdir -p "${TMPDIR}" + echo "${TMPDIR}" +} +http_download_curl() { + local_file=$1 + source_url=$2 + header=$3 + if [ -z "$header" ]; then + code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") + else + code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") + fi + if [ "$code" != "200" ]; then + log_debug "http_download_curl received HTTP status $code" + return 1 + fi + return 0 +} +http_download_wget() { + local_file=$1 + source_url=$2 + header=$3 + if [ -z "$header" ]; then + wget -q -O "$local_file" "$source_url" + else + wget -q --header "$header" -O "$local_file" "$source_url" + fi +} +http_download() { + log_debug "http_download $2" + if is_command curl; then + http_download_curl "$@" + return + elif is_command wget; then + http_download_wget "$@" + return + fi + log_crit "http_download unable to find wget or curl" + return 1 +} +http_copy() { + tmp=$(mktemp) + http_download "${tmp}" "$1" "$2" || return 1 + body=$(cat "$tmp") + rm -f "${tmp}" + echo "$body" +} +github_release() { + owner_repo=$1 + version=$2 + test -z "$version" && version="latest" + giturl="https://github.com/${owner_repo}/releases/${version}" + json=$(http_copy "$giturl" "Accept:application/json") + test -z "$json" && return 1 + version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') + test -z "$version" && return 1 + echo "$version" +} +hash_sha256() { + TARGET=${1:-/dev/stdin} + if is_command gsha256sum; then + hash=$(gsha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command sha256sum; then + hash=$(sha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command shasum; then + hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command openssl; then + hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f a + else + log_crit "hash_sha256 unable to find command to compute sha-256 hash" + return 1 + fi +} +hash_sha256_verify() { + TARGET=$1 + checksums=$2 + if [ -z "$checksums" ]; then + log_err "hash_sha256_verify checksum file not specified in arg2" + return 1 + fi + BASENAME=${TARGET##*/} + want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) + if [ -z "$want" ]; then + log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" + return 1 + fi + got=$(hash_sha256 "$TARGET") + if [ "$want" != "$got" ]; then + log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" + return 1 + fi +} +cat /dev/null < This method will download the binary on the local `./bin` directory by default. + +[go]: https://golang.org/ +[snapcraft]: https://snapcraft.io/ +[homebrew]: https://brew.sh/ +[installscript]: https://github.com/go-task/task/blob/master/install-task.sh +[releases]: https://github.com/go-task/task/releases +[godownloader]: https://github.com/goreleaser/godownloader diff --git a/RELEASING_TASK.md b/docs/releasing_task.md similarity index 100% rename from RELEASING_TASK.md rename to docs/releasing_task.md diff --git a/docs/sponsors_and_backers.md b/docs/sponsors_and_backers.md new file mode 100644 index 00000000..6452b789 --- /dev/null +++ b/docs/sponsors_and_backers.md @@ -0,0 +1,16 @@ +# Sponsors and Backers + +## Sponsors + +[![Sponsors](https://opencollective.com/task/sponsors.svg?width=890)][opencollective] + +## Backers + +[![Backers](https://opencollective.com/task/backers.svg?width=890)][opencollective] + +## Contributors + +[![Contributors](https://opencollective.com/task/contributors.svg?width=890)][contributors] + +[opencollective]: https://opencollective.com/task +[contributors]: https://github.com/go-task/task/graphs/contributors diff --git a/TASKFILE_VERSIONS.md b/docs/taskfile_versions.md similarity index 94% rename from TASKFILE_VERSIONS.md rename to docs/taskfile_versions.md index 00ab3cd1..997334a7 100644 --- a/TASKFILE_VERSIONS.md +++ b/docs/taskfile_versions.md @@ -1,9 +1,9 @@ -# Taskfile version +# Taskfile Versions The Taskfile syntax and features changed with time. This document explains what changed on each version and how to upgrade your Taskfile. -# What the Taskfile version mean +## What the Taskfile version mean The Taskfile version follows the Task version. E.g. the change to Taskfile version `2` means that Task `v2.0.0` should be release to support it. @@ -18,7 +18,7 @@ available, but not `3.0.0+`. 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: -```yml +```yaml echo: cmds: - echo "Hello, World!" @@ -37,7 +37,7 @@ At version 2, we introduced the `version:` key, to allow us to envolve Task with new features without breaking existing Taskfiles. The new syntax is as follows: -```yml +```yaml version: '2' tasks: @@ -49,7 +49,7 @@ tasks: Version 2 allows you to write global variables directly in the Taskfile, if you don't want to create a `Taskvars.yml`: -```yml +```yaml version: '2' vars: @@ -72,7 +72,7 @@ The variable priority order changed to the following: A new global option was added to configure the number of variables expansions (which default to 2): -```yml +```yaml version: '2' expansions: 3 @@ -96,7 +96,7 @@ Version 2.1 includes a global `output` option, to allow having more control over how commands output are printed to the console (see [documentation][output] for more info): -```yml +```yaml version: '2' output: prefixed @@ -109,9 +109,9 @@ tasks: ``` From this version it's not also possible to ignore errors of a command or task -(check documentatio [here][ignore_errors]): +(check documentation [here][ignore_errors]): -```yml +```yaml version: '2' tasks: diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..1c5087cc --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,669 @@ +# Usage + +## Getting started + +Create a file called `Taskfile.yml` in the root of your project. +The `cmds` attribute should contain the commands of a task. +The example below allows compiling a Go app and uses [Minify][minify] to concat +and minify multiple CSS files into a single one. + +```yaml +version: '2' + +tasks: + build: + cmds: + - go build -v -i main.go + + assets: + cmds: + - minify -o public/style.css src/css +``` + +Running the tasks is as simple as running: + +```bash +task assets build +``` + +Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh +interpreter. So you can write sh/bash commands and it will work even on +Windows, where `sh` or `bash` are usually not available. Just remember any +executable called must be available by the OS or in PATH. + +If you ommit a task name, "default" will be assumed. + +## Environment + +You can specify environment variables that are added when running a command: + +```yaml +version: '2' + +tasks: + build: + cmds: + - echo $hallo + env: + hallo: welt +``` + +## OS specific task + +If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile +based on the operating system. + +Example: + +Taskfile.yml: + +```yaml +version: '2' + +tasks: + build: + cmds: + - echo "default" +``` + +Taskfile_linux.yml: + +```yaml +version: '2' + +tasks: + build: + cmds: + - echo "linux" +``` + +Will print out `linux` and not `default`. + +Keep in mind that the version of the files should match. Also, when redefining +a task the whole task is replaced, properties of the task are not merged. + +It's also possible to have an OS specific `Taskvars.yml` file, like +`Taskvars_windows.yml`, `Taskfile_linux.yml`, or `Taskvars_darwin.yml`. See the +[variables section](#variables) below. + +## Task directory + +By default, tasks will be executed in the directory where the Taskfile is +located. But you can easily make the task run in another folder informing +`dir`: + +```yaml +version: '2' + +tasks: + serve: + dir: public/www + cmds: + # run http server + - caddy +``` + +## Task dependencies + +You may have tasks that depend on others. Just pointing them on `deps` will +make them run automatically before running the parent task: + +```yaml +version: '2' + +tasks: + build: + deps: [assets] + cmds: + - go build -v -i main.go + + assets: + cmds: + - minify -o public/style.css src/css +``` + +In the above example, `assets` will always run right before `build` if you run +`task build`. + +A task can have only dependencies and no commands to group tasks together: + +```yaml +version: '2' + +tasks: + assets: + deps: [js, css] + + js: + cmds: + - minify -o public/script.js src/js + + css: + cmds: + - minify -o public/style.css src/css +``` + +If there is more than one dependency, they always run in parallel for better +performance. + +If you want to pass information to dependencies, you can do that the same +manner as you would to [call another task](#calling-another-task): + +```yaml +version: '2' + +tasks: + default: + deps: + - task: echo_sth + vars: {TEXT: "before 1"} + - task: echo_sth + vars: {TEXT: "before 2"} + cmds: + - echo "after" + + echo_sth: + cmds: + - echo {{.TEXT}} +``` + +## Calling another task + +When a task has many dependencies, they are executed concurrently. This will +often result in a faster build pipeline. But in some situations you may need +to call other tasks serially. In this case, just use the following syntax: + +```yaml +version: '2' + +tasks: + main-task: + cmds: + - task: task-to-be-called + - task: another-task + - echo "Both done" + + task-to-be-called: + cmds: + - echo "Task to be called" + + another-task: + cmds: + - echo "Another task" +``` + +Overriding variables in the called task is as simple as informing `vars` +attribute: + +```yaml +version: '2' + +tasks: + main-task: + cmds: + - task: write-file + vars: {FILE: "hello.txt", CONTENT: "Hello!"} + - task: write-file + vars: {FILE: "world.txt", CONTENT: "World!"} + + write-file: + cmds: + - echo "{{.CONTENT}}" > {{.FILE}} +``` + +The above syntax is also supported in `deps`. + +## Prevent unnecessary work + +If a task generates something, you can inform Task the source and generated +files, so Task will prevent to run them if not necessary. + +```yaml +version: '2' + +tasks: + build: + deps: [js, css] + cmds: + - go build -v -i main.go + + js: + cmds: + - minify -o public/script.js src/js + sources: + - src/js/**/*.js + generates: + - public/script.js + + css: + cmds: + - minify -o public/style.css src/css + sources: + - src/css/**/*.css + generates: + - public/style.css +``` + +`sources` and `generates` can be files or file patterns. When both are given, +Task will compare the modification date/time of the files to determine if it's +necessary to run the task. If not, it will just print a message like +`Task "js" is up to date`. + +If you prefer this check to be made by the content of the files, instead of +its timestamp, just set the `method` property to `checksum`. +You will probably want to ignore the `.task` folder in your `.gitignore` file +(It's there that Task stores the last checksum). + +```yaml +version: '2' + +tasks: + build: + cmds: + - go build . + sources: + - ./*.go + generates: + - app{{exeExt}} + method: checksum +``` + +> TIP: method `none` skips any validation and always run the task. + +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: + +```yaml +version: '2' + +tasks: + generate-files: + cmds: + - mkdir directory + - touch directory/file1.txt + - touch directory/file2.txt + # test existence of files + status: + - test -d directory + - test -f directory/file1.txt + - test -f directory/file2.txt +``` + +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. + +## Variables + +When doing interpolation of variables, Task will look for the below. +They are listed below in order of importance (e.g. most important first): + +- Variables declared locally in the task +- Variables given while calling a task from another. + (See [Calling another task](#calling-another-task) above) +- Variables declared in the `vars:` option in the `Taskfile` +- Variables available in the `Taskvars.yml` file +- Environment variables + +Example of sending parameters with environment variables: + +```bash +$ TASK_VARIABLE=a-value task do-something +``` + +Since some shells don't support above syntax to set environment variables +(Windows) tasks also accepts a similar style when not in the beginning of +the command. Variables given in this form are only visible to the task called +right before. + +```bash +$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!" +``` + +Example of locally declared vars: + +```yaml +version: '2' + +tasks: + print-var: + cmds: + echo "{{.VAR}}" + vars: + VAR: Hello! +``` + +Example of global vars in a `Taskfile.yml`: + +```yaml +version: '2' + +vars: + GREETING: Hello from Taskfile! + +tasks: + greet: + cmds: + - echo "{{.GREETING}}" +``` + +Example of `Taskvars.yml` file: + +```yaml +PROJECT_NAME: My Project +DEV_MODE: production +GIT_COMMIT: {sh: git log -n 1 --format=%h} +``` + +### Variables expansion + +Variables are expanded 2 times by default. You can change that by setting the +`expansions:` option. Change that will be necessary if you compose many +variables together: + +```yaml +version: '2' + +expansions: 3 + +vars: + FOO: foo + BAR: bar + BAZ: baz + FOOBAR: "{{.FOO}}{{.BAR}}" + FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}" + +tasks: + default: + cmds: + - echo "{{.FOOBARBAZ}}" +``` + +### Dynamic variables + +The below syntax (`sh:` prop in a variable) is considered a dynamic variable. +The value will be treated as a command and the output assigned. If there is one +or more trailing newlines, the last newline will be trimmed. + +```yaml +version: '2' + +tasks: + build: + cmds: + - go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go + vars: + GIT_COMMIT: + sh: git log -n 1 --format=%h +``` + +This works for all types of variables. + +## Go's template engine + +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/) +are available. The following example gets the current date in a given format: + +```yaml +version: '2' + +tasks: + print-date: + cmds: + - echo {{now | date "2006-01-02"}} +``` + +Task also adds the following functions: + +- `OS`: Returns operating system. Possible values are "windows", "linux", + "darwin" (macOS) and "freebsd". +- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm" + or "s390x". +- `splitLines`: Splits Unix (\n) and Windows (\r\n) styled newlines. +- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space. +- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\` + path format to `/`. +- `fromSlash`: Oposite of `toSlash`. Does nothing on Unix, but on Windows + converts a string from `\` path format to `/`. +- `exeExt`: Returns the right executable extension for the current OS + (`".exe"` for Windows, `""` for others). + +Example: + +```yaml +version: '2' + +tasks: + print-os: + cmds: + - echo '{{OS}} {{ARCH}}' + - echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}' + # This will be path/to/file on Unix but path\to\file on Windows + - echo '{{fromSlash "path/to/file"}}' + enumerated-file: + vars: + CONTENT: | + foo + bar + cmds: + - | + cat << EOF > output.txt + {{range $i, $line := .CONTENT | splitLines -}} + {{printf "%3d" $i}}: {{$line}} + {{end}}EOF +``` + +## Help + +Running `task --list` (or `task -l`) lists all tasks with a description. +The following taskfile: + +```yaml +version: '2' + +tasks: + build: + desc: Build the go binary. + cmds: + - go build -v -i main.go + + test: + desc: Run all the go tests. + cmds: + - go test -race ./... + + js: + cmds: + - minify -o public/script.js src/js + + css: + cmds: + - minify -o public/style.css src/css +``` + +would print the following output: + +```bash +* build: Build the go binary. +* test: Run all the go tests. +``` + +## Silent mode + +Silent mode disables echoing of commands before Task runs it. +For the following Taskfile: + +```yaml +version: '2' + +tasks: + echo: + cmds: + - echo "Print something" +``` + +Normally this will be print: + +```sh +echo "Print something" +Print something +``` + +With silent mode on, the below will be print instead: + +```sh +Print something +``` + +There's three ways to enable silent mode: + +* At command level: + +```yaml +version: '2' + +tasks: + echo: + cmds: + - cmd: echo "Print something" + silent: true +``` + +* At task level: + +```yaml +version: '2' + +tasks: + echo: + cmds: + - echo "Print something" + silent: true +``` + +* Or globally with `--silent` or `-s` flag + +If you want to suppress stdout instead, just redirect a command to `/dev/null`: + +```yaml +version: '2' + +tasks: + echo: + cmds: + - echo "This will print nothing" > /dev/null +``` + +## Dry run mode + +Dry run mode (`--dry`) compiles and steps through each task, printing the commands +that would be run without executing them. This is useful for debugging your Taskfiles. + +## Ignore errors + +You have the option to ignore errors during command execution. +Given the following Taskfile: + +```yaml +version: '2' + +tasks: + echo: + cmds: + - exit 1 + - echo "Hello World" +``` + +Task will abort the execution after running `exit 1` because the status code `1` stands for `EXIT_FAILURE`. +However it is possible to continue with execution using `ignore_error`: + +```yaml +version: '2' + +tasks: + echo: + cmds: + - cmd: exit 1 + ignore_error: true + - echo "Hello World" +``` + +`ignore_error` can also be set for a task, which mean errors will be supressed +for all commands. But keep in mind this option won't propagate to other tasks +called either by `deps` or `cmds`! + +## Output syntax + +By default, Task just redirect the STDOUT and STDERR of the running commands +to the shell in real time. This is good for having live feedback for log +printed by commands, but the output can become messy if you have multiple +commands running at the same time and printing lots of stuff. + +To make this more customizable, there are currently three different output +options you can choose: + +- `interleaved` (default) +- `group` +- `prefixed` + +To choose another one, just set it to root in the Taskfile: + +```yaml +version: '2' + +output: 'group' + +tasks: + # ... +``` + + The `group` output will print the entire output of a command once, after it + finishes, so you won't have live feedback for commands that take a long time + to run. + + The `prefix` output will prefix every line printed by a command with + `[task-name] ` as the prefix, but you can customize the prefix for a command + with the `prefix:` attribute: + + ```yaml +version: '2' + +output: prefixed + +tasks: + default: + deps: + - task: print + vars: {TEXT: foo} + - task: print + vars: {TEXT: bar} + - task: print + vars: {TEXT: baz} + + print: + cmds: + - echo "{{.TEXT}}" + prefix: "print-{{.TEXT}}" + silent: true +``` + +```bash +$ task default +[print-foo] foo +[print-bar] bar +[print-baz] baz +``` + +## Watch tasks + +If you give a `--watch` or `-w` argument, task will watch for file changes +and run the task again. This requires the `sources` attribute to be given, +so task know which files to watch. + +[gotemplate]: https://golang.org/pkg/text/template/ +[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify diff --git a/internal/taskfile/read/taskfile.go b/internal/taskfile/read/taskfile.go index e9c08960..dab897b9 100644 --- a/internal/taskfile/read/taskfile.go +++ b/internal/taskfile/read/taskfile.go @@ -14,9 +14,12 @@ import ( // Taskfile reads a Taskfile for a given directory func Taskfile(dir string) (*taskfile.Taskfile, error) { path := filepath.Join(dir, "Taskfile.yml") + if _, err := os.Stat(path); err != nil { + return nil, fmt.Errorf(`No Taskfile.yml found. Use "task --init" to create a new one`) + } t, err := readTaskfile(path) if err != nil { - return nil, fmt.Errorf(`No Taskfile.yml found. Use "task --init" to create a new one`) + return nil, err } for namespace, path := range t.Includes { diff --git a/status.go b/status.go index 853ae246..0459efee 100644 --- a/status.go +++ b/status.go @@ -12,9 +12,9 @@ import ( // Status returns an error if any the of given tasks is not up-to-date func (e *Executor) Status(calls ...taskfile.Call) error { for _, call := range calls { - t, ok := e.Taskfile.Tasks[call.Task] - if !ok { - return &taskNotFoundError{taskName: call.Task} + t, err := e.CompiledTask(call) + if err != nil { + return err } isUpToDate, err := isTaskUpToDate(e.Context, t) if err != nil { diff --git a/watch.go b/watch.go index 43d346b2..9660c057 100644 --- a/watch.go +++ b/watch.go @@ -2,7 +2,10 @@ package task import ( "context" + "os" + "os/signal" "strings" + "syscall" "time" "github.com/go-task/task/internal/taskfile" @@ -40,6 +43,8 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error { return err } + closeOnInterrupt(w) + go func() { for { select { @@ -66,6 +71,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error { e.Logger.Errf("%v", err) } case <-w.Closed: + cancel() return } } @@ -84,6 +90,19 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error { return w.Start(time.Second) } +func isContextError(err error) bool { + return err == context.Canceled || err == context.DeadlineExceeded +} + +func closeOnInterrupt(w *watcher.Watcher) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM) + go func() { + <-ch + w.Close() + }() +} + func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...taskfile.Call) error { oldWatchedFiles := make(map[string]struct{}) for f := range w.WatchedFiles() { @@ -140,12 +159,3 @@ func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...taskfile.Ca } return nil } - -func isContextError(err error) bool { - switch err { - case context.Canceled, context.DeadlineExceeded: - return true - default: - return false - } -}