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};
11pub use subplot::SubplotError;
12use subplot::{get_basedir_from, SCENARIO_FILTER_EVERYTHING};
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#[deprecated(
36    since = "0.13.0",
37    note = "The `subplot-build` crate has been replaced by a macro in `subplotlib` and will not be updated past 0.13 of Subplot"
38)]
39#[instrument(level = "trace")]
40pub fn codegen<P>(filename: P) -> Result<(), SubplotError>
41where
42    P: AsRef<Path> + Debug,
43{
44    let filename = filename.as_ref();
45    match _codegen(filename) {
46        Ok(()) => Ok(()),
47        Err(e) => {
48            eprintln!(
49                "\n\n\nsubplot_build::codegen({}) failed: {e}",
50                filename.display(),
51            );
52            let mut es = e.source();
53            while let Some(source) = es {
54                eprintln!("caused by: {source}");
55                es = source.source();
56            }
57            eprintln!("\n\n");
58            Err(e)
59        }
60    }
61}
62
63fn _codegen(filename: &Path) -> Result<(), SubplotError> {
64    let span = span!(Level::TRACE, "codegen_buildrs");
65    let _enter = span.enter();
66
67    event!(Level::TRACE, "Generating code in build.rs");
68
69    // Decide the name of the generated test program.
70    let out_dir = var_os("OUT_DIR").expect("OUT_DIR is not defined in the environment");
71    let out_dir = Path::new(&out_dir);
72    let test_rs =
73        buildrs_output(out_dir, filename, "rs").expect("could not create output filename");
74
75    // Generate test program.
76    let output = subplot::codegen(
77        filename,
78        &test_rs,
79        Some("rust"),
80        &SCENARIO_FILTER_EVERYTHING,
81    )?;
82
83    // Write instructions for Cargo to check if build scripts needs
84    // re-running.
85    let base_path = get_basedir_from(filename);
86    let meta = output.doc.meta();
87    for filename in meta.markdown_filenames() {
88        buildrs_deps(&base_path, Some(filename.as_path()));
89    }
90    buildrs_deps(&base_path, meta.bindings_filenames());
91    let docimpl = output
92        .doc
93        .meta()
94        .document_impl("rust")
95        .expect("We managed to codegen rust, yet the spec is missing?");
96    buildrs_deps(&base_path, docimpl.functions_filenames());
97    buildrs_deps(&base_path, Some(filename));
98
99    event!(Level::TRACE, "Finished generating code");
100    Ok(())
101}
102
103fn buildrs_deps<'a>(base_path: &Path, filenames: impl IntoIterator<Item = &'a Path>) {
104    for filename in filenames {
105        let filename = base_path.join(filename);
106        if filename.exists() {
107            println!("cargo:rerun-if-changed={}", filename.display());
108        }
109    }
110}
111
112fn buildrs_output(dir: &Path, filename: &Path, new_extension: &str) -> Option<PathBuf> {
113    if let Some(basename) = filename.file_name() {
114        let basename = Path::new(basename);
115        if let Some(stem) = basename.file_stem() {
116            let stem = Path::new(stem);
117            return Some(dir.join(stem.with_extension(new_extension)));
118        }
119    }
120    None
121}