forked from EduCraft/curriculum-project-hub
9590e15236
三件事,服务于"把恒一小奥 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>
65 lines
2.8 KiB
Rust
65 lines
2.8 KiB
Rust
//! Build script: stage a clean copy of the repo `render/` package into `OUT_DIR`
|
|
//! so `src/embedded.rs` can `include_dir!` it without tripping over the dev-only
|
|
//! self-referential symlink at `render/vendor/local-packages/`.
|
|
//!
|
|
//! Why this exists: `include_dir!` walks a directory at compile time and follows
|
|
//! symlinks. The repo keeps `render/vendor/local-packages/local/cph-render/0.1.0`
|
|
//! as a symlink pointing back at `render/` (a gitignored dev convenience for
|
|
//! typst's local-package resolution). Embedding `render/` directly would make
|
|
//! `include_dir!` recurse into that link forever. The runtime never needs that
|
|
//! link (the engine resolves `@local/cph-render` from the render dir directly,
|
|
//! and `@preview/*` from `vendor/typst-packages/`), so we copy everything EXCEPT
|
|
//! `vendor/local-packages/` into `OUT_DIR/render` and embed that staged tree.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
fn main() {
|
|
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let render_src = manifest_dir.join("..").join("..").join("render");
|
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
|
let staged = out_dir.join("render");
|
|
|
|
// Rebuild the staged tree from scratch each run (cheap — ~140 KiB).
|
|
if staged.exists() {
|
|
let _ = std::fs::remove_dir_all(&staged);
|
|
}
|
|
copy_tree(&render_src, &staged).expect("stage render/ for embedding");
|
|
|
|
// Re-run when the render package changes.
|
|
println!("cargo:rerun-if-changed={}", render_src.display());
|
|
// Expose the staged path to the crate via an env var include_dir! can read.
|
|
println!("cargo:rustc-env=CPH_STAGED_RENDER_DIR={}", staged.display());
|
|
}
|
|
|
|
/// Recursively copy `src` → `dst`, skipping the dev-only `vendor/local-packages`
|
|
/// subtree and never following symlinks into it.
|
|
fn copy_tree(src: &Path, dst: &Path) -> std::io::Result<()> {
|
|
std::fs::create_dir_all(dst)?;
|
|
for entry in std::fs::read_dir(src)? {
|
|
let entry = entry?;
|
|
let name = entry.file_name();
|
|
let from = entry.path();
|
|
let to = dst.join(&name);
|
|
|
|
// Skip the self-referential dev symlink tree.
|
|
if from.ends_with("vendor/local-packages") || name == *"local-packages" {
|
|
continue;
|
|
}
|
|
|
|
// Resolve symlinks/metadata without following into a cycle: use
|
|
// `symlink_metadata` so a symlinked dir is treated by its link, not its
|
|
// (recursive) target. We only copy real files and real directories.
|
|
let meta = std::fs::symlink_metadata(&from)?;
|
|
let ft = meta.file_type();
|
|
if ft.is_symlink() {
|
|
// Skip any symlink (the only one in the tree is the dev convenience).
|
|
continue;
|
|
} else if ft.is_dir() {
|
|
copy_tree(&from, &to)?;
|
|
} else if ft.is_file() {
|
|
std::fs::copy(&from, &to)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|