关于 implement-prepare-environment-script
用于生成一次性环境准备脚本的 Skill:在一致模式下暂存构建内容,并为一致性测试预备隔离依赖。它涵盖不同语言及 Bash、PowerShell 环境。
主要功能
- 01将构建内容暂存到临时工作目录
- 02准备隔离依赖和构建产物
- 03与一致性测试脚本协同
- 04涵盖 Bash 和 PowerShell 模式
适用场景
- 01避免多次一致性测试重复安装依赖
- 02为项目准备一致性测试环境
- 03生成不修改源目录的准备脚本
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ plainlang/ plain-forge --skill implement-prepare-environment-script安装后在 Claude Code 中输入/implement-prepare-environment-script确认已加载。
详细介绍
实现环境准备脚本
此技能会生成一个可执行脚本,用于执行一致、与语言无关的模式所规定的每次构建一次的设置,这些设置会在运行一致性测试之前完成。
参考实现为 assets/prepare_environment_java.sh 和 assets/prepare_environment_python.sh。请先阅读它们——你生成的每个脚本都必须忠实地将该模式转换为目标语言的工具链,并适配用户的 shell 环境。
环境准备脚本的用途
prepare_environment_<lang> 是 run_conformance_tests_<lang> 的可选配套脚本。它会在一致性测试脚本之前立即运行,用于:
- 将构建内容暂存到工作文件夹中(与一致性测试脚本使用的文件夹相同)。
- 预热依赖缓存/构建产物,让一致性测试脚本可以从冷启动开始运行,而无需重新下载或重新编译任何内容。
如果项目中存在此脚本,相应一致性测试脚本自身的依赖安装阶段就会降级为“仅激活”(请参阅 implement-conformance-testing-script/SKILL.md → “Skipping setup when prepare_environment exists”)。如果不存在,一致性测试脚本就会内联完成设置。
此脚本存在的原因(结构性原因)
渲染器会为每个功能规格调用一次一致性测试运行器,而不是每次渲染调用一次。模块中的每个功能规格都有自己的 plain_modules/<module>/tests/<spec>/ 文件夹;渲染器为新规格生成代码后,会运行同一模块中所有先前规格的一致性测试,以检测回归。对于有 N 个功能规格的模块,每次渲染大约会调用一致性测试脚本 N 次。
如果没有环境准备脚本,这 N 次调用中的每一次都会从头完整安装依赖(Python venv + pip install、完整的 Maven 依赖树、npm ci、cargo build 等)。每次渲染都要支付 N 次这样的开销,这会占据非平凡项目渲染耗时的大部分。
prepare_environment_<lang> 的作用是将这笔开销分摊为每次渲染安装一次:
- 准备脚本运行一次,安装所有内容,并将内容填充到工作文件夹
<temp>/<lang>_<basename>/中的项目本地隔离位置(./.venv、./node_modules、./.m2、./.gocache、./.cargo、./.pub-cache)。在此技能中,<temp>/<lang>_<basename>表示:系统临时目录(Bash 中为/tmp,PowerShell 中为[System.IO.Path]::GetTempPath())、简短的语言标识符,以及构建文件夹参数的基本名称($(basename "$1")/Split-Path -Leaf)——绝不能使用原始参数,因为当前渲染器传入的是绝对路径。 - 然后,一致性测试脚本会运行 N 次,每次调用都使用其仅激活变体,连接到已填充的工作文件夹,并完全跳过安装步骤。
- 最终效果:安装开销从
N × install-cost降至1 × install-cost + N × cheap-attach-cost。
这就是准备后再运行一致性测试这一拆分方式存在的全部原因。如果项目的功能规格很少,安装开销可以忽略不计,那么生成环境准备脚本就是白费功夫——使用内联安装变体的一致性测试脚本即可。如果项目有许多规格(或依赖项开销很大、需要 GPU 构建、浏览器二进制文件等),那么实际上就必须准备环境。是否为每个项目采用这种方式由用户决定;此技能就是执行这一决策的工具。
prepare_environment 仅用于一致性测试,不用于单元测试
**常见且代价高昂的错误:**误以为
prepare_environment_<lang>是一个通用的“为所有测试脚本预热环境”的步骤,单元测试运行器也依赖它。事实并非如此。
prepare_environment_<lang> 仅用于设置工作文件夹,供 run_conformance_tests_<lang> 随后使用(通过一致性测试脚本的仅激活变体)。它与 run_unittests_<lang> 毫无关系:
- 单元测试运行器是完全自包含的。它会在自己的
<temp>/<lang>_<basename>/工作文件夹中完成暂存,并在每次运行时内联安装自身依赖(pip install -r requirements.txt、npm ci、mvn、cargo fetch等),退出时还会删除该文件夹。 - 单元测试运行器绝不会读取
prepare_environment填充的工作文件夹。即便两个脚本碰巧使用相同的<temp>/<lang>_<basename>/命名约定,它们也会使用相互独立的工作文件夹——每个脚本都会清除并重新构建自己的副本。 - 单元测试运行器不要求事先运行
prepare_environment。用户和 CI 系统经常会直接运行单元测试进行冒烟检查,而不调用一致性测试,这种用法必须继续可行。 - 单元测试运行器没有仅激活变体。
implement-unit-testing-script每次都会生成一个自包含脚本——双变体模式仅适用于一致性测试运行器。
编写 prepare_environment_<lang> 时,严格限定其范围,只处理一致性测试脚本所需的内容。不要加入单元测试运行器需要、但一致性测试不需要的依赖安装;不要暂存单元测试运行器会读取的文件;不要假设单元测试运行器会使用你生成的内容。如果你发现自己正要这样做,请停下——正确做法是让 prepare_environment 保持原样,并由单元测试运行器内联处理自身依赖。
prepare-environment 脚本与其他脚本的区别
此脚本的大部分结构与它的两个同类脚本(run_unittests_<lang> 和 run_conformance_tests_<lang>)相同,但存在以下区别:
- **一个位置参数:
<build_folder>。**没有一致性测试文件夹——那是本脚本的输入,而不是一致性测试脚本的输入。 - **不执行测试。**此脚本只负责暂存和安装/构建,不会运行单元测试或一致性测试。
- **不设“未发现测试”的防护。**原因相同——此处不会运行测试。
- **副作用必须对一致性测试脚本可见。**此脚本执行的所有操作(工作文件夹名称、依赖隔离位置、已安装的构件)都必须与一致性测试脚本预期找到的内容完全一致。请参阅协调契约。
其他内容——工具链检查、构建暂存、依赖隔离、退出代码——都相同。
首先选择 Shell 类型
编写任何内容之前,请先确定脚本必须面向哪种 Shell 类型——这取决于运行此技能的宿主机器,而非语言。请主动检测宿主操作系统;不要默认使用 Bash。
| 宿主操作系统 | 输出 | 检测方法 |
|---|---|---|
| macOS | .sh | uname -s 返回 Darwin |
| Linux(包括 WSL、CI runner) | .sh | uname -s 返回 Linux |
| 原生 Windows(PowerShell、cmd) | .ps1 | $OS 环境变量包含 Windows_NT,或者 uname -s 返回 MINGW* / MSYS* / CYGWIN*(Windows 上的 Git Bash / MSYS2 / Cygwin Shell) |
如果不确定,请运行 uname -s 2>/dev/null || echo "$OS"——检查之前不要询问用户。
如果不同团队成员或 CI runner 会在 macOS/Linux 和 Windows 上使用该项目,请生成此脚本的 .sh 和 .ps1 两个版本——它们是相互对应的机械转换版本,共享退出代码和隔离路径,由编排器(或用户的 CI)在运行时选择正确的版本。相应的 run_conformance_tests_<lang> 脚本也必须成对生成。
如果确实无法判断(例如在无法访问 Shell 的沙盒中运行),请询问用户——但只能在上述检测失败之后。
两种 Shell 类型都适用相同的模式。只有语法不同。
模式
每个 prepare-environment 脚本都必须按以下顺序执行这些步骤:
- 工具链检查。 验证所需的语言运行时/构建工具(以及任何要求的版本)是否已安装。如果没有,打印错误并以代码
69退出。 - 参数验证。 要求提供一个位置参数:
<build_folder>。如果缺少该参数,打印用法说明并以代码69退出。渲染器会将此参数作为绝对路径传入(旧版渲染器传入的是相对路径)——脚本必须同时兼容这两种情况,因此第 3 步中的工作文件夹名称必须从参数的基本名称构造,不能直接使用原始参数。 - 设置工作目录。 将工作文件夹定义为
<temp>/<lang>_<basename>——此路径必须与一致性脚本使用的路径完全相同(Bash 中为/tmp/<lang>_$(basename "$1"),PowerShell 中为Join-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)")。清空该文件夹(rm -rf/Remove-Item -Recurse -Force)并重新创建。此文件夹——且仅限此文件夹——是后续所有写入操作的目标。不要注册清理 trap /finally删除操作——与另外两个同类脚本不同,prepare 会有意在退出时保留已填充的工作文件夹;该文件夹就是一致性脚本要附加的交付物。 - 复制构建内容。 将
<build_folder>($1)中的所有内容递归复制到工作文件夹。完成此步骤后,脚本的其余部分必须将源文件夹($1)视为只读。 - 进入工作目录。
cd/Set-Location到<temp>/<lang>_<basename>。如果失败,以代码69退出。所有后续步骤都在工作文件夹内执行;绝不能写回$1。 - 在
<temp>/<lang>_<basename>内的隔离环境中安装依赖/预构建产物。 设置专属于当前工作文件夹的依赖位置(Python venv 为./.venv,本地./node_modules,项目专用 Maven 仓库为./.m2等),并将所有依赖安装/解析到该位置。对于必须先构建才能运行测试的语言(Java、Rust、Go),还要生成构建产物,并将其放到一致性脚本能够找到的位置——必须在工作文件夹内,绝不能放在$1内或用户的主目录中。绝不能安装到源构建文件夹($1)、用户的全局缓存(~/.m2、系统级pip、~/.cargo、~/.npm等),或<temp>/<lang>_<basename>之外的任何位置。如果任何子步骤失败,以代码69退出(不要传递 Maven/pip/npm 的退出码——未完全准备好的环境本身就是不可恢复的错误)。各语言的具体要求请参见依赖隔离。
到此为止。没有第 7 步。依赖安装完成且(如适用)构建产物已放入本地仓库后,该脚本的工作就结束了——已填充的工作文件夹会有意保留,以供一致性运行器使用。
build 文件夹为只读 — 硬性规则
作为 $1 传入的源 build 文件夹仅供输入。Prepare 会在步骤 4 中读取一次,以填充工作文件夹;此后脚本绝不能:
- 在其中安装依赖(不得在
$1内执行pip install或npm install,不得执行会写入$1的mvn install,不得让 Cargo 构建产物落在$1下), - 在其中写入虚拟环境、
node_modules、.m2、.gocache、.cargo或.pub-cache目录, - 在其中预构建(所有编译输出——
target/、build/、dist/、原生二进制文件、生成的源代码——都必须位于<temp>/<lang>_<basename>内), - 在其中创建日志、缓存或临时文件。
build 文件夹与渲染器(默认位于 plain_modules/<module>/code)以及一致性脚本共享。一致性脚本会通过 if [ ! -d "/tmp/<lang>_$(basename "$1")" ] 检查工作文件夹是否已暂存,并且预期 $1 本身与渲染后立即的状态完全相同。写入 $1 会破坏渲染器对“生成了什么”的判断;如果项目提交了 $1,还会导致 git 状态频繁变化;并且(如果一致性脚本在自身设置期间执行 rm -rf $1),还会悄无声息地删除 Prepare 所做的工作。
将内容暂存到 <temp>/<lang>_<basename> 的目的,就是让源 build 文件夹始终是渲染生成的干净、可复现产物,不会有构建残留落入用户的项目。所有依赖、编译后的类、二进制文件和缓存都必须放在工作文件夹内——因为一致性脚本的仅激活变体正是要连接到这个文件夹。
如果你正准备执行任何 cwd 为 $1 或目标路径以 $1/ 开头的命令,请停下。要么将操作移到 <temp>/<lang>_<basename>,要么就是在做脚本绝不能做的事。
协调约定
这是此技能中最重要的部分。Prepare 环境的脚本若与其对应的一致性脚本对内容放置的位置没有达成一致,那还不如没有 Prepare 脚本——它既浪费时间,又会营造环境已预热、实则正确位置并未预热的假象。
两个脚本必须就以下各项达成一致:
| 内容 | 重要原因 |
|---|---|
工作文件夹路径(<temp>/<lang>_<basename>) | 一致性脚本会 cd 到此文件夹。如果 Prepare 使用了不同的名称,一致性脚本看到的就是空文件夹或刚暂存的文件夹,从而重新执行所有工作。两个脚本必须以相同方式推导路径:系统临时目录 + <lang>_ + $1 的 basename。 |
| 工作文件夹生命周期 | Prepare 会在开始时清空并重新创建该文件夹,并在退出时保留其中的内容。仅激活的一致性脚本会连接到此文件夹,并且绝不删除它——无论成功还是失败都不会删除。只有 Prepare 会在下次运行时重新创建它。任一脚本中的清理 trap 都会破坏这对脚本旨在实现的成本摊销。 |
依赖隔离位置(./.venv、./.m2、./node_modules、./.gocache、./.cargo 等)——相对于工作文件夹 | 如果 Prepare 填充的是 ~/.m2,而一致性脚本读取的是 ./.m2,预热的缓存就不可见。两个脚本都必须使用工作文件夹内项目本地的路径。 |
| 构建产物位置(Java/Rust/Go) | 一致性测试项目依赖构建产物。必须能在一致性脚本所预期的确切位置找到它(例如,Java 产物须安装到同一个项目本地 ./.m2 中)。 |
| 工具链版本 | 如果 Prepare 使用 Java 21 运行,而一致性脚本使用 Java 17 运行,就会在测试时而非 Prepare 阶段出现类文件不兼容。两个脚本都应执行相同的工具链检查。 |
如有疑问,先阅读一致性脚本,并严格遵循其中的假设。
约定
两种 shell 风格共用:
- 退出代码:
69— 不可恢复:缺少参数、缺少工具链、无法进入工作文件夹、依赖安装/构建失败。此处将所有失败都视为不可恢复——prepare 没有“软”失败模式。0— 成功。
- 工作文件夹命名:
<temp>/<lang>_<basename>— 系统临时目录(Bash 中为/tmp,PowerShell 中为[System.IO.Path]::GetTempPath()),加上语言的短标识符(java、python、node、go、rust等)和第一个(也是唯一一个)参数的基本名称($(basename "$1")/Split-Path -Leaf)。绝不要将原始参数嵌入路径——它可能是绝对路径,拼接后会产生错误的重复路径。所有依赖安装、构建输出、缓存和预构建产物都放在此文件夹中。第 4 步之后,脚本不应再触及$1。 - **退出时不清理。**Prepare 会保留已填充内容的工作文件夹——该文件夹就是它的交付物。不要添加
trap … EXIT/finally删除操作;下次 prepare 清空并重新创建该文件夹时会回收它(操作系统最终也会回收临时目录)。 - **日志记录——尽可能详细。**从渲染完成到显示绿色/红色符合性结果期间,操作人员能看到的只有此脚本;prepare 失败时,唯一可供任何人调查的证据就是它的 stdout/stderr。应将脚本视为生产运行手册,而非安静的辅助程序。具体而言:
- 执行每个步骤前都要宣布该步骤,并给出涉及的每个变量的确切值——解析出的源文件夹、工作文件夹、语言、检测到的工具链版本、隔离根目录、依赖清单路径,以及即将运行的安装/预构建命令。仅说“正在安装依赖”毫无用处;“正在从
./requirements.txt将依赖安装到虚拟环境./.venv(Python 3.11.6,路径为/usr/local/bin/python3)”才足以用于排查。 - **执行每个重要命令前都要回显。**在 Bash 中,对脚本主体使用
set -x(或在每次调用前立即执行echo "+ <cmd>");在 PowerShell 中,设置$VerbosePreference = 'Continue',并使用Write-Verbose/Write-Host打印包含参数的每条命令行。 - **打印清晰的分节横幅。**每个模式步骤都应有类似
===== [4/6] Copy build into working folder =====的横幅,方便快速浏览长日志。 - 打印解析后的绝对路径,而不只是相对名称。在另一台机器上查看日志的操作人员需要知道 venv /
.m2/.gocache/.cargo/.pub-cache实际位于何处。 - 在步骤 1 中打印工具链自己的
--version输出(以及其解析出的路径),即使成功也要打印。这是 prepare 在本地成功、却在 CI 中失败时最有用的取证信息。 - 原样输出安装/预构建过程的结果——不要将其管道传到
/dev/null,也不要重定向到工作文件夹内的日志文件。操作人员必须能在脚本自己的输出流中看到每一行依赖解析器输出、每一条编译警告和每一行产物安装信息。移除 Java 参考实现中由VERBOSE控制的包装——prepare 是最慢的阶段,也是取证数据最有价值的阶段。 - 失败时,在以
69退出前打印即将运行的内容——确切命令、工作目录以及相关环境变量(PYTHONPATH、NODE_PATH、GOMODCACHE、CARGO_HOME、MAVEN_OPTS、PUB_CACHE、PATH)。 - 打印最终摘要行,列出语言、工作文件夹、隔离根目录、耗时和退出代码
0,让操作人员清楚知道准备了什么,以及符合性检查会在哪里查找。 详细输出并非噪音——能在自身输出中记录操作过程的脚本,意味着排查只需 30 秒,而不是 30 分钟。绝不要为了简洁而牺牲详细程度。
- 执行每个步骤前都要宣布该步骤,并给出涉及的每个变量的确切值——解析出的源文件夹、工作文件夹、语言、检测到的工具链版本、隔离根目录、依赖清单路径,以及即将运行的安装/预构建命令。仅说“正在安装依赖”毫无用处;“正在从
- 使用
date +%s.%N(Bash)/Get-Date(PowerShell)为依赖设置计时,并在末尾打印耗时行。这是最慢的阶段,也是此脚本存在的全部原因;耗时能告诉你它是否真的节省了时间。根据上述详细日志规则,耗时行是最终摘要的组成部分。
依赖隔离
依赖环境必须位于 $WORKING_FOLDER 内部,这样一致性脚本才能找到它,也能避免其他语言或项目的并发运行相互干扰。请选择该语言最惯用的隔离机制,并确保它与一致性脚本读取的位置一致:
| 语言 | 隔离机制 | 准备命令(在 $WORKING_FOLDER 内运行) |
|---|---|---|
| Python | ./.venv 下的 venv | python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt |
| Node.js | 本地 ./node_modules(默认) | npm ci(首选)或 npm install |
| Java | 项目专用的 Maven 仓库 ./.m2 | mvn -Dmaven.repo.local="$(pwd)/.m2" install -DskipTests(构建并将项目自己的 jar 安装到仓库,以便依赖它的测试项目能够解析该 jar) |
| Go | ./.gocache 下的模块缓存 | GOMODCACHE="$PWD/.gocache" go mod download && GOMODCACHE="$PWD/.gocache" go build ./... |
| Rust | ./.cargo 下的 cargo home | CARGO_HOME="$PWD/.cargo" cargo build --tests(一次性编译依赖和测试) |
| Flutter | ./.pub-cache 下的 pub 缓存 | PUB_CACHE="$PWD/.pub-cache" flutter pub get && flutter precache |
注意:
- 安装/预构建命令中的所有路径都相对于
<temp>/<lang>_<basename>。 这就是为什么脚本会在第 5 步cd到工作文件夹——从这一步起,./.venv、./node_modules、./.m2、./.gocache、./.cargo、./.pub-cache以及任何编译输出(target/、build/、dist/、本机二进制文件)都会位于<temp>/<lang>_<basename>下,而不会位于$1下,也不会位于用户主目录下。 - Java/Rust/Go 必须编译,不能只下载。 如果你只解析元数据,一致性脚本会超时/从头重新编译。请使用
mvn install、cargo build --tests、go build ./...(不要只运行dependency:resolve/cargo fetch/go mod download)。 - Python 和 Node.js 只需安装——它们会在测试时解释执行/即时编译,因此
pip install/npm ci就足够了。 - 始终传入隔离标志/环境变量。 不带
-Dmaven.repo.local的mvn、不带CARGO_HOME的cargo等命令会将文件写入用户主目录,而不是$WORKING_FOLDER。一致性脚本会在错误的位置查找,预热也就白费了——还会污染用户的主目录。 - 将安装失败视为
exit 69。 与会传递测试命令退出码的一致性脚本不同,准备脚本无法判断“用户的测试是否合理地失败了”——这里的任何失败都意味着环境不可用,仅此而已。
Bash 注意事项
- Shebang:
#!/bin/bash。 - 文件命名:
prepare_environment_<lang>.sh,放在assets/中。 - 参数:
$1。 - 设置为可执行:
chmod +x assets/prepare_environment_<lang>.sh。 - 检查
cd是否失败: 使用参考脚本中的cd ... 2>/dev/null+[ $? -ne 0 ]模式。成功日志行要放在失败检查之后,不要放在之前,否则失败时日志会说谎。
PowerShell 注意事项
- 不要使用 shebang。 改为在顶部使用
param([Parameter(Mandatory=$true)][string]$BuildFolder)块。 - 文件命名:
prepare_environment_<lang>.ps1,放在assets/中。 - 退出码: 使用
exit 69等。(PowerShell 与 Bash 一样支持这些退出码。) - 工具链检查: 优先使用
Get-Command <tool> -ErrorAction SilentlyContinue;需要指定版本时,解析工具的--version输出。 - 文件系统操作: 使用
Test-Path、Remove-Item -Recurse -Force、New-Item -ItemType Directory、Copy-Item -Recurse、Set-Location。为路径加引号,以处理其中的空格。 - 不需要
chmod步骤。 如果执行策略可能阻止脚本运行,请向用户说明Set-ExecutionPolicy -Scope CurrentUser RemoteSigned,不要将其写入脚本。
工作流
- 检测宿主操作系统,以选择脚本类型。运行
uname -s 2>/dev/null || echo "$OS",并按照 Pick the Shell First 中的规则执行:Darwin/Linux→.sh,Windows_NT/MINGW*/MSYS*/CYGWIN*→.ps1。如果项目同时面向 macOS/Linux 和 Windows(多操作系统团队或 CI),则计划同时生成.sh和.ps1文件——分别重复步骤 3–8。 - 确认目标语言、依赖清单文件(
pom.xml、requirements.txt/pyproject.toml、package.json、go.mod、Cargo.toml等),并且——至关重要的是——如果已有对应的run_conformance_tests_<lang>脚本,先阅读该脚本,以了解需要复用哪些隔离路径和工具链版本。如有任何不清楚之处,请先询问。 - 阅读 assets/prepare_environment_java.sh,以了解确切的结构。注意:该参考文件仍与上述契约有所不同(见反模式)——应遵循契约,而非其中的缺陷。
- 将六个模式步骤分别转换为目标语言和Shell 对应的命令。工具链检查和依赖安装/构建是特定于语言的部分;其余部分是在 Bash 与 PowerShell 语法之间进行机械转换。
- 从依赖隔离表中选择依赖隔离机制。确认其与对应的
run_conformance_tests_<lang>脚本所使用的路径一致。 - 将新脚本保存到适当的
test_scripts/位置(例如test_scripts/prepare_environment_<lang>.sh/.ps1)。对于 Bash 脚本,运行chmod +x。 - 更新
config.yaml,使其引用新脚本。 添加或更新prepare-environment-script:键,并填写新建脚本的路径(例如,prepare-environment-script: test_scripts/prepare_environment_<lang>.sh)。此步骤为必需项——渲染器需要通过此引用,在运行一致性测试前调用准备脚本。 - 将脚本作为关联资源链接到基础
.plain文件中。 脚本写入磁盘后,必须在项目的基础.plain模块中引用——通常是包含一致性测试策略的***test reqs***模块——以便渲染器将脚本内容视为准备契约的权威依据。使用add-resource技能,在规范的相关部分添加标准 Markdown 链接(例如[prepare environment](test_scripts/prepare_environment_<lang>.sh)),并根据.plain文件的位置解析路径。脚本是单个文本文件,因此直接满足关联资源契约——不要链接test_scripts/目录,不要在规范中转述脚本内容,也不要在行内重复脚本命令。如果多个基础.plain模块引用了准备契约(例如,一个宿主模块及其继承该契约的集成模块),则需从每个需要该契约的模块链接脚本;关联资源应放在拥有测试需求的规范旁边,而非默认放在链条顶端。添加链接后,重新阅读规范,确认链接路径能解析到刚写入的脚本。 - 协调一致性测试脚本(见下一节)。 只要项目中已有匹配的
run_conformance_tests_<lang>脚本,此步骤就为必需项。 - 两个脚本都已就位且
config.yaml已更新后,成对重新阅读:并排打开准备脚本和一致性测试脚本,确认二者的工作目录名称、隔离路径和工具链版本一致。
协调现有的一致性测试脚本
添加 prepare_environment_<lang> 脚本会改变对应 run_conformance_tests_<lang> 脚本的契约——准备脚本现在处理的所有事项,都必须从一致性测试脚本中移除,否则准备脚本的工作会被覆盖(因重新暂存)或重复(因重新安装)。每次使用此技能时都必须执行此协调步骤。
分步操作
- 查找项目中是否已有合规性测试脚本。检查常见位置(
test_scripts/run_conformance_tests_<lang>.sh/.ps1,或项目的config.yaml中conformance-tests-script:键指向的位置)。 - 如果不存在 → 停止。无需协调。 合规性测试脚本稍后会由
implement-conformance-testing-script生成,该脚本已了解仅激活变体。 - 如果存在 → 修改脚本。 找出并移除现在由 prepare 负责的步骤:
| 合规性测试中的步骤 | 如果 prepare 存在,你必须…… |
|---|---|
暂存区块(对工作文件夹执行 rm -rf + mkdir -p + 将 $1/* cp -R 到其中) | 彻底移除。 替换为检查:WORKING_FOLDER="/tmp/<lang>_$(basename "$1")"; if [ ! -d "$WORKING_FOLDER" ]; then echo "Error: build folder missing — run prepare_environment_<lang>.sh first."; exit 69; fi |
安装依赖项/预构建(pip install、mvn install -DskipTests、npm ci、cargo build --tests 等) | 彻底移除。 替换为检查隔离位置是否存在(Python 的 .venv/bin/activate、Java 的 .m2/、Node 的 node_modules/ 等),如果不存在则退出 69。 |
激活步骤(Python source .venv/bin/activate、Java -Dmaven.repo.local=$(pwd)/.m2) | 保留。 否则测试命令无法访问准备好的依赖项。 |
退出时清理工作文件夹(trap 'rm -rf "$WORKING_FOLDER"' EXIT / 在 finally 中执行 Remove-Item) | 彻底移除。 仅激活变体绝不会删除工作文件夹——其生命周期由 prepare 负责。保留该 trap 会在第一次合规性测试运行后清除预热环境,导致之后每次运行都无法通过“缺少已准备环境”检查。 |
| 测试执行 +“未发现测试”检查 + 退出码传递 | 保持不变。 |
- 确认合规性测试脚本的退出码仍符合
implement-conformance-testing-script的要求——新的“缺少已准备环境”检查应退出69(不可恢复的调用错误),“未发现测试”检查仍应退出1,并且测试命令的退出码仍应传递。 - 运行冒烟检查:
prepare_environment_<lang>.sh <build_folder> && run_conformance_tests_<lang>.sh <build_folder> <conformance_tests_folder>应能端到端成功运行。如果 prepare 成功后,合规性测试立即报错“缺少已准备环境”,则两个脚本对工作文件夹路径或隔离位置的约定不一致——请返回协调约定。
何时跳过此协调
- 合规性测试脚本尚不存在。 无需协调。
- 合规性测试脚本中已看不到内联暂存或安装操作。 它之前已按仅激活变体生成——保持原样。
- 用户明确要求 prepare 和合规性测试彼此独立(例如,让合规性测试无需 prepare 即可单独运行)。请在两个脚本的顶部注释中清楚说明,并跳过协调。请注意,这样会失去 prepare 原本要提供的所有加速效果。
反模式
- (严重错误)不要通过此脚本预热单元测试运行器。
prepare_environment_<lang>仅供一致性脚本使用。单元测试运行器(implement-unit-testing-script)始终完全自包含——每次调用时都会一次性完成暂存、安装和运行,无论是否存在准备脚本。不要为了“节省时间”而在prepare_environment中添加单元测试依赖安装步骤;单元测试运行器不会读取你生成的内容,而且这种耦合会破坏准备脚本与一致性脚本之间仅激活的契约。参见上文的prepare_environment仅供一致性测试使用——不适用于单元测试。 - (严重错误)不要在源构建文件夹(
$1)中安装、构建或以其他方式写入内容。 第 4 步之后,作为$1传入的构建文件夹是只读输入。所有安装内容、缓存、构建产物(target/、build/、dist/、原生二进制文件、生成的源文件)、日志和临时文件都必须放入<temp>/<lang>_<basename>。这意味着绝不能以$1作为cwd或目标运行pip install、npm install、mvn install、cargo build或go build;绝不能让 venv /node_modules/.m2/.gocache/.cargo/.pub-cache目录出现在$1中;也绝不能在$1/下的任何路径生成预构建产物。将内容暂存到系统临时目录的核心目的,是让构建文件夹保持为渲染器生成的干净产物;写入其中会破坏渲染器所见的状态,导致 git 状态频繁变动,而且如果一致性脚本自行重新暂存$1,这些内容可能会被悄无声息地删除。 - (严重错误)不要通过拼接原始构建文件夹参数来构造路径。 当前的渲染器会将
$1作为绝对路径传入。类似WORKING_FOLDER=.tmp/$1或WORKING_FOLDER=<lang>_$1的写法会悄悄生成重复路径(/project//abs/path/...)——典型症状是出现“文件夹不存在”或ImportError: Start directory is not importable错误,错误指向的路径中项目根目录重复出现。始终使用$(basename "$1")/Split-Path -Leaf。 - (严重错误)不要添加在退出时删除工作文件夹的清理陷阱。 填充好的工作文件夹就是此脚本的交付内容——仅激活的一致性运行器会在每次渲染时多次使用它。退出时删除该文件夹,会导致每次一致性运行都因“准备好的环境缺失”而失败。(单元测试脚本和安装内联的一致性脚本会自行清理;准备脚本和仅激活的一致性脚本则不会。)
- 不要写入用户的全局依赖缓存(
~/.m2、系统范围的pip、~/.cargo、~/.npm等)。一致性脚本会从项目本地缓存读取内容;写入全局缓存对它不可见,还会污染用户的主目录。 - 不要使用与一致性脚本不同的工作文件夹名称。 两者必须完全一致。如果更改其中一个,也要更改另一个。
- 不要运行测试——无论是单元测试、一致性测试还是冒烟测试都不行。准备脚本的职责是“设置环境”;运行测试属于其同级脚本的职责。
- 不要传递
mvn/pip/npm的非69退出代码。 安装失败意味着环境不可用。遇到任何失败都应使用exit 69,这样编排器才能区分“准备失败”和“测试失败”。 - 即使“所有人都已安装”,也不要跳过工具链检查——调用系统依靠退出代码
69检测运行时是否缺失,而且准备脚本通常是运行的第一个脚本,因此在此处报告缺少 JDK / Python / Node 的成本最低。 - 不要在检查
cd是否成功之前打印“已移至……”这一行。 参考脚本就是这样做的,但失败时会输出误导信息。应将日志放在检查之后,或者改为打印“正在尝试进入……”。 - 不要原地复用源文件夹。 始终先将其复制到
<temp>/<lang>_<basename>;一致性脚本依赖这种隔离。 - 不要更改 Bash 与 PowerShell 版本之间的退出代码约定。 同一语言的
.sh和.ps1在相同失败情况下必须使用相同的退出代码。 - 不要编写跨 Shell 混合脚本(例如,检测 PowerShell 的
.sh脚本,或检测 Bash 的.ps1脚本)。每种 Shell 都应提供一个使用相应扩展名的脚本。 - 不要忘记记录安装耗时。 没有耗时日志,就无法判断准备脚本是否真的节省了实际运行时间,还是只是执行了一遍一致性脚本本来会内联完成的相同工作。
- 生成准备脚本后,不要放任已有的
run_conformance_tests_<lang>脚本不变。 如果一致性脚本仍会自行暂存和安装,那么准备脚本完成的工作就会在每次运行时被清除(通过rm -rf),或重复执行(通过重新安装)——这会完全抵消此技能的目的。务必执行协调现有的一致性脚本步骤。 - 不要未经检查主机操作系统就默认使用 Bash。 在原生 Windows(Git Bash / WSL 之外)上,
.sh脚本甚至无法运行;而在 macOS/Linux 上,.ps1脚本同样毫无用处。选择文件扩展名之前,务必先执行先选择 Shell中的检测。如果项目支持两者,就同时生成两个文件——并记得也要协调两种版本中对应的一致性脚本。 - 不要忘记更新
config.yaml。 创建准备环境脚本后,务必在config.yaml中添加或更新prepare-environment-script:键,以引用新脚本。如果缺少此项,渲染器就不知道应在运行一致性测试前调用准备脚本,整个优化也会被跳过。 - 不要忘记在基础
.plain文件中将脚本链接为资源。 如果脚本只在config.yaml中被引用,它对规范契约不可见——渲染器会将prepare-environment-script:视为构建系统指针,而不是模型读取的测试需求契约的一部分。使用add-resource技能,从拥有一致性 / 准备测试需求的.plain模块添加指向该脚本的 Markdown 链接(参见工作流第 8 步)。如果省略此链接资源,模型作者和审查者就无法看到它必须满足的实际环境准备契约,却要编写和审查一致性测试代码——因此也无法推断一致性测试必须激活哪些隔离路径。 - 不要编写过于简略的脚本。 静默执行的步骤、安装 / 预构建输出中的
>/dev/null重定向、缺少--version输出、缺失的失败提示横幅,以及缺少最终摘要行,都会让脚本比它准备的代码更难调试。请严格遵循约定下的日志记录——尽可能详尽规则:每个步骤都要说明正在执行的操作,每条命令都要回显,每次失败都要打印即将运行的命令及其位置,最终摘要要列出语言、工作文件夹、隔离根目录和耗时。