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