Skip to main content

patchouly_build/
lib.rs

1pub mod extract;
2pub mod generate;
3mod structs;
4
5use std::{
6    error::Error,
7    fs,
8    path::{Path, PathBuf},
9    process::Command,
10};
11
12/// Compiles and extracts stencils from a stencils crate
13///
14/// ## Usage
15///
16/// Note that the current implementation hardcodes a lot of things:
17/// - The source directory of the stencils crate should be `src/`.
18/// - The internal `.rlib` format used by Rust is assumed to be
19///   located under some certain directories, named `libXXX.rlib`
20///   and is an object file.
21/// - The output directory of `cargo rustc --release` is assumed
22///   to be under `$CARGO_TARGET_DIR/release/`.
23///
24/// If things above are met, this function will probably work by
25/// compiling and extracting stencils into a `$OUT_DIR/{}_stencils.rs`
26/// file, where `{}` is the lowercase of the name specified in your
27/// `setup_stencils!("...");` macro call in the stencils crate.
28/// For example, `setup_stencils!("Calc");` will generate a file
29/// named `calc_stencils.rs`, which you may include with:
30///
31/// ```ignore
32/// include!(concat!(env!("OUT_DIR"), "/calc_stencils.rs"));
33/// ```
34pub fn extract(rel_stencils_dir: &str) -> Result<(), Box<dyn Error>> {
35    let out_dir = std::env::var("OUT_DIR")?;
36    let out_path = Path::new(&out_dir).canonicalize()?;
37    // We need a different target dir to prevent deadlock
38    let target_dir = find_target_dir(&out_path)?.join("patchouly");
39    let current_dir = std::env::current_dir()?;
40    let stencils_dir = current_dir
41        .as_path()
42        .parent()
43        .ok_or("expected to be in a workspace")?
44        .join(rel_stencils_dir)
45        .canonicalize()?;
46    assert!(
47        stencils_dir.exists(),
48        "stencils dir {} does not exist",
49        stencils_dir.display()
50    );
51
52    // compile
53    let status = Command::new("cargo")
54        .current_dir(&stencils_dir)
55        .args([
56            "rustc",
57            "--release",
58            "--lib",
59            "--target-dir",
60            target_dir.to_str().unwrap(),
61            "--",
62            "-C",
63            "relocation-model=static",
64        ])
65        .status()?;
66    if !status.success() {
67        return Err("failed to compile stencils crate".into());
68    }
69
70    println!("cargo:rerun-if-changed={}/src", stencils_dir.display());
71
72    let rlib = target_dir.join("release").join(dir_to_libname(&stencils_dir)?);
73    let extraction = extract::extract(&rlib)?;
74    generate::generate(extraction, &out_path)?;
75
76    Ok(())
77}
78
79fn dir_to_libname(rel: &Path) -> Result<String, Box<dyn Error>> {
80    let manifest = fs::read_to_string(rel.join("Cargo.toml"))?;
81    let name = manifest
82        .lines()
83        .map(str::trim)
84        .find_map(|line| {
85            line.strip_prefix("name = ")
86                .map(|value| value.trim_matches('"'))
87        })
88        .ok_or("package name not found in stencils Cargo.toml")?;
89    Ok(format!("lib{}.rlib", name.replace("-", "_")))
90}
91
92fn find_target_dir(out_dir: &Path) -> Result<PathBuf, Box<dyn Error>> {
93    let profile = std::env::var("PROFILE")?;
94    for parent in out_dir.ancestors() {
95        if parent.ends_with(&profile) {
96            return Ok(parent.parent().ok_or("failed to find target dir")?.to_path_buf());
97        }
98    }
99    Err("failed to find target dir".into())
100}