use crate::config::{config_from_metadata, ConfigError};
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 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 = binary_target_names(cargo_metadata);
let config = config_from_metadata(cargo_metadata).map_err(BuildBinariesError::ConfigError)?;
let buildpack_target_binary_path = if binary_target_names.contains(&config.buildpack_target) {
build_binary(
project_path.as_ref(),
cargo_metadata,
cargo_profile,
cargo_env.to_owned(),
target_triple.as_ref(),
&config.buildpack_target,
)
.map_err(|error| BuildBinariesError::BuildError(config.buildpack_target.clone(), error))
} else {
Err(BuildBinariesError::MissingBuildpackTarget(
config.buildpack_target.clone(),
))
}?;
let mut additional_target_binary_paths = HashMap::new();
for additional_binary_target_name in binary_target_names
.iter()
.filter(|name| *name != &config.buildpack_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,
})
}
pub 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::IoError)?;
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 struct BuildpackBinaries {
pub buildpack_target_binary_path: PathBuf,
pub additional_target_binary_paths: HashMap<String, PathBuf>,
}
#[derive(Debug)]
pub enum BuildError {
IoError(std::io::Error),
UnexpectedCargoExitStatus(ExitStatus),
}
#[derive(Debug)]
pub enum BuildBinariesError {
ConfigError(ConfigError),
BuildError(String, BuildError),
MissingBuildpackTarget(String),
}
fn binary_target_names(cargo_metadata: &Metadata) -> Vec<String> {
cargo_metadata
.root_package()
.map(|root_package| {
root_package
.targets
.iter()
.filter_map(|target| {
if target.kind.contains(&String::from("bin")) {
Some(target.name.clone())
} else {
None
}
})
.collect()
})
.unwrap_or_default()
}