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 pub fn pkg_name(&self) -> Option<String> {
138 if self.cargo_opts.packages.len() > 1 {
139 return None;
140 }
141 self.cargo_opts.packages.first().map(|s| s.to_string())
142 }
143
144 pub fn bin_name(&self) -> Option<String> {
145 if self.cargo_opts.bin.len() > 1 {
146 return None;
147 }
148 self.cargo_opts.bin.first().map(|s| s.to_string())
149 }
150}
151
152impl Serialize for Build {
153 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: serde::Serializer,
156 {
157 use serde::ser::SerializeStruct;
158
159 let field_count = self.output_format.is_some() as usize
161 + self.lambda_dir.is_some() as usize
162 + self.flatten.is_some() as usize
163 + self.compiler.is_some() as usize
164 + self.include.is_some() as usize
165 + self.arm64 as usize
166 + self.x86_64 as usize
167 + self.extension as usize
168 + self.internal as usize
169 + self.skip_target_check as usize
170 + self.disable_optimizations as usize
171 + self.cargo_opts.manifest_path.is_some() as usize
172 + self.cargo_opts.bins as usize
173 + !self.cargo_opts.bin.is_empty() as usize
174 + self.cargo_opts.examples as usize
175 + !self.cargo_opts.example.is_empty() as usize
176 + self.cargo_opts.all_targets as usize
177 + !self.cargo_opts.packages.is_empty() as usize
178 + self.cargo_opts.workspace as usize
179 + !self.cargo_opts.exclude.is_empty() as usize
180 + self.cargo_opts.tests as usize
181 + !self.cargo_opts.test.is_empty() as usize
182 + self.cargo_opts.benches as usize
183 + !self.cargo_opts.bench.is_empty() as usize
184 + count_common_options(&self.cargo_opts.common);
185
186 let mut state = serializer.serialize_struct("Build", field_count)?;
187
188 if let Some(ref output_format) = self.output_format {
190 state.serialize_field("output_format", output_format)?;
191 }
192 if let Some(ref lambda_dir) = self.lambda_dir {
193 state.serialize_field("lambda_dir", lambda_dir)?;
194 }
195 if let Some(ref flatten) = self.flatten {
196 state.serialize_field("flatten", flatten)?;
197 }
198 if let Some(ref compiler) = self.compiler {
199 state.serialize_field("compiler", compiler)?;
200 }
201 if let Some(ref include) = self.include {
202 state.serialize_field("include", include)?;
203 }
204
205 if self.arm64 {
207 state.serialize_field("arm64", &true)?;
208 }
209 if self.x86_64 {
210 state.serialize_field("x86_64", &true)?;
211 }
212 if self.extension {
213 state.serialize_field("extension", &true)?;
214 }
215 if self.internal {
216 state.serialize_field("internal", &true)?;
217 }
218 if self.skip_target_check {
219 state.serialize_field("skip_target_check", &true)?;
220 }
221 if self.disable_optimizations {
222 state.serialize_field("disable_optimizations", &true)?;
223 }
224
225 if let Some(ref manifest_path) = self.cargo_opts.manifest_path {
227 state.serialize_field("manifest_path", manifest_path)?;
228 }
229 if self.cargo_opts.release {
230 state.serialize_field("release", &true)?;
231 }
232 if self.cargo_opts.bins {
233 state.serialize_field("bins", &true)?;
234 }
235 if !self.cargo_opts.bin.is_empty() {
236 state.serialize_field("bin", &self.cargo_opts.bin)?;
237 }
238 if self.cargo_opts.examples {
239 state.serialize_field("examples", &true)?;
240 }
241 if !self.cargo_opts.example.is_empty() {
242 state.serialize_field("example", &self.cargo_opts.example)?;
243 }
244 if self.cargo_opts.all_targets {
245 state.serialize_field("all_targets", &true)?;
246 }
247 if !self.cargo_opts.packages.is_empty() {
248 state.serialize_field("packages", &self.cargo_opts.packages)?;
249 }
250 if self.cargo_opts.workspace {
251 state.serialize_field("workspace", &true)?;
252 }
253 if !self.cargo_opts.exclude.is_empty() {
254 state.serialize_field("exclude", &self.cargo_opts.exclude)?;
255 }
256 if self.cargo_opts.tests {
257 state.serialize_field("tests", &true)?;
258 }
259 if !self.cargo_opts.test.is_empty() {
260 state.serialize_field("test", &self.cargo_opts.test)?;
261 }
262 if self.cargo_opts.benches {
263 state.serialize_field("benches", &true)?;
264 }
265 if !self.cargo_opts.bench.is_empty() {
266 state.serialize_field("bench", &self.cargo_opts.bench)?;
267 }
268 serialize_common_options::<S>(&mut state, &self.cargo_opts.common)?;
269
270 state.end()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use cargo_options::CommonOptions;
278 use serde_json::json;
279
280 #[test]
281 fn test_serialize_minimal_build() {
282 let build = Build::default();
283 let serialized = serde_json::to_value(&build).unwrap();
284
285 assert_eq!(serialized, json!({}));
286 }
287
288 #[test]
289 fn test_serialize_with_optional_fields() {
290 let build = Build {
291 lambda_dir: Some(PathBuf::from("/tmp/lambda")),
292 compiler: Some(CompilerOptions::Cross),
293 include: Some(vec!["file1.txt".to_string(), "file2.txt".to_string()]),
294 ..Default::default()
295 };
296
297 let serialized = serde_json::to_value(&build).unwrap();
298
299 assert_eq!(
300 serialized,
301 json!({
302 "lambda_dir": "/tmp/lambda",
303 "compiler": { "type": "cross" },
304 "include": ["file1.txt", "file2.txt"]
305 })
306 );
307 }
308
309 #[test]
310 fn test_serialize_with_boolean_fields() {
311 let build = Build {
312 arm64: true,
313 extension: true,
314 skip_target_check: true,
315 ..Default::default()
316 };
317
318 let serialized = serde_json::to_value(&build).unwrap();
319
320 assert_eq!(
321 serialized,
322 json!({
323 "arm64": true,
324 "extension": true,
325 "skip_target_check": true
326 })
327 );
328 }
329
330 #[test]
331 fn test_serialize_with_cargo_opts() {
332 let build = Build {
333 cargo_opts: CargoBuild {
334 common: CommonOptions {
335 target: vec!["x86_64-unknown-linux-gnu".to_string()],
336 features: vec!["feature1".to_string(), "feature2".to_string()],
337 all_features: true,
338 profile: Some("release".to_string()),
339 ..Default::default()
340 },
341 ..Default::default()
342 },
343 ..Default::default()
344 };
345
346 let serialized = serde_json::to_value(&build).unwrap();
347
348 assert_eq!(
349 serialized,
350 json!({
351 "target": ["x86_64-unknown-linux-gnu"],
352 "features": ["feature1", "feature2"],
353 "all_features": true,
354 "profile": "release"
355 })
356 );
357 }
358
359 #[test]
360 fn test_serialize_complete_build() {
361 let build = Build {
362 output_format: Some(OutputFormat::Zip),
364 lambda_dir: Some(PathBuf::from("/tmp/lambda")),
365 arm64: true,
366 extension: true,
367 compiler: Some(CompilerOptions::CargoZigbuild),
368 include: Some(vec!["include1".to_string()]),
369
370 cargo_opts: CargoBuild {
372 common: CommonOptions {
373 target: vec!["x86_64-unknown-linux-gnu".to_string()],
374 features: vec!["feature1".to_string()],
375 all_features: true,
376 ..Default::default()
377 },
378 ..Default::default()
379 },
380 ..Default::default()
381 };
382
383 let serialized = serde_json::to_value(&build).unwrap();
384
385 assert_eq!(
386 serialized,
387 json!({
388 "output_format": "zip",
389 "lambda_dir": "/tmp/lambda",
390 "arm64": true,
391 "extension": true,
392 "compiler": { "type": "cargo_zigbuild" },
393 "include": ["include1"],
394 "target": ["x86_64-unknown-linux-gnu"],
395 "features": ["feature1"],
396 "all_features": true
397 })
398 );
399 }
400}