//! Subplot build script. //! //! This code is run as part of `cargo build` and is used to walk the `share/` //! directory and include the contents therein as embedded resources in the //! subplot binary as well as a few other things. //! //! https://doc.rust-lang.org/cargo/reference/build-scripts.html //! //! The output of this script is some directives to Cargo as to when to rerun //! the build script for subplot (by watching the share/ tree) and also a file //! which is included directly as the `EMBEDDED_FILES` constant in the //! `src/resources.rs` file. use anyhow::{anyhow, Result}; use std::io::Write; use std::path::{Component, Path, PathBuf}; use walkdir::WalkDir; /// Decide if a path in `share/` should be ignored. /// /// We ignore paths which contain any component which meets any /// of the following criteria: /// /// 1. Starting with a `.` /// 2. Ending with `~` or `.bak` /// 3. being called `__pycache__` /// 4. Not being valid unicode /// /// The last item is important because the inputs to subplot are all /// utf-8 encoded files, so we want to be sure any resources can be referred /// to, which means they must be unicode compliant too. fn path_ignore(path: &Path) -> bool { // We have a number of rules for ignoring any given path. path.components().any(|component| { match component { Component::Prefix(_) => true, Component::RootDir => true, Component::CurDir => true, Component::ParentDir => true, Component::Normal(part) => { if let Some(part) = part.to_str() { // We don't want dot files, tilde files, bak files // or the python cache. Add other things here if // there's stuff ending up in the resources tree // which we don't want. part.starts_with('.') || part.ends_with('~') || part.ends_with(".bak") || part == "__pycache__" } else { // We don't want any non-unicode paths true } } } }) } /// Gather the `share/` files for inclusion in the binary. /// /// This walks the `share/` tree in the source code and for any files therein, /// which are not ignored by [`path_ignore()`] we add them to the return list. fn gather_share_files() -> Result> { let base_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("share"); println!("cargo:rerun-if-changed={}", base_path.display()); let mut ret = Vec::new(); for entry in WalkDir::new(&base_path) { let entry = entry?; let entry_path = entry.path().strip_prefix(&base_path)?; if entry_path.components().count() == 0 || path_ignore(entry_path) { continue; } // If this path part is a directory, we want to watch it but we don't // represent directories in the resources tree if entry.file_type().is_dir() { println!("cargo:rerun-if-changed={}", entry.path().display()); continue; } // If this is a symlink we need cargo to watch the target of the link // even though we'll treat it as a basic file when we embed it if entry.file_type().is_symlink() { let target = std::fs::read_link(entry.path())?; println!("cargo:rerun-if-changed={}", target.display()); } println!("cargo:rerun-if-changed={}", entry.path().display()); ret.push(entry_path.into()); } if ret.is_empty() { return Err(anyhow!( "Unable to find any resources in `{}`", base_path.display() )); } Ok(ret) } /// Write out the resource file for inclusion into subplot. /// /// This takes the list of files generated by [`gather_share_files()`] and /// creates a source file which can be included into subplot which causes all /// those files and their contents to be embedded into the subplot binary. fn write_out_resource_file<'a>(paths: impl Iterator) -> Result<()> { let mut out_path: PathBuf = std::env::var("OUT_DIR")?.into(); out_path.push("embedded_files.rs"); let mut fh = std::fs::File::create(out_path)?; writeln!(fh, "&[")?; for entry in paths { let sharepath = Path::new("/share").join(entry); writeln!( fh, r#"({:?}, include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), {:?}))),"#, entry.display(), sharepath.display() )?; } writeln!(fh, "]")?; fh.flush()?; Ok(()) } /// Adopt an environment variable into the build. /// /// This entails watching `SUBPLOT_{var}` and setting build environment /// to contain `BUILTIN_{var}` to either the env value, or `{def}` if not /// provided. fn adopt_env_var(var: &str, def: &str) { println!("cargo:rerun-if-env-changed=SUBPLOT_{var}", var = var); if let Ok(value) = std::env::var(format!("SUBPLOT_{var}", var = var)) { println!( "cargo:rustc-env=BUILTIN_{var}={value}", var = var, value = value ); } else { println!("cargo:rustc-env=BUILTIN_{var}={def}", var = var, def = def); } } fn main() { println!("cargo:rerun-if-env-changed=DEB_BUILD_OPTIONS"); let paths = gather_share_files().expect("Unable to scan the share tree"); write_out_resource_file(paths.iter().map(PathBuf::as_path)) .expect("Unable to write the resource file out"); adopt_env_var("DOT_PATH", "dot"); adopt_env_var("PLANTUML_JAR_PATH", "/usr/share/plantuml/plantuml.jar"); adopt_env_var("JAVA_PATH", "java"); }