subplot_build/
lib.rs

1//! Subplot test program building from `build.rs`
2//!
3//! This crate provides the Subplot code generation facility in a way
4//! that's meant to be easy to use from another project's `build.rs`
5//! module.
6
7use std::env::var_os;
8use std::error::Error;
9use std::fmt::Debug;
10use std::path::{Path, PathBuf};
11use subplot::get_basedir_from;
12pub use subplot::SubplotError;
13use tracing::{event, instrument, span, Level};
14
15/// Generate code for one document, inside `build.rs`.
16///
17/// The output files will be written to the directory specified in the
18/// OUT_DIR environment variable. That variable is set by Cargo, when
19/// a crate is built.
20///
21/// Also emit instructions for Cargo so it knows to re-run `build.rs`
22/// whenever the input subplot or any of the bindings or functions
23/// files it refers to changes. See
24/// <https://doc.rust-lang.org/cargo/reference/build-scripts.html> for
25/// details.
26///
27/// ```
28/// use subplot_build::codegen;
29/// # let dir = tempfile::tempdir().unwrap();
30/// # std::env::set_var("OUT_DIR", dir.path());
31///
32/// codegen("foo.md").ok(); // ignoring error to keep example short
33/// # dir.close().unwrap()
34/// ```
35#[instrument(level = "trace")]
36pub fn codegen<P>(filename: P) -> Result<(), SubplotError>
37where
38    P: AsRef<Path> + Debug,
39{
40    let filename = filename.as_ref();
41    match _codegen(filename) {
42        Ok(()) => Ok(()),
43        Err(e) => {
44            eprintln!(
45                "\n\n\nsubplot_build::codegen({}) failed: {e}",
46                filename.display(),
47            );
48            let mut es = e.source();
49            while let Some(source) = es {
50                eprintln!("caused by: {source}");
51                es = source.source();
52            }
53            eprintln!("\n\n");
54            Err(e)
55        }
56    }
57}
58
59fn _codegen(filename: &Path) -> Result<(), SubplotError> {
60    let span = span!(Level::TRACE, "codegen_buildrs");
61    let _enter = span.enter();
62
63    event!(Level::TRACE, "Generating code in build.rs");
64
65    // Decide the name of the generated test program.
66    let out_dir = var_os("OUT_DIR").expect("OUT_DIR is not defined in the environment");
67    let out_dir = Path::new(&out_dir);
68    let test_rs =
69        buildrs_output(out_dir, filename, "rs").expect("could not create output filename");
70
71    // Generate test program.
72    let output = subplot::codegen(filename, &test_rs, Some("rust"))?;
73
74    // Write instructions for Cargo to check if build scripts needs
75    // re-running.
76    let base_path = get_basedir_from(filename);
77    let meta = output.doc.meta();
78    for filename in meta.markdown_filenames() {
79        buildrs_deps(&base_path, Some(filename.as_path()));
80    }
81    buildrs_deps(&base_path, meta.bindings_filenames());
82    let docimpl = output
83        .doc
84        .meta()
85        .document_impl("rust")
86        .expect("We managed to codegen rust, yet the spec is missing?");
87    buildrs_deps(&base_path, docimpl.functions_filenames());
88    buildrs_deps(&base_path, Some(filename));
89
90    event!(Level::TRACE, "Finished generating code");
91    Ok(())
92}
93
94fn buildrs_deps<'a>(base_path: &Path, filenames: impl IntoIterator<Item = &'a Path>) {
95    for filename in filenames {
96        let filename = base_path.join(filename);
97        if filename.exists() {
98            println!("cargo:rerun-if-changed={}", filename.display());
99        }
100    }
101}
102
103fn buildrs_output(dir: &Path, filename: &Path, new_extension: &str) -> Option<PathBuf> {
104    if let Some(basename) = filename.file_name() {
105        let basename = Path::new(basename);
106        if let Some(stem) = basename.file_stem() {
107            let stem = Path::new(stem);
108            return Some(dir.join(stem.with_extension(new_extension)));
109        }
110    }
111    None
112}