cargo_lambda_build/
lib.rs1use cargo_lambda_interactive::{error::InquireError, is_user_cancellation_error};
2use cargo_lambda_metadata::{
3 cargo::{
4 CargoMetadata, binary_targets_from_metadata,
5 build::{Build, OutputFormat},
6 cargo_release_profile_config, target_dir_from_metadata,
7 },
8 fs::copy_and_replace,
9};
10use miette::{IntoDiagnostic, Report, Result, WrapErr};
11use std::{
12 collections::HashSet,
13 fs::create_dir_all,
14 path::{Path, PathBuf},
15 str::FromStr,
16};
17use target_arch::TargetArch;
18use tracing::{debug, warn};
19
20pub use cargo_zigbuild::Zig;
21
22mod archive;
23pub use archive::{BinaryArchive, BinaryData, BinaryModifiedAt, create_binary_archive, zip_binary};
24
25mod compiler;
26use compiler::{build_command, build_profile};
27
28mod error;
29use error::BuildError;
30
31mod target_arch;
32use target_arch::validate_linux_target;
33
34mod toolchain;
35use toolchain::rustup_cmd;
36
37mod zig;
38pub use zig::{
39 InstallOption, check_installation, install_options, install_zig, print_install_options,
40};
41
42#[tracing::instrument(skip(build, metadata), target = "cargo_lambda")]
43pub async fn run(build: &mut Build, metadata: &CargoMetadata) -> Result<()> {
44 tracing::trace!(options = ?build, "building project");
45
46 if (build.arm64 || build.x86_64) && !build.cargo_opts.target.is_empty() {
47 Err(BuildError::InvalidTargetOptions)?;
48 }
49
50 let target_arch = if build.arm64 {
51 TargetArch::arm64()
52 } else if build.x86_64 {
53 TargetArch::x86_64()
54 } else {
55 match build.cargo_opts.target.first() {
57 Some(target) => {
58 validate_linux_target(target)?;
59 TargetArch::from_str(target)?
60 }
61 None => TargetArch::from_host()?,
62 }
63 };
64
65 build.cargo_opts.target = vec![target_arch.to_string()];
66
67 let build_examples = build.cargo_opts.examples || !build.cargo_opts.example.is_empty();
68 let binaries = binary_targets_from_metadata(metadata, build_examples);
69 debug!(binaries = ?binaries, "found new target binaries to build");
70
71 let binaries = if !build.cargo_opts.bin.is_empty() {
72 let mut final_binaries = HashSet::with_capacity(binaries.len());
73
74 for name in &build.cargo_opts.bin {
75 if !binaries.contains(name) {
76 return Err(BuildError::FunctionBinaryMissing(name.into()).into());
77 }
78 final_binaries.insert(name.into());
79 }
80
81 final_binaries
82 } else {
83 binaries
84 };
85
86 let compiler_option = build.compiler.clone().unwrap_or_default();
87 if compiler_option.is_local_cargo() {
88 if !target_arch.compatible_host_linker() && !target_arch.is_static_linking() {
92 return Err(BuildError::InvalidCompilerOption.into());
93 }
94 }
95
96 if is_release_profile(build) && !build.disable_optimizations {
97 let release_optimizations =
98 cargo_release_profile_config(metadata).map_err(BuildError::MetadataError)?;
99 build.cargo_opts.config.extend(
100 release_optimizations
101 .into_iter()
102 .map(String::from)
103 .collect::<Vec<_>>(),
104 );
105
106 let build_flags = format!(
107 "build.rustflags=[\"-C\", \"target-cpu={}\"]",
108 target_arch.target_cpu()
109 );
110 build.cargo_opts.config.push(build_flags);
111
112 debug!(config = ?build.cargo_opts.config, "release optimizations");
113 }
114
115 let profile = build_profile(&build.cargo_opts, &compiler_option);
116 let skip_target_check = build.skip_target_check || which::which(rustup_cmd()).is_err();
117 let cmd = build_command(
118 &compiler_option,
119 &build.cargo_opts,
120 &target_arch,
121 metadata,
122 skip_target_check,
123 )
124 .await;
125
126 let mut cmd = match cmd {
127 Ok(cmd) => cmd,
128 Err(err) if downcasted_user_cancellation(&err) => return Ok(()),
129 Err(err) => return Err(err),
130 };
131
132 let mut child = cmd.spawn().map_err(BuildError::FailedBuildCommand)?;
133 let status = child.wait().map_err(BuildError::FailedBuildCommand)?;
134 if !status.success() {
135 std::process::exit(status.code().unwrap_or(1));
136 }
137
138 let target_dir = target_dir_from_metadata(metadata).unwrap_or_else(|_| PathBuf::from("target"));
140 let target_dir = Path::new(&target_dir);
141 let lambda_dir = if let Some(dir) = &build.lambda_dir {
142 dir.clone()
143 } else {
144 target_dir.join("lambda")
145 };
146
147 let mut base = target_dir
148 .join(target_arch.rustc_target_without_glibc_version())
149 .join(profile);
150 if build_examples {
151 base = base.join("examples");
152 }
153
154 let mut found_binaries = false;
155 for name in &binaries {
156 let binary = base.join(name);
157 debug!(binary = ?binary, exists = binary.exists(), "checking function binary");
158
159 if binary.exists() {
160 found_binaries = true;
161
162 let bootstrap_dir = if build.extension {
163 lambda_dir.join("extensions")
164 } else {
165 match build.flatten {
166 Some(ref n) if n == name => lambda_dir.clone(),
167 _ => lambda_dir.join(name),
168 }
169 };
170 create_dir_all(&bootstrap_dir)
171 .into_diagnostic()
172 .wrap_err_with(|| format!("error creating lambda directory {bootstrap_dir:?}"))?;
173
174 let data = BinaryData::new(name.as_str(), build.extension, build.internal);
175
176 match build.output_format() {
177 OutputFormat::Binary => {
178 let output_location = bootstrap_dir.join(data.binary_name());
179 copy_and_replace(&binary, &output_location)
180 .into_diagnostic()
181 .wrap_err_with(|| {
182 format!("error moving the binary `{binary:?}` into the output location `{output_location:?}`")
183 })?;
184 }
185 OutputFormat::Zip => {
186 zip_binary(binary, bootstrap_dir, &data, build.include.clone())?;
187 }
188 }
189 }
190 }
191 if !found_binaries {
192 warn!(
193 ?base,
194 "no binaries found in target directory after build, try using the --bin, --example, or --package options to build specific binaries"
195 );
196 }
197
198 Ok(())
199}
200
201fn downcasted_user_cancellation(err: &Report) -> bool {
202 match err.root_cause().downcast_ref::<InquireError>() {
203 Some(err) => is_user_cancellation_error(err),
204 None => false,
205 }
206}
207
208fn is_release_profile(build: &Build) -> bool {
209 build.cargo_opts.release
210 || build
211 .cargo_opts
212 .profile
213 .as_deref()
214 .is_some_and(|p| p == "release")
215}