just – casey
🤖 只是一个命令运行器
关键指标一览
README 详细介绍
just
just is a handy way to save and run project-specific commands.
This readme is also available as a book. The
book reflects the latest release, whereas the
readme on GitHub
reflects latest master.
(中文文档在 这里,
快看过来!)
Commands, called recipes, are stored in a file called justfile with syntax
inspired by make:

You can then run them with just RECIPE:
$ just test-all
cc *.c -o main
./test --all
Yay, all your tests passed!
just has a ton of useful features, and many improvements over make:
justis a command runner, not a build system, so it avoids much of
<code class="ra0-md-code">make</code>'s complexity and idiosyncrasies.
No need for .PHONY recipes!
- Linux, macOS, Windows, and other reasonable unixes are supported with no
additional dependencies. (Although if your system doesn't have an sh,
you'll need to choose a different shell.)
- Errors are specific and informative, and syntax errors are reported along
with their source context.
- Recipes can accept command line arguments, including
justhas a rich expression language and many
built-in-functions.
- Wherever possible, errors are resolved statically. Unknown recipes and
circular dependencies are reported before anything runs.
justloads <code class="ra0-md-code">.env</code> files, making it easy to populate
environment variables.
- Recipes can be listed from the command line.
- Command line completion scripts are
available for most popular shells.
- Recipes can be written in
arbitrary languages, like Python or Node.js.
justcan be invoked from any subdirectory, not just the directory that
contains the justfile.
justfilescan be organized into multiple files using modules
and imports.
- And much more!
If you need help with just, please feel free to open an issue or ping me on
Discord. Feature requests and bug reports are
always welcome!
Installation
Just can be installed using your favorite package manager, by
downloading pre-built binaries, or building from source
with cargo install just.
Prerequisites
just should run on any system with a reasonable sh, including Linux, macOS,
and the BSDs.
Windows
On Windows, just works with the sh provided by
Git for Windows,
GitHub Desktop, or
Cygwin. After installation, sh must be available in
the PATH of the shell you want to invoke just from.
If you'd rather not install sh, you can use the shell setting to use the
shell of your choice.
Like PowerShell:
# use PowerShell instead of sh:
set shell := ["powershell.exe", "-c"]
hello:
Write-Host "Hello, world!"
…or cmd.exe:
# use cmd.exe instead of sh:
set shell := ["cmd.exe", "/c"]
list:
dir
You can also set the shell using command-line arguments. For example, to use
PowerShell, launch just with --shell powershell.exe --shell-arg -c.
(PowerShell is installed by default on Windows 7 SP1 and Windows Server 2008 R2
SP1 and later, and cmd.exe is quite fiddly, so PowerShell is recommended for
most Windows users.)
Packages
Cross-platform
| Package Manager | Package | Command |
|---|---|---|
| arkade | just | arkade get just |
| asdf | just | asdf plugin add justasdf install just <version> |
| Cargo | just | cargo install just |
| Cargo Binstall | just | cargo binstall just |
| Conda | just | conda install -c conda-forge just |
| Homebrew | just | brew install just |
| Nix | just | nix-env -iA nixpkgs.just |
| npm | rust-just | npm install -g rust-just |
| pipx | rust-just | pipx install rust-just |
| Snap | just | snap install --edge --classic just |
| Spack | just | spack install just |
| uv | rust-just | uv tool install rust-just |
BSD
| Operating System | Package Manager | Package | Command |
|---|---|---|---|
| FreeBSD | pkg | just | pkg install just |
| OpenBSD | pkg_* | just | pkg_add just |
Linux
| Operating System | Package Manager | Package | Command |
|---|---|---|---|
| Alpine | apk-tools | just | apk add just |
| Arch | pacman | just | pacman -S just |
Debian 13 and Ubuntu 24.04 derivatives | apt | just | apt install just |
| Fedora | DNF | just | dnf install just |
| Gentoo | Portage | dev-build/just | emerge -av dev-build/just |
| NixOS | Nix | just | nix-env -iA nixos.just |
| openSUSE | Zypper | just | zypper in just |
| Solus | eopkg | just | eopkg install just |
| Void | XBPS | just | xbps-install -S just |
Windows
| Package Manager | Package | Command |
|---|---|---|
| Chocolatey | just | choco install just |
| Scoop | just | scoop install just |
| Windows Package Manager | Casey/Just | winget install --id Casey.Just --exact |
macOS
| Package Manager | Package | Command |
|---|---|---|
| MacPorts | just | port install just |
Pre-Built Binaries
Pre-built binaries for Linux, macOS, and Windows can be found on
the releases page.
You can use the following command on Linux, macOS, or Windows to download the
latest release, just replace DEST with the directory where you'd like to putjust:
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to DEST
For example, to install just to ~/bin:
# create ~/bin
mkdir -p ~/bin
# download and extract just to ~/bin/just
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/bin
# add `~/bin` to the paths that your shell searches for executables
# this line should be added to your shell's initialization file,
# e.g. `~/.bashrc` or `~/.zshrc`
export PATH="$PATH:$HOME/bin"
# just should now be executable
just --help
Note that install.sh may fail on GitHub Actions, or in other environments
where many machines share IP addresses. install.sh calls GitHub APIs in order
to determine the latest version of just to install, and those API calls are
rate-limited on a per-IP basis. To make install.sh more reliable in such
circumstances, pass a specific tag to install with --tag.
Another way to avoid rate-limiting is to pass a GitHub authentication token toinstall.sh as an environment variable named GITHUB_TOKEN, allowing it to
authenticate its requests.
Releases include a SHA256SUM file
which can be used to verify the integrity of pre-built binary archives.
To verify a release, download the pre-built binary archive along with theSHA256SUM file and run:
shasum --algorithm 256 --ignore-missing --check SHA256SUMS
GitHub Actions
just can be installed on GitHub Actions in a few ways.
Using package managers pre-installed on GitHub Actions runners on macOS withbrew install just, and on Windows with choco install just.
With extractions/setup-just:
- uses: extractions/setup-just@v3
with:
just-version: 1.5.0 # optional semver specification, otherwise latest
Or with taiki-e/install-action:
- uses: taiki-e/install-action@just
Docker
just is available as a Docker image from
the GitHub Container Registry.
To copy just into a Docker image, add the following line to yourDockerfile:
COPY --from=ghcr.io/casey/just:latest /just /usr/local/bin/
After copying, just may also be used as part of a docker build:
RUN just
Release RSS Feed
An RSS feed of just releases is
available here.
Node.js Installation
just-install can be used to automate
installation of just in Node.js applications.
just is a great, more robust alternative to npm scripts. If you want to
include just in the dependencies of a Node.js application, just-install
will install a local, platform-specific binary as part of the npm install
command. This removes the need for every developer to install just
independently using one of the processes mentioned above. After installation,
the just command will work in npm scripts or with npx. It's great for teams
who want to make the setup process for their project as easy as possible.
For more information, see the
just-install README file.
Nix Flake
The just repository includes a
<code class="ra0-md-code">flake.nix</code> that defines
a nix flake, allowing you to use just
as an input to another flake:
{
inputs = {
just.url = "github:casey/just";
}
outputs = {self, nixpkgs, just}: {
}
}
Backwards Compatibility
With the release of version 1.0, just features a strong commitment to
backwards compatibility and stability.
Future releases will not introduce backwards incompatible changes that make
existing justfiles stop working, or break working invocations of the
command-line interface.
This does not, however, preclude fixing outright bugs, even if doing so might
break justfiles that rely on their behavior.
There will never be a just 2.0. Any desirable backwards-incompatible changes
will be opt-in on a per-justfile basis, so users may migrate at their
leisure.
Features that aren't yet ready for stabilization are marked as unstable and may
be changed or removed at any time. Using unstable features produces an error by
default, which can be suppressed by passing the --unstable flag,set unstable, or setting the environment variable JUST_UNSTABLE to any
value other than false, 0, or the empty string.
Requiring a Minimum Just Version
If you use features of just which require a particular version, you may use
the minimum-version1.55.0 setting to make it an error to use older
versions of just:
set minimum-version := '1.55.0'
If just encounters a minimum version greater than its own version, it will
print an error message with the required version, which is hopefully better
than the confused error message it would have otherwise produced.
The minimum-version setting should be placed at the top of the justfile,
before any usage of the new feature that it guards.
Any features which change the lexer in forward-incompatible ways will still
produce an unhelpful error message, as the minimum version check is implemented
in the parser, which runs after the lexer.
Editor Support
justfile syntax is close enough to make that you may want to tell your
editor to use make syntax highlighting for just.
Vim and Neovim
Vim version 9.1.1042 or better and Neovim version 0.11 or better support
Justfile syntax highlighting out of the box, thanks to
pbnj.
vim-just
The vim-just plugin provides syntax
highlighting for justfiles.
Install it with your favorite package manager, like
Plug:
call plug#begin()
Plug 'NoahTheDuke/vim-just'
call plug#end()
Or with Vim's built-in package support:
mkdir -p ~/.vim/pack/vendor/start
cd ~/.vim/pack/vendor/start
git clone https://github.com/NoahTheDuke/vim-just.git
tree-sitter-just
tree-sitter-just is an
Nvim Treesitter plugin
for Neovim.
Emacs
just-mode provides syntax
highlighting and automatic indentation of justfiles. It is available on
MELPA as just-mode.
justl provides commands for executing and
listing recipes.
You can add the following to an individual justfile to enable make mode on
a per-file basis:
# Local Variables:
# mode: makefile
# End:
Visual Studio Code
An extension for VS Code is available here.
Unmaintained VS Code extensions include
skellock/vscode-just and
sclu1034/vscode-just.
JetBrains IDEs
A plugin for JetBrains IDEs by linux_china is
available here.
Kakoune
Kakoune supports justfile syntax highlighting out of the box, thanks to
TeddyDD.
Helix
Helix supports justfile syntax highlighting
out-of-the-box since version 23.05.
Sublime Text
The Just package by
nk9 with just syntax and some other tools is
available on PackageControl.
Micro
Micro supports Justfile syntax highlighting
out of the box, thanks to tomodachi94.
Zed
The zed-just extension by
jackTabsCode is available on the
Zed extensions page.
Other Editors
Feel free to send me the commands necessary to get syntax highlighting working
in your editor of choice so that I may include them here.
Language Server Protocol
just-lsp provides a [language server
protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol)
implementation, enabling features such as go-to-definition, inline diagnostics,
and code completion.
Model Context Protocol
just-mcp provides a
model context protocol
adapter to allow LLMs to query the contents of justfiles and run recipes.
Quick Start
See the installation section for how to install just on your computer. Try
running just --version to make sure that it's installed correctly.
For an overview of the syntax, check out
this cheatsheet.
Once just is installed and working, create a file named justfile in the
root of your project with the following contents:
recipe-name:
echo 'This is a recipe!'
# this is a comment
another-recipe:
@echo 'This is another recipe.'
When you invoke just, it looks for a file named justfile in the current
directory and upwards, so you can invoke it from any subdirectory of your
project.
The search for a justfile is case insensitive, so any case, like Justfile,JUSTFILE, or JuStFiLe, will work. just will also look for files with the
name .justfile, in case you'd like to hide a justfile.
By default, running just with no arguments runs the first recipe in thejustfile:
$ just
echo 'This is a recipe!'
This is a recipe!
One or more arguments specify the recipe(s) to run:
$ just another-recipe
This is another recipe.
just prints each command to standard error before running it, which is whyecho 'This is a recipe!' was printed. This is suppressed for lines starting
with @, which is why echo 'This is another recipe.' was not printed.
Recipes stop running if a command fails. Here cargo publish will only run ifcargo test succeeds:
publish:
cargo test
# tests passed, time to publish!
cargo publish
Recipes can depend on other recipes. Here the test recipe depends on thebuild recipe, so build will run before test:
build:
cc main.c foo.c bar.c -o main
test: build
./test
sloc:
@echo "`wc -l *.c` lines of code"
$ just test
cc main.c foo.c bar.c -o main
./test
testing… all tests passed!
Recipes without dependencies will run in the order they're given on the command
line:
$ just build sloc
cc main.c foo.c bar.c -o main
1337 lines of code
Dependencies will always run first, even if they are passed after a recipe that
depends on them:
$ just test build
cc main.c foo.c bar.c -o main
./test
testing… all tests passed!
Recipes may depend on recipes in submodules:
mod foo
baz: foo::bar
Examples
A variety of justfiles can be found in the
examples directory and on
GitHub.
Recipes
The Default Recipe
When just is invoked without a recipe, it runs the recipe with the[default] attribute, or the first recipe in the justfile if no recipe has
the [default] attribute.
This recipe might be the most frequently run command in the project, like
running the tests:
test:
cargo test
You can also use dependencies to run multiple recipes by default:
default: lint build test
build:
echo Building…
test:
echo Testing…
lint:
echo Linting…
If no recipe makes sense as the default recipe, you can usedefault-list1.52.0 to list the available recipes instead:
set default-list := true
Recipe Parameters
Recipes may have parameters. Here recipe build has a parameter calledtarget:
build target:
@echo 'Building {{target}}…'
cd {{target}} && make
To pass arguments on the command line, put them after the recipe name:
$ just build my-awesome-project
Building my-awesome-project…
cd my-awesome-project && make
To pass arguments to a dependency, put the dependency in parentheses along with
the arguments:
default: (build "main")
build target:
@echo 'Building {{target}}…'
cd {{target}} && make
Variables can also be passed as arguments to dependencies:
target := "main"
_build version:
@echo 'Building {{version}}…'
cd {{version}} && make
build: (_build target)
A command's arguments can be passed to a dependency by putting the dependency in
parentheses along with the arguments:
build target:
@echo "Building {{target}}…"
push target: (build target)
@echo 'Pushing {{target}}…'
Parameters may have default values:
default := 'all'
test target tests=default:
@echo 'Testing {{target}}:{{tests}}…'
./test --tests {{tests}} {{target}}
Parameters with default values may be omitted:
$ just test server
Testing server:all…
./test --tests all server
Or supplied:
$ just test server unit
Testing server:unit…
./test --tests unit server
Default values may be arbitrary expressions, but expressions containing the+, &&, ||, or / operators must be parenthesized:
arch := "wasm"
test triple=(arch + "-unknown-unknown") input=(arch / "input.dat"):
./test {{triple}}
The last parameter of a recipe may be variadic, indicated with either a + or
a * before the argument name:
backup +FILES:
scp {{FILES}} me@server.com:
Variadic parameters prefixed with + accept _one or more_ arguments and expand
to a string containing those arguments separated by spaces:
$ just backup FAQ.md GRAMMAR.md
scp FAQ.md GRAMMAR.md me@server.com:
FAQ.md 100% 1831 1.8KB/s 00:00
GRAMMAR.md 100% 1666 1.6KB/s 00:00
Variadic parameters prefixed with * accept _zero or more_ arguments and
expand to a string containing those arguments separated by spaces, or an empty
string if no arguments are present:
commit MESSAGE *FLAGS:
git commit {{FLAGS}} -m "{{MESSAGE}}"
Variadic parameters can be assigned default values. These are overridden by
arguments passed on the command line:
test +FLAGS='-q':
cargo test {{FLAGS}}
The number of arguments a variadic parameter accepts may be limited with the[arg(ARG, min=MIN)] and [arg(ARG, max=MAX)] attributes1.56.0,
which require lists to be enabled:
set unstable
set lists
[arg('FILES', min='2', max='4')]
backup +FILES:
scp {{FILES}} me@server.com:
min and max also apply to default values.
{{…}} substitutions may need to be quoted if they contain spaces. For
example, if you have the following recipe:
search QUERY:
lynx https://www.google.com/?q={{QUERY}}
And you type:
$ just search "cat toupee"
just will run the command lynx https://www.google.com/?q=cat toupee, which
will get parsed by sh as lynx, https://www.google.com/?q=cat, andtoupee, and not the intended lynx and https://www.google.com/?q=cat toupee.
You can fix this by adding quotes:
search QUERY:
lynx 'https://www.google.com/?q={{QUERY}}'
Parameters prefixed with a $ will be exported as environment variables:
foo $bar:
echo $bar
Parameters may be constrained to match regular expression patterns using the[arg("name", pattern=PATTERN)] attribute1.45.0:
[arg('n', pattern='d+')]
double n:
echo $(({{n}} * 2))
The value of pattern may be a const expression1.55.0.
A leading ^ and trailing $ are added to the pattern, so it must match the
entire argument value.
You may constrain the pattern to a number of alternatives using the |
operator:
[arg('flag', pattern='--help|--version')]
info flag:
just {{flag}}
Regular expressions are provided by the
Rust <code class="ra0-md-code">regex</code> crate. See the
syntax documentation for usage
examples.
Usage information for a recipe may be printed with the --usage
subcommand1.46.0:
$ just --usage foo
Usage: just foo [OPTIONS] bar
Arguments:
bar
Help strings may be added to arguments using the [arg(ARG, help=HELP)] attribute:
[arg("bar", help="hello")]
foo bar:
The value help may be a const expression1.55.0.
$ just --usage foo
Usage: just foo bar
Arguments:
bar hello
Recipe Flags and Options
Recipe parameters are positional by default.
In this justfile:
@foo bar:
echo bar={{bar}}
The parameter bar is positional:
$ just foo hello
bar=hello
The [arg(ARG, long=OPTION)]1.46.0 attribute can be used to make a
parameter a long option.
In this justfile:
[arg("bar", long="bar")]
foo bar:
The parameter bar is given with the --bar option:
$ just foo --bar hello
bar=hello
Options may also be passed with --name=value syntax:
$ just foo --bar=hello
bar=hello
The value of long may be omitted, in which case the option defaults to the
name of the parameter. With the following justfile, bar may be passed with--bar:
[arg("bar", long)]
foo bar:
The [arg(ARG, short=OPTION)]1.46.0 attribute can be used to make a
parameter a short option.
In this justfile:
[arg("bar", short="b")]
foo bar:
The parameter bar is given with the -b option:
$ just foo -b hello
bar=hello
The value of short may be omitted, in which case the option defaults to the
first character of the name of the parameter. With the following justfile,bar may be passed with -b:
[arg("bar", short)]
foo bar:
If a parameter has both a long and short option, it may be passed using either.
Multiple short options may be combined1.55.0, for example -abc is
equivalent to -a -b -c. A short option which takes a value may appear last,
for example -abcd VALUE.
Variadic * and + parameters may be options, in which case the option is
repeatable, with each occurrence contributing one value:
[arg('file', long)]
backup +file:
scp {{file}} me@server.com:
$ just backup --file FAQ.md --file GRAMMAR.md
scp FAQ.md GRAMMAR.md me@server.com:
As with positional variadic parameters, + options must be passed at least
once, whereas * options may be omitted.
The [arg(ARG, value=VALUE, …)]1.46.0 attribute can be used withlong or short to make a parameter a flag which does not take a value.VALUE may be an expression1.54.0.
In this justfile:
[arg("bar", long="bar", value="hello")]
foo bar:
The parameter bar is given with the --bar option, but does not take a
value, and instead takes the value given in the [arg] attribute:
$ just foo --bar
bar=hello
This is useful for unconditionally requiring a flag like --force on dangerous
commands.
A flag is optional if its parameter has a default:
[arg("bar", long="bar", value="hello")]
foo bar="goodbye":
Causing it to receive the default when not passed in the invocation:
$ just foo
bar=goodbye
Avoiding Argument Splitting
Given this justfile:
foo argument:
touch {{argument}}
The following command will create two files, some and argument.txt:
$ just foo "some argument.txt"
The user's shell will parse "some argument.txt" as a single argument, but
when just replaces touch {{argument}} with touch some argument.txt, the
quotes are not preserved, and touch will receive two arguments.
There are a few ways to avoid this: quoting, positional arguments, and exported
arguments.
Quoting
Quotes can be added around the {{argument}} interpolation:
foo argument:
touch '{{argument}}'
This preserves just's ability to catch variable name typos before running,
for example if you were to write {{argument}}, but will not do what you want
if the value of argument contains single quotes.
Positional Arguments
The positional-arguments setting causes all arguments to be passed as
positional arguments, allowing them to be accessed with $1, $2, …, and$@, which can then be double-quoted to avoid further splitting by the shell:
set positional-arguments
foo argument:
touch "$1"
This defeats just's ability to catch typos, for example if you type $2
instead of $1, but works for all possible values of argument, including
those with double quotes.
Exported Arguments
All arguments are exported when the export setting is set:
set export
foo argument:
touch "$argument"
Or individual arguments may be exported by prefixing them with $:
foo $argument:
touch "$argument"
This defeats just's ability to catch typos, for example if you type$argument, but works for all possible values of argument, including those
with double quotes.
Positional Arguments
If positional-arguments is true, recipe arguments will be passed as
positional arguments to commands. For shell recipes, argument $0 will be the
name of the recipe.
For example, running this recipe:
set positional-arguments
@foo bar:
echo $0
echo $1
Will produce the following output:
$ just foo hello
foo
hello
When using an sh-compatible shell, such as bash or zsh, $@ expands to
the positional arguments given to the recipe, starting from one. When used
within double quotes as "$@", arguments including whitespace will be passed
on as if they were double-quoted. That is, "$@" is equivalent to "$1" "$2"…
When there are no positional parameters, "$@" and $@ expand to nothing
(i.e., they are removed).
This example recipe will print arguments one by one on separate lines:
set positional-arguments
@test *args='':
bash -c 'while (( "$#" )); do echo - $1; shift; done' -- "$@"
Running it with _two_ arguments:
$ just test foo "bar baz"
- foo
- bar baz
Positional arguments may also be turned on a per-recipe basis with the[positional-arguments] attribute1.29.0:
[positional-arguments]
@foo bar:
echo $0
echo $1
Note that PowerShell does not handle positional arguments in the same way as
other shells, so turning on positional arguments will likely break recipes that
use PowerShell.
If using PowerShell 7.4 or better, the -CommandWithArgs flag will make
positional arguments work as expected:
set shell := ['pwsh.exe', '-CommandWithArgs']
set positional-arguments
print-args a b c:
Write-Output @($args[1..($args.Count - 1)])
Dependencies
Dependencies run before recipes that depend on them:
a: b
@echo A
b:
@echo B
$ just a
B
A
In a given invocation of just, a recipe with the same arguments will only run
once, regardless of how many times it appears in the command-line invocation,
or how many times it appears as a dependency:
a:
@echo A
b: a
@echo B
c: a
@echo C
$ just a a a a a
A
$ just b c
A
B
C
Multiple recipes may depend on a recipe that performs some kind of setup, and
when those recipes run, that setup will only be performed once:
build:
cc main.c
test-foo: build
./a.out --test foo
test-bar: build
./a.out --test bar
$ just test-foo test-bar
cc main.c
./a.out --test foo
./a.out --test bar
Recipes in a given run are only skipped when they receive the same arguments:
build:
cc main.c
test TEST: build
./a.out --test {{TEST}}
$ just test foo test bar
cc main.c
./a.out --test foo
./a.out --test bar
Running Recipes at the End of a Recipe
Normal dependencies of a recipe always run before a recipe starts. That is to
say, the dependee always runs before the depender. These dependencies are
called "prior dependencies".
A recipe can also have subsequent dependencies, which run immediately after the
recipe and are introduced with an &&:
a:
echo 'A!'
b: a && c d
echo 'B!'
c:
echo 'C!'
d:
echo 'D!'
…running _b_ prints:
$ just b
echo 'A!'
A!
echo 'B!'
B!
echo 'C!'
C!
echo 'D!'
D!
Running Recipes in the Middle of a Recipe
just doesn't support running recipes in the middle of another recipe, but you
can call just recursively in the middle of a recipe. Given the followingjustfile:
a:
echo 'A!'
b: a
echo 'B start!'
just c
echo 'B end!'
c:
echo 'C!'
…running _b_ prints:
$ just b
echo 'A!'
A!
echo 'B start!'
B start!
echo 'C!'
C!
echo 'B end!'
B end!
This has limitations, since recipe c is run with an entirely new invocation
of just: Assignments will be recalculated, dependencies might run twice, and
command line arguments will not be propagated to the child just process.
Parallelism
Dependencies may be run in parallel with the [parallel] attribute.
In this justfile, foo, bar, and baz will execute in parallel whenmain is run:
[parallel]
main: foo bar baz
foo:
sleep 1
bar:
sleep 1
baz:
sleep 1
The number of simultaneously running recipes may be limited with the --jobs
option1.56.0. The num_jobs() function returns the number of jobs,
falling back to the empty list if --jobs was not passed.
GNU parallel may be used to run recipe lines concurrently:
parallel:
#!/usr/bin/env -S parallel --shebang --ungroup --jobs {{ num_cpus() }}
echo task 1 start; sleep 3; echo task 1 done
echo task 2 start; sleep 3; echo task 2 done
echo task 3 start; sleep 3; echo task 3 done
echo task 4 start; sleep 3; echo task 4 done
Documentation Comments
Comments immediately preceding a recipe will appear in just --list:
# build stuff
build:
./bin/build
# test stuff
test:
./bin/test
$ just --list
Available recipes:
build # build stuff
test # test stuff
The [doc] attribute can be used to set or suppress a recipe's doc comment:
# This comment won't appear
[doc('Build stuff')]
build:
./bin/build
# This one won't either
[doc]
test:
./bin/test
$ just --list
Available recipes:
build # Build stuff
test
The value of [doc] may be a const expression1.56.0.
Groups
Recipes and modules may be annotated with one or more group names:
[group('lint')]
js-lint:
echo 'Running JS linter…'
[group('rust recipes')]
[group('lint')]
rust-lint:
echo 'Running Rust linter…'
[group('lint')]
cpp-lint:
echo 'Running C++ linter…'
# not in any group
email-everyone:
echo 'Sending mass email…'
Recipes are listed by group:
$ just --list
Available recipes:
email-everyone # not in any group
[lint]
cpp-lint
js-lint
rust-lint
[rust recipes]
rust-lint
just --list --unsorted prints recipes in their justfile order within each group:
$ just --list --unsorted
Available recipes:
(no group)
email-everyone # not in any group
[lint]
js-lint
rust-lint
cpp-lint
[rust recipes]
rust-lint
Groups can be listed with --groups:
$ just --groups
Recipe groups:
lint
rust recipes
Use just --groups --unsorted to print groups in their justfile order.
Aliases
Aliases allow recipes to be invoked on the command line with alternative names:
alias b := build
build:
echo 'Building!'
$ just b
echo 'Building!'
Building!
The target of an alias may be a recipe in a submodule:
mod foo
alias baz := foo::bar
Or a module1.55.0:
mod frontend
alias f := frontend
$ just f build
Private Recipes
Recipes and aliases whose name starts with a _ are omitted from just --list:
test: _test-helper
./bin/test
_test-helper:
./bin/super-secret-test-helper-stuff
$ just --list
Available recipes:
test
And from just --summary:
$ just --summary
test
The [private] attribute1.10.0 may also be used to hide recipes or
aliases without needing to change the name:
[private]
foo:
[private]
alias b := bar
bar:
$ just --list
Available recipes:
bar
This is useful for helper recipes which are only meant to be used as
dependencies of other recipes.
Enabling and Disabling Items
The [android], [dragonfly], [freebsd], [linux], [macos], [netbsd],[openbsd], [unix], and [windows] attributes are conditional attributes.
By default, items are always enabled. An item with one or more conditional
attributes will only be enabled when one or more of those conditional
attributes is active.
The conditional attributes originally applied only to recipes, but may now be
applied to all top-level items1.56.0.
This can be used to write justfiles that behave differently depending on
which operating system they run on. The run recipe in this justfile will
compile and run main.c, using a different C compiler and using the correct
output binary name for that compiler depending on the operating system:
[unix]
run:
cc main.c
./a.out
[windows]
run:
cl main.c
main.exe
Similarly, a setting can be made conditional on the current operating system:
[unix]
set shell := ['sh', '-cu']
[windows]
set shell := ['cmd', '/c']
Allow Duplicate Recipes
If allow-duplicate-recipes is set to true, defining multiple recipes with
the same name is not an error and the last definition is used. Defaults tofalse.
set allow-duplicate-recipes
@foo:
echo foo
@foo:
echo bar
$ just foo
bar
Expressions
Variables and Assignments
Module-level variables may be created by assigning them a value with :=:
foo := "hello"
bar := "world"
baz:
echo {{ foo + " " + bar }}
All variables in a module may be printed:
$ just --evaluate
bar := "world"
foo := "hello"
Or the value of a single variable:
$ just --evaluate foo
hello
All variables in a submodule or a single variable in a submodule may be printed
with a path to the submodule or variable1.49.0:
$ just --evaluate bob::bar
x := "world"
y := "hello"
$ just --evaluate bob::bar::y
hello
The format of exported variables may be controlled with--evaluate-format1.49.0:
$ just --evaluate --evaluate-format shell
bar="world"
foo="hello"
The default format is --evaluate-format just:
$ just --evaluate --evaluate-format just
bar := "world"
foo := "hello"
Allow Duplicate Variables
If allow-duplicate-variables is set to true, defining multiple variables
with the same name is not an error and the last definition is used. Defaults tofalse.
set allow-duplicate-variables
a := "foo"
a := "bar"
@foo:
echo {{a}}
$ just foo
bar
Lazy
The lazy setting1.47.0 causes the evaluator to skip evaluating
unused variables. This can be beneficial when a justfile contains variables
that are expensive to evaluate but only sometimes used.
In the following justfile, token will be skipped when only invoking bar:
set lazy
token := `expensive-script-to-get-credentials`
foo:
curl -H "Authorization: Bearer {{ token }}" https://example.com/foo
bar:
cargo test
Because just cannot determine when exported variables are used, assignments
with export and assignments in a module with set export will always be
evaluated.
Expressions and Substitutions
Various operators and function calls are supported in expressions, which may be
used in assignments, default recipe arguments, and inside recipe body {{…}}
substitutions.
tmpdir := `mktemp -d`
version := "0.2.7"
tardir := tmpdir / "awesomesauce-" + version
tarball := tardir + ".tar.gz"
config := quote(config_dir() / ".project-config")
publish:
rm -f {{tarball}}
mkdir {{tardir}}
cp README.md *.c {{ config }} {{tardir}}
tar zcvf {{tarball}} {{tardir}}
scp {{tarball}} me@server.com:release/
rm -rf {{tarball}} {{tardir}}
Concatenation
The + operator returns the left-hand argument concatenated with the
right-hand argument:
foobar := 'foo' + 'bar'
Logical Operators
The logical operators && and || can be used to coalesce
values1.37.0, similar to Python's and and or. The only false
value is the empty list []; every other value, including the empty string'', is true.
These operators require set lists1.53.0, which is currently
unstable.
The && operator returns the empty list if the left-hand argument is false,
otherwise it returns the right-hand argument:
foo := [] && 'goodbye' # []
bar := 'hello' && 'goodbye' # 'goodbye'
The || operator returns the left-hand argument if it is true, otherwise it
returns the right-hand argument:
foo := [] || 'goodbye' # 'goodbye'
bar := 'hello' || 'goodbye' # 'hello'
Joining Paths
The / operator can be used to join two strings with a slash:
foo := "a" / "b"
$ just --evaluate foo
a/b
Note that a / is added even if one is already present:
foo := "a/"
bar := foo / "b"
$ just --evaluate bar
a//b
Absolute paths can also be constructed1.5.0:
foo := / "b"
$ just --evaluate foo
/b
The / operator uses the / character, even on Windows. Thus, using the /
operator should be avoided with paths that use universal naming convention
(UNC), i.e., those that start with \, since forward slashes are not
supported with UNC paths.
Escaping {{
To write a recipe containing {{, use {{{{:
braces:
echo 'I {{{{LOVE}} curly braces!'
(An unmatched }} is ignored, so it doesn't need to be escaped.)
Another option is to put all the text you'd like to escape inside of an
interpolation:
braces:
echo '{{'I {{LOVE}} curly braces!'}}'
Yet another option is to use {{ "{{" }}:
braces:
echo 'I {{ "{{" }}LOVE}} curly braces!'
Strings
'single', "double", and '''triple''' quoted string literals are
supported. Unlike in recipe bodies, {{…}} interpolations are not supported
inside strings.
Double-quoted strings support escape sequences:
carriage-return := "r"
double-quote := """
newline := "n"
no-newline := "
"
slash := "\"
tab := "t"
unicode-codepoint := "u{1F916}"
$ just --evaluate
"arriage-return := "
double-quote := """
newline := "
"
no-newline := ""
slash := ""
tab := " "
unicode-codepoint := "🤖"
The unicode character escape sequence u{…}1.36.0 accepts up to
six hex digits.
Strings may contain line breaks:
single := '
hello
'
double := "
goodbye
"
Single-quoted strings do not recognize escape sequences:
escapes := 'tnr"\'
$ just --evaluate
escapes := "tnr"\"
Indented versions of both single- and double-quoted strings, delimited by
triple single- or double-quotes, are supported. Indented string lines are
stripped of a leading line break, and leading whitespace common to all
non-blank lines:
# this string will evaluate to `foonbarn`
x := '''
foo
bar
'''
# this string will evaluate to `abcn wuvnxyzn`
y := """
abc
wuv
xyz
"""
Similar to unindented strings, indented double-quoted strings process escape
sequences, and indented single-quoted strings ignore escape sequences. Escape
sequence processing takes place after unindentation. The unindentation
algorithm does not take escape-sequence produced whitespace or newlines into
account.
Shell-expanded strings
Strings prefixed with x are shell expanded1.27.0:
foobar := x'~/$FOO/${BAR}'
| Value | Replacement |
|---|---|
$VAR |
value of environment variable VAR |
${VAR} |
value of environment variable VAR |
${VAR:-DEFAULT} |
value of environment variable VAR, or DEFAULT if VAR is not set |
Leading ~ |
path to current user's home directory |
Leading ~USER |
path to USER's home directory |
This expansion is performed at compile time, so variables from .env files and
exported just variables cannot be used. However, this allows shell expanded
strings to be used in places like settings and import paths, which cannot
depend on just variables