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