Skip to main content

ic_build/cargo/selection/
mod.rs

1//! Typed recipe choices shared by Cargo metadata and build commands.
2
3use std::{
4    collections::BTreeSet,
5    ffi::OsString,
6    path::{Component, Path},
7};
8
9#[cfg(test)]
10mod tests;
11
12/// Caller-selected features for one Cargo graph.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct CargoFeatureSelection {
15    /// Features to activate, ordered and deduplicated before passing to Cargo.
16    pub features: BTreeSet<String>,
17    /// Activate every feature in the selected packages.
18    pub all_features: bool,
19    /// Whether Cargo activates the selected packages' default features.
20    pub default_features: bool,
21}
22
23impl Default for CargoFeatureSelection {
24    fn default() -> Self {
25        Self {
26            features: BTreeSet::new(),
27            default_features: true,
28            all_features: false,
29        }
30    }
31}
32
33impl CargoFeatureSelection {
34    pub(crate) fn arguments(&self) -> Vec<OsString> {
35        let mut arguments = Vec::new();
36        if self.all_features {
37            arguments.push("--all-features".into());
38        }
39        if !self.default_features {
40            arguments.push("--no-default-features".into());
41        }
42        if !self.features.is_empty() {
43            arguments.push(
44                format!(
45                    "--features={}",
46                    self.features
47                        .iter()
48                        .map(String::as_str)
49                        .collect::<Vec<_>>()
50                        .join(",")
51                )
52                .into(),
53            );
54        }
55        arguments
56    }
57}
58
59/// Caller-selected profile, features and network/lock policy for one recipe.
60///
61/// Package, target, manifest, output paths and message format belong to the
62/// enclosing build specification. Arbitrary Cargo arguments are not accepted.
63/// Use both locked and offline for Cargo's frozen policy.
64/// Defaults select Cargo's dev profile and default features, with network and
65/// lockfile policy inherited from the caller's environment/command factory.
66#[derive(Clone, Debug, Default, Eq, PartialEq)]
67pub struct CargoSelection {
68    /// Named Cargo profile; None selects dev. Release renders as --release.
69    pub profile: Option<String>,
70    /// Explicit features and whether default features are enabled.
71    pub features: CargoFeatureSelection,
72    /// Require Cargo to preserve the selected lockfile.
73    pub locked: bool,
74    /// Prevent Cargo from accessing the network.
75    pub offline: bool,
76}
77
78impl CargoSelection {
79    pub(crate) fn profile_target_dir(&self) -> Result<&str, &'static str> {
80        let profile = self.profile.as_deref().unwrap_or("dev");
81        let directory = match profile {
82            "dev" | "test" => "debug",
83            "bench" => "release",
84            custom => custom,
85        };
86        let mut components = Path::new(directory).components();
87        if !matches!(
88            (components.next(), components.next()),
89            (Some(Component::Normal(_)), None)
90        ) {
91            return Err("Cargo profile must select one normal output directory component");
92        }
93        Ok(directory)
94    }
95
96    pub(crate) fn metadata_arguments(&self) -> Vec<OsString> {
97        graph_arguments(&self.features, self.locked, self.offline)
98    }
99
100    pub(crate) fn build_arguments(&self) -> Vec<OsString> {
101        let mut arguments = Vec::new();
102        if let Some(profile) = &self.profile {
103            arguments.push(if profile == "release" {
104                "--release".into()
105            } else {
106                format!("--profile={profile}").into()
107            });
108        }
109        arguments.extend(self.metadata_arguments());
110        arguments
111    }
112}
113
114/// Encode graph choices identically for observations and build recipes.
115pub(crate) fn graph_arguments(
116    features: &CargoFeatureSelection,
117    locked: bool,
118    offline: bool,
119) -> Vec<OsString> {
120    let mut arguments = Vec::new();
121    for (enabled, flag) in [(locked, "--locked"), (offline, "--offline")] {
122        if enabled {
123            arguments.push(flag.into());
124        }
125    }
126    arguments.extend(features.arguments());
127    arguments
128}