use std::env;
use std::path::PathBuf;
use std::process::{self, Command};
use anyhow::{Context, Result};
use clap::Parser;
use fs_err as fs;
use crate::macos::LIBICONV_TBD;
use crate::zig::prepare_zig_linker;
#[derive(Debug, Parser)]
#[clap(setting = clap::AppSettings::DeriveDisplayOrder, after_help = "Run `cargo help build` for more detailed information.")]
pub struct Build {
#[clap(short = 'q', long)]
pub quiet: bool,
#[clap(
short = 'p',
long = "package",
value_name = "SPEC",
multiple_values = true
)]
pub packages: Vec<String>,
#[clap(long)]
pub workspace: bool,
#[clap(long, value_name = "SPEC", multiple_values = true)]
pub exclude: Vec<String>,
#[clap(long)]
pub all: bool,
#[clap(short = 'j', long, value_name = "N")]
pub jobs: Option<usize>,
#[clap(long)]
pub lib: bool,
#[clap(long, value_name = "NAME", multiple_values = true)]
pub bin: Vec<String>,
#[clap(long)]
pub bins: bool,
#[clap(long, value_name = "NAME", multiple_values = true)]
pub example: Vec<String>,
#[clap(long)]
pub examples: bool,
#[clap(long, value_name = "NAME", multiple_values = true)]
pub test: Vec<String>,
#[clap(long)]
pub tests: bool,
#[clap(long, value_name = "NAME", multiple_values = true)]
pub bench: Vec<String>,
#[clap(long)]
pub benches: bool,
#[clap(long)]
pub all_targets: bool,
#[clap(short = 'r', long)]
pub release: bool,
#[clap(long, value_name = "PROFILE-NAME")]
pub profile: Option<String>,
#[clap(long, multiple_values = true)]
pub features: Vec<String>,
#[clap(long)]
pub all_features: bool,
#[clap(long)]
pub no_default_features: bool,
#[clap(long, value_name = "TRIPLE", env = "CARGO_BUILD_TARGET")]
pub target: Option<String>,
#[clap(long, value_name = "DIRECTORY", parse(from_os_str))]
pub target_dir: Option<PathBuf>,
#[clap(long, value_name = "PATH", parse(from_os_str))]
pub out_dir: Option<PathBuf>,
#[clap(long, value_name = "PATH", parse(from_os_str))]
pub manifest_path: Option<PathBuf>,
#[clap(long)]
pub ignore_rust_version: bool,
#[clap(long, value_name = "FMT", multiple_values = true)]
pub message_format: Vec<String>,
#[clap(long)]
pub build_plan: bool,
#[clap(long)]
pub unit_graph: bool,
#[clap(long)]
pub future_incompat_report: bool,
#[clap(short = 'v', long, parse(from_occurrences), max_occurrences = 2)]
pub verbose: usize,
#[clap(long, value_name = "WHEN")]
pub color: Option<String>,
#[clap(long)]
pub frozen: bool,
#[clap(long)]
pub locked: bool,
#[clap(long)]
pub offline: bool,
#[clap(long, value_name = "KEY=VALUE", multiple_values = true)]
pub config: Vec<String>,
#[clap(short = 'Z', value_name = "FLAG", multiple_values = true)]
pub unstable_flags: Vec<String>,
}
impl Build {
pub fn execute(&self) -> Result<()> {
let mut build = Command::new("cargo");
build.arg("build");
let rust_target = self
.target
.as_ref()
.map(|target| target.split_once('.').map(|(t, _)| t).unwrap_or(target));
if self.quiet {
build.arg("--quiet");
}
for pkg in &self.packages {
build.arg("--package").arg(pkg);
}
if self.workspace {
build.arg("--workspace");
}
for item in &self.exclude {
build.arg("--excude").arg(item);
}
if self.all {
build.arg("--all");
}
if let Some(jobs) = self.jobs {
build.arg("--jobs").arg(jobs.to_string());
}
if self.lib {
build.arg("--lib");
}
for bin in &self.bin {
build.arg("--bin").arg(bin);
}
if self.bins {
build.arg("--bins");
}
for example in &self.example {
build.arg("--example").arg(example);
}
if self.examples {
build.arg("--examples");
}
for test in &self.test {
build.arg("--test").arg(test);
}
if self.tests {
build.arg("--tests");
}
for bench in &self.bench {
build.arg("--bench").arg(bench);
}
if self.benches {
build.arg("--benches");
}
if self.all_targets {
build.arg("--all-targets");
}
if self.release {
build.arg("--release");
}
if let Some(profile) = self.profile.as_ref() {
build.arg("--profile").arg(profile);
}
for feature in &self.features {
build.arg("--features").arg(feature);
}
if self.all_features {
build.arg("--all-features");
}
if self.no_default_features {
build.arg("--no-default-features");
}
if let Some(rust_target) = rust_target {
build.arg("--target").arg(&rust_target);
}
if let Some(dir) = self.target_dir.as_ref() {
build.arg("--target-dir").arg(dir);
}
if let Some(dir) = self.out_dir.as_ref() {
build.arg("--out-dir").arg(dir);
}
if let Some(path) = self.manifest_path.as_ref() {
build.arg("--manifest-path").arg(path);
}
if self.ignore_rust_version {
build.arg("--ignore-rust-version");
}
for fmt in &self.message_format {
build.arg("--message-format").arg(fmt);
}
if self.build_plan {
build.arg("--build-plan");
}
if self.unit_graph {
build.arg("--unit-graph");
}
if self.future_incompat_report {
build.arg("--future-incompat-report");
}
if self.verbose > 0 {
build.arg(format!("-{}", "v".repeat(self.verbose)));
}
if let Some(color) = self.color.as_ref() {
build.arg("--color").arg(color);
}
if self.frozen {
build.arg("--frozen");
}
if self.locked {
build.arg("--locked");
}
if self.offline {
build.arg("--offline");
}
for config in &self.config {
build.arg("--config").arg(config);
}
for flag in &self.unstable_flags {
build.arg("-Z").arg(flag);
}
if let Some(target) = self.target.as_ref() {
let rustc_meta = rustc_version::version_meta()?;
let host_target = &rustc_meta.host;
if host_target != target {
if let Some(rust_target) = rust_target {
let (zig_cc, zig_cxx) = prepare_zig_linker(target)?;
let env_target = rust_target.to_uppercase().replace("-", "_");
build.env("TARGET_CC", &zig_cc);
build.env("TARGET_CXX", &zig_cxx);
build.env(format!("CARGO_TARGET_{}_LINKER", env_target), &zig_cc);
self.setup_os_deps()?;
if rust_target.contains("windows-gnu") {
build.env("WINAPI_NO_BUNDLED_LIBRARIES", "1");
}
if host_target == rust_target
&& matches!(rustc_meta.channel, rustc_version::Channel::Nightly)
{
build.env("CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST", "true");
build.env("CARGO_TARGET_APPLIES_TO_HOST", "false");
}
}
}
}
let mut child = build.spawn().context("Failed to run cargo build")?;
let status = child.wait().expect("Failed to wait on cargo build process");
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
Ok(())
}
fn setup_os_deps(&self) -> Result<()> {
if let Some(target) = self.target.as_ref() {
if target.contains("apple") {
let target_dir = self
.target_dir
.clone()
.or_else(|| {
self.manifest_path
.as_ref()
.map(|m| m.parent().unwrap().join("target"))
})
.unwrap_or_else(|| {
env::current_dir()
.expect("Failed to get current working directory")
.join("target")
})
.join(target);
let profile = match self.profile.as_deref() {
Some("dev" | "test") => "debug",
Some("release" | "bench") => "release",
Some(profile) => profile,
None => {
if self.release {
"release"
} else {
"debug"
}
}
};
let deps_dir = target_dir.join(profile).join("deps");
fs::create_dir_all(&deps_dir)?;
fs::write(deps_dir.join("libiconv.tbd"), LIBICONV_TBD)?;
}
}
Ok(())
}
}