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
147
148
149
use std::env;
use std::path::PathBuf;
use std::process::Command;

const BUILD_ERROR_MSG: &str = "Unable to build botan.";
const SRC_DIR_ERROR_MSG: &str = "Unable to find the source directory.";
const SRC_DIR: &str = "botan";
const INCLUDE_DIR: &str = "build/include/botan";

macro_rules! pathbuf_to_string {
    ($s: ident) => {
        $s.to_str().expect(BUILD_ERROR_MSG).to_string()
    };
}

fn env_name_for(opt: &'static str) -> String {
    assert!(opt[0..2] == *"--");
    let to_var = opt[2..].to_uppercase().replace('-', "_");
    format!("BOTAN_CONFIGURE_{to_var}")
}

fn configure(build_dir: &str) {
    let mut configure = Command::new("python3");
    configure.arg("configure.py");
    configure.arg(format!("--with-build-dir={build_dir}"));
    configure.arg("--build-targets=static");
    configure.arg("--without-documentation");
    configure.arg("--no-install-python-module");
    configure.arg("--distribution-info=https://crates.io/crates/botan-src");
    #[cfg(debug_assertions)]
    configure.arg("--with-debug-info");

    // On Windows we require the amalgamation, to work around the fact that
    // otherwise the linker command lines become too long for Windows
    #[cfg(target_os = "windows")]
    configure.arg("--amalgamation");

    let args = [
        "--os",
        "--cpu",
        "--compiler-cache",
        "--cc",
        "--cc-min-version",
        "--cc-bin",
        "--cc-abi-flags",
        "--cxxflags",
        "--extra-cxxflags",
        "--ldflags",
        "--ar-command",
        "--ar-options",
        "--msvc-runtime",
        "--with-endian",
        "--with-os-features",
        "--without-os-features",
        "--system-cert-bundle",
        "--with-local-config",
        "--boost-library-name",
        "--module-policy",
        "--enable-modules",
        "--disable-modules",
        "--library-suffix",
        "--prefix",
        "--libdir",
        "--includedir",
        "--link-method",
    ];

    let flags = [
        "--optimize-for-size",
        "--no-optimizations",
        "--amalgamation",
        "--minimized-build",
        "--with-openssl",
        "--with-commoncrypto",
        "--with-sqlite3",
    ];

    for arg_name in &args {
        let env_name = env_name_for(arg_name);
        if let Ok(arg_val) = env::var(env_name) {
            let arg = format!("{arg_name}={arg_val}");
            configure.arg(arg);
        }
    }

    for flag_name in &flags {
        let env_name = env_name_for(flag_name);
        if env::var(env_name).is_ok() {
            configure.arg(flag_name);
        }
    }

    let status = configure
        .spawn()
        .expect(BUILD_ERROR_MSG)
        .wait()
        .expect(BUILD_ERROR_MSG);
    if !status.success() {
        panic!("configure terminated unsuccessfully");
    }
}

#[cfg(target_os = "windows")]
fn hack_the_makefile(path: &str) {
    let mut contents = std::fs::read_to_string(path).expect("Unable to read makefile contents");

    contents = contents.replace("/botan.lib", "\\botan.lib");

    std::fs::write(path, contents).expect("WRITE FAILED");
}

fn make(build_dir: &str) {
    let mut cmd = Command::new("make");
    // Set MAKEFLAGS to the content of CARGO_MAKEFLAGS
    // to give jobserver (parallel builds) support to the
    // spawned sub-make.
    if let Ok(val) = env::var("CARGO_MAKEFLAGS") {
        cmd.env("MAKEFLAGS", val);
    } else {
        eprintln!("Can't set MAKEFLAGS as CARGO_MAKEFLAGS couldn't be read");
    }

    #[cfg(target_os = "windows")]
    hack_the_makefile(&format!("{build_dir}/Makefile"));

    let status = cmd
        .arg("-f")
        .arg(format!("{build_dir}/Makefile"))
        .arg("libs")
        .spawn()
        .expect(BUILD_ERROR_MSG)
        .wait()
        .expect(BUILD_ERROR_MSG);
    if !status.success() {
        panic!("make terminated unsuccessfully");
    }
}

pub fn build() -> (String, String) {
    let src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SRC_DIR);
    let build_dir = env::var_os("OUT_DIR").map_or(src_dir.to_owned(), PathBuf::from);
    let build_dir = build_dir.join("botan");
    let include_dir = build_dir.join(INCLUDE_DIR);
    let build_dir = pathbuf_to_string!(build_dir);
    env::set_current_dir(&src_dir).expect(SRC_DIR_ERROR_MSG);
    configure(&build_dir);
    make(&build_dir);
    (build_dir, pathbuf_to_string!(include_dir))
}