cargo_lambda_metadata/cargo/
build.rs1use std::path::PathBuf;
2
3use cargo_options::Build as CargoBuild;
4use clap::{Args, ValueHint};
5use serde::{Deserialize, Serialize};
6use strum_macros::{Display, EnumString};
7
8use crate::cargo::{count_common_options, serialize_common_options};
9
10#[derive(Args, Clone, Debug, Default, Deserialize)]
11#[command(
12 name = "build",
13 after_help = "Full command documentation: https://www.cargo-lambda.info/commands/build.html"
14)]
15pub struct Build {
16 #[arg(short, long)]
18 #[serde(default)]
19 pub output_format: Option<OutputFormat>,
20
21 #[arg(short, long, value_hint = ValueHint::DirPath)]
23 #[serde(default)]
24 pub lambda_dir: Option<PathBuf>,
25
26 #[arg(long)]
28 #[serde(default)]
29 pub arm64: bool,
30
31 #[arg(long)]
33 #[serde(default)]
34 pub x86_64: bool,
35
36 #[arg(long)]
38 #[serde(default)]
39 pub extension: bool,
40
41 #[arg(long, requires = "extension")]
43 #[serde(default)]
44 pub internal: bool,
45
46 #[arg(long)]
49 #[serde(default)]
50 pub flatten: Option<String>,
51
52 #[arg(long)]
54 #[serde(default)]
55 pub skip_target_check: bool,
56
57 #[arg(short, long, env = "CARGO_LAMBDA_COMPILER")]
59 #[serde(default)]
60 pub compiler: Option<CompilerOptions>,
61
62 #[arg(long)]
64 #[serde(default)]
65 pub disable_optimizations: bool,
66
67 #[arg(short, long)]
69 #[serde(default)]
70 pub include: Option<Vec<String>>,
71
72 #[command(flatten)]
73 #[serde(default, flatten)]
74 pub cargo_opts: CargoBuild,
75}
76
77#[derive(Clone, Debug, Default, Deserialize, Display, EnumString, PartialEq, Serialize)]
78#[strum(ascii_case_insensitive)]
79#[serde(rename_all = "snake_case")]
80pub enum OutputFormat {
81 #[default]
82 Binary,
83 Zip,
84}
85
86#[derive(Clone, Debug, Default, Deserialize, Display, Eq, PartialEq, Serialize)]
87#[serde(tag = "type", rename_all = "snake_case")]
88pub enum CompilerOptions {
89 #[default]
90 CargoZigbuild,
91 Cargo(CargoCompilerOptions),
92 Cross,
93}
94
95impl From<String> for CompilerOptions {
96 fn from(s: String) -> Self {
97 match s.to_lowercase().as_str() {
98 "cargo" => Self::Cargo(CargoCompilerOptions::default()),
99 "cross" => Self::Cross,
100 _ => Self::CargoZigbuild,
101 }
102 }
103}
104
105impl CompilerOptions {
106 pub fn is_local_cargo(&self) -> bool {
107 matches!(self, CompilerOptions::Cargo(_))
108 }
109
110 pub fn is_cargo_zigbuild(&self) -> bool {
111 matches!(self, CompilerOptions::CargoZigbuild)
112 }
113}
114
115#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
116pub struct CargoCompilerOptions {
117 #[serde(default)]
118 pub subcommand: Option<Vec<String>>,
119 #[serde(default)]
120 pub extra_args: Option<Vec<String>>,
121}
122
123impl Build {
124 pub fn manifest_path(&self) -> PathBuf {
125 self.cargo_opts
126 .manifest_path
127 .clone()
128 .unwrap_or_else(|| "Cargo.toml".into())
129 }
130
131 pub fn output_format(&self) -> &OutputFormat {
132 self.output_format.as_ref().unwrap_or(&OutputFormat::Binary)
133 }
134}
135
136impl Serialize for Build {
137 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138 where
139 S: serde::Serializer,
140 {
141 use serde::ser::SerializeStruct;
142
143 let field_count = self.output_format.is_some() as usize
145 + self.lambda_dir.is_some() as usize
146 + self.flatten.is_some() as usize
147 + self.compiler.is_some() as usize
148 + self.include.is_some() as usize
149 + self.arm64 as usize
150 + self.x86_64 as usize
151 + self.extension as usize
152 + self.internal as usize
153 + self.skip_target_check as usize
154 + self.disable_optimizations as usize
155 + self.cargo_opts.manifest_path.is_some() as usize
156 + self.cargo_opts.bins as usize
157 + !self.cargo_opts.bin.is_empty() as usize
158 + self.cargo_opts.examples as usize
159 + !self.cargo_opts.example.is_empty() as usize
160 + self.cargo_opts.all_targets as usize
161 + !self.cargo_opts.packages.is_empty() as usize
162 + self.cargo_opts.workspace as usize
163 + !self.cargo_opts.exclude.is_empty() as usize
164 + self.cargo_opts.tests as usize
165 + !self.cargo_opts.test.is_empty() as usize
166 + self.cargo_opts.benches as usize
167 + !self.cargo_opts.bench.is_empty() as usize
168 + count_common_options(&self.cargo_opts.common);
169
170 let mut state = serializer.serialize_struct("Build", field_count)?;
171
172 if let Some(ref output_format) = self.output_format {
174 state.serialize_field("output_format", output_format)?;
175 }
176 if let Some(ref lambda_dir) = self.lambda_dir {
177 state.serialize_field("lambda_dir", lambda_dir)?;
178 }
179 if let Some(ref flatten) = self.flatten {
180 state.serialize_field("flatten", flatten)?;
181 }
182 if let Some(ref compiler) = self.compiler {
183 state.serialize_field("compiler", compiler)?;
184 }
185 if let Some(ref include) = self.include {
186 state.serialize_field("include", include)?;
187 }
188
189 if self.arm64 {
191 state.serialize_field("arm64", &true)?;
192 }
193 if self.x86_64 {
194 state.serialize_field("x86_64", &true)?;
195 }
196 if self.extension {
197 state.serialize_field("extension", &true)?;
198 }
199 if self.internal {
200 state.serialize_field("internal", &true)?;
201 }
202 if self.skip_target_check {
203 state.serialize_field("skip_target_check", &true)?;
204 }
205 if self.disable_optimizations {
206 state.serialize_field("disable_optimizations", &true)?;
207 }
208
209 if let Some(ref manifest_path) = self.cargo_opts.manifest_path {
211 state.serialize_field("manifest_path", manifest_path)?;
212 }
213 if self.cargo_opts.release {
214 state.serialize_field("release", &true)?;
215 }
216 if self.cargo_opts.bins {
217 state.serialize_field("bins", &true)?;
218 }
219 if !self.cargo_opts.bin.is_empty() {
220 state.serialize_field("bin", &self.cargo_opts.bin)?;
221 }
222 if self.cargo_opts.examples {
223 state.serialize_field("examples", &true)?;
224 }
225 if !self.cargo_opts.example.is_empty() {
226 state.serialize_field("example", &self.cargo_opts.example)?;
227 }
228 if self.cargo_opts.all_targets {
229 state.serialize_field("all_targets", &true)?;
230 }
231 if !self.cargo_opts.packages.is_empty() {
232 state.serialize_field("packages", &self.cargo_opts.packages)?;
233 }
234 if self.cargo_opts.workspace {
235 state.serialize_field("workspace", &true)?;
236 }
237 if !self.cargo_opts.exclude.is_empty() {
238 state.serialize_field("exclude", &self.cargo_opts.exclude)?;
239 }
240 if self.cargo_opts.tests {
241 state.serialize_field("tests", &true)?;
242 }
243 if !self.cargo_opts.test.is_empty() {
244 state.serialize_field("test", &self.cargo_opts.test)?;
245 }
246 if self.cargo_opts.benches {
247 state.serialize_field("benches", &true)?;
248 }
249 if !self.cargo_opts.bench.is_empty() {
250 state.serialize_field("bench", &self.cargo_opts.bench)?;
251 }
252 serialize_common_options::<S>(&mut state, &self.cargo_opts.common)?;
253
254 state.end()
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use cargo_options::CommonOptions;
262 use serde_json::json;
263
264 #[test]
265 fn test_serialize_minimal_build() {
266 let build = Build::default();
267 let serialized = serde_json::to_value(&build).unwrap();
268
269 assert_eq!(serialized, json!({}));
270 }
271
272 #[test]
273 fn test_serialize_with_optional_fields() {
274 let build = Build {
275 lambda_dir: Some(PathBuf::from("/tmp/lambda")),
276 compiler: Some(CompilerOptions::Cross),
277 include: Some(vec!["file1.txt".to_string(), "file2.txt".to_string()]),
278 ..Default::default()
279 };
280
281 let serialized = serde_json::to_value(&build).unwrap();
282
283 assert_eq!(
284 serialized,
285 json!({
286 "lambda_dir": "/tmp/lambda",
287 "compiler": { "type": "cross" },
288 "include": ["file1.txt", "file2.txt"]
289 })
290 );
291 }
292
293 #[test]
294 fn test_serialize_with_boolean_fields() {
295 let build = Build {
296 arm64: true,
297 extension: true,
298 skip_target_check: true,
299 ..Default::default()
300 };
301
302 let serialized = serde_json::to_value(&build).unwrap();
303
304 assert_eq!(
305 serialized,
306 json!({
307 "arm64": true,
308 "extension": true,
309 "skip_target_check": true
310 })
311 );
312 }
313
314 #[test]
315 fn test_serialize_with_cargo_opts() {
316 let build = Build {
317 cargo_opts: CargoBuild {
318 common: CommonOptions {
319 target: vec!["x86_64-unknown-linux-gnu".to_string()],
320 features: vec!["feature1".to_string(), "feature2".to_string()],
321 all_features: true,
322 profile: Some("release".to_string()),
323 ..Default::default()
324 },
325 ..Default::default()
326 },
327 ..Default::default()
328 };
329
330 let serialized = serde_json::to_value(&build).unwrap();
331
332 assert_eq!(
333 serialized,
334 json!({
335 "target": ["x86_64-unknown-linux-gnu"],
336 "features": ["feature1", "feature2"],
337 "all_features": true,
338 "profile": "release"
339 })
340 );
341 }
342
343 #[test]
344 fn test_serialize_complete_build() {
345 let build = Build {
346 output_format: Some(OutputFormat::Zip),
348 lambda_dir: Some(PathBuf::from("/tmp/lambda")),
349 arm64: true,
350 extension: true,
351 compiler: Some(CompilerOptions::CargoZigbuild),
352 include: Some(vec!["include1".to_string()]),
353
354 cargo_opts: CargoBuild {
356 common: CommonOptions {
357 target: vec!["x86_64-unknown-linux-gnu".to_string()],
358 features: vec!["feature1".to_string()],
359 all_features: true,
360 ..Default::default()
361 },
362 ..Default::default()
363 },
364 ..Default::default()
365 };
366
367 let serialized = serde_json::to_value(&build).unwrap();
368
369 assert_eq!(
370 serialized,
371 json!({
372 "output_format": "zip",
373 "lambda_dir": "/tmp/lambda",
374 "arm64": true,
375 "extension": true,
376 "compiler": { "type": "cargo_zigbuild" },
377 "include": ["include1"],
378 "target": ["x86_64-unknown-linux-gnu"],
379 "features": ["feature1"],
380 "all_features": true
381 })
382 );
383 }
384}