Skip to main content

Using the Task Tool

·11 mins

Task is a task runner and build tool. It provides a consistent framework for sets of tasks, enabling you to run the same workflows on multiple platforms and environments.

How Task Works #

Each copy of Task is a single executable file, with versions for Linux, macOS and Windows. This executable is relatively small, being about 8Mb for the 64-bit Linux version. It uses sets of tasks that are defined in YAML files, and includes a shell interpreter, so that you can use the same syntax for tasks on any platform.

This means that you can use Task in any environment. It only requires a copy of the Task executable, and has no configuration files apart from the YAML files that contain the tasks.

It also provides features for you to customise the behavior of your tasks for the different environments that you might use. The built-in template functions enable you to get consistent inputs for your tasks across different platforms. When needed, you can define operating system specific files, so that Task uses the specific implementation for the current platform.

Task includes two other important features: conditional execution of tasks and running tasks on file changes. These features are designed to be usable with any type of software development.

Here is an example of a Taskfile.yml, with a build task that only runs when the sources change:

# Tasks for a Hugo static website project
#
# Hugo: https://gohugo.io

version: "3"

silent: true

tasks:
  default:
    cmds:
      - task: build

  build:
    desc: Build Website
    cmds:
      - hugo
    sources:
      - content/**/*.md
    generates:
      - public/**

  clean:
    desc: Delete generated files
    cmds:
      - for: [".hugo_build.lock", "public"]
        cmd: rm -fr {{.ITEM}}

  deploy:
    desc: Deploy Website
    deps: [build]
    cmds:
      - hugo deploy

  serve:
    desc: Run Website in development server
    cmds:
      - hugo server

Installing Task #

Consider using a tool version manager like mise or asdf to install Task. For example, this command installs the latest version of Task with mise:

mise use -gy task

If you have a Dev Container configuration for a project, use the go-task feature, as shown in the section below.

To add Task to container images or systems without a tool version manager, see the section below for how to install Task with a script.

All of these tools enable you to have multiple versions of Task and use the version of Task that is required for each project that you work on. If possible, avoid using operating system packages. A package installs a single shared copy of Task, and is likely to provide an older version.

Installing Task with a Script #

The Task project provide a script for downloading task from GitHub. You may either fetch this installation script each time, as the documentation describes, or save it.

If you build Docker container images that contain a copy of Task, use a saved copy of the script. This ensures that container image builds are consistent.

To save the installation script:

curl -L https://taskfile.dev/install.sh > install-task.sh

To use the installation script, call it with the Git tag and the -b option. The Git tag specifies the version of Task. The -b option specifies which directory to install it to:

./install-task.sh -b $HOME/.local/bin v3.37.2
Exclude the path for the Task executable file from version control. If you use the script to download a copy of Task into a development project, make sure that the .gitignore (or equivalent) excludes it from version control.

Installing Task with Operating System Packages #

If you do need to install Task with an operating system package manager, it is available for several popular systems. For example, these commands install Task:

winget install Task.Task  # winget on Microsoft Windows
brew install go-task      # Homebrew on macOS
sudo dnf install go-task  # dnf on Fedora Linux

Enabling Visual Studio Code Integration #

To use Task with Visual Studio Code, install the redhat.vscode-yaml and task.vscode-task extensions.

The vscode-yaml extension enables YAML formatting and validation, and vscode-task adds a graphical integration for running tasks.

You must install Task to use the vscode-task extension.

Enabling Autocompletion #

To enable autocompletion for Task in a shell, download the appropriate script and install it into the correct location. For example, this command enables autocompletion in the fish shell:

curl -L https://raw.githubusercontent.com/go-task/task/main/completion/fish/task.fish > $HOME/.config/fish/completions/task.fish

The Task project currently provides autocompletions for Bash, zsh, fish and PowerShell.

Adding Task to a Dev Container #

If you are using a Dev Container, you can add the feature go-task to your devcontainer.json file to download Task from GitHub:

    "features": {
        "ghcr.io/devcontainers-contrib/features/go-task:1": {
            "version": "3.37.2"
        }
    }

Ensure that you also include the redhat.vscode-yaml and task.vscode-task extensions in the devcontainer.json file:

    "customizations": {
        "vscode": {
            "extensions": [
                "redhat.vscode-yaml",
                "task.vscode-task"
            ]
        }
    }

The vscode-yaml extension enables YAML formatting and validation, and vscode-task adds a graphical integration for running tasks.

Creating a User Taskfile.yml for Global Tasks #

To define tasks that are available at any time, create a file with the name Taskfile.yml in your home directory.

Create a task in the Taskfile.yml with the name default. When Task is invoked without the name of a task, it runs the default task in the Taskfile.yml.

This example user Taskfile.yml includes a default task that lists the available tasks:

version: "3"

silent: true

tasks:
  default:
    cmds:
      - task: list

  list:
    desc: List available tasks
    cmds:
      - task --list

  system-info:
    desc: Display system information
    cmds:
      - "echo CPU architecture: {{ARCH}}"
      - "echo Operating system: {{OS}}"

The user Taskfile.yml requires the option -g to run:

task -g system-info

For convenience, add an alias to your shell configuration. For example, add these lines in .config/fish/config.fish to enable an alias in the Fish shell:

# Add abbr to call tasks in user Taskfile.yml by typing ".t TASK-NAME"
if command -s task > /dev/null
    abbr --add .t task -g
end

This means that you run a task in the user Taskfile.yml by entering .t followed by the name of the task:

.t system-info

To list the tasks in your user Taskfile.yml, you can type .t and press the Enter key:

.t

This runs the default task. The example Taskfile.yml configures this to display a list of tasks.

Using Task in a Project #

First, add the .task directory to the exclusions for source control. This directory is used to hold files for tracking changes.

Use task –init to create a Taskfile.yml in the root directory of your project.

Always use the name Taskfile.yml for Task files. This enables tools that support JSON Schemas to identify the format of the files, so that they can provide autocompletion and validation.

If a project only requires one small set of tasks, then use a single Taskfile.yml. If you need to manage several sets of tasks, use these features:

  1. Taskfiles in subdirectories
  2. Includes

Adding Taskfile.yml files in subdirectories enables you to override the set of tasks for a project when you change your working directory in the project. This lets you define sets of tasks that are appropriate to the context.

Task includes enable you to define groups of tasks that can be added to any Taskfile.yml. These groups automatically become namespaces, which ensures that tasks with the same name do not override each other. For example, if you create an include for python and an include for web, they may both have a task called test, which you can call as python:task and web:test.

Using Includes #

If you decide to use Task includes in your project, consider following these guidelines:

  • Create the first task in the root Taskfile.yml with the name default. When Task is invoked without a namespace or task name, it runs the default task in the Taskfile.yml.
  • Create subdirectory called tasks/. For each namespace, create a directory with the same name as the namespace, with a Taskfile.yml file in the directory. Write the tasks for the namespace in the relevant Taskfile.yml file. Use includes in the root Taskfile.yml file to enable these namespaces.
  • Use the root Taskfile.yml to define standard tasks for the project. Each of these should call the relevant tasks in one or more namespaces. Avoid writing tasks in the root Taskfile.yml that do anything other than running tasks that are defined in namespaces.
  • Remember to include a default task for each namespace. This means that the default task runs when a user types the name of the namespace without specifying the name of the task.
  • Specify any relevant aliases for a namespace with the includes attribute.

This diagram shows the suggested directory structure for a project with task includes:

.
|
| - tasks/
|    |
|    |- pre-commit
|    |    |
|    |    |- Taskfile.yml
|    |
|    |- package
|         |
|         |- Taskfile_darwin.yml
|         |- Taskfile_linux.yml
|         |- Taskfile_windows.yml
|
|- Taskfile.yml

Example Taskfile.yml for a Project #

# Tasks for the Task runner:
#
# https://taskfile.dev

version: "3"

silent: true

# Namespaces
includes:
  package: tasks/package/Taskfile_{{OS}}.yml
  pre-commit: tasks/pre-commit

# Top-level tasks
tasks:
  default:
    cmds:
      - task: list

  bootstrap:
    desc: Set up environment for development
    cmds:
      - task: pre-commit:setup

  build:
    desc: Build packages
    cmds:
      - task: package:build

  clean:
    desc: Delete generated files
    cmds:
      - task: package:clean

  fmt:
    desc: Format code
    aliases: [format]
    cmds:
      - task: pre-commit:run
        vars: { HOOK_ID: "ruff-format" }

  lint:
    desc: Run all checks
    aliases: [check]
    cmds:
      - task: pre-commit:check

  list:
    desc: List available tasks
    cmds:
      - task --list

The default task runs the list task, so this command displays a list of all of the available tasks, including the tasks in the namespaces:

task

Example Namespace of Tasks for a Project #

# Tasks for pre-commit
#
# https://pre-commit.com/

version: "3"

silent: true

tasks:
  default:
    cmds:
      - task: check

  check:
    desc: Check the project with pre-commit
    cmds:
      - pre-commit run --all-files

  run:
    desc: Run a specific pre-commit check on the project
    cmds:
      - pre-commit run "{{.HOOK_ID}}" --all-files
    requires:
      vars: [HOOK_ID]

  setup:
    desc: Setup pre-commit for use
    cmds:
      - pre-commit install

The default task in this file runs check, so this command runs the check task:

task pre-commit

The result is the same as this command:

task pre-commit:check

Writing Taskfile.yml files #

Follow the style guidelines when writing tasks. Here are some extra suggestions:

  • Use a YAML formatter to format your Task files. The redhat.vscode-yaml extension adds support for formatting YAML files to Visual Studio Code. The Prettier tool formats YAML files, and can be used in any environment.
  • Always put a desc attribute for each task. The description appears next to the task in the output of task –list.
  • Consider adding a summary attribute for each task. The summary appears in the output of task –summary TASK-NAME.
  • Use argument forwarding or wildcard task names to get inputs for a task from the command-line.
  • Specify the requires attribute for each task that uses a variable. This ensures that the task has the necessary variables.
  • Use dotenv files to get configuration from files.
  • Use Bash shell syntax for tasks. Task uses mvdan/sh to provide the equivalent of the bash shell.
  • To ensure that your tasks are portable, check the options for UNIX commands that you call in tasks, such as rm. Operating systems provide different implementations of these commands, which means that the options may not be consistent across different environments.
  • When it is possible, use the template functions instead of shell commands, because these will behave consistently across different environments.
  • Provide operating system specific Taskfiles when necessary.
Dependencies run in parallel. This means that dependencies of a task should not depend one another. If you want to ensure that tasks run in sequence, see the documentation on Calling Another Task.

Running Tasks #

To run a task in a Taskfile.yml, enter task followed by the name of the task:

task example-task

If a task accepts forwarding of arguments, add the arguments to the command:

task example-task -- my-argument-value

You may set variables by specifying environment values with the Task command:

MY_VARIABLE_NAME=my-variable-value task example-task

Checking Taskfile.yml files #

The Task project publish the schema for Task files as a JSON Schema. This means that any software that supports JSON Schemas for YAML documents can automatically check your Task files. For example, Visual Studio Code will automatically do this when the redhat.vscode-yaml extension is installed.

To validate the Task files on the command-line, use a YAML linter that supports JSON Schemas, such as check-jsonschema. The check-jsonschema tool automatically includes the schema for Task files.

The check-jsonschema project also provides a pre-commit hook to check Task files before changes are committed to source control.

Validating Task files with pre-commit #

To validate Task files before you commit them to source control, add the pre-commit hook for check-jsonschema to the pre-commit configuration for your project:

- repo: https://github.com/python-jsonschema/check-jsonschema
  rev: 0.28.4
  hooks:
    - id: check-taskfile

Once this is added to your project you may run the same check at any time with the pre-commit command-line tool:

pre-commit run check-taskfile --all-files

Testing a Task #

To test a task, run it with the –dry option:

task --dry TASK-NAME

This compiles and prints tasks in the order that they would be run.

To debug a task, ensure that silent is not enabled in the appropriate Taskfile.yml, so that the outputs of the commands are visible:

silent: false

Resources #