Skip to main content

redskull_lib/
recipe.rs

1//! Recipe data model for conda meta.yaml / recipe.yaml.
2
3/// Jinja2 preamble with name and version variables.
4pub struct Preamble {
5    pub name: String,
6    pub version: String,
7}
8
9pub struct Package {}
10
11pub struct Source {
12    pub url: String,
13    pub filename: String,
14    pub sha256: String,
15}
16
17pub struct Build {
18    pub name: String,
19    pub with_run_exports: bool,
20    pub max_pin: String,
21    /// If Some, use inline script instead of build.sh.
22    pub script: Option<String>,
23}
24
25pub struct Requirement {
26    pub name: String,
27    pub version: Option<String>,
28    /// Platform selector, e.g. "not osx", "not win", "not arm64".
29    pub selector: Option<String>,
30}
31
32pub struct Requirements {
33    pub build: Vec<Requirement>,
34    pub host: Vec<Requirement>,
35    pub run: Vec<Requirement>,
36}
37
38pub struct Test {
39    pub commands: Vec<String>,
40}
41
42pub struct About {
43    pub home: Option<String>,
44    pub license: Option<String>,
45    pub license_family: Option<String>,
46    /// List of license files (rendered as YAML list when multiple).
47    pub license_file: Vec<String>,
48    pub summary: Option<String>,
49    pub dev_url: Option<String>,
50    pub doc_url: Option<String>,
51}
52
53pub struct Extra {
54    pub additional_platforms: Vec<String>,
55    pub recipe_maintainers: Vec<String>,
56    pub identifiers: Vec<String>,
57    pub skip_platforms: Vec<String>,
58}
59
60pub struct Recipe {
61    pub preamble: Preamble,
62    pub package: Package,
63    pub source: Source,
64    pub build: Build,
65    pub requirements: Requirements,
66    pub test: Test,
67    pub about: About,
68    pub extra: Extra,
69}
70
71impl Test {
72    pub fn from_binaries(binaries: &[&str]) -> Self {
73        Self { commands: binaries.iter().map(|b| format!("{b} --help")).collect() }
74    }
75
76    pub fn from_binaries_version(binaries: &[&str]) -> Self {
77        Self { commands: binaries.iter().map(|b| format!("{b} --version")).collect() }
78    }
79}
80
81impl Requirement {
82    pub fn simple(name: &str) -> Self {
83        Self { name: name.to_string(), version: None, selector: None }
84    }
85}
86
87/// Configuration for which build tools to include in requirements.
88pub struct BuildToolNeeds {
89    pub pkg_config: bool,
90    pub make: bool,
91    pub cmake: bool,
92}
93
94impl Requirements {
95    /// Create requirements for a Rust crate.
96    ///
97    /// * `cargo_bundle_licenses` - include CBL in build deps
98    /// * `has_c_deps` - crate links C code (adds compiler('c'))
99    /// * `has_cxx_deps` - crate links C++ code (adds compiler('cxx'))
100    /// * `has_bindgen` - crate uses bindgen (adds clangdev and compiler('c'))
101    /// * `build_tools` - which build tools to include (pkg-config, make, cmake)
102    pub fn for_rust_crate(
103        cargo_bundle_licenses: bool,
104        has_c_deps: bool,
105        has_cxx_deps: bool,
106        has_bindgen: bool,
107        build_tools: &BuildToolNeeds,
108    ) -> Self {
109        let mut build = vec![];
110
111        // bindgen invokes a C compiler on wrapper headers at build time, so
112        // any recipe that pulls in clangdev must also expose `compiler('c')`.
113        // Bioconda recipes that emit `clangdev` without `compiler('c')` fail
114        // the `should_use_compilers` lint.
115        if has_c_deps || has_bindgen {
116            build.push(Requirement::simple("{{ compiler('c') }}"));
117        }
118        if has_cxx_deps {
119            build.push(Requirement::simple("{{ compiler('cxx') }}"));
120        }
121        // Bioconda's compiler_needs_stdlib_c lint requires stdlib('c') whenever any
122        // {{ compiler(...) }} appears — including compiler('rust') — so emit it
123        // unconditionally alongside the Rust compiler below.
124        build.push(Requirement::simple("{{ stdlib('c') }}"));
125        build.push(Requirement::simple("{{ compiler('rust') }}"));
126
127        if cargo_bundle_licenses {
128            build.push(Requirement::simple("cargo-bundle-licenses"));
129        }
130
131        if has_bindgen {
132            build.push(Requirement::simple("clangdev"));
133        }
134
135        if build_tools.pkg_config {
136            build.push(Requirement::simple("pkg-config"));
137        }
138        if build_tools.make {
139            build.push(Requirement::simple("make"));
140        }
141        if build_tools.cmake {
142            build.push(Requirement::simple("cmake"));
143        }
144
145        Self { build, host: vec![], run: vec![] }
146    }
147
148    pub fn add_host(&mut self, name: &str, selector: Option<&str>) {
149        self.host.push(Requirement {
150            name: name.to_string(),
151            version: None,
152            selector: selector.map(String::from),
153        });
154    }
155
156    pub fn add_run(&mut self, name: &str, selector: Option<&str>) {
157        self.run.push(Requirement {
158            name: name.to_string(),
159            version: None,
160            selector: selector.map(String::from),
161        });
162    }
163}