summaryrefslogtreecommitdiff
path: root/build.rs
blob: 2276f93bb0a6be37838a873f44e8af56766e0659 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! 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<Vec<PathBuf>> {
    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<Item = &'a Path>) -> 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}");
    if let Ok(value) = std::env::var(format!("SUBPLOT_{var}")) {
        println!("cargo:rustc-env=BUILTIN_{var}={value}",);
    } else {
        println!("cargo:rustc-env=BUILTIN_{var}={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");
}