cargo_bazel/cli/
splice.rs1use std::fs::File;
4use std::path::PathBuf;
5use std::process::Stdio;
6
7use anyhow::Context;
8use camino::Utf8PathBuf;
9use clap::Parser;
10use itertools::Itertools;
11
12use crate::cli::Result;
13use crate::config::Config;
14use crate::metadata::{Cargo, CargoUpdateRequest, TreeResolver};
15use crate::splicing::{
16 generate_lockfile, Splicer, SplicerKind, SplicingManifest, WorkspaceMetadata,
17};
18
19#[derive(Parser, Debug)]
21#[clap(about = "Command line options for the `splice` subcommand", version)]
22pub struct SpliceOptions {
23 #[clap(long)]
25 pub splicing_manifest: PathBuf,
26
27 #[clap(long)]
29 pub cargo_lockfile: Option<PathBuf>,
30
31 #[clap(long, env = "CARGO_BAZEL_REPIN", num_args=0..=1, default_missing_value = "true")]
33 pub repin: Option<CargoUpdateRequest>,
34
35 #[clap(long)]
38 pub workspace_dir: Option<Utf8PathBuf>,
39
40 #[clap(long)]
42 pub output_dir: PathBuf,
43
44 #[clap(long)]
46 pub dry_run: bool,
47
48 #[clap(long)]
50 pub cargo_config: Option<PathBuf>,
51
52 #[clap(long)]
54 pub config: PathBuf,
55
56 #[clap(long, env = "CARGO")]
58 pub cargo: PathBuf,
59
60 #[clap(long, env = "RUSTC")]
62 pub rustc: PathBuf,
63
64 #[clap(long)]
66 pub repository_name: String,
67
68 #[clap(long)]
72 pub skip_cargo_lockfile_overwrite: bool,
73
74 #[clap(long)]
79 pub nonhermetic_root_bazel_workspace_dir: Utf8PathBuf,
80}
81
82pub fn splice(opt: SpliceOptions) -> Result<()> {
84 let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)
86 .context("Failed to parse splicing manifest")?;
87
88 let temp_dir;
90 let splicing_dir = match &opt.workspace_dir {
91 Some(dir) => dir.clone(),
92 None => {
93 temp_dir = tempfile::tempdir().context("Failed to generate temporary directory")?;
94 Utf8PathBuf::from_path_buf(temp_dir.as_ref().to_path_buf())
95 .unwrap_or_else(|path| panic!("Temporary directory wasn't valid UTF-8: {:?}", path))
96 }
97 };
98
99 let splicer = Splicer::new(splicing_dir.clone(), splicing_manifest)?;
101 let prepared_splicer = splicer.prepare()?;
102
103 let cargo = Cargo::new(opt.cargo, opt.rustc.clone());
104
105 let manifest_path = prepared_splicer
107 .splice(&splicing_dir, &opt.nonhermetic_root_bazel_workspace_dir)
108 .with_context(|| format!("Failed to splice workspace {}", opt.repository_name))?;
109
110 let cargo_lockfile = if let Some(cargo_lockfile_path) = opt
112 .cargo_lockfile
113 .as_ref()
114 .filter(|_| opt.skip_cargo_lockfile_overwrite)
115 {
116 cargo_lock::Lockfile::load(cargo_lockfile_path).context(format!(
117 "Failed to load lockfile: {}",
118 cargo_lockfile_path.display()
119 ))?
120 } else {
121 generate_lockfile(
122 &manifest_path,
123 &opt.cargo_lockfile,
124 cargo.clone(),
125 &opt.repin,
126 )
127 .context("Failed to generate lockfile")?
128 };
129
130 let config = Config::try_from_path(&opt.config).context("Failed to parse config")?;
131
132 let resolver_data = TreeResolver::new(cargo.clone())
133 .generate(
134 manifest_path.as_path_buf(),
135 &config.supported_platform_triples,
136 )
137 .context("Failed to generate features")?;
138
139 WorkspaceMetadata::write_registry_urls_and_feature_map(
141 &cargo,
142 &cargo_lockfile,
143 resolver_data,
144 manifest_path.as_path_buf(),
145 manifest_path.as_path_buf(),
146 )
147 .context("Failed to write registry URLs and feature map")?;
148
149 std::fs::create_dir_all(&opt.output_dir).with_context(|| {
151 format!(
152 "Failed to create directories for {}",
153 opt.output_dir.display()
154 )
155 })?;
156
157 let metadata_json = File::create(opt.output_dir.join("metadata.json"))?;
158
159 cargo
161 .metadata_command_with_options(
162 manifest_path.as_path_buf().as_ref(),
163 vec!["--locked".to_owned()],
164 )?
165 .cargo_command()
166 .stdout(Stdio::from(metadata_json))
167 .stderr(Stdio::null())
168 .status()
169 .context("Failed to generate cargo metadata")?;
170
171 let cargo_lockfile_path = manifest_path
172 .as_path_buf()
173 .parent()
174 .with_context(|| {
175 format!(
176 "The path {} is expected to have a parent directory",
177 manifest_path.as_path_buf()
178 )
179 })?
180 .join("Cargo.lock");
181
182 std::fs::copy(cargo_lockfile_path, opt.output_dir.join("Cargo.lock"))
183 .context("Failed to copy lockfile")?;
184
185 if let SplicerKind::Workspace { path, .. } = prepared_splicer {
186 let metadata = cargo.metadata_command_with_options(
187 path.as_std_path(),
188 vec![String::from("--no-deps")],
189 )?.exec().with_context(|| {
190 format!(
191 "Error spawning cargo in child process to compute crate paths for workspace '{}'",
192 path
193 )
194 })?;
195 let contents = metadata
196 .packages
197 .into_iter()
198 .map(|package| package.manifest_path)
199 .join("\n");
200 std::fs::write(opt.output_dir.join("extra_paths_to_track"), contents)?;
201 }
202 Ok(())
203}