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(¤t_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}