Skip to content

Tasks and plugins

Kargo has four extension points:

Mechanism Use it to
Tasks Name shell commands, like npm scripts or make targets
Hooks Run a task or program automatically before or after builds, tests and packaging
Compiler plugins Add any Kotlin compiler plugin from Maven
Subcommands Add new kargo <name> commands

A package’s tasks start with its environment variables: the ones from [env] and the .env files, then the task’s own env, which wins over them. --env-file picks other .env files for a task, as in kargo task --env-file .env.staging deploy.

kargo.toml
[tasks]
proto = "protoc --kotlin_out=src-jvm proto/*.proto"
gen = { run = ["mkdir -p build", "./gen.sh"], description = "Generate code", hooks = ["pre-build"] }
site = { run = "mkdocs build", cwd = "website", env = { SITE_URL = "https://example.com" } }
ci = { depends-on = ["proto", "gen"], run = "kargo test --locked" }
Terminal window
kargo task # list tasks with their descriptions
kargo task proto # run one
kargo proto # shorthand: any task name works as a subcommand
kargo proto --verbose # extra arguments are appended to the task's last command
kargo task # list tasks with their descriptions (or `kargo task --list`)
kargo task fmt # run one
kargo fmt # shorthand: any task name works as a subcommand
kargo fmt --dry-run # extra arguments are appended to the task's last command

Built-in commands take precedence: a task named build, test or fmt runs only as kargo task build, since kargo build compiles your project and kargo fmt and kargo lint format and lint your code. Kargo warns about such a task whenever you build, and kargo task and kargo --help mark it. Hooks don’t care about the name, so a hidden task still runs as a hook.

Key Meaning
run A command or a list of commands, run in order
windows-run Replaces run on Windows; when it is not set, run is used everywhere
shell The shell to run the commands in: sh, bash, cmd, powershell or pwsh (default: sh on Unix, cmd on Windows)
description Shown by kargo task, kargo --help and shell completions
depends-on Tasks to run first (cycles are rejected)
env Extra environment variables
cwd Working directory, relative to the project root
hooks Lifecycle hooks that run this task automatically

Commands run with sh -c on Unix and cmd /C on Windows. They receive KARGO_TASK plus the KARGO_* variables listed below.

By default tasks run in cmd on Windows, so Unix syntax such as $KARGO_PKG_NAME, mkdir -p or ./gen.sh doesn’t work there: cmd expands %KARGO_PKG_NAME% instead. You have two ways to write tasks that work everywhere.

Use one shell on every platform. Set shell = "sh" and write POSIX commands once:

kargo.toml
[tasks]
envs = { run = "echo $KARGO_PKG_NAME $KARGO_PROFILE", shell = "sh" }
gen = { run = ["mkdir -p build", "./gen.sh"], shell = "sh" }

On Windows Kargo uses the sh from Git for Windows, even when only git is on your PATH, or any other sh on PATH (MSYS2, Cygwin). If none is found, the task fails and tells you so. shell = "pwsh" works the same way for PowerShell 7, which also runs on macOS and Linux.

With pwsh (PowerShell 7.3 or newer) a command that exits with a non-zero code stops the task, and that exit code becomes the task’s, like with sh and cmd. Windows PowerShell 5.1 (shell = "powershell") keeps running after a failing native command, so only cmdlet errors and the exit code of the script’s last native command fail the task; use pwsh, or check $LASTEXITCODE after each command, when a step in the middle must stop the task.

Arguments after -- in kargo task <name> -- <args> reach the last command as written. In cmd a % in them is escaped so %NAME% stays literal text.

Give Windows its own commands. When there is no shared shell, put the Windows version in windows-run:

kargo.toml
[tasks]
envs = { run = "echo $KARGO_PKG_NAME $KARGO_PROFILE", windows-run = "echo %KARGO_PKG_NAME% %KARGO_PROFILE%" }
gen = { run = ["mkdir -p build", "./gen.sh"], windows-run = ["if not exist build mkdir build", "gen.bat"] }

On Windows Kargo runs windows-run if it is set and run otherwise; on every other platform it always runs run. Commands that work in both shells, such as protoc, need only run.

In a workspace, [workspace.tasks] in the workspace’s kargo.toml are tasks that run at the workspace root. Members can inherit them, and [workspace.plugins] too:

kargo.toml (the workspace)
[workspace.tasks]
release-notes = "./tools/notes.sh"
codegen = { run = "./tools/codegen.sh", hooks = ["pre-build"] }
[workspace.plugins.notify]
command = "tools/notify.sh"
hooks = ["post-build"]
apps/server/kargo.toml
[tasks]
codegen.workspace = true # runs in apps/server before each of its builds
[plugins]
notify.workspace = true

At the workspace root, kargo <name> runs the workspace’s task, or else the task of every member that defines one, in dependency order. Inside a member it runs the member’s task, or else the workspace’s. Tasks and hooks of a member also get KARGO_WORKSPACE_DIR, the workspace root.

Code that a tool generates from other files (protobuf classes, an OpenAPI client, a BuildConfig) is better declared with a [[generate]] step than with a pre-build task. A step writes outside src/, skips itself while its inputs are unchanged, and tells the editors where its output is.

Hooks are lifecycle points: pre-build, post-build, pre-test, post-test, pre-run and post-package. kargo check runs pre-build hooks too, so generated sources get checked, but not post-build. Attach tasks with hooks = [...] (see above), or attach any executable as a hook plugin:

kargo.toml
[plugins.notify]
command = "./scripts/notify.sh" # relative to the project; bare names are looked up on PATH
args = ["--quiet"] # the hook name is appended as the last argument
hooks = ["post-build", "post-test"]
env = { CHANNEL = "builds" }

A failing hook fails the build.

Key Meaning
command The executable to run
windows-command Replaces command on Windows; when it is not set, command is used everywhere
args Arguments passed before the hook name
windows-args Replaces args on Windows; when it is not set, args is used everywhere
hooks Lifecycle hooks that run the plugin
env Extra environment variables

command is started directly, not through a shell. On Windows a command ending in .sh runs through sh (from Git for Windows or on PATH), so the example above works on every platform. Other commands must be real executables there (.exe, .bat or .cmd).

To run something else on Windows, set windows-command (and windows-args if the arguments differ):

kargo.toml
[plugins.notify]
command = "./scripts/notify.sh"
windows-command = "./scripts/notify.bat"
hooks = ["post-build"]

Hooks receive these environment variables:

Variable Available
KARGO_HOOK always (e.g. post-build)
KARGO_MANIFEST_DIR, KARGO_PKG_NAME, KARGO_PKG_VERSION, KARGO_PKG_GROUP always
KARGO_WORKSPACE_DIR in a workspace: the workspace root
KARGO_PROFILE, KARGO_TARGET_DIR, KARGO_KOTLIN_VERSION always
KARGO_CLASSPATH, KARGO_JVM_JAR after a JVM build
KARGO_PACKAGE after kargo package: the fat jar, or a library’s jar; with -t android, the first App Bundle (or AAR for a library)
KARGO_TEST_RESULT after tests
KARGO_BUILD_LOCK always; see below

Kargo holds the build-directory lock while hooks run. A hook can still call kargo on the same project, because KARGO_BUILD_LOCK tells the nested command that its parent already holds the lock.

scripts/publish.sh
#!/bin/sh
set -e
mkdir -p dist
cp "$KARGO_PACKAGE" "dist/$KARGO_PKG_NAME.jar"
echo "copied to dist/$KARGO_PKG_NAME.jar"
[plugins.publish]
command = "./scripts/publish.sh"
hooks = ["post-package"]

Any Kotlin compiler plugin published to Maven can be used. Give its coordinates, its plugin ID and any options:

kargo.toml
[plugins.power-assert]
maven = "org.jetbrains.kotlin:kotlin-power-assert-compiler-plugin-embeddable:2.4.21"
id = "org.jetbrains.kotlin.powerassert"
options = { function = "kotlin.assert" }

More examples:

# Make classes with these annotations open (like the Spring preset)
[plugins.allopen]
maven = "org.jetbrains.kotlin:kotlin-allopen-compiler-plugin-embeddable:2.4.21"
id = "org.jetbrains.kotlin.allopen"
options = { annotation = "com.example.Open" }
# Generate no-arg constructors
[plugins.noarg]
maven = "org.jetbrains.kotlin:kotlin-noarg-compiler-plugin-embeddable:2.4.21"
id = "org.jetbrains.kotlin.noarg"
options = { annotation = "com.example.NoArg" }

List everything that’s configured:

Terminal window
kargo plugins

Like Cargo, any executable named kargo-<name> in ~/.kargo/bin or on your PATH becomes kargo <name>:

~/.kargo/bin/kargo-loc
#!/bin/sh
# Count lines of Kotlin in the project
find src* tests* -name '*.kt' | xargs wc -l | tail -1
Terminal window
$ chmod +x ~/.kargo/bin/kargo-loc
$ kargo loc
412 total

If a task and a subcommand share a name, the task wins.