Skip to main content

redskull_lib/
build_script.rs

1//! Build script generation for Rust conda recipes.
2//! Supports both inline `script:` (simple recipes) and `build.sh` (complex recipes).
3
4pub struct BuildScript {
5    use_locked: bool,
6    use_cbl: bool,
7    use_bindgen: bool,
8    native_deps: bool,
9    workspace_path: Option<String>,
10    force_build_sh: bool,
11    cargo_net_git_fetch: bool,
12    strip_binaries: bool,
13    binary_names: Vec<String>,
14}
15
16impl Default for BuildScript {
17    fn default() -> Self {
18        Self {
19            use_locked: true,
20            use_cbl: false,
21            use_bindgen: false,
22            native_deps: false,
23            workspace_path: None,
24            force_build_sh: false,
25            cargo_net_git_fetch: false,
26            strip_binaries: false,
27            binary_names: vec![],
28        }
29    }
30}
31
32impl BuildScript {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    pub fn locked(mut self, v: bool) -> Self {
38        self.use_locked = v;
39        self
40    }
41
42    pub fn cargo_bundle_licenses(mut self, v: bool) -> Self {
43        self.use_cbl = v;
44        self
45    }
46
47    pub fn needs_bindgen(mut self, v: bool) -> Self {
48        self.use_bindgen = v;
49        self
50    }
51
52    pub fn has_native_deps(mut self, v: bool) -> Self {
53        self.native_deps = v;
54        self
55    }
56
57    pub fn workspace_path(mut self, path: &str) -> Self {
58        self.workspace_path = Some(path.to_string());
59        self
60    }
61
62    pub fn force_build_sh(mut self, v: bool) -> Self {
63        self.force_build_sh = v;
64        self
65    }
66
67    pub fn cargo_net_git_fetch(mut self, v: bool) -> Self {
68        self.cargo_net_git_fetch = v;
69        self
70    }
71
72    pub fn strip_binaries(mut self, v: bool) -> Self {
73        self.strip_binaries = v;
74        self
75    }
76
77    pub fn binaries(mut self, names: Vec<String>) -> Self {
78        self.binary_names = names;
79        self
80    }
81
82    /// Whether this recipe needs a separate build.sh (vs inline script:).
83    pub fn needs_build_sh(&self) -> bool {
84        self.force_build_sh
85            || self.use_cbl
86            || self.use_bindgen
87            || self.native_deps
88            || self.workspace_path.is_some()
89    }
90
91    /// Generate the cargo install command line.
92    fn cargo_install_cmd(&self) -> String {
93        let path = self.workspace_path.as_deref().unwrap_or(".");
94        let locked = if self.use_locked { " --locked" } else { "" };
95        format!("cargo install --no-track{locked} --verbose --root \"${{PREFIX}}\" --path {path}")
96    }
97
98    /// Generate inline script content (for simple recipes).
99    pub fn inline_script(&self) -> String {
100        self.cargo_install_cmd()
101    }
102
103    /// Generate build.sh content (for complex recipes).
104    pub fn to_build_sh(&self) -> String {
105        let mut out = String::new();
106        out.push_str("#!/bin/bash\nset -euo pipefail\n\n");
107
108        // Standard env vars
109        out.push_str("export RUST_BACKTRACE=1\n");
110
111        if self.cargo_net_git_fetch {
112            out.push_str("export CARGO_NET_GIT_FETCH_WITH_CLI=true\n");
113        }
114        out.push('\n');
115
116        if self.native_deps {
117            out.push_str("export CPPFLAGS=\"${CPPFLAGS} -I${PREFIX}/include\"\n");
118            out.push_str("export LDFLAGS=\"${LDFLAGS} -L${PREFIX}/lib\"\n");
119            out.push_str("export CFLAGS=\"${CFLAGS} -O3\"\n");
120            // Allow Cargo build scripts that link against host libs (e.g. libgit2)
121            // to find them at build time, before conda-build's prefix replacement.
122            out.push_str(
123                "export LD_LIBRARY_PATH=\"${PREFIX}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}\"\n",
124            );
125            out.push_str(
126                "export DYLD_FALLBACK_LIBRARY_PATH=\"${PREFIX}/lib${DYLD_FALLBACK_LIBRARY_PATH:+:${DYLD_FALLBACK_LIBRARY_PATH}}\"\n\n",
127            );
128        }
129
130        if self.use_bindgen {
131            out.push_str("export BINDGEN_EXTRA_CLANG_ARGS=\"${CFLAGS} ${CPPFLAGS}\"\n\n");
132        }
133
134        if self.use_cbl {
135            out.push_str("cargo-bundle-licenses --format yaml --output THIRDPARTY.yml\n\n");
136        }
137
138        out.push_str(&self.cargo_install_cmd());
139        out.push('\n');
140
141        if self.strip_binaries && !self.binary_names.is_empty() {
142            out.push('\n');
143            for bin in &self.binary_names {
144                out.push_str(&format!(
145                    "${{STRIP}} \"${{PREFIX}}/bin/{bin}\" 2>/dev/null || true\n"
146                ));
147            }
148        }
149
150        out
151    }
152}