Files
oam/knowledge base/task.md
2025-03-19 17:42:21 +01:00

7.5 KiB

Task

Task runner aiming to be simpler and easier to use than GNU Make.

  1. TL;DR
  2. Usage
  3. Variables
  4. Call other tasks
    1. Call root tasks from non-flattened included files
  5. Troubleshooting
    1. Dry run does not print the commands that would be executed
  6. Further readings
    1. Sources

TL;DR

Taskfiles are Task's Makefile counterpart.
Taskfiles are written in YAML.

Tasks in Task are what targets are for Make.

Task leverages mvdan.cc/sh to run commands, which is a native Go shell interpreter.
It allows to write sh/bash commands and have them work even where sh or bash are usually not available (e.g.: Windows) as long as any called executable is available in PATH.

Pros:

  • Taskfiles are more readable than Makefiles. Specifically:

    • There is no need to explicitly use tabs in tasks' definitions.
    • There is no need for special symbols, like @.
    • Environment variables management is easier.

Cons:

  • Taskfiles are written in YAML. ≈(・ཀ・≈)
    That makes them very much similar to [Gitlab / Azure Devops]'s pipelines, and if one has any experience with them one knows what a pain that can be.

Uses Go's text/template and slim-sprig packages to interpolate values.

Environment variables (env: {}, dotenv: []) are available in the shell used by commands.
Variables (vars: {}) are only available to Task while executing templates, but will default to environment variables with the same name.

Setup
# Install the executable.
brew install 'go-task'
choco install 'go-task'
docker pull 'taskfile/task'
dnf install 'go-task'
go install 'github.com/go-task/task/v3/cmd/task@latest'
pip install --user 'go_task_bin'
snap install 'task' --classic
zypper install 'https://github.com/go-task/task/releases/download/v3.39.2/task_linux_amd64.rpm'

# Setup the shell's completion.
task --completion 'fish' > ~/'.config/fish/completions/task.fish'
task --completion 'zsh'  > '/usr/local/share/zsh/site-functions/_task'
task --completion 'bash' > '/etc/bash_completion.d/task'

# Create a new 'Taskfile.yml' file in the current folder.
task --init
Usage
# Run tasks.
# No tasks given --> assumed one named 'default'
task
task 'assets'
task -v 'build:python' 'deploy:app'

# Simulate running tasks.
task -n 'bootstrap'
task --dry --verbose 'lint' 'validate:ansible'

Usage

  1. Create a file called Taskfile.yml, taskfile.yml, Taskfile.yaml, taskfile.yaml, Taskfile.dist.yml, taskfile.dist.yml, Taskfile.dist.yaml, or taskfile.dist.yaml (ordered by priority) in the root of one's project.

  2. Run tasks by their name:

    task 'assets' 'build:python'
    task --dry 'bootstrap'
    

    If task names are omitted, Task will try and execute a task named default.

Variables

Set environment variables at global or task level with env: {}.
They are made available in the shell used by commands.

env:
  SOME_VAR: some DEFAULT value
tasks:
  env_vars:test:
    env:
      SOME_VAR: some value
    cmds:
      - echo $SOME_VAR

Exported and command-specific shell variables take precedence over the ones defined in the Taskfile.

$ task env_vars:test
some value

$ set SOME_VAR 'some OTHER value'
$ task env_vars:test
some value

$ set -x SOME_VAR 'some OTHER value'
$ task env_vars:test
some OTHER value

$ SOME_VAR='some EPHEMERAL value' task env_vars:test
some EPHEMERAL value

Task accepts setting environment variables inside the command itself like make due to some shells not supporting the usual syntax.

# These are equivalent
CONTENT='Hello, World!' FILE=file.txt MESSAGE="All done!" task write-file print
task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"

Include environment variables from .env-like files at global or task level with dotenv: [].
Non-existing files are ignored in a similar manner to make's -include directive (with the prefixed dash).

dotenv:
  - .env
  - .env.local
tasks:
  env_vars:test:
    dotenv:
      - .env.task
      - .env.task.local

Environment variables set in env: {} take precedence over the ones loaded from dotenv at the same level.

graph LR
  gd["`dotenv: []`"] --- ge["`env: {}`"]
  ge["`env: {}`"] --- td["`task.dotenv: []`"]
  td["`task.dotenv: []`"] --- te["`task.env: {}`"]
  te["`task.env: {}`"] --- se["`shell.export`"]
  se["`shell.export`"] --- ce["`command.env`"]

Variables (vars: {}) are only available to Task while executing templates, but will default to environment variables with the same name.

Task execution looks for variables in the following order (first-come-first-served):

  • Variables declared in the task's definition.
  • Variables provided when calling a task from another.
  • Variables defined in included Taskfiles.
  • Variables provided when including Taskfiles.
  • Global variables.
  • Environment variables.
graph LR
  ev["`environment variables`"] --- gv["`vars: {}`"]
  gv["`vars: {}`"] --- iv["`includes[].vars: {}`"]
  iv["`includes[].vars: {}`"] --- it["`includes[]`"]
  it["`includes[]`"] --- tc["`task.cmd.'task'`"]
  tc["`task.cmd.'task'`"] --- te["`task.env`"]

Call other tasks

Use task: followed by the called task name as the command in the calling task.

tasks:
  task:being:called: { … }
  task:calling:
    cmd: task: task:being:called

Call root tasks from non-flattened included files

Refer an empty namespace by prepending the name of the task with :.

# $ROOT/Taskfile.yml
includes:
  subproject:
    taskfile: subproject
    dir: subproject
tasks:
  task:of:interest: { … }
# $ROOT/subproject/Taskfile.yml
tasks:
  some:task:
    cmd:
      task: :task:of:interest

Troubleshooting

Dry run does not print the commands that would be executed

Root cause

Command simulations do not print commands to output when setting silent: true at any level.

Solution

Force the print using -v, --verbose when silent is set to true, or set it to false at task level.

Further readings

Sources