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.
[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" }kargo task # list tasks with their descriptionskargo task proto # run onekargo proto # shorthand: any task name works as a subcommandkargo proto --verbose # extra arguments are appended to the task's last commandkargo task # list tasks with their descriptions (or `kargo task --list`)kargo task fmt # run onekargo fmt # shorthand: any task name works as a subcommandkargo fmt --dry-run # extra arguments are appended to the task's last commandBuilt-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.
Windows
Section titled “Windows”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:
[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:
[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
Section titled “In a workspace”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:
[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"][tasks]codegen.workspace = true # runs in apps/server before each of its builds
[plugins]notify.workspace = trueAt 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:
[plugins.notify]command = "./scripts/notify.sh" # relative to the project; bare names are looked up on PATHargs = ["--quiet"] # the hook name is appended as the last argumenthooks = ["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):
[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.
Example: copy the jar after packaging
Section titled “Example: copy the jar after packaging”#!/bin/shset -emkdir -p distcp "$KARGO_PACKAGE" "dist/$KARGO_PKG_NAME.jar"echo "copied to dist/$KARGO_PKG_NAME.jar"[plugins.publish]command = "./scripts/publish.sh"hooks = ["post-package"]Compiler plugins
Section titled “Compiler plugins”Any Kotlin compiler plugin published to Maven can be used. Give its coordinates, its plugin ID and any options:
[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:
kargo pluginsCustom subcommands
Section titled “Custom subcommands”Like Cargo, any executable named kargo-<name> in ~/.kargo/bin or on your PATH becomes kargo <name>:
#!/bin/sh# Count lines of Kotlin in the projectfind src* tests* -name '*.kt' | xargs wc -l | tail -1$ chmod +x ~/.kargo/bin/kargo-loc$ kargo loc 412 totalIf a task and a subcommand share a name, the task wins.