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:
2026-06-23 12:09:49 +08:00
parent c6b4d439ac
commit 9590e15236
12 changed files with 809 additions and 18 deletions
+13 -11
View File
@@ -35,6 +35,7 @@
//! VirtualPath)`, and a diagnostic span is a `DiagSpan` (not a bare `Span`).
mod diag;
mod embedded;
mod manifest;
mod world;
@@ -68,8 +69,9 @@ pub struct Engine {
impl Engine {
/// Construct an engine, discovering system + embedded fonts. The render
/// package directory is taken from the `CPH_RENDER_DIR` environment variable
/// when set, otherwise defaults to `<repo>/render` resolved relative to this
/// crate (`CARGO_MANIFEST_DIR/../../render`).
/// when set, otherwise the **embedded** render package is extracted to a
/// per-user cache dir and used (see [`default_render_dir`]). This keeps an
/// installed `cph` self-contained — no source checkout required at runtime.
pub fn new() -> Self {
Self::with_render_dir(default_render_dir())
}
@@ -234,14 +236,14 @@ fn map_all(world: &LessonWorld, diags: &[typst::diag::SourceDiagnostic]) -> Vec<
.collect()
}
/// `<crate>/../../render` — the repo's render package, used when `CPH_RENDER_DIR`
/// is unset.
/// The render-package directory the engine resolves `@local/cph-render` (and the
/// vendored `@preview/*`) from when no explicit dir is given.
///
/// Honors `CPH_RENDER_DIR` first (dev override → live repo `render/`); otherwise
/// returns a per-user cache dir holding the **embedded** render tree, extracted
/// on demand (see [`embedded`]). This is what makes a `cargo install`-ed `cph`
/// self-contained: it no longer depends on a source checkout living at a
/// compile-time path.
fn default_render_dir() -> PathBuf {
if let Ok(dir) = std::env::var("CPH_RENDER_DIR") {
return PathBuf::from(dir);
}
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("render")
embedded::resolve_render_dir()
}