cargo_bazel/cli/
splice.rs

1//! The cli entrypoint for the `splice` subcommand
2
3use std::path::PathBuf;
4
5use anyhow::Context;
6use camino::Utf8PathBuf;
7use clap::Parser;
8
9use crate::cli::Result;
10use crate::config::Config;
11use crate::metadata::{
12    write_metadata, Cargo, CargoUpdateRequest, Generator, MetadataGenerator, TreeResolver,
13};
14use crate::splicing::{generate_lockfile, Splicer, SplicingManifest, WorkspaceMetadata};
15
16/// Command line options for the `splice` subcommand
17#[derive(Parser, Debug)]
18#[clap(about = "Command line options for the `splice` subcommand", version)]
19pub struct SpliceOptions {
20    /// A generated manifest of splicing inputs
21    #[clap(long)]
22    pub splicing_manifest: PathBuf,
23
24    /// The path to a [Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html) file.
25    #[clap(long)]
26    pub cargo_lockfile: Option<PathBuf>,
27
28    /// The desired update/repin behavior
29    #[clap(long, env = "CARGO_BAZEL_REPIN", num_args=0..=1, default_missing_value = "true")]
30    pub repin: Option<CargoUpdateRequest>,
31
32    /// The directory in which to build the workspace. If this argument is not
33    /// passed, a temporary directory will be generated.
34    #[clap(long)]
35    pub workspace_dir: Option<Utf8PathBuf>,
36
37    /// The location where the results of splicing are written.
38    #[clap(long)]
39    pub output_dir: PathBuf,
40
41    /// If true, outputs will be printed instead of written to disk.
42    #[clap(long)]
43    pub dry_run: bool,
44
45    /// The path to a Cargo configuration file.
46    #[clap(long)]
47    pub cargo_config: Option<PathBuf>,
48
49    /// The path to the config file (containing [crate::config::Config].)
50    #[clap(long)]
51    pub config: PathBuf,
52
53    /// The path to a Cargo binary to use for gathering metadata
54    #[clap(long, env = "CARGO")]
55    pub cargo: PathBuf,
56
57    /// The path to a rustc binary for use with Cargo
58    #[clap(long, env = "RUSTC")]
59    pub rustc: PathBuf,
60
61    #[clap(long)]
62    pub nonhermetic_root_bazel_workspace_dir: PathBuf,
63}
64
65/// Combine a set of disjoint manifests into a single workspace.
66pub fn splice(opt: SpliceOptions) -> Result<()> {
67    // Load the all config files required for splicing a workspace
68    let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)
69        .context("Failed to parse splicing manifest")?;
70
71    // Determine the splicing workspace
72    let temp_dir;
73    let splicing_dir = match &opt.workspace_dir {
74        Some(dir) => dir.clone(),
75        None => {
76            temp_dir = tempfile::tempdir().context("Failed to generate temporary directory")?;
77            Utf8PathBuf::from_path_buf(temp_dir.as_ref().to_path_buf())
78                .unwrap_or_else(|path| panic!("Temporary directory wasn't valid UTF-8: {:?}", path))
79        }
80    };
81
82    // Generate a splicer for creating a Cargo workspace manifest
83    let splicer = Splicer::new(splicing_dir, splicing_manifest)?;
84
85    let cargo = Cargo::new(opt.cargo, opt.rustc.clone());
86
87    // Splice together the manifest
88    let manifest_path = splicer
89        .splice_workspace(&opt.nonhermetic_root_bazel_workspace_dir)
90        .context("Failed to splice workspace")?;
91
92    // Generate a lockfile
93    let cargo_lockfile = generate_lockfile(
94        &manifest_path,
95        &opt.cargo_lockfile,
96        cargo.clone(),
97        &opt.repin,
98    )
99    .context("Failed to generate lockfile")?;
100
101    let config = Config::try_from_path(&opt.config).context("Failed to parse config")?;
102
103    let resolver_data = TreeResolver::new(cargo.clone())
104        .generate(
105            manifest_path.as_path_buf(),
106            &config.supported_platform_triples,
107        )
108        .context("Failed to generate features")?;
109    // Write the registry url info to the manifest now that a lockfile has been generated
110    WorkspaceMetadata::write_registry_urls_and_feature_map(
111        &cargo,
112        &cargo_lockfile,
113        resolver_data,
114        manifest_path.as_path_buf(),
115        manifest_path.as_path_buf(),
116    )
117    .context("Failed to write registry URLs and feature map")?;
118
119    let output_dir = opt.output_dir.clone();
120
121    // Write metadata to the workspace for future reuse
122    let (cargo_metadata, _) = Generator::new()
123        .with_cargo(cargo)
124        .with_rustc(opt.rustc)
125        .generate(manifest_path.as_path_buf())
126        .context("Failed to generate cargo metadata")?;
127
128    let cargo_lockfile_path = manifest_path
129        .as_path_buf()
130        .parent()
131        .with_context(|| {
132            format!(
133                "The path {} is expected to have a parent directory",
134                manifest_path.as_path_buf()
135            )
136        })?
137        .join("Cargo.lock");
138
139    // Generate the consumable outputs of the splicing process
140    std::fs::create_dir_all(&output_dir)
141        .with_context(|| format!("Failed to create directories for {}", &output_dir.display()))?;
142
143    write_metadata(&opt.output_dir.join("metadata.json"), &cargo_metadata)
144        .context("Failed to write metadata")?;
145
146    std::fs::copy(cargo_lockfile_path, output_dir.join("Cargo.lock"))
147        .context("Failed to copy lockfile")?;
148
149    Ok(())
150}