mise generate task-stubs:为 mise 任务生成可直接执行的 stub 脚本
【免费下载链接】misedev tools, env vars, task runner项目地址: https://gitcode.com/GitHub_Trending/mi/mise
mise generate task-stubs是 mise(dev tools、env vars、task runner)提供的一条生成类命令,它的作用是在项目目录(默认bin/)下为每个任务生成一层薄薄的 stub 脚本,让团队成员、CI 乃至尚未安装 mise 的贡献者都能以./bin/<task>的方式直接运行项目任务。阅读本文后,你将掌握该命令的全部参数(--dir、--mise-bin、--windows-launcher)、stub 的生成原理、Windows 下.cmd/.exe启动器的差异,以及重新生成时保证不覆盖用户自有文件的校验机制,并能将其与mise generate install-script组合成一套完整的项目任务分发方案。
命令概览:让任务脱离mise run也能被调用
在 docs/cli/generate/task-stubs.md 中,该命令被定义为:
- 用法:
mise generate task-stubs [FLAGS] - 副作用:修改文件系统状态(生成 stub 文件)
其核心行为是:默认在./bin/<task>路径下构建每个任务的 stub 脚本。生成出的 stub 是一层极薄的包装,内部真正执行的是mise run <task> "$@",因此任务本身依然由 mise 的任务运行器调度,享受任务配置、工具链环境与参数处理等完整能力,而调用者只需像运行普通命令一样敲./bin/test即可。
mise 将其定位为与mise generate install-script配合使用:install-script 负责生成一个"下载并执行 mise"的安装脚本,task-stubs 负责生成任务的调用入口,两者结合可以让项目贡献者不安装 mise 也能运行项目任务。对应源码位于 src/cli/generate/task_stubs.rs,模块注释中明确写出了这一设计意图。
mise tasks add test -- echo 'running tests' mise generate task-stubs ./bin/test # running tests生成的 stub 长什么样
从源码实现 src/cli/generate/task_stubs.rs 可以看出,一个标准的 POSIX stub 内容为:
#!/bin/sh # generated by mise task-stubs exec mise run test "$@"这里有几个值得注意的实现细节:
- 脚本以
# generated by mise task-stubs作为归属标记(ownership marker),后续重新生成时,mise 依靠这行标记判断该文件是否为自己生成、是否可以安全覆盖(见 is_generated_task_stub 的逐行解析逻辑)。 mise_bin(默认mise)会经过shell_words::quote处理,确保包含空格或特殊字符的路径在 shell 中安全展开。- 任务名使用不带扩展名的 display name(如
deploy.sh文件任务生成的是bin/deploy而非bin/deploy.sh)。 exec关键字让 stub 进程直接替换为 mise 进程,"$@"原样透传所有调用参数。- 生成后立即通过
file::make_executable赋予可执行权限,并打印Wrote to <路径>(见 run())。
源码中还保留了一个旧版 stub 形态(generate_legacy,不含# generated by mise task-stubs标记行),用于识别和迁移旧版本 mise 生成的、没有归属标记的旧 stub——这是重生成时能安全替换历史文件的关键兼容措施。
参数详解
-d --dir <DIR>:stub 输出目录
- 默认值:
bin - 作用:指定 stub 生成的目录。例如
--dir bin会在bin/下生成,--dir tools则在tools/下生成。嵌套任务的 stub 会按任务名层级放入对应子目录(见下文"嵌套任务"一节)。
-m --mise-bin <MISE_BIN>:stub 内部调用的 mise 可执行文件
- 默认值:
mise - 作用:控制 stub 脚本中
exec <MISE_BIN> run <task> "$@"的二进制路径。 - 典型用法:
--mise-bin=./bin/mise配合mise generate install-script生成的本地 mise——这样贡献者在没有系统级 mise 的情况下,stub 会调用项目自带的./bin/mise。 - Windows 注意事项:Windows 上路径会"按原样运行",因此
./bin/mise这样的路径必须在它旁边配一个 Windows 启动器才能执行,需要用mise generate install-script --write ./bin/mise --windows生成。而默认值mise是一个裸名字,由 cmd 通过 PATH 解析,无需任何额外文件。
该参数在 src/cli/generate/task_stubs.rs 中定义。值得注意的是,stub 生成方还内置了一个防呆警告warn_if_windows_cannot_run(见 src/cli/generate/task_stubs.rs):如果--mise-bin指向一个路径、且该路径旁没有 Windows 可直接执行的.cmd/.bat/.exe启动器,会在所有平台上发出警告——因为.cmd启动器在任何平台上都会生成(stub 是要提交进仓库的,Windows 上运行的人不一定是生成它的人),而生成者恰恰最可能看不到 Windows 侧的失败。判断逻辑windows_can_run完全遵循 Windows 的规则:裸名字视为可执行(经 PATH + PATHEXT 解析)、路径则以扩展名和旁侧文件(大小写不敏感匹配)为准(见 src/cli/generate/task_stubs.rs)。
--windows-launcher <WINDOWS_LAUNCHER>:Windows 启动器形态
- 可选值:
cmd、exe - 默认值:
cmd - 作用:决定在每个 stub 旁边额外生成的 Windows 启动器形式。之所以需要它,是因为 Windows 无法直接执行
#!/bin/sh的 stub 脚本,因此无论在哪台主机上生成,都会为每个 stub 附写一个启动文件(stub 与启动器都要提交进仓库,见 run() 中的注释说明)。
两种形态的取舍如下:
| 形态 | 生成文件 | 优点 | 代价 |
|---|---|---|---|
cmd(默认) | <task>.cmd | 任何平台都能生成;文件小 | cmd.exe 在%*展开前会整行重新解析,因此从 PowerShell 调用时,包含& ^ \| " %VAR%等字符的参数无法原样到达任务 |
exe | <task>.exe | 原生可执行文件,从每个 shell 接收的参数都不变 | 是 Windows 构建版自带的 mise-shim.exe 的字节拷贝,只能在 Windows 上生成;每个任务在通常要提交的目录里多出约 220KB |
选择exe时,实现会通过find_mise_shim_bin(见 src/shims.rs)在真实 mise.exe 旁或 PATH 中查找mise-shim.exe;若找不到(例如在 Linux 上生成),命令会直接报错而不是悄悄回退到.cmd——因为 stub 是要提交的,悄悄写入与请求不同的文件等于把错误塞进别人的提交(见 Launchers::resolve)。--windows-launcher的定义见 src/cli/generate/task_stubs.rs。
-h --help:查看帮助
打印命令帮助信息,与 mise 其他子命令一致。
Windows 启动器的底层实现:参数恢复协议
cmd形态的启动器并不是简单的mise run <task> %*。源码 windows_launcher_body 展示了一套完整的"参数恢复"协议,值得深入了解:
- 问题根源:cmd.exe 在批处理文件运行前就会解析整条命令行。从 PowerShell 调用
.cmd时,PowerShell 不知道目标是批处理文件,会把参数不加引号地传给cmd /c,于是& ^ | " < >等字符在%*展开前就丢失了,%VAR%也会被提前展开。源码注释记录了一个实测数据:同样的 17 种参数形态经mise run转发,有 9 种到达结果不同。 - 恢复手段:cmd 虽然破坏了参数,但原始文本仍保留在
CMDCMDLINE伪变量中。启动器将其复制到真实变量(使用!CMDCMDLINE!延迟展开,因为%CMDCMDLINE%会在特殊字符解析前被替换、在第一个&处截断),再交给 mise——mise 会用原生程序的运行时规则重新切分参数。参数走环境变量而非命令行传递,避免 cmd 获得第二次解析机会。 - 守卫分支:当 cmd 并非为这个启动器而启动(如交互式提示符、被其他批处理
call)时,参数已经由 shell 正确切分,直接走%*回退分支,且不能执行exit(否则会关闭调用者的 shell);当确实由本启动器启动时,用exit(而非exit /b)阻止 cmd 继续执行同一行&之后排队的内容,避免把后续命令的失败误报为任务的退出码。 - 归属标记:启动器第二行固定写入
rem generated by mise(常量WINDOWS_LAUNCHER_MARKER,见 src/cli/generate/mod.rs),重新生成时据此识别"这是 mise 写的",绝不覆盖手写的同名.cmd。 - 无条件加引号:嵌入启动器命令行的
--mise-bin路径和任务名都会经过cmd_quote(见 src/cli/generate/mod.rs)——%被写成%%、整体加双引号。加引号是无条件的:带引号的裸名字依然能通过 PATH 解析,没有任何代价,而一条规则永远比"判断 cmd 元字符集合"更容易保持正确。
exe形态则没有这些烦恼:原生可执行文件直接接收 argv,参数天然无损。这正是--windows-launcher=exe存在的原因。e2e 测试 e2e/generate/test_generate_task_stubs 中对启动器的每一处关键特征都有断言:"mise" run "xxx" __MISE_LAUNCHER_ARGS__ %*、rem generated by mise、!CMDCMDLINE!、goto mise_launcher_fallback与exit !ERRORLEVEL!,并明确拒绝%CMDCMDLINE%这种旧写法。
嵌套任务:父 stub 与_default
当父任务与嵌套任务同时存在时,父任务的 stub 会被写到<parent>/_default。例如任务work:containers(父)与work:containers:pg、work:containers:redis(子)会生成:
bin/work/containers/_default # 父任务 work:containers 的 stub bin/work/containers/pg # 子任务 bin/work/containers/redis # 子任务路径解析逻辑在resolve_stub_paths(见 src/cli/generate/task_stubs.rs):若某个 stub 路径是另一个 stub 路径的前缀(父路径),父路径就拼接_default;若多个任务映射到同一路径,则直接报错multiple tasks map to task stub path <path>。单元测试resolves_parent_and_nested_task_paths(见 src/cli/generate/task_stubs.rs)精确固定了这一行为,e2e 测试中bin/work/containers/_default、bin/work/containers/pg.cmd、bin/work/containers/_default.cmd的存在性也验证了启动器会一并落入嵌套目录。
对于同名任务 stub 的迁移,源码同样周到:文件任务以文件名为名(旧版生成bin/build.sh),现在改为以任务名命名(bin/build)。重新生成时,若旧路径存在且内容确为 mise 生成的 stub,会连同其启动器一起迁移到新路径;若旧路径上是别人的文件,则整个运行报错中止,绝不越权删除(见 validate_stub_paths)。
重新生成的安全性:只动"自己的东西"
mise generate task-stubs可以反复执行,且刻意设计成可重复、可迁移、不误伤:
- stub 归属校验:目标路径若是符号链接、非普通文件、或内容并非 mise 生成的 stub,运行直接失败(
cannot write task stub because ...),避免覆盖用户自有脚本。 - 启动器归属校验:
bin/<task>.cmd是项目可能已经在用的常见命名,因此校验要求该文件必须携带rem generated by mise标记;.exe因无法内置标记,则通过与 mise-shim.exe 逐字节比较确认归属(先比大小、再比内容,见 file_contents_eq)。无法读取或无法证明归属的文件一律"保留原样",错误方向永远偏向保守。 - 形态切换清理:由于 Windows 解析
.exe优先于.cmd,同一 stub 的两种启动器是互斥的。一次运行写出新形态启动器的同时,会删除自己生成的另一种形态,避免切换--windows-launcher后仍运行着刚被替换掉的旧启动器(见 remove_owned_launcher);若旧.exe无法证明是 mise 写的(如在非 Windows 主机上重新生成),会发出warn_if_shadowed警告提示手工处理。 - 目录回退:当嵌套任务被删除、只剩父任务时,重新生成会把已生成的
bin/work/containers/目录(其中只有 mise 生成的 stub 与启动器)整体折叠回单个bin/work/containers叶子 stub;目录里若混有用户文件,则拒绝折叠并报错(见 validate_generated_stub_tree)。
这些保护在 e2e 测试 e2e/generate/test_generate_task_stubs 中有大量直接证据:blocked-bin/work is not a directory、xxx is a symbolic link、xxx.cmd is not a generated launcher、legacy-bin/legacy/user-script is not a generated task stub等失败断言,全部确认"mise 只替换自己生成的文件,绝不触碰用户资产"。
完整工作流:让贡献者不装 mise 也能跑任务
将 task-stubs 与 install-script 组合,可以构建一套对贡献者零依赖的任务执行方案:
# 1. 定义一个任务 mise tasks add test -- echo 'running tests' # 2. 生成项目自带的 mise 安装脚本(提交进仓库) mise generate install-script --write ./bin/mise # 3. 为 Windows 贡献者生成 mise 的启动器 mise generate install-script --write ./bin/mise --windows # 4. 让 stub 调用项目自带的 mise mise generate task-stubs --mise-bin=./bin/mise # 5. 贡献者无需安装 mise,直接运行任务 ./bin/test # running tests其中第 2、3 步的详细参数说明见 install-script 文档。需要注意:如果省略第 3 步,Windows 上的bin/<task>.cmd会因为找不到可执行的./bin/mise而失败,此时生成命令会在所有平台上发出no Windows launcher beside it警告提醒补上--windows。
相关文档
- Tasks 任务总览:任务的 TOML 配置、文件任务、运行方式与运行时的环境变量。
- mise generate 子命令族:
install-script、config、devcontainer、git-pre-commit、github-action、task-docs、tool-stub等同族命令。 - 全局标志与参数语法:mise 所有子命令共用的全局标志(
--verbose、--debug等)。 - 核心实现:src/cli/generate/task_stubs.rs、src/cli/generate/mod.rs(启动器协议)、src/shims.rs(
find_mise_shim_bin)。 - 端到端验证:e2e/generate/test_generate_task_stubs。
【免费下载链接】misedev tools, env vars, task runner项目地址: https://gitcode.com/GitHub_Trending/mi/mise
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考