mcpField
J
just

just

官方精选Agent-ready
github.com开发者工具36.5kjust今天更新
代码仓库访问官网

关于 just

just 是一个任务运行器,用于管理项目特定的开发命令。它使用 justfile 来定义任务,支持参数化和任务链接,跨平台工作。

主要功能

  1. 01在 justfile 中定义项目任务并使用简单语法运行
  2. 02列出可用任务或查看任务定义
  3. 03组合多个任务创建复杂的开发工作流

适用场景

  1. 01标准化项目构建、测试和部署命令
  2. 02为开发人员提供一致的任务接口而不考虑操作系统
  3. 03将重复的开发操作自动化为可复用的任务

安装

选择你的系统或包管理器,复制命令到终端运行。

$
brew install just

安装完成后运行just --version确认安装成功。

命令示例

常用命令按使用顺序排列。

01
just --list
02
just build
03
just test

详细介绍

目录↗️

just


just 是一种便捷的方式,可用于保存和运行项目专属命令。

这份自述文件也以书籍的形式提供。书籍内容对应最新版本,而 GitHub 上的自述文件 对应最新的 master 分支。

(中文文档在 这里, 快看过来!)

命令(称为配方)存储在名为 justfile 的文件中,其语法受 make 启发:

截图

然后,你可以使用 just RECIPE 运行这些命令:

$ just test-all
cc *.c -o main
./test --all
Yay, all your tests passed!

just 有许多实用功能,也在很多方面优于 make:

如果你需要 just 方面的帮助,欢迎提交 issue,或在 Discord 上联系我。我们始终欢迎功能请求和错误报告!

安装

你可以使用喜欢的包管理器安装 Just,也可以下载 预构建二进制文件,或使用 cargo install just 从源代码构建。

前置条件

just 应能在任何具有常见 sh 的系统上运行,包括 Linux、macOS 和 BSD 系统。

Windows

在 Windows 上,just 可使用由 Git for Windows、 GitHub Desktop 或 Cygwin 提供的 sh。安装后,sh 必须位于你想从中调用 just 的 shell 的 PATH 中。

如果你不想安装 sh,可以使用 shell 设置来选择所用的 shell。

例如,使用 PowerShell:

# use PowerShell instead of sh:
set shell := ["powershell.exe", "-c"]

hello:
  Write-Host "Hello, world!"

……或使用 cmd.exe:

# use cmd.exe instead of sh:
set shell := ["cmd.exe", "/c"]

list:
  dir

你也可以通过命令行参数设置 shell。例如,要使用 PowerShell,请使用 --shell powershell.exe --shell-arg -c 启动 just。

(Windows 7 SP1 和 Windows Server 2008 R2 SP1 及更高版本默认安装了 PowerShell,而且 cmd.exe 相当繁琐,因此推荐大多数 Windows 用户使用 PowerShell。)

软件包
跨平台
软件包管理器软件包命令
arkadejustarkade get just
asdfjust asdf plugin add just
asdf install just <version>
Cargojustcargo install just
Cargo Binstalljustcargo binstall just
Condajustconda install -c conda-forge just
Homebrewjustbrew install just
Nixjustnix-env -iA nixpkgs.just
npmrust-justnpm install -g rust-just
pipxrust-justpipx install rust-just
Snapjustsnap install --edge --classic just
Spackjustspack install just
uvrust-justuv tool install rust-just
#### BSD
Operating SystemPackage ManagerPackageCommand
FreeBSDpkgjustpkg install just
OpenBSDpkg_*justpkg_add just
Linux
Operating SystemPackage ManagerPackageCommand
Alpineapk-toolsjustapk add just
Archpacmanjustpacman -S just
Debian 13 and Ubuntu 24.04 derivativesaptjustapt install just
FedoraDNFjustdnf install just
GentooPortagedev-build/just emerge -av dev-build/just
NixOSNixjustnix-env -iA nixos.just
openSUSEZypperjustzypper in just
Soluseopkgjusteopkg install just
VoidXBPSjustxbps-install -S just
Windows
包管理器软件包命令
Chocolateyjustchoco install just
Scoopjustscoop install just
Windows 包管理器Casey/Justwinget install --id Casey.Just --exact
macOS
包管理器软件包命令
MacPortsjustport install just

just 软件包版本表

预构建二进制文件

Linux、macOS 和 Windows 的预构建二进制文件可在 发布页面找到。

在 Linux、macOS 或 Windows 上,可以使用以下命令下载最新版本,只需将 DEST 替换为你想放置 just 的目录:

curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to DEST

例如,将 just 安装到 ~/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

请注意,在 GitHub Actions 上,或在许多机器共用 IP 地址的其他环境中,install.sh 可能会失败。install.sh 会调用 GitHub API 来确定要安装的 just 最新版本,而这些 API 调用会按 IP 地址进行速率限制。若要在此类情况下提高 install.sh 的可靠性,请使用 --tag 指定要安装的标签。

另一种避免速率限制的方法,是将 GitHub 身份验证令牌作为名为 GITHUB_TOKEN 的环境变量传递给 install.sh,以便它对请求进行身份验证。

发布版本包含一个 SHA256SUM 文件,可用于验证预构建二进制归档的完整性。

若要验证某个发布版本,请下载预构建二进制归档和 SHA256SUM 文件,然后运行:

shasum --algorithm 256 --ignore-missing --check SHA256SUMS
GitHub Actions

可以通过几种方式在 GitHub Actions 上安装 just。

使用 GitHub Actions macOS 运行器上预装的包管理器,运行 brew install just;在 Windows 上运行 choco install just。

使用 extractions/setup-just:

- uses: extractions/setup-just@v3
  with:
    just-version: 1.5.0  # optional semver specification, otherwise latest

或者使用 taiki-e/install-action:

- uses: taiki-e/install-action@just
Docker

可从 GitHub 容器注册表获取 just 的 Docker 镜像。

若要将 just 复制到 Docker 镜像中,请在 Dockerfile 中添加以下行:

COPY --from=ghcr.io/casey/just:latest /just /usr/local/bin/

复制完成后,也可以在 Docker 构建过程中使用 just:

RUN just
发布 RSS 订阅源

此处提供 just 发布版本的 RSS 订阅源。

Node.js 安装

just-install 可用于在 Node.js 应用中自动安装 just。

just 是 npm 脚本的优秀且更可靠的替代方案。如果你想将 just 添加到 Node.js 应用的依赖项中,just-install 会在执行 npm install 命令时安装适用于本地平台的二进制文件。这样,每位开发者就不必再使用上述某种方式单独安装 just。安装完成后,便可在 npm 脚本中使用 just 命令,也可通过 npx 使用。对于希望尽可能简化项目设置流程的团队来说,这是个很好的选择。

如需了解更多信息,请参阅 just-install README 文件。

Nix Flake

just 仓库包含一个 flake.nix,其中定义了 nix flake,因此你可以将 just 用作另一个 flake 的输入:

{
  inputs = {
    just.url = "github:casey/just";
  }

  outputs = {self, nixpkgs, just}: {
  }
}

向后兼容性

自 1.0 版本发布以来,just 始终高度重视向后兼容性与稳定性。

未来版本不会引入向后不兼容的更改,导致现有的 justfile 无法运行,或破坏命令行接口原本可正常执行的调用。

不过,这并不妨碍修复明确的 Bug,即使这样做可能会破坏依赖其行为的 justfile。

永远不会有 just 2.0。任何值得采用的向后不兼容更改都将按每个 justfile 单独选择启用,因此用户可以自行决定何时迁移。

尚未准备好稳定使用的功能会标记为不稳定,并可能随时更改或移除。默认情况下,使用不稳定功能会报错;传入 --unstable 标志、设置 set unstable,或将环境变量 JUST_UNSTABLE 设为任何不是 false、0 或空字符串的值,即可抑制此错误。

要求最低 Just 版本

如果你使用的 just 功能要求特定版本,可以使用 minimum-version1.55.0 设置,让使用较旧版本的 just 时报错:

set minimum-version := '1.55.0'

如果 just 遇到的最低版本要求高于自身版本,它会打印一条错误消息,指出所需版本;这应该比它原本会显示的令人困惑的错误消息更清楚。

minimum-version 设置应放在 justfile 的顶部,位于任何受其保护的新功能用法之前。

任何以向前不兼容的方式更改词法分析器的功能,仍会产生不够明确的错误消息,因为最低版本检查是在解析器中实现的,而解析器在词法分析器之后运行。

编辑器支持

justfile 语法与 make 足够相似,因此你可能希望让编辑器使用 make 语法高亮来处理 just。

Vim 和 Neovim

得益于 pbnj,Vim 9.1.1042 或更高版本以及 Neovim 0.11 或更高版本开箱即支持 Justfile 语法高亮。

vim-just

vim-just 插件为 justfile 提供语法高亮。

你可以使用自己喜欢的包管理器安装,例如 Plug:

call plug#begin()

Plug 'NoahTheDuke/vim-just'

call plug#end()

也可以使用 Vim 内置的包支持:

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 是适用于 Neovim 的 Nvim Treesitter 插件。

Emacs

just-mode 提供 justfile 的语法高亮和自动缩进。可在 MELPA 上获取,包名为 just-mode。

justl 提供执行和列出配方的命令。

你可以将以下内容添加到单个 justfile 中,以便在该文件中启用 make 模式:

# Local Variables:
# mode: makefile
# End:
Visual Studio Code

VS Code 扩展可在此处获取。

已停止维护的 VS Code 扩展包括 skellock/vscode-just 和 sclu1034/vscode-just。

JetBrains IDEs

由 linux_china 开发的 JetBrains IDE 插件 可在此处获取。

Kakoune

感谢 TeddyDD,Kakoune 开箱即支持 justfile 语法高亮。

Helix

Helix 从 23.05 版本起开箱即支持 justfile 语法高亮。

Sublime Text

由 nk9 开发的 Just 包包含 just 语法和一些其他工具,可在 PackageControl 上获取。

Micro

感谢 tomodachi94,Micro 开箱即支持 Justfile 语法高亮。

Zed

由 jackTabsCode 开发的 zed-just 扩展可在 Zed 扩展页面获取。

其他编辑器

欢迎将让你所用的编辑器实现语法高亮所需的命令发送给我,以便我将其添加到这里。

语言服务器协议

just-lsp 提供了语言服务器协议实现,支持跳转到定义、内联诊断和代码补全等功能。

Model Context Protocol

just-mcp 提供了一个 model context protocol 适配器,让大模型能够查询 justfiles 的内容并运行配方。

快速入门

请参阅安装部分,了解如何在计算机上安装 just。尝试运行 just --version,确认已正确安装。

如需了解语法概览,请查看 这份速查表。

安装并正常运行 just 后,请在项目根目录创建一个名为 justfile 的文件,内容如下:

recipe-name:
  echo 'This is a recipe!'

# this is a comment
another-recipe:
  @echo 'This is another recipe.'

调用 just 时,它会在当前目录及其上级目录中查找名为 justfile 的文件,因此你可以从项目中的任意子目录调用它。

搜索 justfile 时不区分大小写,因此 Justfile、JUSTFILE 或 JuStFiLe 等任何大小写形式都有效。如果你想隐藏 justfile,just 也会查找名为 .justfile 的文件。

默认情况下,不带参数运行 just 会运行 justfile 中的第一个配方:

$ just
echo 'This is a recipe!'
This is a recipe!

一个或多个参数用于指定要运行的配方:

$ just another-recipe
This is another recipe.

just 会在运行每条命令之前将其打印到标准错误,因此会打印 echo 'This is a recipe!'。以 @ 开头的行不会显示命令,因此 echo 'This is another recipe.' 不会被打印。

如果命令失败,配方就会停止运行。此处只有 cargo test 成功后才会运行 cargo publish:

publish:
  cargo test
  # tests passed, time to publish!
  cargo publish

配方可以依赖其他配方。此处 test 配方依赖 build 配方,因此会先运行 build,再运行 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!

没有依赖项的配方会按照它们在命令行中给出的顺序运行:

$ just build sloc
cc main.c foo.c bar.c -o main
1337 lines of code

即使依赖项出现在依赖它的配方之后,依赖项也总会先运行:

$ just test build
cc main.c foo.c bar.c -o main
./test
testing… all tests passed!

配方可以依赖子模块中的配方:

mod foo

baz: foo::bar

示例

各种 justfile 示例可在 examples 目录和 GitHub 上找到。

配方

默认配方

调用 just 时如果没有指定配方,它会运行带有 [default] 属性的配方;如果没有配方带有 [default] 属性,则运行 justfile 中的第一个配方。

这个配方可以是项目中最常运行的命令,例如运行测试:

test:
  cargo test

你也可以使用依赖项默认运行多个配方:

default: lint build test

build:
  echo Building…

test:
  echo Testing…

lint:
  echo Linting…

如果没有适合作为默认配方的配方,可以使用 default-list1.52.0 来列出可用配方:

set default-list := true
配方参数

配方可以带参数。例如,配方 build 有一个名为 target 的参数:

build target:
  @echo 'Building {{target}}…'
  cd {{target}} && make

要在命令行中传递参数,请将它们放在配方名称之后:

$ just build my-awesome-project
Building my-awesome-project…
cd my-awesome-project && make

要向依赖项传递参数,请将依赖项和参数一起放在括号中:

default: (build "main")

build target:
  @echo 'Building {{target}}…'
  cd {{target}} && make

也可以将变量作为参数传递给依赖项:

target := "main"

_build version:
  @echo 'Building {{version}}…'
  cd {{version}} && make

build: (_build target)

可以将命令的参数传递给依赖项,方法是将依赖项和参数一起放在 括号中:

build target:
  @echo "Building {{target}}…"

push target: (build target)
  @echo 'Pushing {{target}}…'

参数可以有默认值:

default := 'all'

test target tests=default:
  @echo 'Testing {{target}}:{{tests}}…'
  ./test --tests {{tests}} {{target}}

有默认值的参数可以省略:

$ just test server
Testing server:all…
./test --tests all server

也可以提供:

$ just test server unit
Testing server:unit…
./test --tests unit server

默认值可以是任意表达式,但包含 +、&&、|| 或 / 运算符的表达式必须放在括号中:

arch := "wasm"

test triple=(arch + "-unknown-unknown") input=(arch / "input.dat"):
  ./test {{triple}}

配方的最后一个参数可以是可变参数,在参数名称前加 + 或 * 表示:

backup +FILES:
  scp {{FILES}} [email protected]:

前缀为 + 的可变参数接受 一个或多个 参数,并展开为由空格分隔的参数字符串:

$ just backup FAQ.md GRAMMAR.md
scp FAQ.md GRAMMAR.md [email protected]:
FAQ.md                  100% 1831     1.8KB/s   00:00
GRAMMAR.md              100% 1666     1.6KB/s   00:00

前缀为 * 的可变参数接受 零个或多个 参数,并展开为由空格分隔的参数字符串;如果没有参数,则展开为空字符串:

commit MESSAGE *FLAGS:
  git commit {{FLAGS}} -m "{{MESSAGE}}"

可变参数可以设置默认值。命令行传入的参数会覆盖这些默认值:

test +FLAGS='-q':
  cargo test {{FLAGS}}

可使用 [arg(ARG, min=MIN)] 和 [arg(ARG, max=MAX)] 属性1.56.0 限制可变参数接受的参数数量;使用这些属性需要启用列表:

set unstable
set lists

[arg('FILES', min='2', max='4')]
backup +FILES:
  scp {{FILES}} [email protected]:

min 和 max 同样适用于默认值。

如果 {{…}} 替换内容中包含空格,可能需要加引号。例如,假设你有以下配方:

search QUERY:
  lynx https://www.google.com/?q={{QUERY}}

然后输入:

$ just search "cat toupee"

just 将运行命令 lynx https://www.google.com/?q=cat toupee。sh 会将其解析为 lynx、https://www.google.com/?q=cat 和 toupee,而不是预期的 lynx 和 https://www.google.com/?q=cat toupee。

你可以通过添加引号来解决这个问题:

search QUERY:
  lynx 'https://www.google.com/?q={{QUERY}}'

前缀为 $ 的参数会被导出为环境变量:

foo $bar:
  echo $bar

可以使用 [arg("name", pattern=PATTERN)] 属性1.45.0限制参数,使其匹配正则表达式模式:

[arg('n', pattern='\d+')]
double n:
  echo $(({{n}} * 2))

pattern 的值可以是常量表达式1.55.0。

模式会自动添加开头的 ^ 和结尾的 $,因此必须匹配整个参数值。

可以使用 | 运算符将模式限制为若干种备选项:

[arg('flag', pattern='--help|--version')]
info flag:
  just {{flag}}

正则表达式由 Rust regex crate 提供。有关用法示例,请参阅语法文档。

可以使用 --usage 子命令1.46.0打印配方的用法信息:

$ just --usage foo
Usage: just foo [OPTIONS] bar

Arguments:
  bar

可以使用 [arg(ARG, help=HELP)] 属性为参数添加帮助字符串:

[arg("bar", help="hello")]
foo bar:

help 的值可以是常量表达式1.55.0。

$ just --usage foo
Usage: just foo bar

Arguments:
  bar hello
Recipe 标志和选项

Recipe 参数默认按位置传递。

在这个 justfile 中:

@foo bar:
  echo bar={{bar}}

参数 bar 按位置传递:

$ just foo hello
bar=hello

可以使用 [arg(ARG, long=OPTION)]1.46.0 属性,将参数设为长选项。

在这个 justfile 中:

[arg("bar", long="bar")]
foo bar:

参数 bar 通过 --bar 选项传递:

$ just foo --bar hello
bar=hello

选项也可以使用 --name=value 语法传递:

$ just foo --bar=hello
bar=hello

可以省略 long 的值,此时选项默认采用参数的名称。使用以下 justfile 时,可以通过 --bar 传递 bar:

[arg("bar", long)]
foo bar:

可以使用 [arg(ARG, short=OPTION)]1.46.0 属性,将参数设为短选项。

在这个 justfile 中:

[arg("bar", short="b")]
foo bar:

参数 bar 通过 -b 选项传递:

$ just foo -b hello
bar=hello

可以省略 short 的值,此时选项默认采用参数名称的首字母。使用以下 justfile 时,可以通过 -b 传递 bar:

[arg("bar", short)]
foo bar:

如果一个参数同时具有长选项和短选项,可以任选其一传递。

多个短选项可以组合使用1.55.0,例如,-abc 等同于 -a -b -c。需要取值的短选项可以放在最后,例如 -abcd VALUE。

可变参数 * 和 + 也可以设为选项,此时该选项可以重复使用,每次出现都会提供一个值:

[arg('file', long)]
backup +file:
  scp {{file}} [email protected]:
$ just backup --file FAQ.md --file GRAMMAR.md
scp FAQ.md GRAMMAR.md [email protected]:

与按位置传递的可变参数一样,+ 选项必须至少传递一次,而 * 选项可以省略。

可以将 [arg(ARG, value=VALUE, …)]1.46.0 属性与 long 或 short 搭配使用,将参数设为不需要取值的标志。VALUE 可以是表达式1.54.0。

在这个 justfile 中:

[arg("bar", long="bar", value="hello")]
foo bar:

参数 bar 通过 --bar 选项传递,但不需要取值,而是使用 [arg] 属性中给出的值:

$ just foo --bar
bar=hello

这对于在危险命令中强制要求使用 --force 之类的标志很有用。

如果标志对应的参数设置了默认值,该标志就是可选的:

[arg("bar", long="bar", value="hello")]
foo bar="goodbye":

未在调用时传递标志时,它会接收默认值:

$ just foo
bar=goodbye

如果向未定义 --help 或 -h 选项的 recipe 传递该选项,就会打印该 recipe 的帮助字符串并停止执行master。

避免参数拆分

给定以下 justfile:

foo argument:
  touch {{argument}}

以下命令会创建两个文件:some 和 argument.txt:

$ just foo "some argument.txt"

用户的 shell 会将 "some argument.txt" 解析为单个参数,但当 just 将 touch {{argument}} 替换为 touch some argument.txt 时,引号不会保留,touch 会收到两个参数。

有几种避免这种情况的方法:加引号、使用位置参数和导出参数。

加引号

可以在 {{argument}} 插值外加上引号:

foo argument:
  touch '{{argument}}'

这样既能保留 just 在运行前捕获变量名拼写错误的能力,例如写成 {{argument}},但如果 argument 的值包含单引号,就无法达到预期效果。

位置参数

positional-arguments 设置会将所有参数作为位置参数传递,因此可以通过 $1、$2、… 和 $@ 访问它们,再用双引号括起来,避免 shell 进一步拆分:

set positional-arguments

foo argument:
  touch "$1"

这会使 just 无法捕获拼写错误,例如误将 $1 写成 $2,但适用于 argument 的所有可能值,包括包含双引号的值。

导出参数

设置 export 后,所有参数都会被导出:

set export

foo argument:
  touch "$argument"

也可以在单个参数前加上 $ 来导出它:

foo $argument:
  touch "$argument"

这会使 just 无法捕获拼写错误,例如误写成 $argument,但适用于 argument 的所有可能值,包括包含双引号的值。

位置参数

如果 positional-arguments 为 true,配方参数将作为位置参数传递给命令。对于 shell 配方,参数 $0 将是配方名称。

例如,运行此配方:

set positional-arguments

@foo bar:
  echo $0
  echo $1

将产生以下输出:

$ just foo hello
foo
hello

使用 sh 兼容的 shell(例如 bash 或 zsh)时,$@ 会展开为传递给配方的位置参数,从第一个参数开始。在双引号中使用时,如 "$@",包含空白字符的参数会像用双引号括起来一样传递。也就是说,"$@" 等同于 "$1" "$2"…… 没有位置参数时,"$@" 和 $@ 会展开为空(即被移除)。

此示例配方会将参数逐个打印在单独的行上:

set positional-arguments

@test *args='':
  bash -c 'while (( "$#" )); do echo - $1; shift; done' -- "$@"

使用两个参数运行它:

$ just test foo "bar baz"
- foo
- bar baz

也可以通过 [positional-arguments] 属性1.29.0,为单个配方启用位置参数:

[positional-arguments]
@foo bar:
  echo $0
  echo $1

请注意,PowerShell 处理位置参数的方式与其他 shell 不同,因此启用位置参数很可能会导致使用 PowerShell 的配方无法正常运行。

如果使用 PowerShell 7.4 或更高版本,-CommandWithArgs 标志可使位置参数按预期工作:

set shell := ['pwsh.exe', '-CommandWithArgs']
set positional-arguments

print-args a b c:
  Write-Output @($args[1..($args.Count - 1)])
依赖项

依赖项会在依赖它们的配方之前运行:

a: b
  @echo A

b:
  @echo B
$ just a
B
A

在一次 just 调用中,同一配方在参数相同的情况下只会运行一次,无论它在命令行调用中出现多少次,或作为依赖项出现多少次:

a:
  @echo A

b: a
  @echo B

c: a
  @echo C
$ just a a a a a
A
$ just b c
A
B
C

多个配方可以依赖执行某种设置操作的配方;这些配方运行时,设置操作只会执行一次:

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

只有在接收到相同参数时,单次运行中的配方才会被跳过:

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
在配方结束后运行配方

配方的普通依赖项总是在配方开始之前运行。也就是说,被依赖的配方总是在依赖它的配方之前运行。这些依赖项称为“前置依赖项”。

配方也可以有后续依赖项,它们会紧接在配方之后运行,并使用 && 引入:

a:
  echo 'A!'

b: a && c d
  echo 'B!'

c:
  echo 'C!'

d:
  echo 'D!'

……运行 b 会打印:

$ just b
echo 'A!'
A!
echo 'B!'
B!
echo 'C!'
C!
echo 'D!'
D!
在配方中途运行配方

just 不支持在另一个配方中途运行配方,但你可以在配方中途递归调用 just。给定以下 justfile:

a:
  echo 'A!'

b: a
  echo 'B start!'
  just c
  echo 'B end!'

c:
  echo 'C!'

……运行 b 会打印:

$ just b
echo 'A!'
A!
echo 'B start!'
B start!
echo 'C!'
C!
echo 'B end!'
B end!

这种方式有一些限制,因为配方 c 会通过全新的 just 调用运行:赋值会重新计算,依赖项可能会运行两次,并且命令行参数不会传递给子 just 进程。

并行执行

可以通过 [parallel] 属性并行运行依赖项。

在此 justfile 中,运行 main 时,foo、bar 和 baz 将并行执行:

[parallel]
main: foo bar baz

foo:
  sleep 1

bar:
  sleep 1

baz:
  sleep 1

可以通过 --jobs 选项1.56.0限制同时运行的配方数量。num_jobs() 函数会返回作业数量;如果未传入 --jobs,则返回空列表。

可以使用 GNU parallel 并发运行配方行:

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
文档注释

紧邻配方之前的注释会显示在 just --list 中:

# build stuff
build:
  ./bin/build

# test stuff
test:
  ./bin/test
$ just --list
Available recipes:
    build # build stuff
    test # test stuff

可以使用 [doc] 属性设置或抑制配方的文档注释:

# 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

[doc] 的值可以是常量表达式1.56.0。

分组

可以为配方和模块标注一个或多个组名:

[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…'

配方会按组列出:

$ just --list
Available recipes:
    email-everyone # not in any group

    [lint]
    cpp-lint
    js-lint
    rust-lint

    [rust recipes]
    rust-lint

just --list --unsorted 会按各组中配方在 justfile 里的顺序打印配方:

$ 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 列出组:

$ just --groups
Recipe groups:
  lint
  rust recipes

使用 just --groups --unsorted 可按组在 justfile 中的顺序打印组名。

别名

别名允许使用其他名称在命令行中调用配方:

alias b := build

build:
  echo 'Building!'
$ just b
echo 'Building!'
Building!

别名的目标可以是子模块中的配方:

mod foo

alias baz := foo::bar

也可以是模块1.55.0:

mod frontend

alias f := frontend
$ just f build
私有配方

名称以 _ 开头的配方和别名不会显示在 just --list 中:

test: _test-helper
  ./bin/test

_test-helper:
  ./bin/super-secret-test-helper-stuff
$ just --list
Available recipes:
    test

也不会显示在 just --summary 中:

$ just --summary
test

也可以使用 [private] 属性1.10.0隐藏配方或别名,而无需更改名称:

[private]
foo:

[private]
alias b := bar

bar:
$ just --list
Available recipes:
    bar

这对于仅供其他配方作为依赖项使用的辅助配方很有用。

启用和禁用项目

[android]、[dragonfly]、[freebsd]、[linux]、[macos]、[netbsd]、[openbsd]、[unix] 和 [windows] 属性是条件属性。默认情况下,项目始终处于启用状态。带有一个或多个条件属性的项目仅会在其中一个或多个条件属性生效时启用。

条件属性最初仅适用于配方,但现在也可应用于所有顶层项目1.56.0。

这可用于编写在不同操作系统上运行时行为各异的 justfile。此 justfile 中的 run 配方会编译并运行 main.c,根据操作系统使用不同的 C 编译器,并采用对应编译器的正确输出二进制文件名:

[unix]
run:
  cc main.c
  ./a.out

[windows]
run:
  cl main.c
  main.exe

同样,也可以根据当前操作系统设置某项配置:

[unix]
set shell := ['sh', '-cu']

[windows]
set shell := ['cmd', '/c']
允许重复配方

如果将 allow-duplicate-recipes 设为 true,则可以定义多个同名配方而不会报错,并使用最后一个定义。默认值为 false。

set allow-duplicate-recipes

@foo:
  echo foo

@foo:
  echo bar
$ just foo
bar

表达式

变量和赋值

可以通过使用 := 为模块级变量赋值来创建变量:

foo := "hello"
bar := "world"

baz:
  echo {{ foo + " " + bar }}

可以打印模块中的所有变量:

$ just --evaluate
bar := "world"
foo := "hello"

也可以打印单个变量的值:

$ just --evaluate foo
hello

可以通过指定子模块或变量的路径,打印子模块中的所有变量或其中的单个变量1.49.0:

$ just --evaluate bob::bar
x := "world"
y := "hello"
$ just --evaluate bob::bar::y
hello

可以使用 --evaluate-format1.49.0 控制导出变量的格式:

$ just --evaluate --evaluate-format shell
bar="world"
foo="hello"

默认格式为 --evaluate-format just:

$ just --evaluate --evaluate-format just
bar := "world"
foo := "hello"
允许重复变量

如果将 allow-duplicate-variables 设为 true,则可以定义多个同名变量而不会报错,并使用最后一个定义。默认值为 false。

set allow-duplicate-variables

a := "foo"
a := "bar"

@foo:
  echo {{a}}
$ just foo
bar
惰性求值

lazy 设置1.47.0会使求值器跳过未使用变量的求值。当 justfile 包含求值开销较大、但仅在某些情况下使用的变量时,这会很有用。

在以下 justfile 中,仅调用 bar 时会跳过 token:

set lazy

token := `expensive-script-to-get-credentials`

foo:
  curl -H "Authorization: Bearer {{ token }}" https://example.com/foo

bar:
  cargo test

由于 just 无法确定导出变量何时会被使用,带有 export 的赋值以及在设置了 set export 的模块中的赋值始终会进行求值。

带有 eager1.47.1 关键字的赋值始终会进行求值。

在以下 justfile 中,无论是否使用 setup,都会对其求值:

set lazy

eager setup := `echo 'setup: yes' > config.yaml`

test:
  cargo test
表达式和替换

表达式中支持各种运算符和函数调用,可用于赋值、配方参数默认值以及配方主体中的 {{…}} 替换。

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}} [email protected]:release/
  rm -rf {{tarball}} {{tardir}}
拼接

+ 运算符会将左侧参数与右侧参数拼接后返回:

foobar := 'foo' + 'bar'
逻辑运算符

逻辑运算符 && 和 || 可用于合并值1.37.0,类似于 Python 的 and 和 or。唯一的假值是空列表 [];其他所有值,包括空字符串 '',都是真值。

这些运算符需要启用 set lists1.53.0,该功能目前尚不稳定。

如果左侧参数为假,&& 运算符会返回空列表;否则返回右侧参数:

foo := [] && 'goodbye'      # []
bar := 'hello' && 'goodbye' # 'goodbye'

如果左侧参数为真,|| 运算符会返回左侧参数;否则返回右侧参数:

foo := [] || 'goodbye'      # 'goodbye'
bar := 'hello' || 'goodbye' # 'hello'
拼接路径

/ 运算符可用于用斜杠拼接两个字符串:

foo := "a" / "b"
$ just --evaluate foo
a/b

请注意,即使已有 /,也会再添加一个:

foo := "a/"
bar := foo / "b"
$ just --evaluate bar
a//b

也可以构造绝对路径1.5.0:

foo := / "b"
$ just --evaluate foo
/b

/ 运算符使用 / 字符,即使在 Windows 上也是如此。因此,对于使用通用命名约定(UNC)的路径,应避免使用 / 运算符,也就是那些以 \\ 开头的路径,因为 UNC 路径不支持正斜杠。

转义 {{

要在配方中写入 {{,请使用 {{{{:

braces:
  echo 'I {{{{LOVE}} curly braces!'

(未匹配的 }} 会被忽略,因此无需转义。)

另一种方法是将所有要转义的文本放在插值中:

braces:
  echo '{{'I {{LOVE}} curly braces!'}}'

还有一种方法是使用 {{ "{{" }}:

braces:
  echo 'I {{ "{{" }}LOVE}} curly braces!'
字符串

支持使用 'single'、"double" 和 '''triple''' 引号的字符串字面量。与配方主体不同,字符串中不支持 {{…}} 插值。

双引号字符串支持转义序列:

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 := "🤖"

Unicode 字符转义序列 \u{…}1.36.0 最多接受 6 位十六进制数字。

字符串可以包含换行符:

single := '
hello
'

double := "
goodbye
"

单引号字符串不识别转义序列:

escapes := '\t\n\r\"\\'
$ just --evaluate
escapes := "\t\n\r\"\\"

支持使用三个单引号或双引号作为定界符的缩进版单引号和双引号字符串。缩进字符串行会移除开头的换行符,以及所有非空行共有的前导空白:

# this string will evaluate to `foo\nbar\n`
x := '''
  foo
  bar
'''

# this string will evaluate to `abc\n  wuv\nxyz\n`
y := """
  abc
    wuv
  xyz
"""

与非缩进字符串类似,缩进的双引号字符串会处理转义序列,而缩进的单引号字符串会忽略转义序列。转义序列会在去除缩进后处理。去缩进算法不会将转义序列产生的空白或换行符纳入考量。

Shell 展开字符串

以 x 为前缀的字符串会进行 Shell 展开1.27.0:

foobar := x'~/$FOO/${BAR}'
值替换内容
$VAR环境变量 VAR 的值
${VAR}环境变量 VAR 的值
${VAR:-DEFAULT}环境变量 VAR 的值;若未设置 VAR,则使用 DEFAULT
前导 ~当前用户主目录的路径
前导 ~USERUSER 主目录的路径

此展开在编译时执行,因此无法使用 .env 文件中的变量和导出的 just 变量。不过,这样就可以在设置和导入路径等位置使用 Shell 展开字符串,因为这些位置无法依赖 just 变量和 .env 文件。

格式字符串

以 f 为前缀的字符串是格式字符串1.44.0:

name := "world"
message := f'Hello, {{name}}!'

格式字符串可以包含用 {{…}} 定界的插值,其中包含表达式。格式字符串的求值结果是字符串片段与求值后的表达式拼接而成的字符串。

在格式字符串中使用 {{{{ 可包含字面量 {{:

foo := f'I {{{{LOVE} curly braces!'
列表

lists 设置1.53.0允许使用字符串列表作为值。 此功能目前尚不稳定,未来可能会以不兼容旧版本的方式更改。本节说明启用 set lists 后行为上的变化。

许多内置函数如何处理列表尚未确定。本节会提及已更新为接受列表的函数。将列表用于其他任何函数都会报错。join_list() 函数可将列表转换为空格分隔的字符串,以便用于尚未更新的函数。非常欢迎大家反馈内置函数应如何处理列表,以及对列表功能的总体意见!欢迎提交 issue,或在 set lists tracking issue 中留言。

可变参数配方参数是字符串列表,而不是单个空格分隔的字符串。

列表字面量的写法是 [a, b, c]。列表字面量会展开其参数,因为列表中只能包含字符串,不能包含其他列表。例如,[["a", "b"], [], "c"] 的求值结果为 ["a", "b", "c"]。

配方插值和 f 字符串插值中的列表会以空格连接成一个字符串。

依赖项的每个参数都只会绑定到一个参数,而为可变参数依赖项提供多余参数会报错。

使用 *(recipe *argument),可针对列表的每个元素调用一次依赖项。

当参数的实参为空列表时,该参数会取默认值。

向没有默认值的非 * 参数传递空列表会报错。

if 可以省略 else;此时,如果条件为假,if 的求值结果为 []。

assert(condition, message) 和 [confirm(message)] 中的消息值会以空格连接后显示。

+ 和 / 运算符可以组合字符串和列表。字符串与非空列表组合时,会将字符串与列表中的每个元素分别拼接。两个长度相同的列表组合后,会生成一个列表,其中包含两个操作数对应元素的拼接结果。组合长度不同的两个列表会报错。

++ 运算符用于拼接列表。

布尔值

规范的布尔真值是字符串 "true",规范的布尔假值是空列表 []。除空列表外,所有值都为真,包括 ''。

if 或 assert() 的条件可以是任意表达式,求值时会判断其真假。

比较运算符 ==、!=、=~ 和 !~ 可在任何位置使用,不仅限于 if 和 assert(),其求值结果为 "true" 或 []。

如果 value 中的任意元素匹配 regexes 中的任意正则表达式,则 value =~ regexes 为真。如果 value 或 regexes 为空,则为假。

如果 value 中没有任何元素匹配 regexes 中的任何正则表达式,则 value !~ regexes 为真。如果 value 或 regexes 为空,也为真。

值可以用 ! 取反。如果 expression 为 [],则 !expression 的求值结果为 "true";否则结果为 []。

设置

script-interpreter、shell 和 windows-shell 设置会像列表字面量一样将各元素合并。

设置 positional-arguments 后,列表参数会以空格连接;如果参数为可变参数,则每个元素会作为一个位置参数传递。

--dotenv-filename 和 --dotenv-path 选项可以传入多次,dotenv-filename 和 dotenv-path 设置也接受列表,因此可以加载多个环境文件。首先尝试 dotenv-path 的值。如果没有找到任何文件,则在当前目录中搜索 dotenv-filename 中的名称,然后依次搜索其祖先目录;遇到第一个包含任一文件的目录时停止,并加载该目录中所有匹配的文件。如果加载了多个环境文件,列表中靠后的文件中的变量优先级高于靠前的文件。

set dotenv-command 值中的每个元素都会作为命令运行,列表中靠后的命令所提供的变量优先级高于靠前的命令。

属性

[arg(flag)] 属性会将参数设为命令行上无需取值的标志。例如,使用 [arg('foo', long, flag)] 时,传入 --foo 会使 foo 的值为 "true",否则为 []。标志参数不能有默认值。

[arg(multiple)] 属性允许选项或标志重复传入,并将传入的值列表赋给参数。与 flag 或 value=VALUE 结合使用时,每次出现该标志都会分别重复 "true" 或 VALUE。

[arg(min=MIN)] 和 [arg(max=MAX)] 属性1.56.0可用于限制选项或标志可接收的值的数量。

[arg(help)] 的值可以是列表,此时帮助字符串由列表中的各元素以空格连接而成。如果列表为空,则该参数没有帮助字符串。

[arg(pattern)] 的值可以是列表,此时参数只要匹配列表中的任一模式即可被接受。如果值为空列表,则接受任何参数。例如,使用 [arg('foo', pattern=['--help', '--version'])] 时,foo 可以是 --help 或 --version。

在 [env(variable, value)] 中,如果 value 为 [],则不设置 variable。否则,variable 会被设为以空格连接的 value。

函数
  • absolute_path() - 分别应用于列表中的每个元素。
  • append() - 分别应用于列表中的每个元素,且不会按空白字符拆分元素。
  • assert(condition, message) - 求值结果为 condition。
  • bool(value) 将 value 转换为规范布尔值。当 value 为 ""、"0"、"false" 或 [] 时,返回 [];当 value 为 "1" 或 "true" 时,返回 "true"。其他值都会导致错误。可用于解析作为参数或环境变量传入的布尔值。
  • env(keys, default) 按顺序检查 keys 中命名的环境变量,并返回第一个已设置变量的值。如果都未设置,则返回 default;如果省略 default,则会报错。
  • is_dependency() - 返回规范布尔值。
  • join_list(value, separator) - 将 value 合并为一个字符串。元素之间使用 separator 分隔;如果省略 separator,则使用单个空格。
  • len(value) - 返回 value 中的元素数量。
  • path_exists() - 返回规范布尔值。
  • prepend() - 分别应用于列表中的每个元素,且不会按空白字符拆分元素。
  • quote() - 分别应用于列表中的每个元素。
  • semver_matches() - 返回规范布尔值。
  • show(value) - 将 value 转换为包含其字面表示形式的字符串。空列表和含多个元素的列表使用方括号,例如 "[]" 和 "["foo", "bar"]";单元素列表则不使用方括号,例如 "foo"。
  • split(string, separator) - 在 separator 的每次出现处拆分 string,并生成一个列表。如果省略 separator,则按空白字符拆分 string,并去除首尾空白字符。
  • which() - 未找到可执行文件时返回空列表。
示例

每个列表元素都会单独调用 quote():

set unstable
set lists

@foo *args:
  printf '%s\n' {{ quote(args) }}
$ just foo bar 'baz bob'
bar
baz bob

quote(args) 的返回值是 'bar' 'baz bob',而不是不使用 set lists 时得到的 'bar baz bob'。

可变数量的位置参数:

set unstable
set lists
set positional-arguments

foo *args: (bar args 'bob') (baz args)

@bar first second:
  echo first=$1
  echo second=$2

@baz *args:
  echo '$1='$1
  echo '$2='$2
$ just foo one two
first=one two
second=bob
$1=one
$2=two

对于映射依赖项,会针对其带星号参数中的每个元素调用一次;使用 [parallel] 可并行运行:

set unstable
set lists

[parallel]
build target *platform: *(compile target *platform)

@compile target platform:
  echo compiling {{ target }} for {{ platform }}…
$ just build x86 foo bar
compiling foo for x86…
compiling bar for x86…

建议将规范假值 [] 作为选项的默认值:

set unstable
set lists

[arg('bar', long)]
foo bar=[]:
条件表达式

if/else 表达式根据两个表达式的求值结果是否相同,来对不同分支求值:

foo := if "2" == "2" { "Good!" } else { "1984" }

bar:
  @echo "{{foo}}"
$ just bar
Good!

也可以测试不相等:

foo := if "hello" != "goodbye" { "xyz" } else { "abc" }

bar:
  @echo {{foo}}
$ just bar
xyz

还可以与正则表达式进行匹配:

foo := if "hello" =~ 'hel+o' { "match" } else { "mismatch" }

bar:
  @echo {{foo}}
$ just bar
match

正则表达式由 regex crate 提供,其语法记录在 docs.rs 上。由于正则表达式通常使用反斜杠转义序列,建议使用单引号字符串字面量,这样斜杠就能原样传递给正则表达式解析器。

条件表达式采用短路求值,也就是说只会对其中一个分支求值。这样可以确保不应运行时,反引号表达式不会运行。

foo := if env_var("RELEASE") == "true" { `get-something-from-release-database` } else { "dummy-value" }

条件表达式可以在配方中使用:

bar foo:
  echo {{ if foo == "bar" { "hello" } else { "goodbye" } }}

可以链接多个条件表达式:

foo := if "hello" == "goodbye" {
  "xyz"
} else if "a" == "a" {
  "abc"
} else {
  "123"
}

bar:
  @echo {{foo}}
$ just bar
abc
使用反引号进行命令求值

可以使用反引号存储命令的结果:

localhost := `dumpinterfaces | cut -d: -f2 | sed 's/\/.*//' | sed 's/ //g'`

serve:
  ./serve {{localhost}} 8080

由三个反引号界定的缩进反引号块,会像缩进字符串一样去除缩进:

# This backtick evaluates the command `echo foo\necho bar\n`, which produces the value `foo\nbar\n`.
stuff := ```
    echo foo
    echo bar
  ```

有关去除缩进的详细信息,请参阅 Strings 部分。

反引号块不能以 #! 开头。此语法保留供未来升级使用。

shell(…) 函数提供了一种更通用的机制来调用外部命令,包括将变量内容作为命令执行,以及向命令传递参数的功能。

使用错误停止执行

可以使用 error 函数中止执行。例如:

foo := if "hello" == "goodbye" {
  "xyz"
} else if "a" == "b" {
  "abc"
} else {
  error("123")
}

运行时会产生以下错误:

error: Call to function `error` failed: 123
   |
16 |   error("123")
内置函数

just 提供许多可在表达式中使用的内置函数,包括配方正文 {{…}} 替换、赋值和默认参数值。

所有以 _directory 结尾的函数都可以缩写为 _dir。因此,home_directory() 也可以写作 home_dir()。此外,invocation_directory_native() 可以缩写为 invocation_dir_native()。

用户自定义函数

可以定义新函数1.49.0:

set unstable

hello(name) := f"Hello, {{ name }}!"

foo:
  echo '{{ hello("World") }}'

用户自定义函数目前尚不稳定。

函数可以引用同一模块中的赋值:

set unstable

base := "foo"

join(extension) := base + "." + extension

create:
  touch {{ join("c") }}
  touch {{ join("html") }}
  touch {{ join("txt") }}

执行

符号

Shell 配方中的命令可以添加 -、@ 和 ? 符号,且可以组合使用。

@ 符号用于切换命令回显:

foo:
  @echo "This line won't be echoed!"
  echo "This line will be echoed!"

@bar:
  @echo "This line will be echoed!"
  echo "This line won't be echoed!"

- 符号会使配方执行继续进行,即使命令返回非零退出状态:

# execution will continue, even if bar doesn't exist
foo:
  -rmdir bar
  mkdir bar
  echo 'so much good stuff' > bar/stuff.txt

? 符号1.47.0会在命令以状态码 1 退出时停止执行当前配方,但其他配方仍会继续执行。退出状态为 0 时,当前配方会照常继续执行。所有其他退出代码均为保留值,不应使用,因为它们可能会在 just 的未来版本中被赋予含义。

如果未设置 guards,或其值为 false,则 ? 符号会被忽略,并被视为命令的一部分。

set guards

@foo: bar
  echo FOO

@bar:
  ?[[ -f baz ]]
  echo BAR
$ just foo
FOO
$ touch baz
$ just foo
BAR
FOO
静默配方

可以在配方名称前加上 @,以反转每一行前 @ 的含义:

@quiet:
  echo hello
  echo goodbye
  @# all done!

现在只有以 @ 开头的行会回显:

$ just quiet
hello
goodbye
# all done!

可以使用 set quiet 将 justfile 中的所有配方设为静默:

set quiet

foo:
  echo "This is quiet"

@foo2:
  echo "This is also quiet"

[no-quiet] 属性会覆盖此设置:

set quiet

foo:
  echo "This is quiet"

[no-quiet]
foo2:
  echo "This is not quiet"

Shebang 配方默认是静默的:

foo:
  #!/usr/bin/env bash
  echo 'Foo!'
$ just foo
Foo!

在 Shebang 配方名称前添加 @,会使 just 在执行配方前打印配方内容:

@bar:
  #!/usr/bin/env bash
  echo 'Bar!'
$ just bar
#!/usr/bin/env bash
echo 'Bar!'
Bar!

配方行执行失败时,just 通常会打印错误消息。可以通过在单个配方上使用 [no-exit-message]1.7.0 属性,或通过 set no-exit-message1.39.0 在模块范围内抑制这些错误消息。如果配方用于封装某个工具,这项功能可能尤其有用:

git *args:
    @git {{args}}
$ just git status
fatal: not a git repository (or any of the parent directories): .git
error: Recipe `git` failed on line 2 with exit code 128

添加此属性,即可在工具以非零代码退出时抑制退出错误消息:

[no-exit-message]
git *args:
    @git {{args}}
$ just git status
fatal: not a git repository (or any of the parent directories): .git
Shebang 配方

以 #! 开头的配方称为 Shebang 配方。执行这类配方时,会先将配方正文保存到文件中,再运行该文件。这样,你就可以使用不同的语言编写配方:

polyglot: python js perl sh ruby nu

python:
  #!/usr/bin/env python3
  print('Hello from python!')

js:
  #!/usr/bin/env node
  console.log('Greetings from JavaScript!')

perl:
  #!/usr/bin/env perl
  print "Larry Wall says Hi!\n";

sh:
  #!/usr/bin/env sh
  hello='Yo'
  echo "$hello from a shell script!"

nu:
  #!/usr/bin/env nu
  let hello = 'Hola'
  echo $"($hello) from a nushell script!"

ruby:
  #!/usr/bin/env ruby
  puts "Hello from ruby!"
$ just polyglot
Hello from python!
Greetings from JavaScript!
Larry Wall says Hi!
Yo from a shell script!
Hola from a nushell script!
Hello from ruby!

在类 Unix 操作系统(包括 Linux 和 macOS)上,执行 Shebang 配方时,会先将配方正文保存到临时目录中的文件,再将文件标记为可执行,然后执行该文件。随后,操作系统会将 Shebang 行解析为命令行并调用该命令,其中包括文件路径。例如,如果配方以 #!/usr/bin/env bash 开头,操作系统最终运行的命令将类似于 /usr/bin/env bash /tmp/PATH_TO_SAVED_RECIPE_BODY。

Shebang 行的拆分方式取决于操作系统。传递带参数的命令时,可能需要使用 -S 标志告知 env 显式拆分参数:

run:
  #!/usr/bin/env -S bash -x
  ls

Windows 不支持 Shebang 行。在 Windows 上,just 会将 Shebang 行拆分为命令和参数,将配方正文保存到文件中,然后调用拆分后的命令和参数,并将已保存配方正文的路径作为最后一个参数添加。例如,在 Windows 上,如果配方以 #! py 开头,操作系统最终运行的命令将类似于 py C:\Temp\PATH_TO_SAVED_RECIPE_BODY。

脚本配方

带有 [script(COMMAND)]1.32.0 属性的配方会作为由 COMMAND 解释的脚本运行。这可以避免 shebang 配方的一些问题,例如在 Windows 上使用 cygpath、需要使用 /usr/bin/env、Unix 各操作系统对 shebang 行拆分方式不一致,以及需要临时目录来执行文件。

带有空 [script] 属性的配方会使用 set script-interpreter := […]1.33.0 的值执行,默认值为 sh -eu,而不是 set shell 的值。

配方主体会经过求值,写入临时目录中的文件,然后通过将文件路径作为参数传递给 COMMAND 来运行。

设置 set default-script := true1.52.0 后,配方默认会成为脚本配方,而非 shell 配方,除非被 [shell] 属性1.52.0 覆盖。

脚本和 Shebang 配方临时文件

脚本配方和 shebang 配方都会将配方主体写入临时文件以供执行。脚本配方通过将该文件传递给命令来执行,而 shebang 配方则直接执行该文件。如果临时文件所在的文件系统以 noexec 方式挂载,或以其他方式不可执行,shebang 配方将无法执行。

just 写入临时文件的目录可通过多种方式配置,优先级从高到低如下:

  • 通过 --tempdir 命令行选项或 JUST_TEMPDIR 环境变量1.41.0进行全局配置。

  • 通过 tempdir 设置按模块配置。

  • 在 Linux 上通过 XDG_RUNTIME_DIR 环境变量进行全局配置。

  • 若以上均未设置,则使用 std::env::temp_dir 返回的目录。

更安全的 Bash Shebang 配方

如果你正在编写 bash shebang 配方,可以考虑添加 set -euxo pipefail:

foo:
  #!/usr/bin/env bash
  set -euxo pipefail
  hello='Yo'
  echo "$hello from Bash!"

这并非严格必要,但 set -euxo pipefail 会启用一些实用功能,让 bash shebang 配方的行为更接近普通的 shell just 配方:

  • set -e 会在命令失败时让 bash 退出。

  • set -u 会在变量未定义时让 bash 退出。

  • set -x 会让 bash 在运行每一行脚本之前将其打印出来。

  • set -o pipefail 会在管道中的命令失败时让 bash 退出。这是 bash 特有的功能,因此普通的 shell just 配方不会启用它。

这些功能结合使用,可以避免许多 shell 脚本常见的陷阱。

Windows 上的 Shebang 配方执行

在 Windows 上,shebang 解释器路径中包含 / 时,会使用随 Cygwin 提供的实用程序 cygpath,将其从 Unix 风格路径转换为 Windows 风格路径。

例如,要在 Windows 上执行此配方:

echo:
  #!/bin/sh
  echo "Hello!"

解释器路径 /bin/sh 会在执行前使用 cygpath 转换为 Windows 风格路径。

如果解释器路径不包含 /,则会直接执行,不进行转换。如果 cygpath 不可用,或者你希望向解释器传递 Windows 风格的路径,这种方式会很有用。

缩进

配方行可以使用空格或制表符缩进,但不能混用。配方中的所有行都必须使用相同类型的缩进,但同一个 justfile 中的不同配方可以使用不同的缩进方式。

每个配方相对于 recipe-name 至少要缩进一级,此后还可以进一步缩进。

下面的 justfile 中,配方分别使用空格(以 · 表示)和制表符(以 → 表示)缩进。

[windows]
set shell := ["pwsh", "-NoLogo", "-NoProfileLoadTime", "-Command"]

set ignore-comments

list-space directory:
··#!pwsh
··foreach ($item in $(Get-ChildItem {{directory}} )) {
····echo $item.Name
··}
··echo ""

# indentation nesting works even when newlines are escaped
list-tab directory:
→ @foreach ($item in $(Get-ChildItem {{directory}} )) { \
→ → echo $item.Name \
→ }
→ @echo ""
PS > just list-space ~
Desktop
Documents
Downloads

PS > just list-tab ~
Desktop
Documents
Downloads
多行结构

没有初始 shebang 的配方会逐行求值并运行,这意味着多行结构可能无法按预期工作。

例如,以下 justfile:

conditional:
  if true; then
    echo 'True!'
  fi

conditional 配方第二行开头多出的空格会导致解析错误:

$ just conditional
error: Recipe line has extra leading whitespace
  |
3 |         echo 'True!'
  |     ^^^^^^^^^^^^^^^^

要解决此问题,可以将条件语句写在一行中、用反斜杠转义换行符,或为配方添加 shebang。以下提供了一些多行结构示例,供参考。

if 语句
conditional:
  if true; then echo 'True!'; fi
conditional:
  if true; then \
    echo 'True!'; \
  fi
conditional:
  #!/usr/bin/env sh
  if true; then
    echo 'True!'
  fi
for 循环
for:
  for file in `ls .`; do echo $file; done
for:
  for file in `ls .`; do \
    echo $file; \
  done
for:
  #!/usr/bin/env sh
  for file in `ls .`; do
    echo $file
  done
while 循环
while:
  while `server-is-dead`; do ping -c 1 server; done
while:
  while `server-is-dead`; do \
    ping -c 1 server; \
  done
while:
  #!/usr/bin/env sh
  while `server-is-dead`; do
    ping -c 1 server
  done
配方主体之外

括号表达式可以跨多行:

abc := ('a' +
        'b'
         + 'c')

abc2 := (
  'a' +
  'b' +
  'c'
)

foo param=('foo'
      + 'bar'
    ):
  echo {{param}}

bar: (foo
        'Foo'
     )
  echo 'Bar!'

以反斜杠结尾的行会延续到下一行,就像两行之间用空格连接一样1.15.0:

a := 'foo' + \
     'bar'

foo param1 \
  param2='foo' \
  *varparam='': dep1 \
                (dep2 'foo')
  echo {{param1}} {{param2}} {{varparam}}

dep1: \
    # this comment is not part of the recipe body
  echo 'dep1'

dep2 \
  param:
    echo 'Dependency with parameter {{param}}'

反斜杠续行也可以用于插值。反斜杠后面的行必须缩进。

recipe:
  echo '{{ \
  "This interpolation " + \
    "has a lot of text." \
  }}'
  echo 'back to recipe body'
在配方中设置变量

配方行由 shell 而非 just 解释,因此无法在配方中间设置 just 变量:

foo:
  x := "hello" # This doesn't work!
  echo {{x}}

虽然可以使用 shell 变量,但还有另一个问题:每个配方行都会由一个新的 shell 实例运行,因此在一行中设置的变量不会在下一行中生效:

foo:
  x=hello && echo $x # This works!
  y=bye
  echo $y            # This doesn't, `y` is undefined here!

解决此问题的最佳方式是使用 shebang 配方。shebang 配方主体会被提取并作为脚本运行,因此整个内容会由同一个 shell 实例运行:

foo:
  #!/usr/bin/env bash
  set -euxo pipefail
  x=hello
  echo $x
配置 Shell

有多种方式可以配置 shell 配方所用的 shell。未以 #! shebang 开头的配方默认使用 shell 配方。配置项的优先级从高到低如下:

  1. --shell 和 --shell-arg 命令行选项。传入其中任一选项后,just 都会忽略当前 justfile 中的所有设置。
  2. set windows-shell := [...](已弃用)
  3. set windows-powershell(已弃用)
  4. set shell := [...]

使用 set shell 时,可通过 [windows] 和 [unix] 属性在 Windows 上使用不同的 shell。

Shell

shell 设置用于控制调用配方行和反引号的命令。shebang 配方不受影响。默认 shell 为 sh -cu。

# use python3 to execute recipe lines and backticks
set shell := ["python3", "-c"]

# use print to capture result of evaluation
foos := `print("foo" * 4)`

foo:
  print("Snake snake snake snake.")
  print("{{foos}}")

just 会将要执行的命令作为参数传入。许多 shell 需要额外的标志(通常是 -c),才能求值第一个参数。

Windows Shell

just 默认在 Windows 上使用 sh。要在 Windows 上使用其他 shell,请为 shell 设置使用 [windows] 属性:

[windows]
set shell := ["powershell.exe", "-NoLogo", "-Command"]

hello:
  Write-Host "Hello, world!"

请参阅 powershell.just,其中的 justfile 展示了如何在所有平台上使用 PowerShell。

Python 3
set shell := ["python3", "-c"]
Bash
set shell := ["bash", "-uc"]
Z Shell
set shell := ["zsh", "-uc"]
Fish
set shell := ["fish", "-c"]
Nushell
set shell := ["nu", "-c"]

如果要将默认表格模式更改为 light:

set shell := ['nu', '-m', 'light', '-c']

Nushell 使用 Rust 编写,支持在 Windows / macOS 和 Linux 上跨平台运行。

环境变量

获取和设置环境变量
导出 just 变量

以 export 关键字开头的赋值会作为环境变量导出到配方中:

export RUST_BACKTRACE := "1"

test:
  # will print a stack trace if it crashes
  cargo test

以 $ 开头的参数会作为环境变量导出:

test $RUST_BACKTRACE="1":
  # will print a stack trace if it crashes
  cargo test

你还可以使用 [env(NAME, VALUE)] 属性,将环境变量导出到指定配方:

[env("RUST_BACKTRACE", "1")]
test:
  # will print a stack trace if it crashes
  cargo test

导出的变量和参数不会导出到同一作用域中的反引号内。

export WORLD := "world"
# This backtick will fail with "WORLD: unbound variable"
BAR := `echo hello $WORLD`
# Running `just a foo` will fail with "A: unbound variable"
a $A $B=`echo $A`:
  echo $A $B

设置 export 后,所有 just 变量都会作为环境变量导出。

取消导出环境变量

可以使用 unexport 关键字取消导出环境变量1.29.0:

unexport FOO

@foo:
  echo $FOO
$ export FOO=bar
$ just foo
sh: FOO: unbound variable
从环境中获取环境变量

环境中的环境变量会自动传递给配方。

print_home_folder:
  @echo "HOME is: '${HOME}'"
$ just
HOME is: '/home/myuser'
根据环境变量设置 just 变量

可以使用 env() 函数将环境变量传递给 just 变量。 请参阅 环境变量。

Dotenv 设置

如果设置了 dotenv-load、dotenv-filename、dotenv-override、dotenv-path 或 dotenv-required 中的任意一项,just 就会尝试从文件中加载环境变量。

如果设置了 dotenv-path,just 会查找指定路径下的文件,该路径可以是绝对路径,也可以是相对于工作目录的路径。

命令行选项 --dotenv-path(简写形式为 -E)可用于在运行时设置或覆盖 dotenv-path。

如果设置了 dotenv-filename,just 会在相对于工作目录及其每一级父目录的指定路径下查找文件。

命令行选项 --dotenv-filename(简写形式为 -F)可用于在运行时设置或覆盖 dotenv-filename。

如果未设置 dotenv-filename,但设置了 dotenv-load 或 dotenv-required,just 会在工作目录及其每一级父目录中查找名为 .env 的文件。

dotenv-filename 和 dotenv-path 类似,但 dotenv-path 只会相对于工作目录进行查找,而 dotenv-filename 会相对于工作目录及其每一级父目录进行查找。

除非设置了 dotenv-required,否则找不到环境文件不会报错。

加载的变量是环境变量,而不是 just 变量,因此必须在配方和反引号中使用 $VARIABLE_NAME 访问。

如果设置了 dotenv-override,环境文件中的变量将覆盖已有的环境变量。

如果设置了 dotenv-command,just 会使用配置的 shell 运行该命令,并将其标准输出作为环境文件加载。

这适用于从密钥管理器或保险库中获取机密:

set dotenv-command := 'sops -d .enc.env'

命令行选项 --dotenv-command 可用于在运行时设置或覆盖 dotenv-command,并且可以多次传入。每个值都会作为命令运行,后续命令中的变量优先级高于前面命令中的变量。

例如,如果你的 .env 文件包含:

# a comment, will be ignored
DATABASE_ADDRESS=localhost:6379
SERVER_PORT=1337

而你的 justfile 包含:

set dotenv-load

serve:
  @echo "Starting server with database $DATABASE_ADDRESS on port $SERVER_PORT…"
  ./server --database $DATABASE_ADDRESS --port $SERVER_PORT

运行 just serve 将输出:

$ just serve
Starting server with database localhost:6379 on port 1337…
./server --database $DATABASE_ADDRESS --port $SERVER_PORT

父模块中加载的环境文件里的变量会被子模块继承。

子模块中的环境文件会被加载1.49.0,并且可以覆盖父模块环境文件中定义的变量。

导出

export 设置会将所有 just 变量导出为环境变量。默认为 false。

set export

a := "hello"

@foo b:
  echo $a
  echo $b
$ just foo goodbye
hello
goodbye
在配方之间共享环境变量

每行配方都会由一个全新的 shell 执行,因此无法在配方之间共享环境变量。

使用 Python 虚拟环境

有些工具,例如 Python 的 venv, 需要加载环境变量才能运行,因此难以与 just 配合使用。作为一种变通方法,你可以直接执行 虚拟环境中的二进制文件:

venv:
  [ -d foo ] || python3 -m venv foo

run: venv
  ./foo/bin/python3 main.py

工作目录

默认情况下,配方运行时的工作目录设为包含 justfile 的目录。

可以使用 [no-cd] 属性,让配方运行时的工作目录设为调用 just 时所在的目录。

@foo:
  pwd

[no-cd]
@bar:
  pwd
$ cd subdir
$ just foo
/
$ just bar
/subdir

请注意,这不会影响 dotenv 文件搜索,也不会影响反引号和 shell() 调用的工作目录。

使用 set no-cd1.51.0,可让当前模块中的所有配方 默认采用相同行为。

可以使用 [no-cd] 和 [working-directory] 属性,针对特定配方覆盖 set no-cd 和 set working-directory 的设置。

你可以使用 set working-directory := '…' 为所有配方覆盖工作目录:

set working-directory := 'bar'

@foo:
  pwd
$ pwd
/home/bob
$ just foo
/home/bob/bar

你可以使用 working-directory 属性1.38.0, 为特定配方覆盖工作目录:

[working-directory: 'bar']
@foo:
  pwd
$ pwd
/home/bob
$ just foo
/home/bob/bar

working-directory 设置或 working-directory 属性的参数可以是表达式1.51.0,其值可以是绝对路径或相对路径。如果是相对路径,则相对于默认工作 目录进行解析。

在配方中更改工作目录

每一行配方都会由一个新 shell 执行,因此,如果你在某一行更改工作目录,不会影响后续行:

foo:
  pwd    # This `pwd` will print the same directory…
  cd bar
  pwd    # …as this `pwd`!

有几种解决方法。其中一种是在要运行的命令所在行调用 cd:

foo:
  cd bar && pwd

另一种方法是使用 shebang 配方。shebang 配方的主体会被提取并作为脚本 运行,因此整个配方会由同一个 shell 实例执行,这样某一行中的 cd 就会影响后续行,就像在 shell 脚本中一样:

foo:
  #!/usr/bin/env bash
  set -euxo pipefail
  cd bar
  pwd
禁用目录切换

just 通常会在执行配方时,将当前目录设为包含 justfile 的目录。可以使用 [no-cd] 属性1.9.0禁用此行为。这样可以创建使用相对于调用目录的路径,或对当前目录进行操作的配方。

例如,以下 commit 配方:

[no-cd]
commit file:
  git add {{file}}
  git commit

可以配合相对于当前目录的路径使用,因为 [no-cd] 会阻止 just 在执行 commit 时更改当前目录。

请注意,这不会影响 dotenv 文件搜索,也不会影响反引号和 shell() 调用的工作目录。

组织结构

导入

可以使用 import 语句将另一个 justfile 的内容包含进来。

如果有以下 justfile:

import 'foo/bar.just'

a: b
  @echo A

并且 foo/bar.just 中有以下文本:

b:
  @echo B

foo/bar.just 将包含在 justfile 中,并定义配方 b:

$ just b
B
$ just a
B
A

import 路径可以是绝对路径,也可以是相对于包含该路径的 justfile 的位置的路径。导入路径开头的 ~/ 会替换为当前用户的主目录。

Justfile 不区分顺序,因此被包含的文件可以引用 import 语句之后定义的变量和配方。

被导入的文件本身也可以包含 import,这些导入会递归处理。

allow-duplicate-recipes 和 allow-duplicate-variables 分别允许重复的配方和变量相互覆盖,而不是产生错误。

在模块内,后定义的内容会覆盖先定义的内容:

set allow-duplicate-recipes

foo:

foo:
  echo 'yes'

涉及 import 时,情况遗憾地会复杂得多,也难以解释。

较浅层的定义总会覆盖较深层的定义,因此顶层的配方会覆盖导入中的配方,而导入中的配方会覆盖其自身所导入内容中的配方。

当两个重复定义被导入且处于相同深度时,较早导入的定义会覆盖较晚导入的定义。

这是因为 just 在处理导入时会使用一个栈,按源代码中的顺序将导入项压入栈中,并始终优先处理栈顶项,因此编译器实际上会较晚处理较早的导入项。

这确实是个 Bug,但由于 just 对向后兼容有非常严格的保证,而且我们竭尽全力避免破坏任何人的 justfile,请参阅议题 #2540,了解我们是否能够修复此问题的讨论。

在 import 关键字后添加 ?,可以使导入变为可选:

import? 'foo/bar.just'

多次导入同一个源文件不会报错1.37.0。这样就可以导入多个 justfile,例如 foo.just 和 bar.just,它们都导入一个包含共享配方的第三个 justfile,例如 baz.just,且重复导入 baz.just 不会报错:

# justfile
import 'foo.just'
import 'bar.just'
# foo.just
import 'baz.just'
foo: baz
# bar.just
import 'baz.just'
bar: baz
# baz
baz:
模块

justfile 可以使用 mod 语句声明模块1.19.0。

mod 语句在 just1.31.0 中正式稳定。

如果你有以下 justfile:

mod bar

a:
  @echo A

并且 bar.just 中有以下文本:

b:
  @echo B

bar.just 将作为子模块包含在 justfile 中。在一个子模块中定义的配方、别名和变量不能在另一个子模块中使用,并且每个模块使用自己的设置。

子模块中的配方可以作为子命令调用:

$ just bar b
B

也可以使用路径语法:

$ just bar::b
B

如果模块名为 foo,just 将在 foo.just、foo/mod.just、foo/justfile 和 foo/.justfile 中搜索模块文件。对于后两种情况,模块文件名可以采用任意大小写形式。

模块语句也可以采用以下形式:

mod foo 'PATH'

这会从 PATH 加载模块的源文件,而不是从常规位置加载。PATH 中开头的 ~/ 会替换为当前用户的主目录。PATH 可以指向模块源文件本身,也可以指向包含名为 mod.just、justfile 或 .justfile 的模块源文件的目录。对于后两种情况,模块文件名可以采用任意大小写形式。

每个模块都会加载环境文件,并遵循该模块相关的设置。父模块中的环境变量对子模块可见。

对于没有 [no-cd] 属性的子模块,其配方运行时的工作目录会设为包含子模块源文件的目录。

即使在子模块配方中调用,justfile() 和 justfile_directory() 始终分别返回根 justfile 的路径及其所在目录。

在 mod 关键字后添加 ?,可以将模块设为可选:

mod? foo

可选模块的源文件缺失时不会报错。如果某个配方或别名直接或间接依赖于缺失的可选模块,该配方或别名将被禁用。尝试调用已禁用的配方或别名会报错,但仍可运行其他未被禁用的配方。1.52.0

没有源文件的可选模块之间不会冲突,因此可以有多个同名的 mod 语句,但它们的源文件路径不同,只要至多存在一个源文件即可:

mod? foo 'bar.just'
mod? foo 'baz.just'

模块可以添加文档注释,这些注释会显示在 --list 输出中1.30.0:

# foo is a great module!
mod foo
$ just --list
Available recipes:
    foo ... # foo is a great module!

模块仍缺少许多功能,例如引用其他模块中的变量。更多信息请参阅模块改进跟踪议题。

在其他目录中调用 justfile

如果传递给 just 的第一个参数包含 /,则会发生以下情况:

  1. 参数会在最后一个 / 处分割。

  2. 最后一个 / 之前的部分会被视为目录。just 将从该目录开始搜索 justfile,而不是从当前目录开始。

  3. 最后一个 / 之后的部分会被视为普通参数;如果该部分为空,则会被忽略。

这看起来可能有些奇怪,但如果你想运行子目录中的 justfile 里的命令,这种方式就很有用。

例如,如果你位于一个包含名为 foo 的子目录的目录中,而该子目录中有一个包含配方 build 的 justfile,且 build 也是默认配方,那么以下写法都等效:

$ (cd foo && just build)
$ just foo/build
$ just foo/

第一个配方之后的其他配方会在同一个 justfile 中查找。例如,以下两种写法都等效:

$ just foo/a b
$ (cd foo && just a b)

它们都会调用 foo/justfile 中的配方 a 和 b。

回退到父级 justfile

如果在某个 justfile 中找不到配方,且已设置 fallback,just 会在父目录及更上层目录中查找 justfile,直至到达根目录。如果遇到的 justfile 中 fallback 设置为 false 或未设置,just 就会停止查找。

例如,假设当前目录包含以下 justfile:

set fallback
foo:
  echo foo

父目录包含以下 justfile:

bar:
  echo bar
$ just bar
Trying ../justfile
echo bar
bar
全局回退 Justfile

可以使用 --global-fallback-justfile <PATH> 设置全局回退 justfile,也可以设置环境变量 JUST_GLOBAL_FALLBACK_JUSTFILE。

如果找不到 justfile,或者回退查找失败,则改用全局回退 justfile。工作目录将设为当前项目的根目录(根据是否存在 .git 等目录来确定);如果找不到此类目录,则设为调用目录。全局回退 justfile 本身也可以使用 set fallback。

传入 --justfile、--global-justfile 或 --working-directory 时,将忽略全局回退 justfile。

远程 Justfile

如果想在多个 justfile 中包含同一个 mod 或 import 源文件,而不必重复复制,可以使用可选的 mod 或 import,并添加一个用于获取模块源文件的配方:

import? 'foo.just'

fetch:
  curl https://raw.githubusercontent.com/casey/just/master/justfile > foo.just

对于上述 justfile,运行 just fetch 后,foo.just 中的配方便可使用。

全局和用户 justfile

如果想让某些配方随处可用,可以选择以下几种方式。

全局 Justfile

just --global-justfile(简称 just -g)会按以下顺序搜索这些路径中的 justfile:

  • $XDG_CONFIG_HOME/just/justfile
  • $HOME/.config/just/justfile
  • $HOME/justfile
  • $HOME/.justfile

你可以将多个项目都会用到的配方放进全局 justfile,以便在任何目录中轻松调用。

用户 justfile 使用技巧

你也可以采用以下某些工作流。这些技巧假设你已在 ~/.user.justfile 创建了一个 justfile,但也可以将此 justfile 放在系统上任何方便的位置。

配方别名

如果想按名称调用 ~/.user.justfile 中的配方,并且不介意为每个配方都创建一个别名,可以将以下内容添加到 shell 的初始化脚本中:

for recipe in `just --justfile ~/.user.justfile --summary`; do
  alias $recipe="just --justfile ~/.user.justfile --working-directory . $recipe"
done

现在,如果你在 ~/.user.justfile 中有一个名为 foo 的配方,只需在命令行中输入 foo 即可运行它。

我花了很长时间才意识到可以这样创建配方别名。尽管我后知后觉,但我很高兴能为你带来 justfile 技术的这项重大进步。

转发别名

如果你不想为每个配方都创建别名,可以只创建一个别名:

alias .j='just --justfile ~/.user.justfile --working-directory .'

现在,如果你在 ~/.user.justfile 中有一个名为 foo 的配方,只需在命令行中输入 .j foo 即可运行它。

我很确定没人真的会用这个功能,不过它确实存在。

¯\_(ツ)_/¯

自定义

你可以通过添加选项来自定义上述别名。例如,如果你希望 justfile 中的配方在主目录而不是当前目录中运行:

alias .j='just --justfile ~/.user.justfile --working-directory ~'
Markdown justfile

如果传给 --justfile 的参数以 .md 结尾,just 会提取未缩进的 just 围栏代码块内容,并将其写入临时目录中的 justfile1.53.0:

# Project

Build the project:

```just
build:
  echo Building…
```
$ just --justfile README.md build
echo Building…
Building…
仅用脚本

在 justfile 顶部添加 shebang 行,并赋予其可执行权限后, 即可将 just 用作脚本解释器:

$ cat > script <<EOF
#!/usr/bin/env just --justfile

foo:
  echo foo
EOF
$ chmod +x script
$ ./script foo
echo foo
foo

执行带有 shebang 的脚本时,系统会将脚本路径作为参数传递给 shebang 中的命令。因此,使用 #!/usr/bin/env just --justfile 作为 shebang 时, 命令将是 /usr/bin/env just --justfile PATH_TO_SCRIPT。

使用上述 shebang 时,just 会将工作目录切换到脚本所在位置。如果你希望 保持工作目录不变,请使用 #!/usr/bin/env just --working-directory . --justfile。

注意:不同操作系统对 shebang 行的拆分方式并不一致。此前的示例仅在 macOS 上测试过。在 Linux 上,你可能需要向 env 传递 -S 标志:

#!/usr/bin/env -S just --justfile

default:
  echo foo
隐藏 justfile

just 会查找名为 justfile 和 .justfile 的 justfile,后者可用于隐藏 justfile。

命令行

列出可用配方

使用 just --list 可以按字母顺序列出配方:

$ just --list
Available recipes:
    build
    test
    deploy
    lint

使用 just --list PATH 可以列出子模块中的配方,其中 PATH 是以空格或 :: 分隔的模块路径:

$ cat justfile
mod foo
$ cat foo.just
mod bar
$ cat bar.just
baz:
$ just --list foo bar
Available recipes:
    baz
$ just --list foo::bar
Available recipes:
    baz

just --summary 的输出更简洁:

$ just --summary
build test deploy lint

传递 --unsorted,可以按照配方在 justfile 中出现的顺序输出:

test:
  echo 'Testing!'

build:
  echo 'Building!'
$ just --list --unsorted
Available recipes:
    test
    build
$ just --summary --unsorted
test build

如果希望 just 默认列出 justfile 中的配方,请设置 default-list1.52.0:

set default-list := true

此设置针对各个模块单独生效,因此,调用启用了 default-list 的模块路径时, 会列出该模块的配方。

你也可以通过设置环境变量 JUST_DEFAULT_LIST=true 或传递 --default-list1.52.0,将列出配方设为默认行为。

可以使用 --list-heading 自定义标题文本:

$ just --list --list-heading $'Cool stuff…\n'
Cool stuff…
    test
    build

也可以使用 --list-prefix 自定义缩进:

$ just --list --list-prefix ····
Available recipes:
····test
····build

--list-heading 的参数会同时替换标题和其后的换行符,因此只要该参数 非空,就应包含换行符。这样设计是为了让你可以传递空字符串,从而完全隐藏 标题行:

$ just --list --list-heading ''
    test
    build
调用多个配方

可以在命令行中一次调用多个配方:

build:
  make web

serve:
  python3 -m http.server -d out 8000
$ just build serve
make web
python3 -m http.server -d out 8000

请注意,带参数的配方会吞掉参数,即使这些参数与其他配方的名称相同也不例外:

build project:
  make {{project}}

serve:
  python3 -m http.server -d out 8000
$ just build serve
make: *** No rule to make target `serve'.  Stop.

可以使用 --one 标志,将命令行调用限制为单个配方:

$ just --one build serve
error: Expected 1 command-line recipe invocation but found 2.
从命令行设置变量

可以从命令行覆盖变量。

os := "linux"

test: build
  ./test --test {{os}}

build:
  ./build {{os}}
$ just
./build linux
./test --test linux

可以在配方前传递任意数量的 NAME=VALUE 形式的参数:

$ just os=plan9
./build plan9
./test --test plan9

或者使用 --set 标志:

$ just --set os bsd
./build bsd
./test --test bsd

可以使用以 :: 分隔的变量路径来覆盖子模块中的变量。名为 foo 的 子模块中的 bar 变量,可通过 foo::bar=VALUE 或 --set foo::bar VALUE 覆盖。

命令行选项

just 支持多种实用的命令行选项,可用于列出、转储和调试配方及变量:

$ just --list
Available recipes:
  js
  perl
  polyglot
  python
  ruby
$ just --show perl
perl:
  #!/usr/bin/env perl
  print "Larry Wall says Hi!\n";
$ just --show polyglot
polyglot: python js perl sh ruby
使用环境变量设置命令行选项

部分命令行选项可以通过环境变量设置。

例如,可以使用 --unstable 标志启用不稳定功能:

$ just --unstable

也可以通过设置 JUST_UNSTABLE 环境变量来启用:

$ export JUST_UNSTABLE=1
$ just

由于子进程会继承环境变量,因此,通过环境变量设置的命令行选项会被 just 的递归调用继承,而通过参数设置的命令行选项则不会。

请查看 just --help,了解哪些选项可以通过环境变量设置。

使用交互式选择器选择要运行的配方

--choose 子命令会让 just 调用选择器,以选择要运行的配方。选择器应从标准输入读取包含配方名称的行,并将一个或多个名称以空格分隔后输出到标准输出。

由于目前无法使用 --choose 运行需要参数的配方,因此不会将这类配方提供给选择器。私有配方和别名也会跳过。

可以使用 --chooser 标志覆盖选择器。如果未指定 --chooser,just 会先检查是否设置了 $JUST_CHOOSER。如果没有设置,选择器默认使用热门模糊查找工具 fzf。

可以在选择器中包含参数,例如 fzf --exact。

选择器的调用方式与配方行相同。例如,如果选择器是 fzf,它会通过 sh -cu 'fzf' 调用;如果覆盖了 shell 或 shell 参数,选择器调用也会遵循这些覆盖设置。

如果希望 just 默认使用选择器来选择配方,可以将以下内容用作默认配方:

default:
  @just --choose
要求配方运行前确认

just 通常会执行所有配方,除非发生错误。[confirm] 属性1.17.0允许配方在运行前要求在终端中确认。向 just 传递 --yes 可覆盖此设置,并自动确认所有标记了此属性的配方。

如果某个配方依赖于需要确认的配方,而该依赖配方未获确认,那么这个配方不会运行;任何在需要确认的配方之后传入的配方也不会运行。

[confirm]
delete-all:
  rm -rf *
自定义确认提示

可以使用 [confirm(PROMPT)]1.23.0 覆盖默认确认提示:

[confirm("Are you sure you want to delete everything?")]
delete-everything:
  rm -rf *

确认提示也可以是一个表达式1.49.0,其中可以引用赋值或配方参数:

[confirm("Deploy to " + env + "?")]
deploy env:
  echo 'Deploying to {{env}}...'
时间戳

just 可以在每条配方命令之前打印时间戳:

recipe:
  echo one
  sleep 2
  echo two
$ just --timestamp recipe
[07:28:46] echo one
one
[07:28:46] sleep 2
[07:28:48] echo two
two

默认情况下,时间戳格式为 HH:MM:SS。可以使用 --timestamp-format 更改格式:

$ just --timestamp recipe --timestamp-format '%H:%M:%S%.3f %Z'
[07:32:11:.349 UTC] echo one
one
[07:32:11:.350 UTC] sleep 2
[07:32:13:.352 UTC] echo two
two

--timestamp-format 的参数是 strftime 风格的格式字符串,详情请参阅 chrono 库文档。

可以使用 [timestamp] 属性1.58.0为特定配方启用时间戳:

[timestamp]
foo:
  echo hello
$ just foo
[07:28:46] echo hello
hello

还可以包含格式字符串:

[timestamp('%H:%M:%S%.3f')]
foo:
  echo hello
$ just foo
[07:28:46.487] echo hello
hello
信号处理

信号是发送给正在运行的程序、用于触发特定行为的消息。例如,按下 ctrl-c 时,SIGINT 会发送给终端前台进程组中的所有进程。

just 会尝试在收到信号请求时退出,同时也会尽量避免留下仍在运行的子进程。这两个目标在一定程度上相互冲突。

如果 just 退出后留下了子进程,用户只能通过 ps aux | grep 查找这些子进程,再手动使用 kill 终止它们,这会很麻烦。

致命信号

用户关闭终端、按下 ctrl-c 或按下 ctrl-\ 时,分别会生成 SIGHUP、SIGINT 和 SIGQUIT,并发送给前台进程组中的所有进程。

SIGTERM 是 kill 命令默认发送的信号,只会传递给指定的目标进程。

如果没有子进程正在运行,just 收到上述任一信号后都会立即退出。

如果有子进程正在运行,just 会等待其终止,以免留下子进程。

此外,收到 SIGTERM 时,just 会将 SIGTERM 转发给任何正在运行的子进程1.41.0,因为与其他致命信号不同,SIGTERM 很可能只发送给了 just。

无论 just 收到致命信号后子进程是否成功终止,just 都会停止执行。

继续执行

可以使用 [continue]1.54.0 属性,让 just 即使收到致命信号,也继续执行,前提是它正在运行的子进程成功退出。

不带参数时,[continue] 会处理 SIGINT(ctrl-c),这样仍可使用 SIGQUIT(ctrl-\)停止执行。

带参数时,可以显式指定要处理的一个或多个信号,例如 "SIGHUP"、"SIGINT" 和 "SIGQUIT"。

在此示例中,如果 main.py 捕获 SIGINT 并成功退出,cleanup 仍会运行,且 just 会成功退出:

[continue]
test: && cleanup
  python3 main.py

cleanup:
  echo cleanup
SIGINFO

在包括 macOS 在内的 BSD 衍生操作系统上,用户按下 ctrl-t 时,SIGINFO 会发送给前台进程组中的所有进程;Linux 不支持此信号。

just 收到该信号后,会打印所有子进程的进程 ID 和命令列表1.41.0。

Windows

在 Windows 上,用户按下 ctrl-c 时,just 的行为就像收到了 SIGINT。不支持其他信号。

Shell 补全脚本

Bash、Elvish、Fish、Nushell、PowerShell 和 Zsh 的 Shell 补全脚本可在发布存档中获取。

just 二进制文件也可以使用 just --completions SHELL 在运行时生成相同的补全脚本:

$ just --completions bash > just
Bash

推荐使用 bash-completions 软件包延迟加载补全脚本:

mkdir -p ~/.local/share/bash-completion/completions
just --completions bash > ~/.local/share/bash-completion/completions/just

如果未安装 bash-completions,可以在 .bashrc 中引入补全脚本:

source <(just --completions bash)

如果你使用 alias j=just 这样的别名,在延迟加载时也应将补全脚本保存为 j:

just --completions bash > ~/.local/share/bash-completion/completions/j

如果不使用延迟加载,则在 .bashrc 中引入补全脚本后添加以下行:

complete -F _clap_complete_just -o bashdefault -o default j
Elvish

在 rc.elv 中:

set edit:completion:arg-completer[just] = { |@args|
  eval (just --completions elvish | slurp)
  set @result = (edit:completion:arg-completer[just] $@args)
  put $@result
}
Fish

将补全脚本保存到补全目录,以便延迟加载:

mkdir -p ~/.config/fish/completions
just --completions fish > ~/.config/fish/completions/just.fish
Nushell

首先保存补全脚本:

just --completions nushell | save -f ($nu.default-config-dir | path join just.nu)

然后在 config.nu 中:

source just.nu
PowerShell

在 PowerShell 的 $PROFILE 中:

just --completions powershell | Out-String | Invoke-Expression
Zsh

首先保存补全脚本:

mkdir -p ~/.zsh/completions
just --completions zsh > ~/.zsh/completions/_just

然后在 .zshrc 中:

fpath=(~/.zsh/completions $fpath)
autoload -U compinit
compinit
Man 手册页

just 可以使用 just --man 打印自己的 man 手册页。Man 手册页采用 roff 编写,这是一种历史悠久的标记语言,也是 Unix 最早的实用应用之一。如果已安装 groff,可以使用 just --man | groff -mandoc -Tascii | less 查看手册页。

格式化和转储 justfile

每个 justfile 在空白和换行方面都有一种规范格式。

你可以使用 --fmt 标志,将当前 justfile 覆盖为符合规范格式的版本:

$ cat justfile
# A lot of blank lines





some-recipe:
  echo "foo"
$ just --fmt
$ cat justfile
# A lot of blank lines

some-recipe:
    echo "foo"

如果通过标准输入使用 --justfile - 读取 justfile,或从 Markdown 文件中提取 justfile,--fmt 会将格式化后的 justfile 输出到标准输出。

请注意,格式化不受任何向后兼容性保证约束,并且可能会不时发生变化。

Recipe 主体默认缩进 4 个空格。你可以通过命令行选项 --indentation、环境变量 JUST_INDENTATION 或 indentation 设置进行更改:

set indentation := "  "

调用 just --fmt --check 会以检查模式运行 --fmt。just 不会覆盖 justfile,而是会在格式正确时以退出码 0 退出;如果格式不正确,则会以退出码 1 退出并打印差异。

你可以使用 --dump 命令将格式化后的 justfile 输出到标准输出:

$ just --dump > formatted-justfile

--dump 命令可以与 --dump-format json 配合使用,以输出 justfile 的 JSON 表示。

缓存的 Recipe

如果缓存中存在与调用匹配的条目,just 会跳过带有 [cache] 属性1.54.0 的 Recipe 调用。[cache] 属性只能用于脚本 Recipe,目前尚不稳定。

例如,如果 image.jpg 存在,且自上次运行以来 image.png 的内容和 convert -version 的输出都未更改,则会跳过此 Recipe:

set unstable

[script]
[cache(inputs = "image.png", outputs = "image.jpg", extra = `convert -version`)]
convert:
  convert image.png image.jpg

与 just 的许多其他功能不同,缓存的 Recipe 本身就很脆弱。使用前,务必了解其局限性。请仔细阅读本节,包括下文友好的提醒。

友好的提醒

just 会毫不犹豫地跳过缓存的 Recipe,但你有责任确保这样做是安全的,并且缓存键包含足够的信息来描述 Recipe 的调用情况,使缓存确实有意义。

特别是,Recipe 运行环境中的许多细节都不会被缓存键捕获。

这些细节包括时间、输入文件、输出文件、系统二进制文件、操作系统版本、数据库、网络上的系统、DNS,以及其他可能改变计算机程序执行方式的种种因素。

尝试基于 just 所采用的粗略启发式方法跳过执行,这种做法有着漫长而不光彩的历史。不过,这无疑是一种方便且强大的工具,我们提供它,希望你觉得它有用。

实现

缓存是一个名为 .justcache 的目录,与 justfile 位于同一目录中,不应将其提交到版本控制系统。该目录包含名为 HASH.json 的缓存条目,其中 HASH 是序列化缓存键 JSON 对象的 BLAKE3 哈希值。

缓存键对象的键包括:

  • body:求值后的配方正文
  • environment:环境变量名称到值的映射
  • executor:脚本解释器或 shebang
  • extension:脚本文件扩展名
  • extra:用户提供的值
  • inputs:文件路径到内容哈希值的映射
  • positional:位置参数
  • recipe:以 :: 分隔的被调用配方模块路径
  • working_directory:当前工作目录

除 extra 和 inputs 外,所有键都会自动填充。

使用 just -vv 可以将被调用配方的缓存键对象打印到标准错误。

environment 中包含的环境变量可通过 [cache(environment = EXPRESSION)] 控制1.59.0。

extra 的值可通过 [cache(extra = EXPRESSION)] 提供,其中 EXPRESSION 是一个任意表达式,求值时配方参数处于作用域内。更改 extra 的值会导致缓存未命中。

运行缓存配方前,just 会创建缓存键、对其进行哈希,并查找对应的缓存条目。

如果缓存条目非空,则跳过调用。

如果缓存条目不存在或为空,则运行调用并向缓存条目写入 {}。

缓存条目会使用文件锁,因此多个 just 进程并发执行缓存配方是安全的。如果两个进程运行具有相同缓存键的配方调用,第一个进程会获取锁、运行配方、写入缓存条目并释放锁。第二个进程会阻塞,直到第一个进程释放锁;随后它会发现条目非空,并跳过调用。

可使用 --no-cache 标志完全绕过缓存。

清除缓存

配方缓存存储在一个名为 .justcache 的目录中,与 justfile 位于同一目录。删除该目录即可清除缓存。

也可以使用 just --clean 清除缓存,该命令可选择性地清除缓存条目:

# clear all cache entries
just --clean

# clear cache entries for recipe `foo`
just --clean foo

# clear cache entries for recipe `baz` in submodule `bar`
just --clean bar baz

# clear cache entries for recipes in submodule module `bar`
just --clean bar

# clear cache entries for recipes in submodule module `bar::bob`
just --clean bar bob

# '::'-separated paths may also be used
just --clean bar::bob
添加和移除环境变量

默认情况下,在 justfile 中导出或取消导出的环境变量会包含在缓存键中;在 justfile 外部导出并由 just 继承的环境变量则不会包含在内。

可使用 [cache(environment = EXPRESSION)] 在缓存键中添加或移除环境变量1.59.0。

在此示例中,环境变量 foo 不会包含在缓存键中,但 PATH 会包含:

set unstable
set lists

export foo := 'bar'

[script]
[cache(environment = ['PATH'])]
build:
  cc lib.c main.c -o main
输入文件

可通过 [cache(inputs = FILES)] 提供输入文件,其中 FILES 是一个表达式,求值时配方参数处于作用域内,且求值结果中的元素为路径。路径可以是绝对路径,也可以是相对于配方工作目录的路径。

每个输入文件都会使用 BLAKE3 进行哈希,并添加到 inputs 缓存键中;该键包含路径到哈希值的映射。

输入文件内容的任何更改都会改变缓存键,导致下一次调用缓存未命中并重新运行。

缺失的输入文件和指向目录的路径都会报错。

在此示例中,如果 lib.c 或 main.c 发生更改,build 配方就会重新运行:

set unstable
set lists

[script]
[cache(inputs = ["lib.c", "main.c"])]
build:
  cc lib.c main.c -o main
输出文件

可以使用 [cache(outputs = FILES)] 指定输出文件,其中 FILES 是一个表达式,会在配方参数的作用域内求值,求值结果为路径。路径可以是绝对路径,也可以是相对于配方工作目录的路径。

输出文件不属于缓存键的一部分。

只有所有输出文件都存在时,才会跳过一次调用;调用成功运行后,如果有任何输出文件不存在,则会报错。

在此示例中,每当 main 缺失时,build 都会重新运行;如果运行后没有生成 main,则会报错:

set unstable
set lists

[script]
[cache(inputs = ["lib.c", "main.c"], outputs = "main")]
build:
  cc lib.c main.c -o main

clean:
  rm -f main

这样一来,如果 clean 删除了 main,build 就会重新运行。

参考

属性

配方、mod 语句和别名都可以使用属性进行注解,以改变其行为。

名称类型描述
[arg(ARG, help="HELP")]1.46.0配方在用法说明中为 ARG 打印帮助字符串 HELP。可以是常量表达式1.55.0。
[arg(ARG, long="LONG")]1.46.0配方要求将参数 ARG 的值作为 --LONG 选项传入。如果参数是可变参数,则此选项可以重复使用1.55.0master。
[arg(ARG, max="MAX")]1.56.0配方限制传入参数 ARG 的值最多为 MAX 个。需要设置 multiple 或使用可变参数。
[arg(ARG, min="MIN")]1.56.0配方要求传入参数 ARG 的值至少为 MIN 个。需要设置 multiple 或使用可变参数。
[arg(ARG, pattern="PATTERN")]1.45.0配方要求参数 ARG 的值匹配正则表达式 PATTERN。可以是常量表达式1.55.0。
[arg(ARG, short="S")]1.46.0配方要求将参数 ARG 的值作为短选项 -S 传入。如果参数是可变参数,则此选项可以重复使用1.55.0。
[arg(ARG, value=VALUE)]1.46.0配方将选项 ARG 设为不接受值的标志。
[cache]1.54.0配方如果缓存中存在匹配项,则跳过配方调用。详情请参阅缓存配方。目前尚不稳定。
[confirm(PROMPT)]1.23.0配方执行配方前,使用自定义提示词要求确认。
[confirm]1.17.0配方执行配方前要求确认。
[continue(SIGNALS)]1.54.0配方如果命令被 SIGNALS 中的任一信号中断且成功退出,则正常继续执行。默认为 SIGINT。
[default]1.43.0配方将此配方设为模块的默认配方。
[doc(DOC)]1.27.0模块、配方将配方或模块的文档注释设为 DOC。可以是常量表达式1.56.0。
[dragonfly]1.47.0任意1.56.0在 DragonFly BSD 上启用此项。
[env(NAME, VALUE)] 1.47.0配方为配方将环境变量 NAME 设为 VALUE。NAME 和 VALUE 可以是表达式1.51.0。
[extension(EXT)]1.32.0配方将 shebang 配方脚本的文件扩展名设为 EXT。如需包含句点,应在 EXT 中加上句点。
[exit-message]1.39.0配方即使设置了 set no-exit-message,配方失败时仍打印错误消息。
[freebsd]1.47.0任意1.56.0在 FreeBSD 上启用此项。
[group(NAME)]1.27.0模块、配方将配方或模块放入组 NAME。
[android]1.50.0任意1.56.0在 Android 上启用此项。
[linux]1.8.0任意1.56.0在 Linux 上启用此项。
[macos]1.8.0任意1.56.0在 macOS 上启用此项。
[metadata(METADATA)]1.42.0配方将 METADATA 附加到配方。
[netbsd]1.47.0任意1.56.0在 NetBSD 上启用此项。
[no-cd]1.9.0配方执行配方前不更改目录。
[no-exit-message]1.7.0配方配方失败时不打印错误消息。
[no-quiet]1.23.0配方覆盖全局静默配方设置,始终回显配方。
[openbsd]1.38.0任意1.56.0在 OpenBSD 上启用此项。
[parallel]1.42.0配方并行运行此配方的依赖项。
[positional-arguments]1.29.0配方为此配方启用位置参数。
[private]1.10.0别名、配方将配方、别名或变量设为私有。请参阅私有配方。
[script(COMMAND)]1.32.0配方将配方作为由 COMMAND 解释的脚本执行。详情请参阅脚本配方。
[script]1.33.0配方将配方作为脚本执行。详情请参阅脚本配方。
[shell]1.52.0配方将配方作为 shell 配方执行,并覆盖 set default-script。
[timestamp(FORMAT)]1.58.0配方按格式 FORMAT 打印命令时间戳。FORMAT 可以是表达式。
[timestamp]1.58.0配方打印命令时间戳。
[unix]1.8.0任意1.56.0在 Unix 系统上启用此项。(包括 macOS。)
[windows]1.8.0任意1.56.0在 Windows 上启用此项。
[working-directory(PATH)]1.38.0配方设置配方的工作目录。PATH 可以是表达式1.51.0,其值可以是相对路径或绝对路径。若为相对路径,则相对于默认工作目录进行解析。
配方可以包含多个属性,既可以分多行书写:
[no-cd]
[private]
foo:
    echo "foo"

也可以用逗号分隔,写在同一行1.14.0:

[no-cd, private]
foo:
    echo "foo"

只有一个参数的属性可以用冒号书写:

[group: 'bar']
foo:
设置

设置用于控制解释和执行。每项设置在 justfile 中最多只能指定一次,位置不限。

例如:

set shell := ["zsh", "-cu"]

foo:
  # this line will be run as `zsh -cu 'ls **/*.txt'`
  ls **/*.txt
设置表
名称值默认值描述
allow-duplicate-recipes布尔值false允许 justfile 中后出现的配方覆盖同名的先前配方。
allow-duplicate-variables布尔值false允许 justfile 中后出现的变量覆盖同名的先前变量。
default-list布尔值false列出配方,而不是运行默认配方。
default-script1.52.0布尔值false将默认配方设为脚本,而不是 shell。
dotenv-command1.54.0字符串-运行命令,并将其输出作为环境文件加载。
dotenv-filename字符串-如果存在,则加载指定名称的 .env 文件。
dotenv-load布尔值false如果存在,则加载 .env 文件。
dotenv-override布尔值false使用 .env 文件中的值覆盖现有环境变量。
dotenv-path字符串-从自定义路径加载 .env 文件;如果文件不存在则报错。此设置会覆盖 dotenv-filename。
dotenv-required布尔值false如果未找到 .env 文件,则报错。
export布尔值false将所有变量导出为环境变量。
fallback布尔值false如果命令行中的第一个配方未找到,则在父目录中搜索 justfile。
guards1.47.0布尔值false启用配方行上的 ? 守卫符号。参见符号。
ignore-comments布尔值false忽略以 # 开头的 shell 配方行。不适用于脚本配方。
indentation1.56.0字符串-设置配方主体的缩进,用于使用 --fmt 或 --dump 格式化时。
lazy1.47.0布尔值false不计算未使用的变量。
lists1.53.0布尔值false值可以是字符串列表,而不是字符串。目前尚不稳定。
minimum-version1.55.0字符串-如果 just 版本低于 minimum-version,则报错。接受格式为 MAJOR.MINOR.PATCH 的字符串,例如 "1.55.0"。
no-cd1.51.0布尔值false按配方属性执行配方时不更改目录。
no-exit-message1.39.0布尔值false配方失败时不打印退出消息。
positional-arguments布尔值false传递位置参数。
quiet布尔值false执行前不回显配方行。
script-interpreter1.33.0[COMMAND, ARGS…]['sh', '-eu']设置用于调用带有空 [script] 属性的配方的命令。
shell[COMMAND, ARGS…]-设置用于调用配方和计算反引号的命令。
tempdir字符串-在 tempdir 中创建临时目录,而不是使用系统默认的临时目录。
unstable1.31.0布尔值false启用不稳定功能。
windows-powershell布尔值false在 Windows 上使用 PowerShell 作为默认 shell。(已弃用。请在 set shell 上使用 [windows] 属性。)
windows-shell[COMMAND, ARGS…]-设置用于调用配方和计算反引号的命令。(已弃用。请在 set shell 上使用 [windows] 属性。)
working-directory1.33.0字符串-设置配方和反引号的工作目录,相对于默认工作目录。

布尔设置可以写为:

set NAME

这等价于:

set NAME := true

非布尔设置可以设为字符串和 表达式1.46.0。

但是,由于设置会影响反引号和许多函数的行为,这些表达式不能直接包含反引号或函数调用,也不能通过引用间接包含。

函数
系统信息
  • arch() — 指令集架构。可能的值包括:"aarch64"、 "arm"、"asmjs"、"hexagon"、"mips"、"msp430"、"powerpc"、 "powerpc64"、"s390x"、"sparc"、"wasm32"、"x86"、"x86_64" 和 "xcore"。
  • num_cpus()1.15.0 — 逻辑 CPU 数量。
  • os() — 操作系统。可能的值包括:"android"、"bitrig"、 "dragonfly"、"emscripten"、"freebsd"、"haiku"、"ios"、"linux"、 "macos"、"netbsd"、"openbsd"、"solaris" 和 "windows"。
  • os_family() — 操作系统系列;可能的值包括:"unix" 和 "windows"。

例如:

system-info:
  @echo "This is an {{arch()}} machine."
$ just system-info
This is an x86_64 machine.

os_family() 函数可用于创建能在各种操作系统上运行的跨平台 justfile。 示例请参见 cross-platform.just 文件。

外部命令
  • shell(command, args...)1.27.0 返回 shell 脚本 command 的标准输出, 并传入零个或多个位置参数 args。用于解释 command 的 shell 与执行配方行时 使用的 shell 相同,可通过 set shell := […] 更改。

    command 会作为第一个参数传入,因此,如果命令是 'echo $@', 使用默认 shell 命令 sh -cu,并传入参数 'foo' 和 'bar',完整命令行将是:

    'sh' '-cu' 'echo $@' 'echo $@' 'foo' 'bar'
    

    这样 $@ 就能按预期工作,而 $1 指第一个参数。$@ 不包含第一个位置参数, 因为该参数应是正在运行的程序的名称。

# arguments can be variables or expressions
file := '/sys/class/power_supply/BAT0/status'
bat0stat := shell('cat $1', file)

# commands can be variables or expressions
command := 'wc -l'
output := shell(command + ' "$1"', 'main.c')

# arguments referenced by the shell command must be used
empty := shell('echo', 'foo')
full := shell('echo $1', 'foo')
error := shell('echo $1')
# Using python as the shell. Since `python -c` sets `sys.argv[0]` to `'-c'`,
# the first "real" positional argument will be `sys.argv[2]`.
set shell := ["python3", "-c"]
olleh := shell('import sys; print(sys.argv[2][::-1])', 'hello')
环境变量
  • env(key)1.15.0 — 获取名为 key 的环境变量;如果该变量不存在,则中止执行。
home_dir := env('HOME')

test:
  echo "{{home_dir}}"
$ just
/home/user1
  • env(key, default)1.15.0 — 获取名为 key 的环境变量;如果该变量不存在, 则返回 default。
  • env_var(key) — env(key) 的弃用别名。
  • env_var_or_default(key, default) — env(key, default) 的弃用别名。
可执行文件
  • require(name)1.39.0 — 在环境变量 PATH 指定的目录中搜索可执行文件 name, 并返回其完整路径;如果不存在名为 name 的可执行文件,则停止并报错。

    bash := require("bash")
    
    @test:
        echo "bash: '{{bash}}'"
    
    $ just
    bash: '/bin/bash'
    
  • which(name)1.39.0 — 在环境变量 PATH 指定的目录中搜索可执行文件 name, 并返回其完整路径;如果未找到,则返回空列表。需要设置 set lists1.53.0。

    set unstable
    set lists
    
    bosh := which("bosh")
    
    @test:
        echo "bosh: '{{bosh}}'"
    
    $ just
    bosh: ''
    
调用信息
  • is_dependency() - 如果当前配方是作为另一配方的依赖项运行,而非直接运行,则返回字符串 true; 否则返回字符串 false。

  • num_jobs()1.56.0 — --jobs 的值;如果未传入该参数,则返回空列表 []。

  • recipe_name()1.53.0 - 返回当前配方的名称。

调用目录
  • invocation_directory() - 获取调用 just 时的当前目录的绝对路径,即 just 在执行命令前 更改目录(执行 chdir)之前所在的目录。在 Windows 上,invocation_directory() 使用 cygpath 将调用目录转换为兼容 Cygwin、以 / 分隔的路径。如需在所有平台上返回原样的调用目录, 请使用 invocation_directory_native()。

例如,如需对“当前目录”(从用户/调用者的角度来看)下的文件调用 rustfmt,请使用以下配方:

rustfmt:
  find {{invocation_directory()}} -name \*.rs -exec rustfmt {} \;

或者,如果命令需要从当前目录运行,可以使用(例如):

build:
  cd {{invocation_directory()}}; ./some_script_that_needs_to_be_run_from_here
  • invocation_directory_native() - 获取调用 just 时的当前目录的绝对路径,即 just 在执行命令前 更改目录(执行 chdir)之前所在的目录。
Justfile 和 Justfile 目录
  • justfile() - 获取当前 justfile 的路径。

  • justfile_directory() - 获取当前 justfile 的父目录路径。

例如,要运行相对于当前 justfile 所在位置的命令:

script:
  {{justfile_directory()}}/scripts/some_script
源文件和源目录
  • source_file()1.27.0 - 获取当前源文件的路径。

  • source_directory()1.27.0 - 获取当前源文件的父目录路径。

在根 justfile 中,source_file() 和 source_directory() 的行为与 justfile() 和 justfile_directory() 相同;但在导入文件或子模块中调用时,它们分别返回当前 import 或 mod 源文件的路径和目录。

模块和模块目录
  • module_file() - 返回当前模块文件的路径。

  • module_directory() - 返回当前模块文件的父目录路径。

  • module_path() - 返回以 :: 分隔的当前模块路径。

在根 justfile 中,module_file() 和 module_directory() 的行为与 justfile() 和 justfile_directory() 相同;但在子模块中调用时,它们分别返回当前 mod 源文件的路径和目录。

Just 进程和可执行文件
  • just_executable() - just 可执行文件的绝对路径。
  • just_pid() - just 可执行文件的进程 ID。
  • just_version()1.55.0 - just 可执行文件的版本。

例如:

just-info:
  @echo The executable is at: {{just_executable()}}
  @echo The process ID is: {{ just_pid() }}
  @echo The version is: {{ just_version() }}
$ just
The executable is at: /bin/just
The process ID is: 420
The version is: 1.54.0
字符串操作
  • append(suffix, s)1.27.0 - 将 suffix 追加到 s 中以空格分隔的字符串后面。append('/src', 'foo bar baz') → 'foo/src bar/src baz/src'
  • prepend(prefix, s)1.27.0 - 将 prefix 添加到 s 中以空格分隔的字符串前面。prepend('src/', 'foo bar baz') → 'src/foo src/bar src/baz'
  • encode_uri_component(s)1.27.0 - 对 s 中除 [A-Za-z0-9_.!~*'()-] 以外的字符进行百分号编码,行为与 JavaScript encodeURIComponent 函数一致。
  • quote(s) - 将所有单引号替换为 '\'',并在 s 前后添加单引号。这足以为许多 Shell(包括大多数 Bourne Shell 的衍生版本)转义特殊字符。
  • replace(s, from, to) - 将 s 中所有的 from 替换为 to。
  • replace_regex(s, regex, replacement) - 将 s 中所有匹配 regex 的内容替换为 replacement。正则表达式由 Rust regex crate提供。用法示例请参阅 语法文档。支持捕获组。replacement 字符串使用 替换字符串语法。
  • trim(s) - 移除 s 开头和结尾的空白字符。
  • trim_end(s) - 移除 s 末尾的空白字符。
  • trim_end_match(s, substring) - 移除 s 中与 substring 匹配的后缀。
  • trim_end_matches(s, substring) - 重复移除 s 中与 substring 匹配的后缀。
  • trim_start(s) - 移除 s 开头的空白字符。
  • trim_start_match(s, substring) - 移除 s 中与 substring 匹配的前缀。
  • trim_start_matches(s, substring) - 重复移除 s 中与 substring 匹配的前缀。
大小写转换
  • capitalize(s)1.7.0 - 将 s 的首字符转换为大写, 其余字符转换为小写。
  • kebabcase(s)1.7.0 - 将 s 转换为 kebab-case。
  • lowercamelcase(s)1.7.0 - 将 s 转换为 lowerCamelCase。
  • lowercase(s) - 将 s 转换为小写。
  • shoutykebabcase(s)1.7.0 - 将 s 转换为 SHOUTY-KEBAB-CASE。
  • shoutysnakecase(s)1.7.0 - 将 s 转换为 SHOUTY_SNAKE_CASE。
  • snakecase(s)1.7.0 - 将 s 转换为 snake_case。
  • titlecase(s)1.7.0 - 将 s 转换为 Title Case。
  • uppercamelcase(s)1.7.0 - 将 s 转换为 UpperCamelCase。
  • uppercase(s) - 将 s 转换为大写。
路径操作
可能失败
  • absolute_path(path) - 工作目录中相对于 path 的绝对路径。在目录 /foo 中,absolute_path("./bar.txt") 的结果为 /foo/bar.txt。
  • canonicalize(path)1.24.0 - 通过解析符号链接,并在可能的情况下移除 ., .. 和多余的 /,规范化 path。
  • extension(path) - path 的扩展名。extension("/foo/bar.txt") 的结果为 txt。
  • file_name(path) - 移除所有前置目录部分后的 path 文件名。 file_name("/foo/bar.txt") 的结果为 bar.txt。
  • file_stem(path) - 不含扩展名的 path 文件名。 file_stem("/foo/bar.txt") 的结果为 bar。
  • parent_directory(path) - path 的父目录。 parent_directory("/foo/bar.txt") 的结果为 /foo。
  • without_extension(path) - 不含扩展名的 path。 without_extension("/foo/bar.txt") 的结果为 /foo/bar。

这些函数可能会失败,例如路径没有扩展名时,此时执行会停止。

不会失败
  • clean(path) - 通过移除多余的路径分隔符、中间的 . 部分,以及在可能的情况下移除 ..,简化 path。clean("foo//bar") 的结果为 foo/bar,clean("foo/..") 的结果为 .,clean("foo/./bar") 的结果为 foo/bar。
  • join(a, b…) - 此函数在 Unix 上使用 /,在 Windows 上使用 \,这可能会 导致不期望的行为。应考虑使用 / 运算符(例如 a / b)作为替代,因为它始终 使用 /,除非你确实需要在 Windows 上使用 \。 将路径 a 与路径 b 拼接。 join("foo/bar", "baz") 的结果为 foo/bar/baz。接受两个或更多参数。
文件系统访问
  • path_exists(path) - 如果路径指向现有实体,则返回字符串 true,否则返回字符串 false。会遍历符号链接;如果路径无法访问或指向失效的符号链接,则返回字符串 false。
  • read(path)1.39.0 - 以字符串形式返回 path 处文件的内容。
断言和错误报告
  • assert(CONDITION, EXPRESSION)1.27.0 - 如果 CONDITION 为 false,则以 EXPRESSION 作为消息报错。EXPRESSION 可以省略1.53.0,
  • error(message) - 中止执行并向用户报告错误 message。
UUID 和哈希生成
  • blake3(string)1.25.0 - 以十六进制字符串形式返回 string 的 BLAKE3 哈希值。
  • blake3_file(path)1.25.0 - 以十六进制字符串形式返回 path 处文件的 BLAKE3 哈希值。
  • sha256(string) - 以十六进制字符串形式返回 string 的 SHA-256 哈希值。
  • sha256_file(path) - 以十六进制字符串形式返回 path 处文件的 SHA-256 哈希值。
  • uuid() - 生成随机的版本 4 UUID。
随机
  • choose(n, alphabet)1.27.0 - 从 alphabet 中随机选择字符,生成长度为 n 的 字符串;alphabet 中的字符不能重复。例如,choose('64', HEX) 会生成随机的 64 位小写十六进制字符串。
日期时间
  • datetime(format)1.30.0 - 返回采用 format 格式的本地时间。
  • datetime_utc(format)1.30.0 - 返回采用 format 格式的 UTC 时间。

datetime 和 datetime_utc 的参数是 strftime 风格的格式字符串。详情请参阅 chrono 库文档。

语义化版本
  • semver_matches(version, requirement)1.16.0 - 检查语义化 version(例如 "0.1.0")是否符合 requirement(例如 ">=0.1.0");若符合,则返回字符串 "true",否则返回字符串 "false"。
样式
  • style(styles)1.37.0 - 返回一个终端转义序列,该序列组合了 styles 中指定的命名样式。

    1.37.0 及更高版本支持以下样式,可用于复现 just 自身的样式:

    • command:回显的配方行
    • error:错误
    • warning:警告

    1.55.0 及更高版本还支持以下命名颜色:

    • black
    • blue
    • cyan
    • green
    • magenta
    • red
    • white
    • yellow

    256 种索引颜色,以介于 0 和 255 之间的整数表示,例如 1 或 67。

    24 位颜色,以 #RRGGBB 或 #RGB 十六进制代码表示,例如 #065535 或 #AAA。

    以及以下显示属性:

    • blink
    • bold
    • dim
    • hidden
    • italic
    • reverse
    • strikethrough
    • underline

    两种流名称1.55.0可根据 just 是否会为输出流着色来控制样式。判断依据包括 --color、JUST_COLOR,以及该流是否连接到终端:

    • stdout
    • stderr

    默认情况下,所有颜色样式都会为前景着色;它们也有显式的前景变体(前缀为 fg:)和背景变体(前缀为 bg:),例如 bg:blue、fg:133 和 #FFF。

    styles 可以是样式列表,此时会组合所有列出的样式,以生成最终的转义序列。

    请注意,style(styles) 返回的转义序列是前缀。使用后可以通过 NORMAL 常量重置样式:

    error message:
      echo '{{style("error") + message + NORMAL}}'
    
  • style(styles, text)1.55.0 - 使用与单参数形式相同的 styles 为 text 设置样式。样式会自动重置,因此无需使用 NORMAL 来重置终端:

    error message:
      echo '{{style("error", message)}}'
    
用户目录

这些函数1.23.0会返回用户专属目录的路径,例如配置、数据、缓存、可执行文件和用户主目录所在的目录。

在 Unix 上,这些函数遵循 XDG 基础目录规范。

在 macOS 和 Windows 上,这些函数会返回系统指定的用户专属目录。例如,在 macOS 上,cache_directory() 会返回 ~/Library/Caches;在 Windows 上则会返回 {FOLDERID_LocalAppData}。

详情请参阅 dirs crate。

  • cache_directory() - 用户专属缓存目录。
  • config_directory() - 用户专属配置目录。
  • config_local_directory() - 用户专属的本地配置目录。
  • data_directory() - 用户专属数据目录。
  • data_local_directory() - 用户专属的本地数据目录。
  • executable_directory() - 用户专属可执行文件目录。
  • home_directory() - 用户的主目录。
  • runtime_directory() - 用户专属运行时目录。仅在 Linux 上定义。

如果希望在所有平台上使用 XDG 基础目录,可以将 env(…) 函数与相应的环境变量及回退值搭配使用。不过请注意,XDG 规范要求忽略非绝对路径;因此,若要与符合规范的应用程序完全兼容,需要执行以下操作:

xdg_config_dir := if env('XDG_CONFIG_HOME', '') =~ '^/' {
  env('XDG_CONFIG_HOME')
} else {
  home_directory() / '.config'
}
常量

预定义了许多常量:

名称值Windows 上的值
HEX1.27.0"0123456789abcdef"
HEXLOWER1.27.0"0123456789abcdef"
HEXUPPER1.27.0"0123456789ABCDEF"
PATH_SEP1.41.0"/""\"
PATH_VAR_SEP1.41.0":"";"
CLEAR1.37.0"\ec"
NORMAL1.37.0"\e[0m"
BOLD1.37.0"\e[1m"
ITALIC1.37.0"\e[3m"
UNDERLINE1.37.0"\e[4m"
INVERT1.37.0"\e[7m"
HIDE1.37.0"\e[8m"
STRIKETHROUGH1.37.0"\e[9m"
BLACK1.37.0"\e[30m"
RED1.37.0"\e[31m"
GREEN1.37.0"\e[32m"
YELLOW1.37.0"\e[33m"
BLUE1.37.0"\e[34m"
MAGENTA1.37.0"\e[35m"
CYAN1.37.0"\e[36m"
WHITE1.37.0"\e[37m"
BG_BLACK1.37.0"\e[40m"
BG_RED1.37.0"\e[41m"
BG_GREEN1.37.0"\e[42m"
BG_YELLOW1.37.0"\e[43m"
BG_BLUE1.37.0"\e[44m"
BG_MAGENTA1.37.0"\e[45m"
BG_CYAN1.37.0"\e[46m"
BG_WHITE1.37.0"\e[47m"
@foo:
  echo {{HEX}}
$ just foo
0123456789abcdef

以 \e 开头的常量是 ANSI 转义序列。

CLEAR 会清屏,效果类似于 clear 命令。其余常量的形式为 \e[Nm,其中 N 是一个整数,用于设置终端显示属性。

终端显示属性转义序列可以组合使用,例如文本粗细 BOLD、文本样式 STRIKETHROUGH、前景色 CYAN 和背景色 BG_BLUE。使用后应跟上 NORMAL,将终端重置为正常状态。

转义序列应加引号,因为某些 shell 会将 [ 视为特殊字符。

@foo:
  echo '{{BOLD + STRIKETHROUGH + CYAN + BG_BLUE}}Hi!{{NORMAL}}'

变更日志

最新版本的变更日志见 CHANGELOG.md。 此前版本的变更日志见 发行页面。也可以使用 just --changelog 让 just 二进制文件打印其变更日志。

其他

文件变更时重新运行配方

watchexec 可以在文件变更时重新运行任意命令。

要在任何文件变更时重新运行配方 foo:

watchexec just foo

有关更多信息,请参阅 watchexec --help,其中也介绍了如何指定要监视变更的文件。

Shell 别名

若要以闪电般的速度运行命令,请在 shell 的配置文件中添加 alias j=just。

语法

GRAMMAR.md 中提供了 justfile 的非规范性语法。

just.sh

在 just 成为功能强大的 Rust 程序之前,它只是一个调用 make 的小型 shell 脚本。旧版本位于 contrib/just.sh。

Node.js package.json 脚本兼容性

以下导出语句可让 just 配方访问本地 Node 模块二进制文件,并使 just 配方命令的行为更接近 Node.js package.json 文件中的 script 条目:

export PATH := "./node_modules/.bin:" + env_var('PATH')
Windows 上的路径

在 Windows 上,除 invocation_directory() 外,所有返回路径的函数都会返回以 \ 分隔的路径。如果不使用 PowerShell 或 cmd.exe,应将这些路径括起来,以防 \ 被解释为转义字符:

ls:
    echo '{{absolute_path(".")}}'

cygpath.exe 是某些面向 Windows 的 Unix 用户空间发行版中包含的可执行文件,例如 Cygwin 和 Windows 版 Git。

just 在两处使用 cygpath.exe:

为保持向后兼容,invocation_directory() 会使用 cygpath.exe 将调用目录转换为 Unix 风格的、以 / 分隔的路径。使用 invocation_directory_native() 可获取原生的 Windows 风格路径。在 Unix 上,invocation_directory() 和 invocation_directory_native() 都会返回相同的 Unix 风格路径。

cygpath.exe 还用于将 Unix 风格的 shebang 行转换为 Windows 路径。也可以使用 [script] 属性作为替代,它不依赖 cygpath.exe。

如果 cygpath.exe 可用,可以使用它在不同路径风格之间进行转换:

foo_unix := '/hello/world'
foo_windows := shell('cygpath --windows $1', foo_unix)

bar_windows := 'C:\hello\world'
bar_unix := shell('cygpath --unix $1', bar_windows)
打印复杂字符串

可以使用 echo 打印字符串,但由于它会处理 \n 之类的转义序列,而且不同实现的 echo 对转义序列的识别各不相同,使用 printf 通常是更好的选择。

printf 接受一个 C 风格的格式字符串和任意数量的参数,并将这些参数插入格式字符串中。

可以将它与缩进的三引号字符串结合使用,以模拟 shell heredoc。

使用 {{…}} 将复杂字符串替换到配方正文中也可能造成问题,因为根据空白和引号的情况,shell 可能会将其拆分为多个参数。将复杂字符串导出为环境变量,再用 "$NAME" 引用(注意双引号),也会有所帮助。

综合以上方法,要将字符串原样打印到标准输出,同时保留其中各种转义序列和引号:

export FOO := '''
  a complicated string with
  some dis\tur\bi\ng escape sequences
  and "quotes" of 'different' kinds
'''

bar:
  printf %s "$FOO"
面向 Agent 的技能

skills/just 中提供了面向 Agent 的技能,可以手动安装,也可以使用 npx skills add casey/just --global 安装。

替代方案与先例

命令运行器可谓多不胜数!与 just 有些相似的替代方案包括:

  • make:启发了 just 的 Unix 构建工具。原始 make 有若干不同的现代衍生版本,包括 FreeBSD Make 和 GNU Make。
  • task:使用 Go 编写的基于 YAML 的命令运行器。
  • maid:使用 JavaScript 编写的基于 Markdown 的命令运行器。
  • microsoft/just:使用 JavaScript 编写的命令运行器。
  • cargo-make:面向 Rust 项目的命令运行器。
  • mmake:make 的包装器,包含多项改进,例如远程 include。
  • robo:使用 Go 编写的基于 YAML 的命令运行器。
  • mask:使用 Rust 编写的基于 Markdown 的命令运行器。
  • makesure:使用 AWK 和 shell 编写的简单、可移植的命令运行器。
  • haku:使用 Rust 编写的类 make 命令运行器。
  • mise:使用 Rust 编写的开发环境工具管理器,支持 TOML 文件中的任务和独立脚本。
元数据

可以使用 [metadata(METADATA)] 属性将字符串列表形式的元数据附加到配方上1.42.0:

[metadata("hello", "goodbye")]
foo:

可以使用 just --dump --dump-format json 读取元数据。

使用 uv 编写 Python 配方

uv 是一款出色的跨平台 Python 项目管理器,使用 Rust 编写。

通过使用 [script] 属性和 script-interpreter 设置,可以轻松配置 just 使用 uv 运行 Python 配方:

set script-interpreter := ['uv', 'run', '--script']

[script]
hello:
  print("Hello from Python!")

[script]
goodbye:
  # /// script
  # requires-python = ">=3.11"
  # dependencies=["sh"]
  # ///
  import sh
  print(sh.echo("Goodbye from Python!"), end='')

当然,也可以使用 shebang:

hello:
  #!/usr/bin/env -S uv run --script
  print("Hello from Python!")
与 mise 集成

mise 是一款用于安装开发工具、管理环境和运行任务的工具。

just 和 mise 配合使用效果很好:just 用于定义和运行任务,mise 用于安装和管理开发工具。

要在 just 中加载 mise 环境:

set dotenv-command := 'mise env'
set dotenv-override

test:
  node --version
  echo $FOO

环境会在 just 启动时加载一次,并可供所有配方使用。必须设置 set dotenv-override,因为 mise env 会返回 PATH 变量的值,而该变量始终已经设置。

mise 会以交互方式提示是否信任 mise.toml。在非交互环境中,应先运行 mise trust。

mise env 不会安装缺失的工具,因此请先运行 mise install,或使用以下方式自动安装缺失的工具:

set dotenv-command := 'mise install -q && mise env'

可以为整个 justfile 覆盖工具版本:

set dotenv-command := 'mise env node@22'

也可以为单个配方覆盖:

[script('mise', 'exec', 'node@22', '--', 'node')]
version:
  console.log(process.version);

[script('mise', 'exec', '[email protected]', '--', 'sh', '-eu')]
test:
  python -m pytest

此外,还可以使用 set shell,通过 mise exec 运行每个 shell 配方中的每一行:

set shell := ['mise', 'exec', '--', 'sh', '-cu']

test:
  node --version

还可以使用 [env] 属性,按配方覆盖工具版本:

set shell := ['mise', 'exec', '--', 'sh', '-cu']

[env('MISE_NODE_VERSION', '22')]
test:
  node --version

请注意,mise exec 的运行时间比 sh 更长,因此对于包含许多命令的 shell 配方,set shell 可能会明显拖慢运行速度。

激活环境

某些工具需要执行激活步骤,例如 Python 虚拟环境:

. .venv/bin/activate

由于这些工具会修改正在运行的 shell 的环境,因此 just 无法代替你执行此激活步骤。不过,有一些变通方法。

管理 Python 环境的最佳变通方法是改用 uv。uv 会为每条命令设置正确的环境,因此无需激活步骤。

如果无法使用这种方法,或者你使用的是其他工具,可以创建一个共享的前置代码段,并将其包含在需要它的脚本配方中。它可以跨越多行,并包含任意数量的步骤:

prelude := '''
  set -eux
  . .venv/bin/activate
'''

[script]
run:
  {{ prelude }}
  python script.py

这种变通方法不适用于 shell 配方,因为每条命令都会启动一个新的 shell。

贡献

说明

just 目前不接受拉取请求。保留贡献部分仅作存档。

just 欢迎你贡献!just 采用限制最少的 CC0 公共领域贡献声明及后备许可发布,因此你的更改也必须采用此许可发布。

入门

just 使用 Rust 编写。使用 rustup 安装 Rust 工具链。

just 经过了广泛测试。所有新功能都必须有单元测试或集成测试覆盖。单元测试位于 src 中,与被测试的代码放在一起, 并对代码进行隔离测试。集成测试位于 tests 目录 中,会通过针对给定的 justfile 和一组命令行参数调用 just,从外部测试 just 二进制文件,并检查输出。

你应该选择最容易为你的功能编写的测试类型,同时确保提供良好的测试覆盖率。

单元测试适合测试内部使用的新 Rust 函数,也有助于开发。一个很好的例子是用于测试 unindent() 函数 的单元测试。该函数用于取消三引号字符串和反引号字符串的缩进。unindent() 有许多 棘手的边缘情况,通过直接调用 unindent() 的单元测试可以轻松覆盖。

集成测试适合确保 just 二进制文件的最终行为正确。集成测试也会覆盖 unindent(),确保对三引号字符串求值时能得到正确的去缩进结果。不过,集成测试并未覆盖 所有可能的情况。这些情况由更快、更简洁的单元测试覆盖,测试会直接调用 unindent()。

集成测试使用 Test 结构体。它是一个构建器,可轻松地使用给定的 justfile、参数和环境变量 调用 just,并检查程序的标准输出、标准错误和退出代码。

贡献工作流
  1. 确认该功能确实受欢迎。应该有一个关于此功能的未关闭议题,并且 @casey 在评论中表示这是个好主意或看起来合理。如果没有,请新建一个议题并征求反馈。

    许多不错的功能无法合并,原因可能是它们不向后兼容、实现方式会使代码库过于复杂, 或者与 just 的设计理念相悖。

  2. 确定功能的设计。如果该功能有多种可能的实现方式或语法,请务必在议题中敲定细节。

  3. 克隆 just 并开始开发。最佳工作流是在编辑器中打开正在处理的代码,同时运行一个在文件 发生变化时重新运行测试的任务。你可以通过安装 cargo-watch,执行 cargo install cargo-watch,然后运行 just watch test 来启动此类任务。

  4. 为你的功能添加一个失败的测试。大多数情况下,这会是一个端到端测试该功能的集成测试。 在 tests 中寻找合适的文件来添加测试, 或在 tests 中新建文件,并在 tests/lib.rs 中添加导入该文件的 mod 语句。

  5. 实现该功能。

  6. 运行 just ci,确保所有测试、Lint 和检查均通过。需要安装 mdBook 和 mdbook-linkcheck。

  7. 提交一个包含新代码的 PR,并确保维护者可以编辑。PR 通常需要变基和一些小调整。如果维护者 无法编辑 PR,那么每次变基和调整都需要重新进行一轮代码审查。如果维护者无法编辑你的 PR, 该 PR 可能会被直接关闭。

  8. 根据反馈进行修改。

  9. 享受 PR 合并时的喜悦!

欢迎随时提交草稿 PR,以便讨论和征求反馈。

提示

以下提示可帮助你着手实现某些类型的新功能,也可以与上述贡献流程结合使用。

添加新属性
  1. 在 tests/attributes.rs 中编写新的集成测试。

  2. 在 Attribute 枚举中添加一个新变体。

  3. 实现新属性的功能。

  4. 运行 just ci,确保所有测试均通过。

Janus

Janus 是一个用于检查对 just 的更改是否会破坏或改变现有 justfile 解释方式的工具。它会收集并分析 GitHub 上公开的 justfile。

在合并特别庞大或复杂的更改之前,应运行 Janus,确保没有问题。无需担心自己运行 Janus;对于需要运行 Janus 的更改,Casey 很乐意代为运行。

最低支持 Rust 版本

最低支持的 Rust 版本(MSRV)为当前稳定版 Rust。它可能可以在更早的 Rust 版本上构建,但不作保证。

新版本发布

just 会频繁发布新版本,让用户尽快使用新功能。

发布提交消息使用以下模板:

Release x.y.z

- Bump version: x.y.z → x.y.z
- Update changelog
- Update changelog contributor credits
- Update dependencies
- Update version references in readme

常见问题

Just 避开了 Make 的哪些特异行为?

make 的某些行为令人困惑、复杂,或者使它不适合作为通用命令运行器。

例如,在某些情况下,make 实际上不会运行配方中的命令。假设你有一个名为 test 的文件,以及以下 makefile:

test:
  ./test

make 会拒绝运行测试:

$ make test
make: `test' is up to date.

make 会假定 test 配方会生成一个名为 test 的文件。由于该文件已存在,而且配方没有其他依赖项,make 便认为无事可做,并退出。

公平地说,将 make 用作构建系统时,这种行为是可取的;但将它用作命令运行器时则不然。你可以使用 make 内置的 .PHONY 目标名称, 针对特定目标禁用这种行为,但其语法冗长且难以记忆。将伪目标列表与配方定义分开书写,也会带来意外定义非伪目标的风险。在 just 中,所有配方都被视为伪目标。

make 的其他特异行为还包括赋值中 = 与 := 的区别、makefile 出错时产生的令人困惑的错误消息、在配方中使用环境变量时需要写 $$,以及不同版本的 make 之间存在的不兼容问题。

Just 与 Cargo 构建脚本有什么关系?

cargo 构建脚本有着相当明确的用途:控制 cargo 如何构建你的 Rust 项目。这可能包括为 rustc 调用添加标志、构建外部依赖,或运行某种代码生成步骤。

另一方面,just 用于开发过程中可能会运行的其他各种命令,例如在不同配置下运行测试、检查代码、将构建产物推送到服务器、删除临时文件等。

此外,尽管 just 是用 Rust 编写的,但无论你的项目使用什么语言或构建系统,都可以使用它。

更多碎碎念

我个人觉得,几乎为每个项目(无论大小)编写一个 justfile 都非常有用。

对于有多位贡献者的大型项目来说,随手就能找到包含项目所需全部命令的文件非常有用。

项目可能有不同的测试、构建、检查、部署等命令,把它们集中放在一个地方很有帮助,也能减少你花在告诉大家该运行哪些命令以及如何输入这些命令上的时间。

而且,有一个方便的地方可以放命令,你很可能会想到其他有用的内容。这些内容属于项目的集体经验,却没有记在任何地方,例如某些版本控制工作流环节所需的晦涩命令、安装项目所有依赖项的命令,或者构建系统可能需要的各种随机标志。

一些配方示例:

  • 部署/发布项目

  • 以发布模式或调试模式构建

  • 在调试模式下运行,或启用日志记录运行

  • 复杂的 git 工作流

  • 更新依赖项

  • 运行不同的测试集,例如快速测试与慢速测试,或以详细输出运行测试

  • 任何你真的应该写下来留存的复杂命令组合,哪怕只是为了能记住它们

即使是小型个人项目,能按名称记住命令,而不必按 ^ 在 shell 历史记录中反向搜索,也很方便。而且,进入一个用随机语言编写、采用神秘构建系统的旧项目时,如果知道所有需要用到的命令都在 justfile 中,并且输入 just 后很可能会发生一些有用(或至少有趣!)的事情,那就实在太好了。

想了解配方的灵感,可以看看 本项目的 justfile, 或者一些 justfile 实际应用示例。

总之,这篇冗长的 README 大概就说到这里。

希望你喜欢使用 just,并在所有计算领域的探索中收获成功与满足!

😸

🔼 返回顶部!

评论

支持 Markdown