1use crate::CargoProfile;
2use crate::cargo::{
3 DetermineBuildpackCargoTargetNameError, cargo_binary_target_names,
4 determine_buildpack_cargo_target_name,
5};
6use cargo_metadata::Metadata;
7use std::collections::HashMap;
8use std::env;
9use std::ffi::OsString;
10use std::path::{Path, PathBuf};
11use std::process::{Command, ExitStatus};
12
13pub(crate) fn build_buildpack_binaries(
25 project_path: impl AsRef<Path>,
26 cargo_metadata: &Metadata,
27 cargo_profile: CargoProfile,
28 cargo_env: &[(OsString, OsString)],
29 target_triple: impl AsRef<str>,
30) -> Result<BuildpackBinaries, BuildBinariesError> {
31 let binary_target_names = cargo_binary_target_names(cargo_metadata);
32 let buildpack_cargo_target = determine_buildpack_cargo_target_name(cargo_metadata)
33 .map_err(BuildBinariesError::CannotDetermineBuildpackCargoTargetName)?;
34
35 let buildpack_target_binary_path = if binary_target_names.contains(&buildpack_cargo_target) {
36 build_binary(
37 project_path.as_ref(),
38 cargo_metadata,
39 cargo_profile,
40 cargo_env.to_owned(),
41 target_triple.as_ref(),
42 &buildpack_cargo_target,
43 )
44 .map_err(|error| BuildBinariesError::BuildError(buildpack_cargo_target.clone(), error))
45 } else {
46 Err(BuildBinariesError::MissingBuildpackTarget(
47 buildpack_cargo_target.clone(),
48 ))
49 }?;
50
51 let mut additional_target_binary_paths = HashMap::new();
52 for additional_binary_target_name in binary_target_names
53 .iter()
54 .filter(|name| *name != &buildpack_cargo_target)
55 {
56 additional_target_binary_paths.insert(
57 additional_binary_target_name.clone(),
58 build_binary(
59 project_path.as_ref(),
60 cargo_metadata,
61 cargo_profile,
62 cargo_env.to_owned(),
63 target_triple.as_ref(),
64 additional_binary_target_name,
65 )
66 .map_err(|error| {
67 BuildBinariesError::BuildError(additional_binary_target_name.clone(), error)
68 })?,
69 );
70 }
71
72 Ok(BuildpackBinaries {
73 buildpack_target_binary_path,
74 additional_target_binary_paths,
75 })
76}
77
78fn build_binary(
100 project_path: impl AsRef<Path>,
101 cargo_metadata: &Metadata,
102 cargo_profile: CargoProfile,
103 mut cargo_env: Vec<(OsString, OsString)>,
104 target_triple: impl AsRef<str>,
105 target_name: impl AsRef<str>,
106) -> Result<PathBuf, BuildError> {
107 let mut cargo_args = vec!["build", "--target", target_triple.as_ref()];
108
109 if env::var_os("CI").is_some() {
110 cargo_args.push("--locked");
111 }
112
113 match cargo_profile {
114 CargoProfile::Dev => {
115 cargo_env.append(&mut vec![
122 (
123 OsString::from("CARGO_PROFILE_DEV_DEBUG"),
124 OsString::from("false"),
125 ),
126 (
127 OsString::from("CARGO_PROFILE_DEV_STRIP"),
128 OsString::from("true"),
129 ),
130 ]);
131 }
132 CargoProfile::Release => {
133 cargo_args.push("--release");
134 cargo_env.push((
135 OsString::from("CARGO_PROFILE_RELEASE_STRIP"),
136 OsString::from("true"),
137 ));
138 }
139 }
140
141 let exit_status = Command::new("cargo")
142 .args(cargo_args)
143 .envs(cargo_env)
144 .current_dir(&project_path)
145 .spawn()
146 .and_then(|mut child| child.wait())
147 .map_err(BuildError::CargoProcessIoError)?;
148
149 if exit_status.success() {
150 let binary_path = cargo_metadata
151 .target_directory
152 .join(target_triple.as_ref())
153 .join(match cargo_profile {
154 CargoProfile::Dev => "debug",
155 CargoProfile::Release => "release",
156 })
157 .join(target_name.as_ref())
158 .into_std_path_buf();
159
160 Ok(binary_path)
161 } else {
162 Err(BuildError::UnexpectedCargoExitStatus(exit_status))
163 }
164}
165
166#[derive(Debug)]
167pub(crate) struct BuildpackBinaries {
168 pub(crate) buildpack_target_binary_path: PathBuf,
170 pub(crate) additional_target_binary_paths: HashMap<String, PathBuf>,
172}
173
174#[derive(thiserror::Error, Debug)]
175pub enum BuildError {
176 #[error("I/O error while running Cargo build process: {0}")]
177 CargoProcessIoError(#[source] std::io::Error),
178 #[error("Cargo unexpectedly exited with status {0}")]
179 UnexpectedCargoExitStatus(ExitStatus),
180}
181
182#[derive(thiserror::Error, Debug)]
183pub enum BuildBinariesError {
184 #[error("Failed to determine Cargo target name for buildpack: {0}")]
185 CannotDetermineBuildpackCargoTargetName(#[source] DetermineBuildpackCargoTargetNameError),
186 #[error("Failed to build binary target {0}: {1}")]
187 BuildError(String, #[source] BuildError),
188 #[error("Binary target {0} couldn't be found")]
189 MissingBuildpackTarget(String),
190}