xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/splicing.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan //! This module is responsible for finding a Cargo workspace
2*d4726bddSHONG Yifan 
3*d4726bddSHONG Yifan pub(crate) mod cargo_config;
4*d4726bddSHONG Yifan mod crate_index_lookup;
5*d4726bddSHONG Yifan mod splicer;
6*d4726bddSHONG Yifan 
7*d4726bddSHONG Yifan use std::collections::{BTreeMap, BTreeSet};
8*d4726bddSHONG Yifan use std::fs;
9*d4726bddSHONG Yifan use std::path::{Path, PathBuf};
10*d4726bddSHONG Yifan use std::str::FromStr;
11*d4726bddSHONG Yifan 
12*d4726bddSHONG Yifan use anyhow::{anyhow, bail, Context, Result};
13*d4726bddSHONG Yifan use cargo_lock::package::SourceKind;
14*d4726bddSHONG Yifan use cargo_toml::Manifest;
15*d4726bddSHONG Yifan use serde::{Deserialize, Serialize};
16*d4726bddSHONG Yifan 
17*d4726bddSHONG Yifan use crate::config::CrateId;
18*d4726bddSHONG Yifan use crate::metadata::{Cargo, CargoUpdateRequest, LockGenerator, TreeResolverMetadata};
19*d4726bddSHONG Yifan use crate::utils;
20*d4726bddSHONG Yifan use crate::utils::starlark::Label;
21*d4726bddSHONG Yifan 
22*d4726bddSHONG Yifan use self::cargo_config::CargoConfig;
23*d4726bddSHONG Yifan use self::crate_index_lookup::CrateIndexLookup;
24*d4726bddSHONG Yifan pub(crate) use self::splicer::*;
25*d4726bddSHONG Yifan 
26*d4726bddSHONG Yifan type DirectPackageManifest = BTreeMap<String, cargo_toml::DependencyDetail>;
27*d4726bddSHONG Yifan 
28*d4726bddSHONG Yifan /// A collection of information used for splicing together a new Cargo manifest.
29*d4726bddSHONG Yifan #[derive(Debug, Default, Serialize, Deserialize, Clone)]
30*d4726bddSHONG Yifan #[serde(deny_unknown_fields)]
31*d4726bddSHONG Yifan pub(crate) struct SplicingManifest {
32*d4726bddSHONG Yifan     /// A set of all packages directly written to the rule
33*d4726bddSHONG Yifan     pub(crate) direct_packages: DirectPackageManifest,
34*d4726bddSHONG Yifan 
35*d4726bddSHONG Yifan     /// A mapping of manifest paths to the labels representing them
36*d4726bddSHONG Yifan     pub(crate) manifests: BTreeMap<PathBuf, Label>,
37*d4726bddSHONG Yifan 
38*d4726bddSHONG Yifan     /// The path of a Cargo config file
39*d4726bddSHONG Yifan     pub(crate) cargo_config: Option<PathBuf>,
40*d4726bddSHONG Yifan 
41*d4726bddSHONG Yifan     /// The Cargo resolver version to use for splicing
42*d4726bddSHONG Yifan     pub(crate) resolver_version: cargo_toml::Resolver,
43*d4726bddSHONG Yifan }
44*d4726bddSHONG Yifan 
45*d4726bddSHONG Yifan impl FromStr for SplicingManifest {
46*d4726bddSHONG Yifan     type Err = serde_json::Error;
47*d4726bddSHONG Yifan 
from_str(s: &str) -> Result<Self, Self::Err>48*d4726bddSHONG Yifan     fn from_str(s: &str) -> Result<Self, Self::Err> {
49*d4726bddSHONG Yifan         serde_json::from_str(s)
50*d4726bddSHONG Yifan     }
51*d4726bddSHONG Yifan }
52*d4726bddSHONG Yifan 
53*d4726bddSHONG Yifan impl SplicingManifest {
try_from_path<T: AsRef<Path>>(path: T) -> Result<Self>54*d4726bddSHONG Yifan     pub(crate) fn try_from_path<T: AsRef<Path>>(path: T) -> Result<Self> {
55*d4726bddSHONG Yifan         let content = fs::read_to_string(path.as_ref())?;
56*d4726bddSHONG Yifan         Self::from_str(&content).context("Failed to load SplicingManifest")
57*d4726bddSHONG Yifan     }
58*d4726bddSHONG Yifan 
resolve(self, workspace_dir: &Path, output_base: &Path) -> Self59*d4726bddSHONG Yifan     pub(crate) fn resolve(self, workspace_dir: &Path, output_base: &Path) -> Self {
60*d4726bddSHONG Yifan         let Self {
61*d4726bddSHONG Yifan             manifests,
62*d4726bddSHONG Yifan             cargo_config,
63*d4726bddSHONG Yifan             ..
64*d4726bddSHONG Yifan         } = self;
65*d4726bddSHONG Yifan 
66*d4726bddSHONG Yifan         let workspace_dir_str = workspace_dir.to_string_lossy();
67*d4726bddSHONG Yifan         let output_base_str = output_base.to_string_lossy();
68*d4726bddSHONG Yifan 
69*d4726bddSHONG Yifan         // Ensure manifests all have absolute paths
70*d4726bddSHONG Yifan         let manifests = manifests
71*d4726bddSHONG Yifan             .into_iter()
72*d4726bddSHONG Yifan             .map(|(path, label)| {
73*d4726bddSHONG Yifan                 let resolved_path = path
74*d4726bddSHONG Yifan                     .to_string_lossy()
75*d4726bddSHONG Yifan                     .replace("${build_workspace_directory}", &workspace_dir_str)
76*d4726bddSHONG Yifan                     .replace("${output_base}", &output_base_str);
77*d4726bddSHONG Yifan                 (PathBuf::from(resolved_path), label)
78*d4726bddSHONG Yifan             })
79*d4726bddSHONG Yifan             .collect();
80*d4726bddSHONG Yifan 
81*d4726bddSHONG Yifan         // Ensure the cargo config is located at an absolute path
82*d4726bddSHONG Yifan         let cargo_config = cargo_config.map(|path| {
83*d4726bddSHONG Yifan             let resolved_path = path
84*d4726bddSHONG Yifan                 .to_string_lossy()
85*d4726bddSHONG Yifan                 .replace("${build_workspace_directory}", &workspace_dir_str)
86*d4726bddSHONG Yifan                 .replace("${output_base}", &output_base_str);
87*d4726bddSHONG Yifan             PathBuf::from(resolved_path)
88*d4726bddSHONG Yifan         });
89*d4726bddSHONG Yifan 
90*d4726bddSHONG Yifan         Self {
91*d4726bddSHONG Yifan             manifests,
92*d4726bddSHONG Yifan             cargo_config,
93*d4726bddSHONG Yifan             ..self
94*d4726bddSHONG Yifan         }
95*d4726bddSHONG Yifan     }
96*d4726bddSHONG Yifan }
97*d4726bddSHONG Yifan 
98*d4726bddSHONG Yifan /// The result of fully resolving a [SplicingManifest] in preparation for splicing.
99*d4726bddSHONG Yifan #[derive(Debug, Serialize, Default)]
100*d4726bddSHONG Yifan pub(crate) struct SplicingMetadata {
101*d4726bddSHONG Yifan     /// A set of all packages directly written to the rule
102*d4726bddSHONG Yifan     pub(crate) direct_packages: DirectPackageManifest,
103*d4726bddSHONG Yifan 
104*d4726bddSHONG Yifan     /// A mapping of manifest paths to the labels representing them
105*d4726bddSHONG Yifan     pub(crate) manifests: BTreeMap<Label, cargo_toml::Manifest>,
106*d4726bddSHONG Yifan 
107*d4726bddSHONG Yifan     /// The path of a Cargo config file
108*d4726bddSHONG Yifan     pub(crate) cargo_config: Option<CargoConfig>,
109*d4726bddSHONG Yifan }
110*d4726bddSHONG Yifan 
111*d4726bddSHONG Yifan impl TryFrom<SplicingManifest> for SplicingMetadata {
112*d4726bddSHONG Yifan     type Error = anyhow::Error;
113*d4726bddSHONG Yifan 
try_from(value: SplicingManifest) -> Result<Self, Self::Error>114*d4726bddSHONG Yifan     fn try_from(value: SplicingManifest) -> Result<Self, Self::Error> {
115*d4726bddSHONG Yifan         let direct_packages = value.direct_packages;
116*d4726bddSHONG Yifan 
117*d4726bddSHONG Yifan         let manifests = value
118*d4726bddSHONG Yifan             .manifests
119*d4726bddSHONG Yifan             .into_iter()
120*d4726bddSHONG Yifan             .map(|(path, label)| {
121*d4726bddSHONG Yifan                 // We read the content of a manifest file to buffer and use `from_slice` to
122*d4726bddSHONG Yifan                 // parse it. The reason is that the `from_path` version will resolve indirect
123*d4726bddSHONG Yifan                 // path dependencies in the workspace to absolute path, which causes the hash
124*d4726bddSHONG Yifan                 // to be unstable. Not resolving implicit data is okay here because the
125*d4726bddSHONG Yifan                 // workspace manifest is also included in the hash.
126*d4726bddSHONG Yifan                 // See https://github.com/bazelbuild/rules_rust/issues/2016
127*d4726bddSHONG Yifan                 let manifest_content = fs::read(&path)
128*d4726bddSHONG Yifan                     .with_context(|| format!("Failed to load manifest '{}'", path.display()))?;
129*d4726bddSHONG Yifan                 let manifest = cargo_toml::Manifest::from_slice(&manifest_content)
130*d4726bddSHONG Yifan                     .with_context(|| format!("Failed to parse manifest '{}'", path.display()))?;
131*d4726bddSHONG Yifan                 Ok((label, manifest))
132*d4726bddSHONG Yifan             })
133*d4726bddSHONG Yifan             .collect::<Result<BTreeMap<Label, Manifest>>>()?;
134*d4726bddSHONG Yifan 
135*d4726bddSHONG Yifan         let cargo_config = match value.cargo_config {
136*d4726bddSHONG Yifan             Some(path) => Some(
137*d4726bddSHONG Yifan                 CargoConfig::try_from_path(&path)
138*d4726bddSHONG Yifan                     .with_context(|| format!("Failed to load cargo config '{}'", path.display()))?,
139*d4726bddSHONG Yifan             ),
140*d4726bddSHONG Yifan             None => None,
141*d4726bddSHONG Yifan         };
142*d4726bddSHONG Yifan 
143*d4726bddSHONG Yifan         Ok(Self {
144*d4726bddSHONG Yifan             direct_packages,
145*d4726bddSHONG Yifan             manifests,
146*d4726bddSHONG Yifan             cargo_config,
147*d4726bddSHONG Yifan         })
148*d4726bddSHONG Yifan     }
149*d4726bddSHONG Yifan }
150*d4726bddSHONG Yifan 
151*d4726bddSHONG Yifan #[derive(Debug, Default, Serialize, Deserialize, Clone)]
152*d4726bddSHONG Yifan pub(crate) struct SourceInfo {
153*d4726bddSHONG Yifan     /// A url where to a `.crate` file.
154*d4726bddSHONG Yifan     pub(crate) url: String,
155*d4726bddSHONG Yifan 
156*d4726bddSHONG Yifan     /// The `.crate` file's sha256 checksum.
157*d4726bddSHONG Yifan     pub(crate) sha256: String,
158*d4726bddSHONG Yifan }
159*d4726bddSHONG Yifan 
160*d4726bddSHONG Yifan /// Information about the Cargo workspace relative to the Bazel workspace
161*d4726bddSHONG Yifan #[derive(Debug, Default, Serialize, Deserialize)]
162*d4726bddSHONG Yifan pub(crate) struct WorkspaceMetadata {
163*d4726bddSHONG Yifan     /// A mapping of crates to information about where their source can be downloaded
164*d4726bddSHONG Yifan     pub(crate) sources: BTreeMap<CrateId, SourceInfo>,
165*d4726bddSHONG Yifan 
166*d4726bddSHONG Yifan     /// The path from the root of a Bazel workspace to the root of the Cargo workspace
167*d4726bddSHONG Yifan     pub(crate) workspace_prefix: Option<String>,
168*d4726bddSHONG Yifan 
169*d4726bddSHONG Yifan     /// Paths from the root of a Bazel workspace to a Cargo package
170*d4726bddSHONG Yifan     pub(crate) package_prefixes: BTreeMap<String, String>,
171*d4726bddSHONG Yifan 
172*d4726bddSHONG Yifan     /// Feature set for each target triplet and crate.
173*d4726bddSHONG Yifan     ///
174*d4726bddSHONG Yifan     /// We store this here because it's computed during the splicing phase via
175*d4726bddSHONG Yifan     /// calls to "cargo tree" which need the full spliced workspace.
176*d4726bddSHONG Yifan     pub(crate) tree_metadata: TreeResolverMetadata,
177*d4726bddSHONG Yifan }
178*d4726bddSHONG Yifan 
179*d4726bddSHONG Yifan impl TryFrom<toml::Value> for WorkspaceMetadata {
180*d4726bddSHONG Yifan     type Error = anyhow::Error;
181*d4726bddSHONG Yifan 
try_from(value: toml::Value) -> Result<Self, Self::Error>182*d4726bddSHONG Yifan     fn try_from(value: toml::Value) -> Result<Self, Self::Error> {
183*d4726bddSHONG Yifan         match value.get("cargo-bazel") {
184*d4726bddSHONG Yifan             Some(v) => v
185*d4726bddSHONG Yifan                 .to_owned()
186*d4726bddSHONG Yifan                 .try_into()
187*d4726bddSHONG Yifan                 .context("Failed to deserialize toml value"),
188*d4726bddSHONG Yifan             None => bail!("cargo-bazel workspace metadata not found"),
189*d4726bddSHONG Yifan         }
190*d4726bddSHONG Yifan     }
191*d4726bddSHONG Yifan }
192*d4726bddSHONG Yifan 
193*d4726bddSHONG Yifan impl TryFrom<serde_json::Value> for WorkspaceMetadata {
194*d4726bddSHONG Yifan     type Error = anyhow::Error;
195*d4726bddSHONG Yifan 
try_from(value: serde_json::Value) -> Result<Self, Self::Error>196*d4726bddSHONG Yifan     fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
197*d4726bddSHONG Yifan         match value.get("cargo-bazel") {
198*d4726bddSHONG Yifan             Some(value) => {
199*d4726bddSHONG Yifan                 serde_json::from_value(value.to_owned()).context("Failed to deserialize json value")
200*d4726bddSHONG Yifan             }
201*d4726bddSHONG Yifan             None => bail!("cargo-bazel workspace metadata not found"),
202*d4726bddSHONG Yifan         }
203*d4726bddSHONG Yifan     }
204*d4726bddSHONG Yifan }
205*d4726bddSHONG Yifan 
206*d4726bddSHONG Yifan impl WorkspaceMetadata {
new( splicing_manifest: &SplicingManifest, member_manifests: BTreeMap<&PathBuf, String>, ) -> Result<Self>207*d4726bddSHONG Yifan     fn new(
208*d4726bddSHONG Yifan         splicing_manifest: &SplicingManifest,
209*d4726bddSHONG Yifan         member_manifests: BTreeMap<&PathBuf, String>,
210*d4726bddSHONG Yifan     ) -> Result<Self> {
211*d4726bddSHONG Yifan         let mut package_prefixes: BTreeMap<String, String> = member_manifests
212*d4726bddSHONG Yifan             .iter()
213*d4726bddSHONG Yifan             .filter_map(|(original_manifest, cargo_package_name)| {
214*d4726bddSHONG Yifan                 let label = match splicing_manifest.manifests.get(*original_manifest) {
215*d4726bddSHONG Yifan                     Some(v) => v,
216*d4726bddSHONG Yifan                     None => return None,
217*d4726bddSHONG Yifan                 };
218*d4726bddSHONG Yifan 
219*d4726bddSHONG Yifan                 let package = match label.package() {
220*d4726bddSHONG Yifan                     Some(package) if !package.is_empty() => PathBuf::from(package),
221*d4726bddSHONG Yifan                     Some(_) | None => return None,
222*d4726bddSHONG Yifan                 };
223*d4726bddSHONG Yifan 
224*d4726bddSHONG Yifan                 let prefix = package.to_string_lossy().to_string();
225*d4726bddSHONG Yifan 
226*d4726bddSHONG Yifan                 Some((cargo_package_name.clone(), prefix))
227*d4726bddSHONG Yifan             })
228*d4726bddSHONG Yifan             .collect();
229*d4726bddSHONG Yifan 
230*d4726bddSHONG Yifan         // It is invald for toml maps to use empty strings as keys. In the case
231*d4726bddSHONG Yifan         // the empty key is expected to be the root package. If the root package
232*d4726bddSHONG Yifan         // has a prefix, then all other packages will as well (even if no other
233*d4726bddSHONG Yifan         // manifest represents them). The value is then saved as a separate value
234*d4726bddSHONG Yifan         let workspace_prefix = package_prefixes.remove("");
235*d4726bddSHONG Yifan 
236*d4726bddSHONG Yifan         let package_prefixes = package_prefixes
237*d4726bddSHONG Yifan             .into_iter()
238*d4726bddSHONG Yifan             .map(|(k, v)| {
239*d4726bddSHONG Yifan                 let prefix_path = PathBuf::from(v);
240*d4726bddSHONG Yifan                 let prefix = prefix_path.parent().unwrap();
241*d4726bddSHONG Yifan                 (k, prefix.to_string_lossy().to_string())
242*d4726bddSHONG Yifan             })
243*d4726bddSHONG Yifan             .collect();
244*d4726bddSHONG Yifan 
245*d4726bddSHONG Yifan         Ok(Self {
246*d4726bddSHONG Yifan             sources: BTreeMap::new(),
247*d4726bddSHONG Yifan             workspace_prefix,
248*d4726bddSHONG Yifan             package_prefixes,
249*d4726bddSHONG Yifan             tree_metadata: TreeResolverMetadata::new(),
250*d4726bddSHONG Yifan         })
251*d4726bddSHONG Yifan     }
252*d4726bddSHONG Yifan 
253*d4726bddSHONG Yifan     /// Update an existing Cargo manifest with metadata about registry urls and target
254*d4726bddSHONG Yifan     /// features that are needed in generator steps beyond splicing.
255*d4726bddSHONG Yifan     #[tracing::instrument(skip_all)]
write_registry_urls_and_feature_map( cargo: &Cargo, lockfile: &cargo_lock::Lockfile, resolver_data: TreeResolverMetadata, input_manifest_path: &Path, output_manifest_path: &Path, ) -> Result<()>256*d4726bddSHONG Yifan     pub(crate) fn write_registry_urls_and_feature_map(
257*d4726bddSHONG Yifan         cargo: &Cargo,
258*d4726bddSHONG Yifan         lockfile: &cargo_lock::Lockfile,
259*d4726bddSHONG Yifan         resolver_data: TreeResolverMetadata,
260*d4726bddSHONG Yifan         input_manifest_path: &Path,
261*d4726bddSHONG Yifan         output_manifest_path: &Path,
262*d4726bddSHONG Yifan     ) -> Result<()> {
263*d4726bddSHONG Yifan         let mut manifest = read_manifest(input_manifest_path)?;
264*d4726bddSHONG Yifan 
265*d4726bddSHONG Yifan         let mut workspace_metaata = WorkspaceMetadata::try_from(
266*d4726bddSHONG Yifan             manifest
267*d4726bddSHONG Yifan                 .workspace
268*d4726bddSHONG Yifan                 .as_ref()
269*d4726bddSHONG Yifan                 .unwrap()
270*d4726bddSHONG Yifan                 .metadata
271*d4726bddSHONG Yifan                 .as_ref()
272*d4726bddSHONG Yifan                 .unwrap()
273*d4726bddSHONG Yifan                 .clone(),
274*d4726bddSHONG Yifan         )?;
275*d4726bddSHONG Yifan 
276*d4726bddSHONG Yifan         // Locate all packages sourced from a registry
277*d4726bddSHONG Yifan         let pkg_sources: Vec<&cargo_lock::Package> = lockfile
278*d4726bddSHONG Yifan             .packages
279*d4726bddSHONG Yifan             .iter()
280*d4726bddSHONG Yifan             .filter(|pkg| pkg.source.is_some())
281*d4726bddSHONG Yifan             .filter(|pkg| pkg.source.as_ref().unwrap().is_registry())
282*d4726bddSHONG Yifan             .collect();
283*d4726bddSHONG Yifan 
284*d4726bddSHONG Yifan         // Collect a unique set of index urls
285*d4726bddSHONG Yifan         let index_urls: BTreeSet<(SourceKind, String)> = pkg_sources
286*d4726bddSHONG Yifan             .iter()
287*d4726bddSHONG Yifan             .map(|pkg| {
288*d4726bddSHONG Yifan                 let source = pkg.source.as_ref().unwrap();
289*d4726bddSHONG Yifan                 (source.kind().clone(), source.url().to_string())
290*d4726bddSHONG Yifan             })
291*d4726bddSHONG Yifan             .collect();
292*d4726bddSHONG Yifan 
293*d4726bddSHONG Yifan         // Load the cargo config
294*d4726bddSHONG Yifan         let cargo_config = {
295*d4726bddSHONG Yifan             // Note that this path must match the one defined in `splicing::setup_cargo_config`
296*d4726bddSHONG Yifan             let config_path = input_manifest_path
297*d4726bddSHONG Yifan                 .parent()
298*d4726bddSHONG Yifan                 .unwrap()
299*d4726bddSHONG Yifan                 .join(".cargo")
300*d4726bddSHONG Yifan                 .join("config.toml");
301*d4726bddSHONG Yifan 
302*d4726bddSHONG Yifan             if config_path.exists() {
303*d4726bddSHONG Yifan                 Some(CargoConfig::try_from_path(&config_path)?)
304*d4726bddSHONG Yifan             } else {
305*d4726bddSHONG Yifan                 None
306*d4726bddSHONG Yifan             }
307*d4726bddSHONG Yifan         };
308*d4726bddSHONG Yifan 
309*d4726bddSHONG Yifan         // Load each index for easy access
310*d4726bddSHONG Yifan         let crate_indexes = index_urls
311*d4726bddSHONG Yifan             .into_iter()
312*d4726bddSHONG Yifan             .map(|(source_kind, url)| {
313*d4726bddSHONG Yifan                 // Ensure the correct registry is mapped based on the give Cargo config.
314*d4726bddSHONG Yifan                 let index_url = if let Some(config) = &cargo_config {
315*d4726bddSHONG Yifan                     config.resolve_replacement_url(&url)?
316*d4726bddSHONG Yifan                 } else {
317*d4726bddSHONG Yifan                     &url
318*d4726bddSHONG Yifan                 };
319*d4726bddSHONG Yifan                 let index = if cargo.use_sparse_registries_for_crates_io()?
320*d4726bddSHONG Yifan                     && index_url == utils::CRATES_IO_INDEX_URL
321*d4726bddSHONG Yifan                 {
322*d4726bddSHONG Yifan                     CrateIndexLookup::Http(crates_index::SparseIndex::from_url(
323*d4726bddSHONG Yifan                         "sparse+https://index.crates.io/",
324*d4726bddSHONG Yifan                     )?)
325*d4726bddSHONG Yifan                 } else if index_url.starts_with("sparse+") {
326*d4726bddSHONG Yifan                     CrateIndexLookup::Http(crates_index::SparseIndex::from_url(index_url)?)
327*d4726bddSHONG Yifan                 } else {
328*d4726bddSHONG Yifan                     match source_kind {
329*d4726bddSHONG Yifan                         SourceKind::Registry => {
330*d4726bddSHONG Yifan                             let index = {
331*d4726bddSHONG Yifan                                 // Load the index for the current url
332*d4726bddSHONG Yifan                                 let index = crates_index::GitIndex::from_url(index_url)
333*d4726bddSHONG Yifan                                     .with_context(|| {
334*d4726bddSHONG Yifan                                         format!("Failed to load index for url: {index_url}")
335*d4726bddSHONG Yifan                                     })?;
336*d4726bddSHONG Yifan 
337*d4726bddSHONG Yifan                                 // Ensure each index has a valid index config
338*d4726bddSHONG Yifan                                 index.index_config().with_context(|| {
339*d4726bddSHONG Yifan                                     format!("`config.json` not found in index: {index_url}")
340*d4726bddSHONG Yifan                                 })?;
341*d4726bddSHONG Yifan 
342*d4726bddSHONG Yifan                                 index
343*d4726bddSHONG Yifan                             };
344*d4726bddSHONG Yifan                             CrateIndexLookup::Git(index)
345*d4726bddSHONG Yifan                         }
346*d4726bddSHONG Yifan                         SourceKind::SparseRegistry => {
347*d4726bddSHONG Yifan                             CrateIndexLookup::Http(crates_index::SparseIndex::from_url(
348*d4726bddSHONG Yifan                                 format!("sparse+{}", index_url).as_str(),
349*d4726bddSHONG Yifan                             )?)
350*d4726bddSHONG Yifan                         }
351*d4726bddSHONG Yifan                         unknown => {
352*d4726bddSHONG Yifan                             return Err(anyhow!(
353*d4726bddSHONG Yifan                                 "'{:?}' crate index type is not supported (caused by '{}')",
354*d4726bddSHONG Yifan                                 &unknown,
355*d4726bddSHONG Yifan                                 url
356*d4726bddSHONG Yifan                             ));
357*d4726bddSHONG Yifan                         }
358*d4726bddSHONG Yifan                     }
359*d4726bddSHONG Yifan                 };
360*d4726bddSHONG Yifan                 Ok((url, index))
361*d4726bddSHONG Yifan             })
362*d4726bddSHONG Yifan             .collect::<Result<BTreeMap<String, _>>>()
363*d4726bddSHONG Yifan             .context("Failed to locate crate indexes")?;
364*d4726bddSHONG Yifan 
365*d4726bddSHONG Yifan         // Get the download URL of each package based on it's registry url.
366*d4726bddSHONG Yifan         let additional_sources = pkg_sources
367*d4726bddSHONG Yifan             .iter()
368*d4726bddSHONG Yifan             .map(|pkg| {
369*d4726bddSHONG Yifan                 let source_id = pkg.source.as_ref().unwrap();
370*d4726bddSHONG Yifan                 let source_url = source_id.url().to_string();
371*d4726bddSHONG Yifan                 let lookup = crate_indexes.get(&source_url).ok_or_else(|| {
372*d4726bddSHONG Yifan                     anyhow!(
373*d4726bddSHONG Yifan                         "Couldn't find crate_index data for SourceID {:?}",
374*d4726bddSHONG Yifan                         source_id
375*d4726bddSHONG Yifan                     )
376*d4726bddSHONG Yifan                 })?;
377*d4726bddSHONG Yifan                 lookup.get_source_info(pkg).map(|source_info| {
378*d4726bddSHONG Yifan                     (
379*d4726bddSHONG Yifan                         CrateId::new(pkg.name.as_str().to_owned(), pkg.version.clone()),
380*d4726bddSHONG Yifan                         source_info,
381*d4726bddSHONG Yifan                     )
382*d4726bddSHONG Yifan                 })
383*d4726bddSHONG Yifan             })
384*d4726bddSHONG Yifan             .collect::<Result<Vec<_>>>()?;
385*d4726bddSHONG Yifan 
386*d4726bddSHONG Yifan         workspace_metaata
387*d4726bddSHONG Yifan             .sources
388*d4726bddSHONG Yifan             .extend(
389*d4726bddSHONG Yifan                 additional_sources
390*d4726bddSHONG Yifan                     .into_iter()
391*d4726bddSHONG Yifan                     .filter_map(|(crate_id, source_info)| {
392*d4726bddSHONG Yifan                         source_info.map(|source_info| (crate_id, source_info))
393*d4726bddSHONG Yifan                     }),
394*d4726bddSHONG Yifan             );
395*d4726bddSHONG Yifan         workspace_metaata.tree_metadata = resolver_data;
396*d4726bddSHONG Yifan         workspace_metaata.inject_into(&mut manifest)?;
397*d4726bddSHONG Yifan 
398*d4726bddSHONG Yifan         write_root_manifest(output_manifest_path, manifest)?;
399*d4726bddSHONG Yifan 
400*d4726bddSHONG Yifan         Ok(())
401*d4726bddSHONG Yifan     }
402*d4726bddSHONG Yifan 
inject_into(&self, manifest: &mut Manifest) -> Result<()>403*d4726bddSHONG Yifan     fn inject_into(&self, manifest: &mut Manifest) -> Result<()> {
404*d4726bddSHONG Yifan         let metadata_value = toml::Value::try_from(self)?;
405*d4726bddSHONG Yifan         let workspace = manifest.workspace.as_mut().unwrap();
406*d4726bddSHONG Yifan 
407*d4726bddSHONG Yifan         match &mut workspace.metadata {
408*d4726bddSHONG Yifan             Some(data) => match data.as_table_mut() {
409*d4726bddSHONG Yifan                 Some(map) => {
410*d4726bddSHONG Yifan                     map.insert("cargo-bazel".to_owned(), metadata_value);
411*d4726bddSHONG Yifan                 }
412*d4726bddSHONG Yifan                 None => bail!("The metadata field is always expected to be a table"),
413*d4726bddSHONG Yifan             },
414*d4726bddSHONG Yifan             None => {
415*d4726bddSHONG Yifan                 let mut table = toml::map::Map::new();
416*d4726bddSHONG Yifan                 table.insert("cargo-bazel".to_owned(), metadata_value);
417*d4726bddSHONG Yifan                 workspace.metadata = Some(toml::Value::Table(table))
418*d4726bddSHONG Yifan             }
419*d4726bddSHONG Yifan         }
420*d4726bddSHONG Yifan 
421*d4726bddSHONG Yifan         Ok(())
422*d4726bddSHONG Yifan     }
423*d4726bddSHONG Yifan }
424*d4726bddSHONG Yifan 
425*d4726bddSHONG Yifan #[derive(Debug)]
426*d4726bddSHONG Yifan pub(crate) enum SplicedManifest {
427*d4726bddSHONG Yifan     Workspace(PathBuf),
428*d4726bddSHONG Yifan     Package(PathBuf),
429*d4726bddSHONG Yifan     MultiPackage(PathBuf),
430*d4726bddSHONG Yifan }
431*d4726bddSHONG Yifan 
432*d4726bddSHONG Yifan impl SplicedManifest {
as_path_buf(&self) -> &PathBuf433*d4726bddSHONG Yifan     pub(crate) fn as_path_buf(&self) -> &PathBuf {
434*d4726bddSHONG Yifan         match self {
435*d4726bddSHONG Yifan             SplicedManifest::Workspace(p) => p,
436*d4726bddSHONG Yifan             SplicedManifest::Package(p) => p,
437*d4726bddSHONG Yifan             SplicedManifest::MultiPackage(p) => p,
438*d4726bddSHONG Yifan         }
439*d4726bddSHONG Yifan     }
440*d4726bddSHONG Yifan }
441*d4726bddSHONG Yifan 
read_manifest(manifest: &Path) -> Result<Manifest>442*d4726bddSHONG Yifan pub(crate) fn read_manifest(manifest: &Path) -> Result<Manifest> {
443*d4726bddSHONG Yifan     let content = fs::read_to_string(manifest)?;
444*d4726bddSHONG Yifan     cargo_toml::Manifest::from_str(content.as_str()).context("Failed to deserialize manifest")
445*d4726bddSHONG Yifan }
446*d4726bddSHONG Yifan 
generate_lockfile( manifest_path: &SplicedManifest, existing_lock: &Option<PathBuf>, cargo_bin: Cargo, update_request: &Option<CargoUpdateRequest>, ) -> Result<cargo_lock::Lockfile>447*d4726bddSHONG Yifan pub(crate) fn generate_lockfile(
448*d4726bddSHONG Yifan     manifest_path: &SplicedManifest,
449*d4726bddSHONG Yifan     existing_lock: &Option<PathBuf>,
450*d4726bddSHONG Yifan     cargo_bin: Cargo,
451*d4726bddSHONG Yifan     update_request: &Option<CargoUpdateRequest>,
452*d4726bddSHONG Yifan ) -> Result<cargo_lock::Lockfile> {
453*d4726bddSHONG Yifan     let manifest_dir = manifest_path
454*d4726bddSHONG Yifan         .as_path_buf()
455*d4726bddSHONG Yifan         .parent()
456*d4726bddSHONG Yifan         .expect("Every manifest should be contained in a parent directory");
457*d4726bddSHONG Yifan 
458*d4726bddSHONG Yifan     let root_lockfile_path = manifest_dir.join("Cargo.lock");
459*d4726bddSHONG Yifan 
460*d4726bddSHONG Yifan     // Remove the file so it's not overwitten if it happens to be a symlink.
461*d4726bddSHONG Yifan     if root_lockfile_path.exists() {
462*d4726bddSHONG Yifan         fs::remove_file(&root_lockfile_path)?;
463*d4726bddSHONG Yifan     }
464*d4726bddSHONG Yifan 
465*d4726bddSHONG Yifan     // Generate the new lockfile
466*d4726bddSHONG Yifan     let lockfile = LockGenerator::new(cargo_bin).generate(
467*d4726bddSHONG Yifan         manifest_path.as_path_buf(),
468*d4726bddSHONG Yifan         existing_lock,
469*d4726bddSHONG Yifan         update_request,
470*d4726bddSHONG Yifan     )?;
471*d4726bddSHONG Yifan 
472*d4726bddSHONG Yifan     // Write the lockfile to disk
473*d4726bddSHONG Yifan     if !root_lockfile_path.exists() {
474*d4726bddSHONG Yifan         bail!("Failed to generate Cargo.lock file")
475*d4726bddSHONG Yifan     }
476*d4726bddSHONG Yifan 
477*d4726bddSHONG Yifan     Ok(lockfile)
478*d4726bddSHONG Yifan }
479*d4726bddSHONG Yifan 
480*d4726bddSHONG Yifan #[cfg(test)]
481*d4726bddSHONG Yifan mod test {
482*d4726bddSHONG Yifan     use super::*;
483*d4726bddSHONG Yifan 
484*d4726bddSHONG Yifan     #[test]
deserialize_splicing_manifest()485*d4726bddSHONG Yifan     fn deserialize_splicing_manifest() {
486*d4726bddSHONG Yifan         let runfiles = runfiles::Runfiles::create().unwrap();
487*d4726bddSHONG Yifan         let path = runfiles::rlocation!(
488*d4726bddSHONG Yifan             runfiles,
489*d4726bddSHONG Yifan             "rules_rust/crate_universe/test_data/serialized_configs/splicing_manifest.json"
490*d4726bddSHONG Yifan         );
491*d4726bddSHONG Yifan 
492*d4726bddSHONG Yifan         let content = std::fs::read_to_string(path).unwrap();
493*d4726bddSHONG Yifan 
494*d4726bddSHONG Yifan         let manifest: SplicingManifest = serde_json::from_str(&content).unwrap();
495*d4726bddSHONG Yifan 
496*d4726bddSHONG Yifan         // Check manifests
497*d4726bddSHONG Yifan         assert_eq!(
498*d4726bddSHONG Yifan             manifest.manifests,
499*d4726bddSHONG Yifan             BTreeMap::from([
500*d4726bddSHONG Yifan                 (
501*d4726bddSHONG Yifan                     PathBuf::from("${build_workspace_directory}/submod/Cargo.toml"),
502*d4726bddSHONG Yifan                     Label::from_str("//submod:Cargo.toml").unwrap()
503*d4726bddSHONG Yifan                 ),
504*d4726bddSHONG Yifan                 (
505*d4726bddSHONG Yifan                     PathBuf::from("${output_base}/external_crate/Cargo.toml"),
506*d4726bddSHONG Yifan                     Label::from_str("@external_crate//:Cargo.toml").unwrap()
507*d4726bddSHONG Yifan                 ),
508*d4726bddSHONG Yifan                 (
509*d4726bddSHONG Yifan                     PathBuf::from("/tmp/abs/path/workspace/Cargo.toml"),
510*d4726bddSHONG Yifan                     Label::from_str("//:Cargo.toml").unwrap()
511*d4726bddSHONG Yifan                 ),
512*d4726bddSHONG Yifan             ])
513*d4726bddSHONG Yifan         );
514*d4726bddSHONG Yifan 
515*d4726bddSHONG Yifan         // Check splicing configs
516*d4726bddSHONG Yifan         assert_eq!(manifest.resolver_version, cargo_toml::Resolver::V2);
517*d4726bddSHONG Yifan 
518*d4726bddSHONG Yifan         // Check packages
519*d4726bddSHONG Yifan         assert_eq!(manifest.direct_packages.len(), 4);
520*d4726bddSHONG Yifan         let package = manifest.direct_packages.get("rand").unwrap();
521*d4726bddSHONG Yifan         assert_eq!(
522*d4726bddSHONG Yifan             package,
523*d4726bddSHONG Yifan             &cargo_toml::DependencyDetail {
524*d4726bddSHONG Yifan                 default_features: false,
525*d4726bddSHONG Yifan                 features: vec!["small_rng".to_owned()],
526*d4726bddSHONG Yifan                 version: Some("0.8.5".to_owned()),
527*d4726bddSHONG Yifan                 ..Default::default()
528*d4726bddSHONG Yifan             }
529*d4726bddSHONG Yifan         );
530*d4726bddSHONG Yifan         let package = manifest.direct_packages.get("cfg-if").unwrap();
531*d4726bddSHONG Yifan         assert_eq!(
532*d4726bddSHONG Yifan             package,
533*d4726bddSHONG Yifan             &cargo_toml::DependencyDetail {
534*d4726bddSHONG Yifan                 git: Some("https://github.com/rust-lang/cfg-if.git".to_owned()),
535*d4726bddSHONG Yifan                 rev: Some("b9c2246a".to_owned()),
536*d4726bddSHONG Yifan                 default_features: true,
537*d4726bddSHONG Yifan                 ..Default::default()
538*d4726bddSHONG Yifan             }
539*d4726bddSHONG Yifan         );
540*d4726bddSHONG Yifan         let package = manifest.direct_packages.get("log").unwrap();
541*d4726bddSHONG Yifan         assert_eq!(
542*d4726bddSHONG Yifan             package,
543*d4726bddSHONG Yifan             &cargo_toml::DependencyDetail {
544*d4726bddSHONG Yifan                 git: Some("https://github.com/rust-lang/log.git".to_owned()),
545*d4726bddSHONG Yifan                 branch: Some("master".to_owned()),
546*d4726bddSHONG Yifan                 default_features: true,
547*d4726bddSHONG Yifan                 ..Default::default()
548*d4726bddSHONG Yifan             }
549*d4726bddSHONG Yifan         );
550*d4726bddSHONG Yifan         let package = manifest.direct_packages.get("cargo_toml").unwrap();
551*d4726bddSHONG Yifan         assert_eq!(
552*d4726bddSHONG Yifan             package,
553*d4726bddSHONG Yifan             &cargo_toml::DependencyDetail {
554*d4726bddSHONG Yifan                 git: Some("https://gitlab.com/crates.rs/cargo_toml.git".to_owned()),
555*d4726bddSHONG Yifan                 tag: Some("v0.15.2".to_owned()),
556*d4726bddSHONG Yifan                 default_features: true,
557*d4726bddSHONG Yifan                 ..Default::default()
558*d4726bddSHONG Yifan             }
559*d4726bddSHONG Yifan         );
560*d4726bddSHONG Yifan 
561*d4726bddSHONG Yifan         // Check cargo config
562*d4726bddSHONG Yifan         assert_eq!(
563*d4726bddSHONG Yifan             manifest.cargo_config,
564*d4726bddSHONG Yifan             Some(PathBuf::from("/tmp/abs/path/workspace/.cargo/config.toml"))
565*d4726bddSHONG Yifan         );
566*d4726bddSHONG Yifan     }
567*d4726bddSHONG Yifan 
568*d4726bddSHONG Yifan     #[test]
splicing_manifest_resolve()569*d4726bddSHONG Yifan     fn splicing_manifest_resolve() {
570*d4726bddSHONG Yifan         let runfiles = runfiles::Runfiles::create().unwrap();
571*d4726bddSHONG Yifan         let path = runfiles::rlocation!(
572*d4726bddSHONG Yifan             runfiles,
573*d4726bddSHONG Yifan             "rules_rust/crate_universe/test_data/serialized_configs/splicing_manifest.json"
574*d4726bddSHONG Yifan         );
575*d4726bddSHONG Yifan 
576*d4726bddSHONG Yifan         let content = std::fs::read_to_string(path).unwrap();
577*d4726bddSHONG Yifan 
578*d4726bddSHONG Yifan         let mut manifest: SplicingManifest = serde_json::from_str(&content).unwrap();
579*d4726bddSHONG Yifan         manifest.cargo_config = Some(PathBuf::from(
580*d4726bddSHONG Yifan             "${build_workspace_directory}/.cargo/config.toml",
581*d4726bddSHONG Yifan         ));
582*d4726bddSHONG Yifan         manifest = manifest.resolve(
583*d4726bddSHONG Yifan             &PathBuf::from("/tmp/abs/path/workspace"),
584*d4726bddSHONG Yifan             &PathBuf::from("/tmp/output_base"),
585*d4726bddSHONG Yifan         );
586*d4726bddSHONG Yifan 
587*d4726bddSHONG Yifan         // Check manifests
588*d4726bddSHONG Yifan         assert_eq!(
589*d4726bddSHONG Yifan             manifest.manifests,
590*d4726bddSHONG Yifan             BTreeMap::from([
591*d4726bddSHONG Yifan                 (
592*d4726bddSHONG Yifan                     PathBuf::from("/tmp/abs/path/workspace/submod/Cargo.toml"),
593*d4726bddSHONG Yifan                     Label::from_str("//submod:Cargo.toml").unwrap()
594*d4726bddSHONG Yifan                 ),
595*d4726bddSHONG Yifan                 (
596*d4726bddSHONG Yifan                     PathBuf::from("/tmp/output_base/external_crate/Cargo.toml"),
597*d4726bddSHONG Yifan                     Label::from_str("@external_crate//:Cargo.toml").unwrap()
598*d4726bddSHONG Yifan                 ),
599*d4726bddSHONG Yifan                 (
600*d4726bddSHONG Yifan                     PathBuf::from("/tmp/abs/path/workspace/Cargo.toml"),
601*d4726bddSHONG Yifan                     Label::from_str("//:Cargo.toml").unwrap()
602*d4726bddSHONG Yifan                 ),
603*d4726bddSHONG Yifan             ])
604*d4726bddSHONG Yifan         );
605*d4726bddSHONG Yifan 
606*d4726bddSHONG Yifan         // Check cargo config
607*d4726bddSHONG Yifan         assert_eq!(
608*d4726bddSHONG Yifan             manifest.cargo_config.unwrap(),
609*d4726bddSHONG Yifan             PathBuf::from("/tmp/abs/path/workspace/.cargo/config.toml"),
610*d4726bddSHONG Yifan         )
611*d4726bddSHONG Yifan     }
612*d4726bddSHONG Yifan 
613*d4726bddSHONG Yifan     #[test]
splicing_metadata_workspace_path()614*d4726bddSHONG Yifan     fn splicing_metadata_workspace_path() {
615*d4726bddSHONG Yifan         let runfiles = runfiles::Runfiles::create().unwrap();
616*d4726bddSHONG Yifan         let workspace_manifest_path = runfiles::rlocation!(
617*d4726bddSHONG Yifan             runfiles,
618*d4726bddSHONG Yifan             "rules_rust/crate_universe/test_data/metadata/workspace_path/Cargo.toml"
619*d4726bddSHONG Yifan         );
620*d4726bddSHONG Yifan         let workspace_path = workspace_manifest_path.parent().unwrap().to_path_buf();
621*d4726bddSHONG Yifan         let child_a_manifest_path = runfiles::rlocation!(
622*d4726bddSHONG Yifan             runfiles,
623*d4726bddSHONG Yifan             "rules_rust/crate_universe/test_data/metadata/workspace_path/child_a/Cargo.toml"
624*d4726bddSHONG Yifan         );
625*d4726bddSHONG Yifan         let child_b_manifest_path = runfiles::rlocation!(
626*d4726bddSHONG Yifan             runfiles,
627*d4726bddSHONG Yifan             "rules_rust/crate_universe/test_data/metadata/workspace_path/child_b/Cargo.toml"
628*d4726bddSHONG Yifan         );
629*d4726bddSHONG Yifan         let manifest = SplicingManifest {
630*d4726bddSHONG Yifan             direct_packages: BTreeMap::new(),
631*d4726bddSHONG Yifan             manifests: BTreeMap::from([
632*d4726bddSHONG Yifan                 (
633*d4726bddSHONG Yifan                     workspace_manifest_path,
634*d4726bddSHONG Yifan                     Label::from_str("//:Cargo.toml").unwrap(),
635*d4726bddSHONG Yifan                 ),
636*d4726bddSHONG Yifan                 (
637*d4726bddSHONG Yifan                     child_a_manifest_path,
638*d4726bddSHONG Yifan                     Label::from_str("//child_a:Cargo.toml").unwrap(),
639*d4726bddSHONG Yifan                 ),
640*d4726bddSHONG Yifan                 (
641*d4726bddSHONG Yifan                     child_b_manifest_path,
642*d4726bddSHONG Yifan                     Label::from_str("//child_b:Cargo.toml").unwrap(),
643*d4726bddSHONG Yifan                 ),
644*d4726bddSHONG Yifan             ]),
645*d4726bddSHONG Yifan             cargo_config: None,
646*d4726bddSHONG Yifan             resolver_version: cargo_toml::Resolver::V2,
647*d4726bddSHONG Yifan         };
648*d4726bddSHONG Yifan         let metadata = SplicingMetadata::try_from(manifest).unwrap();
649*d4726bddSHONG Yifan         let metadata = serde_json::to_string(&metadata).unwrap();
650*d4726bddSHONG Yifan         assert!(
651*d4726bddSHONG Yifan             !metadata.contains(workspace_path.to_str().unwrap()),
652*d4726bddSHONG Yifan             "serialized metadata should not contain absolute path"
653*d4726bddSHONG Yifan         );
654*d4726bddSHONG Yifan     }
655*d4726bddSHONG Yifan }
656