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}
51
52impl Recipe {
53    pub fn prepare(base_path: PathBuf, member: Option<String>) -> Result<Self, anyhow::Error> {
54        let skeleton = Skeleton::derive(base_path, member)?;
55        Ok(Recipe { skeleton })
56    }
57
58    pub fn cook(&self, args: CookArgs) -> Result<(), anyhow::Error> {
59        let current_directory = std::env::current_dir()?;
60        self.skeleton
61            .build_minimum_project(&current_directory, args.no_std)?;
62        if args.no_build {
63            return Ok(());
64        }
65        build_dependencies(&args);
66        self.skeleton
67            .remove_compiled_dummies(
68                current_directory,
69                args.profile,
70                args.target,
71                args.target_dir,
72            )
73            .context("Failed to clean up dummy compilation artifacts.")?;
74        Ok(())
75    }
76}
77
78#[derive(Debug, Clone, Eq, PartialEq)]
79pub enum OptimisationProfile {
80    Release,
81    Debug,
82    Other(String),
83}
84
85#[derive(Debug, Clone, Copy, Eq, PartialEq)]
86pub enum DefaultFeatures {
87    Enabled,
88    Disabled,
89}
90
91#[derive(Debug, Clone, Copy, Eq, PartialEq)]
92pub enum AllFeatures {
93    Enabled,
94    Disabled,
95}
96
97fn build_dependencies(args: &CookArgs) {
98    let CookArgs {
99        profile,
100        command: command_arg,
101        default_features,
102        all_features,
103        features,
104        unstable_features,
105        target,
106        target_dir,
107        target_args,
108        manifest_path,
109        package,
110        workspace,
111        offline,
112        frozen,
113        locked,
114        verbose,
115        timings,
116        bin,
117        no_std: _no_std,
118        bins,
119        no_build: _no_build,
120    } = args;
121    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.");
122    let mut command = Command::new(cargo_path);
123    let command_with_args = match command_arg {
124        CommandArg::Build => command.arg("build"),
125        CommandArg::Check => command.arg("check"),
126        CommandArg::Clippy => command.arg("clippy"),
127        CommandArg::Zigbuild => command.arg("zigbuild"),
128        CommandArg::NoBuild => return,
129    };
130    if profile == &OptimisationProfile::Release {
131        command_with_args.arg("--release");
132    } else if let OptimisationProfile::Other(custom_profile) = profile {
133        command_with_args.arg("--profile").arg(custom_profile);
134    }
135    if default_features == &DefaultFeatures::Disabled {
136        command_with_args.arg("--no-default-features");
137    }
138    if let Some(features) = features {
139        let feature_flag = features.iter().cloned().collect::<Vec<String>>().join(",");
140        command_with_args.arg("--features").arg(feature_flag);
141    }
142    if all_features == &AllFeatures::Enabled {
143        command_with_args.arg("--all-features");
144    }
145    if let Some(unstable_features) = unstable_features {
146        for unstable_feature in unstable_features.iter().cloned() {
147            command_with_args.arg("-Z").arg(unstable_feature);
148        }
149    }
150    if let Some(target) = target {
151        for target in target {
152            command_with_args.arg("--target").arg(target);
153        }
154    }
155    if let Some(target_dir) = target_dir {
156        command_with_args.arg("--target-dir").arg(target_dir);
157    }
158    if target_args.benches {
159        command_with_args.arg("--benches");
160    }
161    if target_args.tests {
162        command_with_args.arg("--tests");
163    }
164    if target_args.examples {
165        command_with_args.arg("--examples");
166    }
167    if target_args.all_targets {
168        command_with_args.arg("--all-targets");
169    }
170    if let Some(manifest_path) = manifest_path {
171        command_with_args.arg("--manifest-path").arg(manifest_path);
172    }
173    if let Some(package) = package {
174        for package in package {
175            command_with_args.arg("--package").arg(package);
176        }
177    }
178    if let Some(binary_target) = bin {
179        for binary_target in binary_target {
180            command_with_args.arg("--bin").arg(binary_target);
181        }
182    }
183    if *workspace {
184        command_with_args.arg("--workspace");
185    }
186    if *offline {
187        command_with_args.arg("--offline");
188    }
189    if *frozen {
190        command_with_args.arg("--frozen");
191    }
192    if *locked {
193        command_with_args.arg("--locked");
194    }
195    if *verbose {
196        command_with_args.arg("--verbose");
197    }
198    if *timings {
199        command_with_args.arg("--timings");
200    }
201    if *bins {
202        command_with_args.arg("--bins");
203    }
204
205    execute_command(command_with_args);
206}
207
208fn execute_command(command: &mut Command) {
209    let mut child = command
210        .envs(std::env::vars())
211        .spawn()
212        .expect("Failed to execute process");
213
214    let exit_status = child.wait().expect("Failed to run command");
215
216    if !exit_status.success() {
217        match exit_status.code() {
218            Some(code) => panic!("Exited with status code: {}", code),
219            None => panic!("Process terminated by signal"),
220        }
221    }
222}