Skip to main content

chef/
recipe.rs

1use crate::Skeleton;
2use anyhow::Context;
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5use std::path::PathBuf;
6use std::process::Command;
7
8#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
9pub struct Recipe {
10    pub skeleton: Skeleton,
11}
12
13pub struct TargetArgs {
14    pub benches: bool,
15    pub tests: bool,
16    pub examples: bool,
17    pub all_targets: bool,
18}
19
20pub enum CommandArg {
21    Build,
22    Check,
23    Clippy,
24    Zigbuild,
25    NoBuild,
26}
27
28pub struct CookArgs {
29    pub profile: OptimisationProfile,
30    pub command: CommandArg,
31    pub default_features: DefaultFeatures,
32    pub all_features: AllFeatures,
33    pub features: Option<HashSet<String>>,
34    pub unstable_features: Option<HashSet<String>>,
35    pub target: Option<Vec<String>>,
36    pub target_dir: Option<PathBuf>,
37    pub target_args: TargetArgs,
38    pub manifest_path: Option<PathBuf>,
39    pub package: Option<Vec<String>>,
40    pub workspace: bool,
41    pub offline: bool,
42    pub locked: bool,
43    pub frozen: bool,
44    pub verbose: bool,
45    pub timings: bool,
46    pub no_std: bool,
47    pub bin: Option<Vec<String>>,
48    pub bins: bool,
49    pub no_build: bool,
50    pub jobs: Option<u16>,
51}
52
53impl Recipe {
54    pub fn prepare(base_path: PathBuf, member: Option<String>) -> Result<Self, anyhow::Error> {
55        let skeleton = Skeleton::derive(base_path, member)?;
56        Ok(Recipe { skeleton })
57    }
58
59    pub fn cook(&self, args: CookArgs) -> Result<(), anyhow::Error> {
60        let current_directory = std::env::current_dir()?;
61        self.skeleton
62            .build_minimum_project(&current_directory, args.no_std)?;
63        if args.no_build {
64            return Ok(());
65        }
66        build_dependencies(&args);
67        self.skeleton
68            .remove_compiled_dummies(
69                current_directory,
70                args.profile,
71                args.target,
72                args.target_dir,
73            )
74            .context("Failed to clean up dummy compilation artifacts.")?;
75        Ok(())
76    }
77}
78
79#[derive(Debug, Clone, Eq, PartialEq)]
80pub enum OptimisationProfile {
81    Release,
82    Debug,
83    Other(String),
84}
85
86#[derive(Debug, Clone, Copy, Eq, PartialEq)]
87pub enum DefaultFeatures {
88    Enabled,
89    Disabled,
90}
91
92#[derive(Debug, Clone, Copy, Eq, PartialEq)]
93pub enum AllFeatures {
94    Enabled,
95    Disabled,
96}
97
98fn build_dependencies(args: &CookArgs) {
99    let CookArgs {
100        profile,
101        command: command_arg,
102        default_features,
103        all_features,
104        features,
105        unstable_features,
106        target,
107        target_dir,
108        target_args,
109        manifest_path,
110        package,
111        workspace,
112        offline,
113        frozen,
114        locked,
115        verbose,
116        timings,
117        bin,
118        no_std: _no_std,
119        bins,
120        no_build: _no_build,
121        jobs,
122    } = args;
123    let cargo_path = std::env::var("CARGO").expect("The `CARGO` environment variable was not set. This is unexpected: it should always be provided by `cargo` when invoking a custom sub-command, allowing `cargo-chef` to correctly detect which toolchain should be used. Please file a bug.");
124    let mut command = Command::new(cargo_path);
125    let command_with_args = match command_arg {
126        CommandArg::Build => command.arg("build"),
127        CommandArg::Check => command.arg("check"),
128        CommandArg::Clippy => command.arg("clippy"),
129        CommandArg::Zigbuild => command.arg("zigbuild"),
130        CommandArg::NoBuild => return,
131    };
132    if profile == &OptimisationProfile::Release {
133        command_with_args.arg("--release");
134    } else if let OptimisationProfile::Other(custom_profile) = profile {
135        command_with_args.arg("--profile").arg(custom_profile);
136    }
137    if default_features == &DefaultFeatures::Disabled {
138        command_with_args.arg("--no-default-features");
139    }
140    if let Some(features) = features {
141        let feature_flag = features.iter().cloned().collect::<Vec<String>>().join(",");
142        command_with_args.arg("--features").arg(feature_flag);
143    }
144    if all_features == &AllFeatures::Enabled {
145        command_with_args.arg("--all-features");
146    }
147    if let Some(unstable_features) = unstable_features {
148        for unstable_feature in unstable_features.iter().cloned() {
149            command_with_args.arg("-Z").arg(unstable_feature);
150        }
151    }
152    if let Some(target) = target {
153        for target in target {
154            command_with_args.arg("--target").arg(target);
155        }
156    }
157    if let Some(target_dir) = target_dir {
158        command_with_args.arg("--target-dir").arg(target_dir);
159    }
160    if target_args.benches {
161        command_with_args.arg("--benches");
162    }
163    if target_args.tests {
164        command_with_args.arg("--tests");
165    }
166    if target_args.examples {
167        command_with_args.arg("--examples");
168    }
169    if target_args.all_targets {
170        command_with_args.arg("--all-targets");
171    }
172    if let Some(manifest_path) = manifest_path {
173        command_with_args.arg("--manifest-path").arg(manifest_path);
174    }
175    if let Some(package) = package {
176        for package in package {
177            command_with_args.arg("--package").arg(package);
178        }
179    }
180    if let Some(binary_target) = bin {
181        for binary_target in binary_target {
182            command_with_args.arg("--bin").arg(binary_target);
183        }
184    }
185    if *workspace {
186        command_with_args.arg("--workspace");
187    }
188    if *offline {
189        command_with_args.arg("--offline");
190    }
191    if *frozen {
192        command_with_args.arg("--frozen");
193    }
194    if *locked {
195        command_with_args.arg("--locked");
196    }
197    if *verbose {
198        command_with_args.arg("--verbose");
199    }
200    if *timings {
201        command_with_args.arg("--timings");
202    }
203    if *bins {
204        command_with_args.arg("--bins");
205    }
206
207    if let Some(count) = jobs {
208        command_with_args.arg("--jobs").arg(count.to_string());
209    }
210
211    execute_command(command_with_args);
212}
213
214fn execute_command(command: &mut Command) {
215    let mut child = command
216        .envs(std::env::vars())
217        .spawn()
218        .expect("Failed to execute process");
219
220    let exit_status = child.wait().expect("Failed to run command");
221
222    if !exit_status.success() {
223        match exit_status.code() {
224            Some(code) => panic!("Exited with status code: {}", code),
225            None => panic!("Process terminated by signal"),
226        }
227    }
228}