use std::{env, process::ExitStatus};
use axoprocess::Cmd;
use axoproject::WorkspaceIdx;
use camino::{Utf8Path, Utf8PathBuf};
use cargo_dist_schema::{DistManifest, TripleName, TripleNameRef};
use crate::{
build::{package_id_string, BuildExpectations},
copy_file,
env::{calculate_cflags, calculate_ldflags, fetch_brew_env, parse_env, select_brew_env},
ArtifactKind, BinaryIdx, BuildStep, DistError, DistGraph, DistGraphBuilder, DistResult,
ExtraBuildStep, GenericBuildStep, SortedMap,
};
impl<'a> DistGraphBuilder<'a> {
pub(crate) fn compute_generic_builds(
&mut self,
workspace_idx: WorkspaceIdx,
) -> DistResult<Vec<BuildStep>> {
let mut targets = SortedMap::<TripleName, Vec<BinaryIdx>>::new();
for (binary_idx, binary) in self.inner.binaries.iter().enumerate() {
if self.workspaces.workspace_for_package(binary.pkg_idx) != workspace_idx {
continue;
}
if !binary.copy_exe_to.is_empty() || !binary.copy_symbols_to.is_empty() {
targets
.entry(binary.target.clone())
.or_default()
.push(BinaryIdx(binary_idx));
}
}
let mut builds = vec![];
for (target, binaries) in targets {
let mut builds_by_pkg_idx = SortedMap::new();
for bin_idx in binaries {
let bin = self.binary(bin_idx);
builds_by_pkg_idx
.entry(bin.pkg_idx)
.or_insert(vec![])
.push(bin_idx);
}
for (pkg_idx, expected_binaries) in builds_by_pkg_idx {
let package = self.workspaces.package(pkg_idx);
let out_dir = if let Some(dir) = package.out_dir.as_ref() {
package.package_root.join(dir)
} else {
package.package_root.clone()
};
builds.push(BuildStep::Generic(GenericBuildStep {
target_triple: target.clone(),
expected_binaries,
working_dir: package.package_root.clone(),
out_dir,
build_command: package
.build_command
.clone()
.expect("A build command is mandatory for non-cargo builds"),
}));
}
}
if !builds.is_empty() {
let options = cargo_only_options(&self.inner);
if !options.is_empty() {
return Err(DistError::CargoOnlyBuildOptions { options });
}
}
Ok(builds)
}
pub(crate) fn compute_extra_builds(&mut self) -> Vec<BuildStep> {
let extra_artifacts = self.inner.artifacts.iter().filter_map(|artifact| {
if let ArtifactKind::ExtraArtifact(extra) = &artifact.kind {
Some(extra)
} else {
None
}
});
let mut by_command = SortedMap::<(Utf8PathBuf, Vec<String>), Vec<Utf8PathBuf>>::new();
for extra in extra_artifacts {
by_command
.entry((extra.working_dir.clone(), extra.command.clone()))
.or_default()
.push(extra.artifact_relpath.clone())
}
by_command
.into_iter()
.map(|((working_dir, build_command), expected_artifacts)| {
BuildStep::Extra(ExtraBuildStep {
working_dir,
build_command,
artifact_relpaths: expected_artifacts,
})
})
.collect()
}
}
fn platform_appropriate_cc(target: &TripleNameRef) -> &str {
if target.is_darwin() {
"clang"
} else if target.is_linux() {
"gcc"
} else if target.is_windows() {
"cl.exe"
} else {
"cc"
}
}
fn platform_appropriate_cxx(target: &TripleNameRef) -> &str {
if target.is_darwin() {
"clang++"
} else if target.is_linux() {
"g++"
} else if target.is_windows() {
"cl.exe"
} else {
"c++"
}
}
fn cargo_only_options(dist_graph: &DistGraph) -> Vec<String> {
let mut out = vec![];
if dist_graph.config.builds.cargo.cargo_auditable {
out.push("cargo-auditable".to_owned());
}
if dist_graph.config.builds.cargo.cargo_cyclonedx {
out.push("cargo-cyclonedx".to_owned());
}
out
}
fn run_build(
dist_graph: &DistGraph,
build_command: &[String],
working_dir: &Utf8Path,
target: Option<&TripleName>,
) -> DistResult<ExitStatus> {
let mut command_string = build_command.to_owned();
let mut desired_extra_env = vec![];
let mut cflags = None;
let mut ldflags = None;
let skip_brewfile = env::var("DO_NOT_USE_BREWFILE").is_ok();
if !skip_brewfile {
if let Some(env_output) = fetch_brew_env(dist_graph, working_dir)? {
let brew_env = parse_env(&env_output)?;
desired_extra_env = select_brew_env(&brew_env);
cflags = Some(calculate_cflags(&brew_env));
ldflags = Some(calculate_ldflags(&brew_env));
}
}
let args = command_string.split_off(1);
let command_name = command_string
.first()
.expect("The build command must contain at least one entry");
let mut command = Cmd::new(command_name, format!("exec build: {command_name}"));
command.current_dir(working_dir);
command.stdout_to_stderr();
for arg in args {
command.arg(arg);
}
command.envs(desired_extra_env);
if let Some(target) = target {
command.env("CARGO_DIST_TARGET", target.as_str());
let cc = std::env::var("CC").unwrap_or(platform_appropriate_cc(target).to_owned());
command.env("CC", cc);
let cxx = std::env::var("CXX").unwrap_or(platform_appropriate_cxx(target).to_owned());
command.env("CXX", cxx);
}
if let Some(cflags) = cflags {
command.env("CFLAGS", &cflags);
command.env("CPPFLAGS", &cflags);
}
if let Some(ldflags) = ldflags {
command.env("LDFLAGS", &ldflags);
}
Ok(command.status()?)
}
pub fn build_generic_target(
dist_graph: &DistGraph,
manifest: &mut DistManifest,
target: &GenericBuildStep,
) -> DistResult<()> {
eprintln!(
"building target ({} via {})",
target.target_triple,
target.build_command.join(" ")
);
let result = run_build(
dist_graph,
&target.build_command,
&target.working_dir,
Some(&target.target_triple),
)?;
if !result.success() {
eprintln!("Build exited non-zero: {}", result);
}
let mut expected = BuildExpectations::new(dist_graph, &target.expected_binaries);
for binary_idx in &target.expected_binaries {
let binary = dist_graph.binary(*binary_idx);
let src_path = target.out_dir.join(&binary.file_name);
expected.found_bins(package_id_string(binary.pkg_id.as_ref()), vec![src_path]);
}
expected.process_bins(dist_graph, manifest)?;
Ok(())
}
pub fn run_extra_artifacts_build(dist: &DistGraph, build: &ExtraBuildStep) -> DistResult<()> {
eprintln!(
"building extra artifacts target (via {})",
build.build_command.join(" ")
);
let result = run_build(dist, &build.build_command, &build.working_dir, None)?;
if !result.success() {
eprintln!("Build exited non-zero: {}", result);
}
for artifact_relpath in &build.artifact_relpaths {
let artifact_name = artifact_relpath.file_name().unwrap();
let src_path = build.working_dir.join(artifact_relpath);
let dest_path = dist.dist_dir.join(artifact_name);
if src_path.exists() {
copy_file(&src_path, &dest_path)?;
} else {
return Err(DistError::MissingBinaries {
pkg_name: "extra build".to_owned(),
bin_name: artifact_name.to_owned(),
});
}
}
Ok(())
}