A tiny, function-based command runner.
Jake lets you define and run project-specific commands in a Jakefile.
build(type: {*debug|release}) {
echo "Building in $type mode..."
}
test() {
@build(debug)
echo "Running tests..."
}
$ jake testHombrew coming soon
Jake is written in Zig. To build from source:
git clone https://github.com/jmwoliver/jake.git
cd jake
zig build -Doptimize=ReleaseFastThe binary will be at zig-out/bin/jake. Move it somewhere in your $PATH:
cp zig-out/bin/jake /usr/local/bin/- Create a
Jakefilein your project root:
build() {
echo "Building..."
cargo build
}
test() {
cargo test
}
fmt() {
cargo fmt
}
- List available targets:
$ jake
Available targets:
build()
fmt()
test()- Run a target:
$ jake build
$ echo "Building..."
Building...
$ cargo build
Compiling myproject v0.1.0Functions are the basic building blocks. A function has a name, optional parameters, and a body of shell commands:
name() {
shell commands here
}
Everything inside the braces is passed to the shell as-is.
Functions can accept parameters:
greet(name) {
echo "Hello, $name!"
}
$ jake greet World
$ echo "Hello, World!"
Hello, World!Mark a default value with * inside a constraint set:
build(mode: {*debug|release}) {
echo "Building in $mode mode"
}
$ jake build # Uses debug (the default)
$ jake build release # Uses releaseUse _ as a placeholder to use the default and skip to later parameters:
test(arch: {arm64|*x86_64}, count) {
echo "Testing on $arch, $count times"
}
$ jake test _ 5 # arch=x86_64 (default), count=5
$ jake test arch=_ 10 # Same result using named syntaxConstrain parameters to a set of allowed values:
deploy(env: {dev|staging|prod}) {
echo "Deploying to $env..."
}
If you pass an invalid value, jake tells you:
$ jake deploy local
error: invalid value 'local' for parameter 'env'
allowed values: dev, staging, prod- Parameters without constraints or defaults are required
- Parameters with a
*defaultare optional - Parameters with constraints but no
*are required (must be one of the options)
# arch is required (no default), type has a default
build(arch: {arm64|x86_64}, type: {*debug|release}) {
echo "Building $arch in $type mode"
}
Use $variable to reference parameters in your commands:
build(name) {
echo "Building $name"
echo "$name build complete"
}
Variables work inside quoted strings too:
greet(name) {
echo "Hello, $name! Welcome to the build."
}
Define local variables with name = "value" syntax:
build(arch: {arm64|*x86_64}, type: {release|*debug}) {
arch_flag = ""
match $arch {
arm64: {
arch_flag = "-Dtarget=aarch64-macos"
}
x86_64: {
arch_flag = "-Dtarget=x86_64-macos"
}
}
build_flag = ""
match $type {
debug: {
build_flag = "-Doptimize=Debug"
}
release: {
build_flag = "-Doptimize=ReleaseFast"
}
}
zig build $build_flag $arch_flag
}
Local variables:
- Can be assigned from string literals (
flag = "-O2") or other variables (target = $arch) - Can be reassigned (mutable)
- Cannot shadow function parameters (this is an error)
- Must be defined before use (undefined variables are caught at parse time)
Use @function() to call another jake function:
build() {
echo "Building..."
}
test() {
@build()
echo "Running tests..."
}
Pass arguments to called functions:
build(arch) {
echo "Building for $arch"
}
test(arch) {
@build($arch)
echo "Testing on $arch"
}
Use match to branch based on constrained parameter values. Match is exhaustive - you must handle all possible values:
build(type: {*debug|release}) {
match $type {
debug: {
zig build
}
release: {
zig build -Doptimize=ReleaseFast
}
}
}
Multiple values can share the same body using |:
build(type: {*debug|release|fast}) {
match $type {
debug: {
zig build
}
release | fast: {
zig build -Doptimize=ReleaseFast
}
}
}
If you forget to handle a value, jake tells you at parse time:
error: match is not exhaustive
--> Jakefile:2:9
|
2 | match $type {
| ^^^^
= hint: missing values: release
Note: match can only be used with constrained parameters (those with {opt1|opt2} syntax). This ensures all cases are known and can be verified.
Lines starting with # are comments:
# This is a comment
build() {
# Another comment
echo "Building..."
}
jake [OPTIONS] [TARGET] [ARGS...]
| Option | Description |
|---|---|
-f <FILE> |
Use a specific Jakefile |
-h, --help |
Show help message |
Positional arguments are assigned in order:
# For: build(arch, type)
$ jake build arm64 releaseNamed arguments use name=value syntax:
$ jake build type=release arch=arm64Default placeholder uses _ to skip a parameter and use its default:
# For: test(arch: {arm64|*x86_64}, count)
$ jake test _ 5 # Use default arch, set count=5
$ jake test arch=_ 10 # Same with named syntaxYou can mix positional and named arguments:
$ jake build arm64 type=release# List all targets
jake
# Run a target
jake build
# With positional arguments
jake test arm64 5
# With named arguments
jake deploy env=staging
# Skip to later parameters using _ placeholder
jake test _ 10
# Call multiple targets with parameters
jake test arm64 10 deploy
# Use a different Jakefile
jake -f build.jake testBuild complex workflows by composing functions:
clean() {
rm -rf dist/
}
build() {
@clean()
mkdir -p dist
go build -o dist/app
}
test() {
@build()
go test ./...
}
release() {
@test()
tar -czf release.tar.gz dist/
}
Get validation for free. No more typos silently breaking builds:
deploy(env: {dev|staging|prod}) {
./deploy.sh --env=$env
}
$ jake deploy production
error: invalid value 'production' for parameter 'env'
allowed values: dev, staging, prodJake provides clear, colorized error messages with source context and hints:
error: undefined function
--> Jakefile:5:3
|
5 | @bild($arch)
| ^^^^
= hint: did you mean 'build'?
| Feature | jake | make | just |
|---|---|---|---|
| Function parameters | Yes | No | Yes |
| Constrained parameter values | Yes | No | No |
| Default parameter values | Yes | No | Yes |
| Local variables | Yes | Yes | Yes |
| Call other tasks | @task() |
Dependencies | Dependencies |
| Exhaustive match on parameters | Yes | No | No |
MIT