forked from EduCraft/curriculum-project-hub
feat(checker+typst): 嵌入 render 使 cph 可 cargo install;实现 shell step 执行器(ADR-0013)
三件事,服务于"把恒一小奥 KenKen 课作者化为合法 native 工程文件"这条主线
(工程文件本身在 repo 外的 sibling playground,不入此提交):
1. cph-typst: 把 render/ 编译期嵌入二进制,`cargo install` 后开箱即用
- build.rs 把 render/ 拷进 OUT_DIR,剔除 dev 自指符号链接 vendor/local-packages
(否则 include_dir! 无限递归)
- embedded.rs 用 include_dir! 嵌入,运行时按版本解压到 per-user cache dir
(CPH_RENDER_DIR 仍优先作 dev override)
- default_render_dir 不再用编译期 CARGO_MANIFEST_DIR 写死路径
2. cph-check + cph-cli: 实现 Step::Shell 执行器(此前仅占位)
- run_shell_target: 跑 check 门控 → 以工程根为 cwd 执行 shell step,首非零退出即停
- target_is_shell + CLI run_shell_build 路由;`check` 不跑 shell(结构校验只看 lesson)
- compile_targets 只保留含 TypstCompile 的 target,纯 shell target 不走 typst 引擎
- 语义:失败属 build-过程错误,不入 6 类诊断(taxonomy 保持 6,显式拒绝加 ShellStep 码)
3. spec + ADR: 在 Export/Render.lean 钉 shell step 执行语义 prose;
ADR-0013(已实现)、ADR-0014(md/HTML 导出后端方向,Proposed/设计先行,未实现)
测试:全 workspace 42 passed(新增 4 个 shell 执行器测试);lake build 25 jobs 绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,14 @@ fn run_build(
|
||||
target: &str,
|
||||
out: Option<PathBuf>,
|
||||
) -> ExitCode {
|
||||
// A target whose steps are shell commands (a tool-generated asset bundle,
|
||||
// ADR-0009 category (b) — e.g. KenKen interactives via `kendoku`) is run by
|
||||
// executing those commands, not by compiling a typst template. Detect that
|
||||
// shape up front and route accordingly.
|
||||
if cph_check::target_is_shell(path, target) {
|
||||
return run_shell_build(path, engine, target);
|
||||
}
|
||||
|
||||
let out_path = out.unwrap_or_else(|| path.join("build").join(format!("{target}.pdf")));
|
||||
|
||||
let (pdf, report) = cph_check::build(path, engine, target);
|
||||
@@ -131,3 +139,46 @@ fn run_build(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a shell-step target: execute its declared commands in the engineering
|
||||
/// root. Arbitrary command execution is **opt-in** — it only happens on an
|
||||
/// explicit `cph build --target <name>`, and each command is printed before it
|
||||
/// runs so the user sees exactly what is executed.
|
||||
fn run_shell_build(path: &std::path::Path, engine: &Engine, target: &str) -> ExitCode {
|
||||
eprintln!("running shell-step target '{target}' (commands execute in {})", path.display());
|
||||
let report = cph_check::run_shell_target(path, engine, target);
|
||||
print_diagnostics(&report.check);
|
||||
|
||||
if report.outcomes.is_empty() && !report.ok {
|
||||
// Refused before running anything (check errors / unknown target / no
|
||||
// shell steps): the diagnostics above explain why.
|
||||
if report.check.has_errors() {
|
||||
eprintln!("build refused: {} errors (fix the lesson first)", report.check.error_count());
|
||||
} else {
|
||||
eprintln!("build failed: target '{target}' has no shell steps to run");
|
||||
}
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
for outcome in &report.outcomes {
|
||||
eprintln!("$ {}", outcome.run);
|
||||
if !outcome.stdout.trim().is_empty() {
|
||||
print!("{}", outcome.stdout);
|
||||
}
|
||||
if !outcome.ok() {
|
||||
eprint!("{}", outcome.stderr);
|
||||
eprintln!(
|
||||
"step failed (exit {})",
|
||||
outcome.status.map(|c| c.to_string()).unwrap_or_else(|| "signal".into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if report.ok {
|
||||
println!("shell-step target '{target}' completed: {} step(s) ok", report.outcomes.len());
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
eprintln!("shell-step target '{target}' failed");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user