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