use crate::cargo::{
cargo_binary_target_names, determine_buildpack_cargo_target_name,
DetermineBuildpackCargoTargetNameError,
};
use crate::CargoProfile;
use cargo_metadata::Metadata;
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
pub(crate) fn build_buildpack_binaries(
project_path: impl AsRef<Path>,
cargo_metadata: &Metadata,
cargo_profile: CargoProfile,
cargo_env: &[(OsString, OsString)],
target_triple: impl AsRef<str>,
) -> Result<BuildpackBinaries, BuildBinariesError> {
let binary_target_names = cargo_binary_target_names(cargo_metadata);
let buildpack_cargo_target = determine_buildpack_cargo_target_name(cargo_metadata)
.map_err(BuildBinariesError::CannotDetermineBuildpackCargoTargetName)?;
let buildpack_target_binary_path = if binary_target_names.contains(&buildpack_cargo_target) {
build_binary(
project_path.as_ref(),
cargo_metadata,
cargo_profile,
cargo_env.to_owned(),
target_triple.as_ref(),
&buildpack_cargo_target,
)
.map_err(|error| BuildBinariesError::BuildError(buildpack_cargo_target.clone(), error))
} else {
Err(BuildBinariesError::MissingBuildpackTarget(
buildpack_cargo_target.clone(),
))
}?;
let mut additional_target_binary_paths = HashMap::new();
for additional_binary_target_name in binary_target_names
.iter()
.filter(|name| *name != &buildpack_cargo_target)
{
additional_target_binary_paths.insert(
additional_binary_target_name.clone(),
build_binary(
project_path.as_ref(),
cargo_metadata,
cargo_profile,
cargo_env.to_owned(),
target_triple.as_ref(),
additional_binary_target_name,
)
.map_err(|error| {
BuildBinariesError::BuildError(additional_binary_target_name.clone(), error)
})?,
);
}
Ok(BuildpackBinaries {
buildpack_target_binary_path,
additional_target_binary_paths,
})
}
fn build_binary(
project_path: impl AsRef<Path>,
cargo_metadata: &Metadata,
cargo_profile: CargoProfile,
mut cargo_env: Vec<(OsString, OsString)>,
target_triple: impl AsRef<str>,
target_name: impl AsRef<str>,
) -> Result<PathBuf, BuildError> {
let mut cargo_args = vec!["build", "--target", target_triple.as_ref()];
match cargo_profile {
CargoProfile::Dev => {
cargo_env.append(&mut vec![
(
OsString::from("CARGO_PROFILE_DEV_DEBUG"),
OsString::from("false"),
),
(
OsString::from("CARGO_PROFILE_DEV_STRIP"),
OsString::from("true"),
),
]);
}
CargoProfile::Release => {
cargo_args.push("--release");
cargo_env.push((
OsString::from("CARGO_PROFILE_RELEASE_STRIP"),
OsString::from("true"),
));
}
}
let exit_status = Command::new("cargo")
.args(cargo_args)
.envs(cargo_env)
.current_dir(&project_path)
.spawn()
.and_then(|mut child| child.wait())
.map_err(BuildError::CargoProcessIoError)?;
if exit_status.success() {
let binary_path = cargo_metadata
.target_directory
.join(target_triple.as_ref())
.join(match cargo_profile {
CargoProfile::Dev => "debug",
CargoProfile::Release => "release",
})
.join(target_name.as_ref())
.into_std_path_buf();
Ok(binary_path)
} else {
Err(BuildError::UnexpectedCargoExitStatus(exit_status))
}
}
#[derive(Debug)]
pub(crate) struct BuildpackBinaries {
pub(crate) buildpack_target_binary_path: PathBuf,
pub(crate) additional_target_binary_paths: HashMap<String, PathBuf>,
}
#[derive(thiserror::Error, Debug)]
pub enum BuildError {
#[error("I/O error while running Cargo build process: {0}")]
CargoProcessIoError(#[source] std::io::Error),
#[error("Cargo unexpectedly exited with status {0}")]
UnexpectedCargoExitStatus(ExitStatus),
}
#[derive(thiserror::Error, Debug)]
pub enum BuildBinariesError {
#[error("Failed to determine Cargo target name for buildpack: {0}")]
CannotDetermineBuildpackCargoTargetName(#[source] DetermineBuildpackCargoTargetNameError),
#[error("Failed to build binary target {0}: {1}")]
BuildError(String, #[source] BuildError),
#[error("Binary target {0} couldn't be found")]
MissingBuildpackTarget(String),
}