use std::fs::File;
use std::path::PathBuf;
use std::process::Stdio;
use anyhow::Context;
use camino::Utf8PathBuf;
use clap::Parser;
use itertools::Itertools;
use crate::cli::Result;
use crate::config::Config;
use crate::metadata::{Cargo, CargoUpdateRequest, TreeResolver};
use crate::splicing::{
generate_lockfile, Splicer, SplicerKind, SplicingManifest, WorkspaceMetadata,
};
#[derive(Parser, Debug)]
#[clap(about = "Command line options for the `splice` subcommand", version)]
pub struct SpliceOptions {
#[clap(long)]
pub splicing_manifest: PathBuf,
#[clap(long)]
pub cargo_lockfile: Option<PathBuf>,
#[clap(long, env = "CARGO_BAZEL_REPIN", num_args=0..=1, default_missing_value = "true")]
pub repin: Option<CargoUpdateRequest>,
#[clap(long)]
pub workspace_dir: Option<Utf8PathBuf>,
#[clap(long)]
pub output_dir: PathBuf,
#[clap(long)]
pub dry_run: bool,
#[clap(long)]
pub cargo_config: Option<PathBuf>,
#[clap(long)]
pub config: PathBuf,
#[clap(long, env = "CARGO")]
pub cargo: PathBuf,
#[clap(long, env = "RUSTC")]
pub rustc: PathBuf,
#[clap(long)]
pub repository_name: String,
#[clap(long)]
pub skip_cargo_lockfile_overwrite: bool,
#[clap(long)]
pub nonhermetic_root_bazel_workspace_dir: Utf8PathBuf,
}
pub fn splice(opt: SpliceOptions) -> Result<()> {
let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)
.context("Failed to parse splicing manifest")?;
let temp_dir;
let splicing_dir = match &opt.workspace_dir {
Some(dir) => dir.clone(),
None => {
temp_dir = tempfile::tempdir().context("Failed to generate temporary directory")?;
Utf8PathBuf::from_path_buf(temp_dir.as_ref().to_path_buf())
.unwrap_or_else(|path| panic!("Temporary directory wasn't valid UTF-8: {:?}", path))
}
};
let splicer = Splicer::new(splicing_dir.clone(), splicing_manifest)?;
let prepared_splicer = splicer.prepare()?;
let cargo = Cargo::new(opt.cargo, opt.rustc.clone());
let manifest_path = prepared_splicer
.splice(&splicing_dir, &opt.nonhermetic_root_bazel_workspace_dir)
.with_context(|| format!("Failed to splice workspace {}", opt.repository_name))?;
let cargo_lockfile = if let Some(cargo_lockfile_path) = opt
.cargo_lockfile
.as_ref()
.filter(|_| opt.skip_cargo_lockfile_overwrite)
{
cargo_lock::Lockfile::load(cargo_lockfile_path).context(format!(
"Failed to load lockfile: {}",
cargo_lockfile_path.display()
))?
} else {
generate_lockfile(
&manifest_path,
&opt.cargo_lockfile,
cargo.clone(),
&opt.repin,
)
.context("Failed to generate lockfile")?
};
let config = Config::try_from_path(&opt.config).context("Failed to parse config")?;
let resolver_data = TreeResolver::new(cargo.clone())
.generate(
manifest_path.as_path_buf(),
&config.supported_platform_triples,
)
.context("Failed to generate features")?;
WorkspaceMetadata::write_registry_urls_and_feature_map(
&cargo,
&cargo_lockfile,
resolver_data,
manifest_path.as_path_buf(),
manifest_path.as_path_buf(),
)
.context("Failed to write registry URLs and feature map")?;
std::fs::create_dir_all(&opt.output_dir).with_context(|| {
format!(
"Failed to create directories for {}",
opt.output_dir.display()
)
})?;
let metadata_json = File::create(opt.output_dir.join("metadata.json"))?;
cargo
.metadata_command_with_options(
manifest_path.as_path_buf().as_ref(),
vec!["--locked".to_owned()],
)?
.cargo_command()
.stdout(Stdio::from(metadata_json))
.stderr(Stdio::null())
.status()
.context("Failed to generate cargo metadata")?;
let cargo_lockfile_path = manifest_path
.as_path_buf()
.parent()
.with_context(|| {
format!(
"The path {} is expected to have a parent directory",
manifest_path.as_path_buf()
)
})?
.join("Cargo.lock");
std::fs::copy(cargo_lockfile_path, opt.output_dir.join("Cargo.lock"))
.context("Failed to copy lockfile")?;
if let SplicerKind::Workspace { path, .. } = prepared_splicer {
let metadata = cargo.metadata_command_with_options(
path.as_std_path(),
vec![String::from("--no-deps")],
)?.exec().with_context(|| {
format!(
"Error spawning cargo in child process to compute crate paths for workspace '{}'",
path
)
})?;
let contents = metadata
.packages
.into_iter()
.map(|package| package.manifest_path)
.join("\n");
std::fs::write(opt.output_dir.join("extra_paths_to_track"), contents)?;
}
Ok(())
}