xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/metadata.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 //! Tools for gathering various kinds of metadata (Cargo.lock, Cargo metadata, Crate Index info).
2 
3 mod dependency;
4 mod metadata_annotation;
5 
6 use std::collections::{BTreeMap, BTreeSet, HashMap};
7 use std::env;
8 use std::ffi::OsString;
9 use std::fs;
10 use std::io::BufRead;
11 use std::path::{Path, PathBuf};
12 use std::process::Command;
13 use std::str::FromStr;
14 use std::sync::{Arc, Mutex};
15 
16 use anyhow::{anyhow, bail, Context, Result};
17 use cargo_lock::Lockfile as CargoLockfile;
18 use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand};
19 use semver::Version;
20 use serde::{Deserialize, Serialize};
21 use tracing::debug;
22 use url::Url;
23 
24 use crate::config::CrateId;
25 use crate::lockfile::Digest;
26 use crate::select::{Select, SelectableScalar};
27 use crate::utils::symlink::symlink;
28 use crate::utils::target_triple::TargetTriple;
29 
30 pub(crate) use self::dependency::*;
31 pub(crate) use self::metadata_annotation::*;
32 
33 // TODO: This should also return a set of [crate-index::IndexConfig]s for packages in metadata.packages
34 /// A Trait for generating metadata (`cargo metadata` output and a lock file) from a Cargo manifest.
35 pub(crate) trait MetadataGenerator {
generate<T: AsRef<Path>>(&self, manifest_path: T) -> Result<(CargoMetadata, CargoLockfile)>36     fn generate<T: AsRef<Path>>(&self, manifest_path: T) -> Result<(CargoMetadata, CargoLockfile)>;
37 }
38 
39 /// Generates Cargo metadata and a lockfile from a provided manifest.
40 pub(crate) struct Generator {
41     /// The path to a `cargo` binary
42     cargo_bin: Cargo,
43 
44     /// The path to a `rustc` binary
45     rustc_bin: PathBuf,
46 }
47 
48 impl Generator {
new() -> Self49     pub(crate) fn new() -> Self {
50         let rustc_bin = PathBuf::from(env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()));
51         Generator {
52             cargo_bin: Cargo::new(
53                 PathBuf::from(env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())),
54                 rustc_bin.clone(),
55             ),
56             rustc_bin,
57         }
58     }
59 
with_cargo(mut self, cargo_bin: Cargo) -> Self60     pub(crate) fn with_cargo(mut self, cargo_bin: Cargo) -> Self {
61         self.cargo_bin = cargo_bin;
62         self
63     }
64 
with_rustc(mut self, rustc_bin: PathBuf) -> Self65     pub(crate) fn with_rustc(mut self, rustc_bin: PathBuf) -> Self {
66         self.rustc_bin = rustc_bin;
67         self
68     }
69 }
70 
71 impl MetadataGenerator for Generator {
generate<T: AsRef<Path>>(&self, manifest_path: T) -> Result<(CargoMetadata, CargoLockfile)>72     fn generate<T: AsRef<Path>>(&self, manifest_path: T) -> Result<(CargoMetadata, CargoLockfile)> {
73         let manifest_dir = manifest_path
74             .as_ref()
75             .parent()
76             .expect("The manifest should have a parent directory");
77         let lockfile = {
78             let lock_path = manifest_dir.join("Cargo.lock");
79             if !lock_path.exists() {
80                 bail!("No `Cargo.lock` file was found with the given manifest")
81             }
82             cargo_lock::Lockfile::load(lock_path)?
83         };
84 
85         let metadata = self
86             .cargo_bin
87             .metadata_command_with_options(manifest_path.as_ref(), vec!["--locked".to_owned()])?
88             .exec()?;
89 
90         Ok((metadata, lockfile))
91     }
92 }
93 
94 /// Cargo encapsulates a path to a `cargo` binary.
95 /// Any invocations of `cargo` (either as a `std::process::Command` or via `cargo_metadata`) should
96 /// go via this wrapper to ensure that any environment variables needed are set appropriately.
97 #[derive(Debug, Clone)]
98 pub(crate) struct Cargo {
99     path: PathBuf,
100     rustc_path: PathBuf,
101     full_version: Arc<Mutex<Option<String>>>,
102     cargo_home: Option<PathBuf>,
103 }
104 
105 impl Cargo {
new(path: PathBuf, rustc: PathBuf) -> Cargo106     pub(crate) fn new(path: PathBuf, rustc: PathBuf) -> Cargo {
107         Cargo {
108             path,
109             rustc_path: rustc,
110             full_version: Arc::new(Mutex::new(None)),
111             cargo_home: None,
112         }
113     }
114 
115     #[cfg(test)]
with_cargo_home(mut self, path: PathBuf) -> Cargo116     pub(crate) fn with_cargo_home(mut self, path: PathBuf) -> Cargo {
117         self.cargo_home = Some(path);
118         self
119     }
120 
121     /// Returns a new `Command` for running this cargo.
command(&self) -> Result<Command>122     pub(crate) fn command(&self) -> Result<Command> {
123         let mut command = Command::new(&self.path);
124         command.envs(self.env()?);
125         if self.is_nightly()? {
126             command.arg("-Zbindeps");
127         }
128         Ok(command)
129     }
130 
131     /// Returns a new `MetadataCommand` using this cargo.
132     /// `manifest_path`, `current_dir`, and `other_options` should not be called on the resturned MetadataCommand - instead pass them as the relevant args.
metadata_command_with_options( &self, manifest_path: &Path, other_options: Vec<String>, ) -> Result<MetadataCommand>133     pub(crate) fn metadata_command_with_options(
134         &self,
135         manifest_path: &Path,
136         other_options: Vec<String>,
137     ) -> Result<MetadataCommand> {
138         let mut command = MetadataCommand::new();
139         command.cargo_path(&self.path);
140         for (k, v) in self.env()? {
141             command.env(k, v);
142         }
143 
144         command.manifest_path(manifest_path);
145         // Cargo detects config files based on `pwd` when running so
146         // to ensure user provided Cargo config files are used, it's
147         // critical to set the working directory to the manifest dir.
148         let manifest_dir = manifest_path
149             .parent()
150             .ok_or_else(|| anyhow::anyhow!("manifest_path {:?} must have parent", manifest_path))?;
151         command.current_dir(manifest_dir);
152 
153         let mut other_options = other_options;
154         if self.is_nightly()? {
155             other_options.push("-Zbindeps".to_owned());
156         }
157         command.other_options(other_options);
158         Ok(command)
159     }
160 
161     /// Returns the output of running `cargo version`, trimming any leading or trailing whitespace.
162     /// This function performs normalisation to work around `<https://github.com/rust-lang/cargo/issues/10547>`
full_version(&self) -> Result<String>163     pub(crate) fn full_version(&self) -> Result<String> {
164         let mut full_version = self.full_version.lock().unwrap();
165         if full_version.is_none() {
166             let observed_version = Digest::bin_version(&self.path)?;
167             *full_version = Some(observed_version);
168         }
169         Ok(full_version.clone().unwrap())
170     }
171 
is_nightly(&self) -> Result<bool>172     pub(crate) fn is_nightly(&self) -> Result<bool> {
173         let full_version = self.full_version()?;
174         let version_str = full_version.split(' ').nth(1);
175         if let Some(version_str) = version_str {
176             let version = Version::parse(version_str).context("Failed to parse cargo version")?;
177             return Ok(version.pre.as_str() == "nightly");
178         }
179         bail!("Couldn't parse cargo version");
180     }
181 
use_sparse_registries_for_crates_io(&self) -> Result<bool>182     pub(crate) fn use_sparse_registries_for_crates_io(&self) -> Result<bool> {
183         let full_version = self.full_version()?;
184         let version_str = full_version.split(' ').nth(1);
185         if let Some(version_str) = version_str {
186             let version = Version::parse(version_str).context("Failed to parse cargo version")?;
187             return Ok(version.major >= 1 && version.minor >= 68);
188         }
189         bail!("Couldn't parse cargo version");
190     }
191 
192     /// Determine if Cargo is expected to be using the new package_id spec. For
193     /// details see <https://github.com/rust-lang/cargo/pull/13311>
194     #[cfg(test)]
uses_new_package_id_format(&self) -> Result<bool>195     pub(crate) fn uses_new_package_id_format(&self) -> Result<bool> {
196         let full_version = self.full_version()?;
197         let version_str = full_version.split(' ').nth(1);
198         if let Some(version_str) = version_str {
199             let version = Version::parse(version_str).context("Failed to parse cargo version")?;
200             return Ok(version.major >= 1 && version.minor >= 77);
201         }
202         bail!("Couldn't parse cargo version");
203     }
204 
env(&self) -> Result<BTreeMap<String, OsString>>205     fn env(&self) -> Result<BTreeMap<String, OsString>> {
206         let mut map = BTreeMap::new();
207 
208         map.insert("RUSTC".into(), self.rustc_path.as_os_str().to_owned());
209 
210         if self.use_sparse_registries_for_crates_io()? {
211             map.insert(
212                 "CARGO_REGISTRIES_CRATES_IO_PROTOCOL".into(),
213                 "sparse".into(),
214             );
215         }
216 
217         if let Some(cargo_home) = &self.cargo_home {
218             map.insert("CARGO_HOME".into(), cargo_home.as_os_str().to_owned());
219         }
220 
221         Ok(map)
222     }
223 }
224 
225 /// A configuration describing how to invoke [cargo update](https://doc.rust-lang.org/cargo/commands/cargo-update.html).
226 #[derive(Debug, Clone, PartialEq, Eq)]
227 pub enum CargoUpdateRequest {
228     /// Translates to an unrestricted `cargo update` command
229     Eager,
230 
231     /// Translates to `cargo update --workspace`
232     Workspace,
233 
234     /// Translates to `cargo update --package foo` with an optional `--precise` argument.
235     Package {
236         /// The name of the crate used with `--package`.
237         name: String,
238 
239         /// If set, the `--precise` value that pairs with `--package`.
240         version: Option<String>,
241     },
242 }
243 
244 impl FromStr for CargoUpdateRequest {
245     type Err = anyhow::Error;
246 
from_str(s: &str) -> Result<Self, Self::Err>247     fn from_str(s: &str) -> Result<Self, Self::Err> {
248         let lower = s.to_lowercase();
249 
250         if ["eager", "full", "all"].contains(&lower.as_str()) {
251             return Ok(Self::Eager);
252         }
253 
254         if ["1", "yes", "true", "on", "workspace", "minimal"].contains(&lower.as_str()) {
255             return Ok(Self::Workspace);
256         }
257 
258         let mut split = s.splitn(2, '=');
259         Ok(Self::Package {
260             name: split.next().map(|s| s.to_owned()).unwrap(),
261             version: split.next().map(|s| s.to_owned()),
262         })
263     }
264 }
265 
266 impl CargoUpdateRequest {
267     /// Determine what arguments to pass to the `cargo update` command.
get_update_args(&self) -> Vec<String>268     fn get_update_args(&self) -> Vec<String> {
269         match self {
270             CargoUpdateRequest::Eager => Vec::new(),
271             CargoUpdateRequest::Workspace => vec!["--workspace".to_owned()],
272             CargoUpdateRequest::Package { name, version } => {
273                 let mut update_args = vec!["--package".to_owned(), name.clone()];
274 
275                 if let Some(version) = version {
276                     update_args.push("--precise".to_owned());
277                     update_args.push(version.clone());
278                 }
279 
280                 update_args
281             }
282         }
283     }
284 
285     /// Calls `cargo update` with arguments specific to the state of the current variant.
update(&self, manifest: &Path, cargo_bin: &Cargo) -> Result<()>286     pub(crate) fn update(&self, manifest: &Path, cargo_bin: &Cargo) -> Result<()> {
287         let manifest_dir = manifest.parent().unwrap();
288 
289         // Simply invoke `cargo update`
290         let output = cargo_bin
291             .command()?
292             // Cargo detects config files based on `pwd` when running so
293             // to ensure user provided Cargo config files are used, it's
294             // critical to set the working directory to the manifest dir.
295             .current_dir(manifest_dir)
296             .arg("update")
297             .arg("--manifest-path")
298             .arg(manifest)
299             .args(self.get_update_args())
300             .output()
301             .with_context(|| {
302                 format!(
303                     "Error running cargo to update packages for manifest '{}'",
304                     manifest.display()
305                 )
306             })?;
307 
308         if !output.status.success() {
309             eprintln!("{}", String::from_utf8_lossy(&output.stdout));
310             eprintln!("{}", String::from_utf8_lossy(&output.stderr));
311             bail!(format!("Failed to update lockfile: {}", output.status))
312         }
313 
314         Ok(())
315     }
316 }
317 
318 pub(crate) struct LockGenerator {
319     /// Interface to cargo.
320     cargo_bin: Cargo,
321 }
322 
323 impl LockGenerator {
new(cargo_bin: Cargo) -> Self324     pub(crate) fn new(cargo_bin: Cargo) -> Self {
325         Self { cargo_bin }
326     }
327 
328     #[tracing::instrument(name = "LockGenerator::generate", skip_all)]
generate( &self, manifest_path: &Path, existing_lock: &Option<PathBuf>, update_request: &Option<CargoUpdateRequest>, ) -> Result<cargo_lock::Lockfile>329     pub(crate) fn generate(
330         &self,
331         manifest_path: &Path,
332         existing_lock: &Option<PathBuf>,
333         update_request: &Option<CargoUpdateRequest>,
334     ) -> Result<cargo_lock::Lockfile> {
335         debug!("Generating Cargo Lockfile for {}", manifest_path.display());
336 
337         let manifest_dir = manifest_path.parent().unwrap();
338         let generated_lockfile_path = manifest_dir.join("Cargo.lock");
339 
340         if let Some(lock) = existing_lock {
341             debug!("Using existing lock {}", lock.display());
342             if !lock.exists() {
343                 bail!(
344                     "An existing lockfile path was provided but a file at '{}' does not exist",
345                     lock.display()
346                 )
347             }
348 
349             // Install the file into the target location
350             if generated_lockfile_path.exists() {
351                 fs::remove_file(&generated_lockfile_path)?;
352             }
353             fs::copy(lock, &generated_lockfile_path)?;
354 
355             if let Some(request) = update_request {
356                 request.update(manifest_path, &self.cargo_bin)?;
357             }
358 
359             // Ensure the Cargo cache is up to date to simulate the behavior
360             // of having just generated a new one
361             let output = self
362                 .cargo_bin
363                 .command()?
364                 // Cargo detects config files based on `pwd` when running so
365                 // to ensure user provided Cargo config files are used, it's
366                 // critical to set the working directory to the manifest dir.
367                 .current_dir(manifest_dir)
368                 .arg("fetch")
369                 .arg("--manifest-path")
370                 .arg(manifest_path)
371                 .output()
372                 .context(format!(
373                     "Error running cargo to fetch crates '{}'",
374                     manifest_path.display()
375                 ))?;
376 
377             if !output.status.success() {
378                 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
379                 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
380                 bail!(format!(
381                     "Failed to fetch crates for lockfile: {}",
382                     output.status
383                 ))
384             }
385         } else {
386             debug!("Generating new lockfile");
387             // Simply invoke `cargo generate-lockfile`
388             let output = self
389                 .cargo_bin
390                 .command()?
391                 // Cargo detects config files based on `pwd` when running so
392                 // to ensure user provided Cargo config files are used, it's
393                 // critical to set the working directory to the manifest dir.
394                 .current_dir(manifest_dir)
395                 .arg("generate-lockfile")
396                 .arg("--manifest-path")
397                 .arg(manifest_path)
398                 .output()
399                 .context(format!(
400                     "Error running cargo to generate lockfile '{}'",
401                     manifest_path.display()
402                 ))?;
403 
404             if !output.status.success() {
405                 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
406                 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
407                 bail!(format!("Failed to generate lockfile: {}", output.status))
408             }
409         }
410 
411         cargo_lock::Lockfile::load(&generated_lockfile_path).context(format!(
412             "Failed to load lockfile: {}",
413             generated_lockfile_path.display()
414         ))
415     }
416 }
417 
418 /// A generator which runs `cargo vendor` on a given manifest
419 pub(crate) struct VendorGenerator {
420     /// The path to a `cargo` binary
421     cargo_bin: Cargo,
422 
423     /// The path to a `rustc` binary
424     rustc_bin: PathBuf,
425 }
426 
427 impl VendorGenerator {
new(cargo_bin: Cargo, rustc_bin: PathBuf) -> Self428     pub(crate) fn new(cargo_bin: Cargo, rustc_bin: PathBuf) -> Self {
429         Self {
430             cargo_bin,
431             rustc_bin,
432         }
433     }
434     #[tracing::instrument(name = "VendorGenerator::generate", skip_all)]
generate(&self, manifest_path: &Path, output_dir: &Path) -> Result<()>435     pub(crate) fn generate(&self, manifest_path: &Path, output_dir: &Path) -> Result<()> {
436         debug!(
437             "Vendoring {} to {}",
438             manifest_path.display(),
439             output_dir.display()
440         );
441         let manifest_dir = manifest_path.parent().unwrap();
442 
443         // Simply invoke `cargo generate-lockfile`
444         let output = self
445             .cargo_bin
446             .command()?
447             // Cargo detects config files based on `pwd` when running so
448             // to ensure user provided Cargo config files are used, it's
449             // critical to set the working directory to the manifest dir.
450             .current_dir(manifest_dir)
451             .arg("vendor")
452             .arg("--manifest-path")
453             .arg(manifest_path)
454             .arg("--locked")
455             .arg("--versioned-dirs")
456             .arg(output_dir)
457             .env("RUSTC", &self.rustc_bin)
458             .output()
459             .with_context(|| {
460                 format!(
461                     "Error running cargo to vendor sources for manifest '{}'",
462                     manifest_path.display()
463                 )
464             })?;
465 
466         if !output.status.success() {
467             eprintln!("{}", String::from_utf8_lossy(&output.stdout));
468             eprintln!("{}", String::from_utf8_lossy(&output.stderr));
469             bail!(format!("Failed to vendor sources with: {}", output.status))
470         }
471 
472         debug!("Done");
473         Ok(())
474     }
475 }
476 
477 /// Feature resolver info about a given crate.
478 #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
479 pub(crate) struct CargoTreeEntry {
480     /// The set of features active on a given crate.
481     #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
482     pub features: BTreeSet<String>,
483 
484     /// The dependencies of a given crate based on feature resolution.
485     #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
486     pub deps: BTreeSet<CrateId>,
487 }
488 
489 impl CargoTreeEntry {
new() -> Self490     pub fn new() -> Self {
491         Self {
492             features: BTreeSet::new(),
493             deps: BTreeSet::new(),
494         }
495     }
496 
is_empty(&self) -> bool497     pub fn is_empty(&self) -> bool {
498         self.features.is_empty() && self.deps.is_empty()
499     }
500 }
501 
502 impl SelectableScalar for CargoTreeEntry {}
503 
504 /// Feature and dependency metadata generated from [TreeResolver].
505 pub(crate) type TreeResolverMetadata = BTreeMap<CrateId, Select<CargoTreeEntry>>;
506 
507 /// Generates metadata about a Cargo workspace tree which supplements the inaccuracies in
508 /// standard [Cargo metadata](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html)
509 /// due lack of [Feature resolver 2](https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2)
510 /// support. This generator can be removed if the following is resolved:
511 /// <https://github.com/rust-lang/cargo/issues/9863>
512 pub(crate) struct TreeResolver {
513     /// The path to a `cargo` binary
514     cargo_bin: Cargo,
515 }
516 
517 impl TreeResolver {
new(cargo_bin: Cargo) -> Self518     pub(crate) fn new(cargo_bin: Cargo) -> Self {
519         Self { cargo_bin }
520     }
521 
522     /// Computes the set of enabled features for each target triplet for each crate.
523     #[tracing::instrument(name = "TreeResolver::generate", skip_all)]
generate( &self, pristine_manifest_path: &Path, target_triples: &BTreeSet<TargetTriple>, ) -> Result<TreeResolverMetadata>524     pub(crate) fn generate(
525         &self,
526         pristine_manifest_path: &Path,
527         target_triples: &BTreeSet<TargetTriple>,
528     ) -> Result<TreeResolverMetadata> {
529         debug!(
530             "Generating features for manifest {}",
531             pristine_manifest_path.display()
532         );
533 
534         let (manifest_path_with_transitive_proc_macros, tempdir) = self
535             .copy_project_with_explicit_deps_on_all_transitive_proc_macros(pristine_manifest_path)
536             .context("Failed to copy project with proc macro deps made direct")?;
537 
538         let mut target_triple_to_child = BTreeMap::new();
539         debug!("Spawning processes for {:?}", target_triples);
540         for target_triple in target_triples {
541             // We use `cargo tree` here because `cargo metadata` doesn't report
542             // back target-specific features (enabled with `resolver = "2"`).
543             // This is unfortunately a bit of a hack. See:
544             // - https://github.com/rust-lang/cargo/issues/9863
545             // - https://github.com/bazelbuild/rules_rust/issues/1662
546             let output = self
547                 .cargo_bin
548                 .command()?
549                 .current_dir(tempdir.path())
550                 .arg("tree")
551                 .arg("--manifest-path")
552                 .arg(&manifest_path_with_transitive_proc_macros)
553                 .arg("--edges")
554                 .arg("normal,build,dev")
555                 .arg("--prefix=depth")
556                 // https://doc.rust-lang.org/cargo/commands/cargo-tree.html#tree-formatting-options
557                 .arg("--format=|{p}|{f}|")
558                 .arg("--color=never")
559                 .arg("--workspace")
560                 .arg("--target")
561                 .arg(target_triple.to_cargo())
562                 .stdout(std::process::Stdio::piped())
563                 .stderr(std::process::Stdio::piped())
564                 .spawn()
565                 .with_context(|| {
566                     format!(
567                         "Error spawning cargo in child process to compute features for target '{}', manifest path '{}'",
568                         target_triple,
569                         manifest_path_with_transitive_proc_macros.display()
570                     )
571                 })?;
572             target_triple_to_child.insert(target_triple, output);
573         }
574         let mut metadata: BTreeMap<CrateId, BTreeMap<TargetTriple, CargoTreeEntry>> =
575             BTreeMap::new();
576         for (target_triple, child) in target_triple_to_child.into_iter() {
577             let output = child
578                 .wait_with_output()
579                 .with_context(|| {
580                     format!(
581                         "Error running cargo in child process to compute features for target '{}', manifest path '{}'",
582                         target_triple,
583                         manifest_path_with_transitive_proc_macros.display()
584                     )
585                 })?;
586             if !output.status.success() {
587                 eprintln!("{}", String::from_utf8_lossy(&output.stdout));
588                 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
589                 bail!(format!("Failed to run cargo tree: {}", output.status))
590             }
591             debug!("Process complete for {}", target_triple);
592             for (crate_id, tree_data) in
593                 parse_features_from_cargo_tree_output(output.stdout.lines())?
594             {
595                 debug!(
596                     "\tFor {}\n\t\tfeatures: {:?}\n\t\tdeps: {:?}",
597                     crate_id, tree_data.features, tree_data.deps
598                 );
599                 metadata
600                     .entry(crate_id.clone())
601                     .or_default()
602                     .insert(target_triple.clone(), tree_data);
603             }
604         }
605         let mut result = TreeResolverMetadata::new();
606         for (crate_id, tree_data) in metadata.into_iter() {
607             let common = CargoTreeEntry {
608                 features: tree_data
609                     .iter()
610                     .fold(
611                         None,
612                         |common: Option<BTreeSet<String>>, (_, data)| match common {
613                             Some(common) => {
614                                 Some(common.intersection(&data.features).cloned().collect())
615                             }
616                             None => Some(data.features.clone()),
617                         },
618                     )
619                     .unwrap_or_default(),
620                 deps: tree_data
621                     .iter()
622                     .fold(
623                         None,
624                         |common: Option<BTreeSet<CrateId>>, (_, data)| match common {
625                             Some(common) => {
626                                 Some(common.intersection(&data.deps).cloned().collect())
627                             }
628                             None => Some(data.deps.clone()),
629                         },
630                     )
631                     .unwrap_or_default(),
632             };
633             let mut select: Select<CargoTreeEntry> = Select::default();
634             for (target_triple, data) in tree_data {
635                 let mut entry = CargoTreeEntry::new();
636                 entry.features.extend(
637                     data.features
638                         .into_iter()
639                         .filter(|f| !common.features.contains(f)),
640                 );
641                 entry
642                     .deps
643                     .extend(data.deps.into_iter().filter(|d| !common.deps.contains(d)));
644                 if !entry.is_empty() {
645                     select.insert(entry, Some(target_triple.to_bazel()));
646                 }
647             }
648             if !common.is_empty() {
649                 select.insert(common, None);
650             }
651             result.insert(crate_id, select);
652         }
653         Ok(result)
654     }
655 
656     // Artificially inject all proc macros as dependency roots.
657     // Proc macros are built in the exec rather than target configuration.
658     // If we do cross-compilation, these will be different, and it will be important that we have resolved features and optional dependencies for the exec platform.
659     // If we don't treat proc macros as roots for the purposes of resolving, we may end up with incorrect platform-specific features.
660     //
661     // Example:
662     // If crate foo only uses a proc macro Linux,
663     // and that proc-macro depends on syn and requires the feature extra-traits,
664     // when we resolve on macOS we'll see we don't need the extra-traits feature of syn because the proc macro isn't used.
665     // But if we're cross-compiling for Linux from macOS, we'll build a syn, but because we're building it for macOS (because proc macros are exec-cfg dependencies),
666     // we'll build syn but _without_ the extra-traits feature (because our resolve told us it was Linux only).
667     //
668     // By artificially injecting all proc macros as root dependencies,
669     // it means we are forced to resolve the dependencies and features for those proc-macros on all platforms we care about,
670     // even if they wouldn't be used in some platform when cfg == exec.
671     //
672     // This is tested by the "keyring" example in examples/musl_cross_compiling - the keyring crate uses proc-macros only on Linux,
673     // and if we don't have this fake root injection, cross-compiling from Darwin to Linux won't work because features don't get correctly resolved for the exec=darwin case.
copy_project_with_explicit_deps_on_all_transitive_proc_macros( &self, pristine_manifest_path: &Path, ) -> Result<(PathBuf, tempfile::TempDir)>674     fn copy_project_with_explicit_deps_on_all_transitive_proc_macros(
675         &self,
676         pristine_manifest_path: &Path,
677     ) -> Result<(PathBuf, tempfile::TempDir)> {
678         let pristine_root = pristine_manifest_path.parent().unwrap();
679         let working_directory = tempfile::tempdir().context("Failed to make tempdir")?;
680         for file in std::fs::read_dir(pristine_root).context("Failed to read dir")? {
681             let source_path = file?.path();
682             let file_name = source_path.file_name().unwrap();
683             if file_name != "Cargo.toml" && file_name != "Cargo.lock" {
684                 let destination = working_directory.path().join(file_name);
685                 symlink(&source_path, &destination).with_context(|| {
686                     format!(
687                         "Failed to create symlink {:?} pointing at {:?}",
688                         destination, source_path
689                     )
690                 })?;
691             }
692         }
693         std::fs::copy(
694             pristine_root.join("Cargo.lock"),
695             working_directory.path().join("Cargo.lock"),
696         )
697         .with_context(|| {
698             format!(
699                 "Failed to copy Cargo.lock from {:?} to {:?}",
700                 pristine_root,
701                 working_directory.path()
702             )
703         })?;
704 
705         let cargo_metadata = self
706             .cargo_bin
707             .metadata_command_with_options(pristine_manifest_path, vec!["--locked".to_owned()])?
708             .manifest_path(pristine_manifest_path)
709             .exec()
710             .context("Failed to run cargo metadata to list transitive proc macros")?;
711         let proc_macros = cargo_metadata
712             .packages
713             .iter()
714             .filter(|p| {
715                 p.targets
716                     .iter()
717                     .any(|t| t.kind.iter().any(|k| k == "proc-macro"))
718             })
719             // Filter out any in-workspace proc macros, populate dependency details for non-in-workspace proc macros.
720             .filter_map(|pm| {
721                 if let Some(source) = pm.source.as_ref() {
722                     let mut detail = DependencyDetailWithOrd(cargo_toml::DependencyDetail {
723                         package: Some(pm.name.clone()),
724                         // Don't forcibly enable default features - if some other dependency enables them, they will still be enabled.
725                         default_features: false,
726                         ..cargo_toml::DependencyDetail::default()
727                     });
728 
729                     let source = match Source::parse(&source.repr, pm.version.to_string()) {
730                         Ok(source) => source,
731                         Err(err) => {
732                             return Some(Err(err));
733                         }
734                     };
735                     source.populate_details(&mut detail.0);
736 
737                     Some(Ok((pm.name.clone(), detail)))
738                 } else {
739                     None
740                 }
741             })
742             .collect::<Result<BTreeSet<_>>>()?;
743 
744         let mut manifest =
745             cargo_toml::Manifest::from_path(pristine_manifest_path).with_context(|| {
746                 format!(
747                     "Failed to parse Cargo.toml file at {:?}",
748                     pristine_manifest_path
749                 )
750             })?;
751 
752         // To add dependencies to a virtual workspace, we need to add them to a package inside the workspace,
753         // we can't just add them to the workspace directly.
754         if !proc_macros.is_empty() && manifest.package.is_none() {
755             if let Some(ref mut workspace) = &mut manifest.workspace {
756                 if !workspace.members.contains(&".".to_owned()) {
757                     workspace.members.push(".".to_owned());
758                 }
759                 manifest.package = Some(cargo_toml::Package::new(
760                     "rules_rust_fake_proc_macro_root",
761                     "0.0.0",
762                 ));
763             }
764             if manifest.lib.is_none() && manifest.bin.is_empty() {
765                 manifest.bin.push(cargo_toml::Product {
766                     name: Some("rules_rust_fake_proc_macro_root_bin".to_owned()),
767                     path: Some("/dev/null".to_owned()),
768                     ..cargo_toml::Product::default()
769                 })
770             }
771         }
772 
773         let mut count_map: HashMap<_, u64> = HashMap::new();
774         for (dep_name, detail) in proc_macros {
775             let count = count_map.entry(dep_name.clone()).or_default();
776             manifest.dependencies.insert(
777                 format!("rules_rust_fake_proc_macro_root_{}_{}", dep_name, count),
778                 cargo_toml::Dependency::Detailed(Box::new(detail.0)),
779             );
780             *count += 1;
781         }
782         let manifest_path_with_transitive_proc_macros = working_directory.path().join("Cargo.toml");
783         crate::splicing::write_manifest(&manifest_path_with_transitive_proc_macros, &manifest)?;
784         Ok((manifest_path_with_transitive_proc_macros, working_directory))
785     }
786 }
787 
788 // cargo_toml::DependencyDetail doesn't implement PartialOrd/Ord so can't be put in a sorted collection.
789 // Wrap it so we can sort things for stable orderings.
790 #[derive(Debug, PartialEq)]
791 struct DependencyDetailWithOrd(cargo_toml::DependencyDetail);
792 
793 impl PartialOrd for DependencyDetailWithOrd {
partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>794     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
795         Some(self.cmp(other))
796     }
797 }
798 
799 impl Ord for DependencyDetailWithOrd {
cmp(&self, other: &Self) -> std::cmp::Ordering800     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
801         let cargo_toml::DependencyDetail {
802             version,
803             registry,
804             registry_index,
805             path,
806             inherited,
807             git,
808             branch,
809             tag,
810             rev,
811             features,
812             optional,
813             default_features,
814             package,
815             unstable: _,
816         } = &self.0;
817 
818         version
819             .cmp(&other.0.version)
820             .then(registry.cmp(&other.0.registry))
821             .then(registry_index.cmp(&other.0.registry_index))
822             .then(path.cmp(&other.0.path))
823             .then(inherited.cmp(&other.0.inherited))
824             .then(git.cmp(&other.0.git))
825             .then(branch.cmp(&other.0.branch))
826             .then(tag.cmp(&other.0.tag))
827             .then(rev.cmp(&other.0.rev))
828             .then(features.cmp(&other.0.features))
829             .then(optional.cmp(&other.0.optional))
830             .then(default_features.cmp(&other.0.default_features))
831             .then(package.cmp(&other.0.package))
832     }
833 }
834 
835 impl Eq for DependencyDetailWithOrd {}
836 
837 #[derive(Debug, PartialEq, Eq)]
838 enum Source {
839     Registry {
840         registry: String,
841         version: String,
842     },
843     Git {
844         git: String,
845         rev: Option<String>,
846         branch: Option<String>,
847         tag: Option<String>,
848     },
849 }
850 
851 impl Source {
parse(string: &str, version: String) -> Result<Source>852     fn parse(string: &str, version: String) -> Result<Source> {
853         let url: Url = Url::parse(string)?;
854         let original_scheme = url.scheme().to_owned();
855         let scheme_parts: Vec<_> = original_scheme.split('+').collect();
856         match &scheme_parts[..] {
857             // e.g. registry+https://github.com/rust-lang/crates.io-index
858             ["registry", scheme] => {
859                 let new_url = set_url_scheme_despite_the_url_crate_not_wanting_us_to(&url, scheme)?;
860                 Ok(Self::Registry {
861                     registry: new_url,
862                     version,
863                 })
864             }
865             // e.g. git+https://github.com/serde-rs/serde.git?rev=9b868ef831c95f50dd4bde51a7eb52e3b9ee265a#9b868ef831c95f50dd4bde51a7eb52e3b9ee265a
866             ["git", scheme] => {
867                 let mut query: HashMap<String, String> = url
868                     .query_pairs()
869                     .map(|(k, v)| (k.into_owned(), v.into_owned()))
870                     .collect();
871 
872                 let mut url = url;
873                 url.set_fragment(None);
874                 url.set_query(None);
875                 let new_url = set_url_scheme_despite_the_url_crate_not_wanting_us_to(&url, scheme)?;
876 
877                 Ok(Self::Git {
878                     git: new_url,
879                     rev: query.remove("rev"),
880                     branch: query.remove("branch"),
881                     tag: query.remove("tag"),
882                 })
883             }
884             _ => {
885                 anyhow::bail!(
886                     "Couldn't parse source {:?}: Didn't recognise scheme",
887                     string
888                 );
889             }
890         }
891     }
892 
populate_details(self, details: &mut cargo_toml::DependencyDetail)893     fn populate_details(self, details: &mut cargo_toml::DependencyDetail) {
894         match self {
895             Self::Registry { registry, version } => {
896                 details.registry_index = Some(registry);
897                 details.version = Some(version);
898             }
899             Self::Git {
900                 git,
901                 rev,
902                 branch,
903                 tag,
904             } => {
905                 details.git = Some(git);
906                 details.rev = rev;
907                 details.branch = branch;
908                 details.tag = tag;
909             }
910         }
911     }
912 }
913 
set_url_scheme_despite_the_url_crate_not_wanting_us_to( url: &Url, new_scheme: &str, ) -> Result<String>914 fn set_url_scheme_despite_the_url_crate_not_wanting_us_to(
915     url: &Url,
916     new_scheme: &str,
917 ) -> Result<String> {
918     let (_old_scheme, new_url_without_scheme) = url.as_str().split_once(':').ok_or_else(|| {
919         anyhow::anyhow!(
920             "Cannot set schme of URL which doesn't contain \":\": {:?}",
921             url
922         )
923     })?;
924     Ok(format!("{new_scheme}:{new_url_without_scheme}"))
925 }
926 
927 /// Parses the output of `cargo tree --format=|{p}|{f}|`. Other flags may be
928 /// passed to `cargo tree` as well, but this format is critical.
parse_features_from_cargo_tree_output<I, S, E>( lines: I, ) -> Result<BTreeMap<CrateId, CargoTreeEntry>> where I: Iterator<Item = std::result::Result<S, E>>, S: AsRef<str>, E: std::error::Error + Sync + Send + 'static,929 fn parse_features_from_cargo_tree_output<I, S, E>(
930     lines: I,
931 ) -> Result<BTreeMap<CrateId, CargoTreeEntry>>
932 where
933     I: Iterator<Item = std::result::Result<S, E>>,
934     S: AsRef<str>,
935     E: std::error::Error + Sync + Send + 'static,
936 {
937     let mut tree_data = BTreeMap::<CrateId, CargoTreeEntry>::new();
938     let mut parents: Vec<CrateId> = Vec::new();
939     for line in lines {
940         let line = line?;
941         let line = line.as_ref();
942         if line.is_empty() {
943             continue;
944         }
945 
946         let parts = line.split('|').collect::<Vec<_>>();
947         if parts.len() != 4 {
948             bail!("Unexpected line '{}'", line);
949         }
950         // We expect the crate id (parts[1]) to be either
951         // "<crate name> v<crate version>" or
952         // "<crate name> v<crate version> (<path>)"
953         // "<crate name> v<crate version> (proc-macro) (<path>)"
954         // https://github.com/rust-lang/cargo/blob/19f952f160d4f750d1e12fad2bf45e995719673d/src/cargo/ops/tree/mod.rs#L281
955         let crate_id_parts = parts[1].split(' ').collect::<Vec<_>>();
956         if crate_id_parts.len() < 2 && crate_id_parts.len() > 4 {
957             bail!(
958                 "Unexpected crate id format '{}' when parsing 'cargo tree' output.",
959                 parts[1]
960             );
961         }
962         let version_str = crate_id_parts[1].strip_prefix('v').ok_or_else(|| {
963             anyhow!(
964                 "Unexpected crate version '{}' when parsing 'cargo tree' output.",
965                 crate_id_parts[1]
966             )
967         })?;
968         let version = Version::parse(version_str).context("Failed to parse version")?;
969         let crate_id = CrateId::new(crate_id_parts[0].to_owned(), version);
970 
971         // Update bookkeeping for dependency tracking.
972         let depth = parts[0]
973             .parse::<usize>()
974             .with_context(|| format!("Unexpected numeric value from cargo tree: {:?}", parts))?;
975         if (depth + 1) <= parents.len() {
976             // Drain parents until we get down to the right depth
977             let range = parents.len() - (depth + 1);
978             for _ in 0..range {
979                 parents.pop();
980             }
981 
982             // If the current parent does not have the same Crate ID, then
983             // it's likely we have moved to a different crate. This can happen
984             // in the following case
985             // ```
986             // ├── proc-macro2 v1.0.81
987             // │   └── unicode-ident v1.0.12
988             // ├── quote v1.0.36
989             // │   └── proc-macro2 v1.0.81 (*)
990             // ```
991             if parents.last() != Some(&crate_id) {
992                 parents.pop();
993                 parents.push(crate_id.clone());
994             }
995         } else {
996             // Start tracking the current crate as the new parent for any
997             // crates that represent a new depth in the dep tree.
998             parents.push(crate_id.clone());
999         }
1000 
1001         // Attribute any dependency that is not the root to it's parent.
1002         if depth > 0 {
1003             // Access the last item in the list of parents.
1004             if let Some(parent) = parents.iter().rev().nth(1) {
1005                 tree_data
1006                     .entry(parent.clone())
1007                     .or_default()
1008                     .deps
1009                     .insert(crate_id.clone());
1010             }
1011         }
1012 
1013         let mut features = if parts[2].is_empty() {
1014             BTreeSet::new()
1015         } else {
1016             parts[2].split(',').map(str::to_owned).collect()
1017         };
1018         tree_data
1019             .entry(crate_id)
1020             .or_default()
1021             .features
1022             .append(&mut features);
1023     }
1024     Ok(tree_data)
1025 }
1026 
1027 /// A helper function for writing Cargo metadata to a file.
write_metadata(path: &Path, metadata: &cargo_metadata::Metadata) -> Result<()>1028 pub(crate) fn write_metadata(path: &Path, metadata: &cargo_metadata::Metadata) -> Result<()> {
1029     let content =
1030         serde_json::to_string_pretty(metadata).context("Failed to serialize Cargo Metadata")?;
1031 
1032     fs::write(path, content).context("Failed to write metadata to disk")
1033 }
1034 
1035 /// A helper function for deserializing Cargo metadata and lockfiles
load_metadata( metadata_path: &Path, ) -> Result<(cargo_metadata::Metadata, cargo_lock::Lockfile)>1036 pub(crate) fn load_metadata(
1037     metadata_path: &Path,
1038 ) -> Result<(cargo_metadata::Metadata, cargo_lock::Lockfile)> {
1039     // Locate the Cargo.lock file related to the metadata file.
1040     let lockfile_path = metadata_path
1041         .parent()
1042         .expect("metadata files should always have parents")
1043         .join("Cargo.lock");
1044     if !lockfile_path.exists() {
1045         bail!(
1046             "The metadata file at {} is not next to a `Cargo.lock` file.",
1047             metadata_path.display()
1048         )
1049     }
1050 
1051     let content = fs::read_to_string(metadata_path)
1052         .with_context(|| format!("Failed to load Cargo Metadata: {}", metadata_path.display()))?;
1053 
1054     let metadata =
1055         serde_json::from_str(&content).context("Unable to deserialize Cargo metadata")?;
1056 
1057     let lockfile = cargo_lock::Lockfile::load(&lockfile_path)
1058         .with_context(|| format!("Failed to load lockfile: {}", lockfile_path.display()))?;
1059 
1060     Ok((metadata, lockfile))
1061 }
1062 
1063 #[cfg(test)]
1064 mod test {
1065     use super::*;
1066 
1067     #[test]
deserialize_cargo_update_request_for_eager()1068     fn deserialize_cargo_update_request_for_eager() {
1069         for value in ["all", "full", "eager"] {
1070             let request = CargoUpdateRequest::from_str(value).unwrap();
1071 
1072             assert_eq!(request, CargoUpdateRequest::Eager);
1073         }
1074     }
1075 
1076     #[test]
deserialize_cargo_update_request_for_workspace()1077     fn deserialize_cargo_update_request_for_workspace() {
1078         for value in ["1", "true", "yes", "on", "workspace", "minimal"] {
1079             let request = CargoUpdateRequest::from_str(value).unwrap();
1080 
1081             assert_eq!(request, CargoUpdateRequest::Workspace);
1082         }
1083     }
1084 
1085     #[test]
deserialize_cargo_update_request_for_package()1086     fn deserialize_cargo_update_request_for_package() {
1087         let request = CargoUpdateRequest::from_str("cargo-bazel").unwrap();
1088 
1089         assert_eq!(
1090             request,
1091             CargoUpdateRequest::Package {
1092                 name: "cargo-bazel".to_owned(),
1093                 version: None
1094             }
1095         );
1096     }
1097 
1098     #[test]
deserialize_cargo_update_request_for_precise()1099     fn deserialize_cargo_update_request_for_precise() {
1100         let request = CargoUpdateRequest::from_str("[email protected]").unwrap();
1101 
1102         assert_eq!(
1103             request,
1104             CargoUpdateRequest::Package {
1105                 name: "[email protected]".to_owned(),
1106                 version: None
1107             }
1108         );
1109     }
1110 
1111     #[test]
deserialize_cargo_update_request_for_precise_pin()1112     fn deserialize_cargo_update_request_for_precise_pin() {
1113         let request = CargoUpdateRequest::from_str("[email protected]=4.5.6").unwrap();
1114 
1115         assert_eq!(
1116             request,
1117             CargoUpdateRequest::Package {
1118                 name: "[email protected]".to_owned(),
1119                 version: Some("4.5.6".to_owned()),
1120             }
1121         );
1122     }
1123 
1124     #[test]
parse_features_from_cargo_tree_output_prefix_none()1125     fn parse_features_from_cargo_tree_output_prefix_none() {
1126         let autocfg_id = CrateId {
1127             name: "autocfg".to_owned(),
1128             version: Version::new(1, 2, 0),
1129         };
1130         let chrono_id = CrateId {
1131             name: "chrono".to_owned(),
1132             version: Version::new(0, 4, 24),
1133         };
1134         let core_foundation_sys_id = CrateId {
1135             name: "core-foundation-sys".to_owned(),
1136             version: Version::new(0, 8, 6),
1137         };
1138         let cpufeatures_id = CrateId {
1139             name: "cpufeatures".to_owned(),
1140             version: Version::new(0, 2, 7),
1141         };
1142         let iana_time_zone_id = CrateId {
1143             name: "iana-time-zone".to_owned(),
1144             version: Version::new(0, 1, 60),
1145         };
1146         let libc_id = CrateId {
1147             name: "libc".to_owned(),
1148             version: Version::new(0, 2, 153),
1149         };
1150         let num_integer_id = CrateId {
1151             name: "num-integer".to_owned(),
1152             version: Version::new(0, 1, 46),
1153         };
1154         let num_traits_id = CrateId {
1155             name: "num-traits".to_owned(),
1156             version: Version::new(0, 2, 18),
1157         };
1158         let proc_macro2_id = CrateId {
1159             name: "proc-macro2".to_owned(),
1160             version: Version::new(1, 0, 81),
1161         };
1162         let quote_id = CrateId {
1163             name: "quote".to_owned(),
1164             version: Version::new(1, 0, 36),
1165         };
1166         let serde_derive_id = CrateId {
1167             name: "serde_derive".to_owned(),
1168             version: Version::new(1, 0, 152),
1169         };
1170         let syn_id = CrateId {
1171             name: "syn".to_owned(),
1172             version: Version::new(1, 0, 109),
1173         };
1174         let time_id = CrateId {
1175             name: "time".to_owned(),
1176             version: Version::new(0, 1, 45),
1177         };
1178         let tree_data_id = CrateId {
1179             name: "tree-data".to_owned(),
1180             version: Version::new(0, 1, 0),
1181         };
1182         let unicode_ident_id = CrateId {
1183             name: "unicode-ident".to_owned(),
1184             version: Version::new(1, 0, 12),
1185         };
1186 
1187         // |tree-data v0.1.0 (/rules_rust/crate_universe/test_data/metadata/tree_data)||
1188         // ├── |chrono v0.4.24|clock,default,iana-time-zone,js-sys,oldtime,std,time,wasm-bindgen,wasmbind,winapi|
1189         // │   ├── |iana-time-zone v0.1.60|fallback|
1190         // │   │   └── |core-foundation-sys v0.8.6|default,link|
1191         // │   ├── |num-integer v0.1.46||
1192         // │   │   └── |num-traits v0.2.18|i128|
1193         // │   │       [build-dependencies]
1194         // │   │       └── |autocfg v1.2.0||
1195         // │   ├── |num-traits v0.2.18|i128| (*)
1196         // │   └── |time v0.1.45||
1197         // │       └── |libc v0.2.153|default,std|
1198         // ├── |cpufeatures v0.2.7||
1199         // │   └── |libc v0.2.153|default,std|
1200         // └── |serde_derive v1.0.152 (proc-macro)|default|
1201         //     ├── |proc-macro2 v1.0.81|default,proc-macro|
1202         //     │   └── |unicode-ident v1.0.12||
1203         //     ├── |quote v1.0.36|default,proc-macro|
1204         //     │   └── |proc-macro2 v1.0.81|default,proc-macro| (*)
1205         //     └── |syn v1.0.109|clone-impls,default,derive,parsing,printing,proc-macro,quote|
1206         //         ├── |proc-macro2 v1.0.81|default,proc-macro| (*)
1207         //         ├── |quote v1.0.36|default,proc-macro| (*)
1208         //         └── |unicode-ident v1.0.12||
1209         let output = parse_features_from_cargo_tree_output(
1210             vec![
1211                 Ok::<&str, std::io::Error>(""), // Blank lines are ignored.
1212                 Ok("0|tree-data v0.1.0 (/rules_rust/crate_universe/test_data/metadata/tree_data)||"),
1213                 Ok("1|chrono v0.4.24|clock,default,iana-time-zone,js-sys,oldtime,std,time,wasm-bindgen,wasmbind,winapi|"),
1214                 Ok("2|iana-time-zone v0.1.60|fallback|"),
1215                 Ok("3|core-foundation-sys v0.8.6|default,link|"),
1216                 Ok("2|num-integer v0.1.46||"),
1217                 Ok("3|num-traits v0.2.18|i128|"),
1218                 Ok("4|autocfg v1.2.0||"),
1219                 Ok("2|num-traits v0.2.18|i128| (*)"),
1220                 Ok("2|time v0.1.45||"),
1221                 Ok("3|libc v0.2.153|default,std|"),
1222                 Ok("1|cpufeatures v0.2.7||"),
1223                 Ok("2|libc v0.2.153|default,std|"),
1224                 Ok("1|serde_derive v1.0.152 (proc-macro)|default|"),
1225                 Ok("2|proc-macro2 v1.0.81|default,proc-macro|"),
1226                 Ok("3|unicode-ident v1.0.12||"),
1227                 Ok("2|quote v1.0.36|default,proc-macro|"),
1228                 Ok("3|proc-macro2 v1.0.81|default,proc-macro| (*)"),
1229                 Ok("2|syn v1.0.109|clone-impls,default,derive,parsing,printing,proc-macro,quote|"),
1230                 Ok("3|proc-macro2 v1.0.81|default,proc-macro| (*)"),
1231                 Ok("3|quote v1.0.36|default,proc-macro| (*)"),
1232                 Ok("3|unicode-ident v1.0.12||"),
1233             ]
1234             .into_iter()
1235         )
1236         .unwrap();
1237         assert_eq!(
1238             BTreeMap::from([
1239                 (
1240                     autocfg_id.clone(),
1241                     CargoTreeEntry {
1242                         features: BTreeSet::new(),
1243                         deps: BTreeSet::new(),
1244                     },
1245                 ),
1246                 (
1247                     chrono_id.clone(),
1248                     CargoTreeEntry {
1249                         features: BTreeSet::from([
1250                             "clock".to_owned(),
1251                             "default".to_owned(),
1252                             "iana-time-zone".to_owned(),
1253                             "js-sys".to_owned(),
1254                             "oldtime".to_owned(),
1255                             "std".to_owned(),
1256                             "time".to_owned(),
1257                             "wasm-bindgen".to_owned(),
1258                             "wasmbind".to_owned(),
1259                             "winapi".to_owned(),
1260                         ]),
1261                         deps: BTreeSet::from([
1262                             iana_time_zone_id.clone(),
1263                             num_integer_id.clone(),
1264                             num_traits_id.clone(),
1265                             time_id.clone(),
1266                         ]),
1267                     }
1268                 ),
1269                 (
1270                     core_foundation_sys_id.clone(),
1271                     CargoTreeEntry {
1272                         features: BTreeSet::from(["default".to_owned(), "link".to_owned()]),
1273                         deps: BTreeSet::new(),
1274                     }
1275                 ),
1276                 (
1277                     cpufeatures_id.clone(),
1278                     CargoTreeEntry {
1279                         features: BTreeSet::new(),
1280                         deps: BTreeSet::from([libc_id.clone()]),
1281                     },
1282                 ),
1283                 (
1284                     iana_time_zone_id,
1285                     CargoTreeEntry {
1286                         features: BTreeSet::from(["fallback".to_owned()]),
1287                         deps: BTreeSet::from([core_foundation_sys_id]),
1288                     }
1289                 ),
1290                 (
1291                     libc_id.clone(),
1292                     CargoTreeEntry {
1293                         features: BTreeSet::from(["default".to_owned(), "std".to_owned()]),
1294                         deps: BTreeSet::new(),
1295                     }
1296                 ),
1297                 (
1298                     num_integer_id,
1299                     CargoTreeEntry {
1300                         features: BTreeSet::new(),
1301                         deps: BTreeSet::from([num_traits_id.clone()]),
1302                     },
1303                 ),
1304                 (
1305                     num_traits_id,
1306                     CargoTreeEntry {
1307                         features: BTreeSet::from(["i128".to_owned()]),
1308                         deps: BTreeSet::from([autocfg_id]),
1309                     }
1310                 ),
1311                 (
1312                     proc_macro2_id.clone(),
1313                     CargoTreeEntry {
1314                         features: BTreeSet::from(["default".to_owned(), "proc-macro".to_owned()]),
1315                         deps: BTreeSet::from([unicode_ident_id.clone()])
1316                     }
1317                 ),
1318                 (
1319                     quote_id.clone(),
1320                     CargoTreeEntry {
1321                         features: BTreeSet::from(["default".to_owned(), "proc-macro".to_owned()]),
1322                         deps: BTreeSet::from([proc_macro2_id.clone()]),
1323                     }
1324                 ),
1325                 (
1326                     serde_derive_id.clone(),
1327                     CargoTreeEntry {
1328                         features: BTreeSet::from(["default".to_owned()]),
1329                         deps: BTreeSet::from([
1330                             proc_macro2_id.clone(),
1331                             quote_id.clone(),
1332                             syn_id.clone()
1333                         ]),
1334                     }
1335                 ),
1336                 (
1337                     syn_id,
1338                     CargoTreeEntry {
1339                         features: BTreeSet::from([
1340                             "clone-impls".to_owned(),
1341                             "default".to_owned(),
1342                             "derive".to_owned(),
1343                             "parsing".to_owned(),
1344                             "printing".to_owned(),
1345                             "proc-macro".to_owned(),
1346                             "quote".to_owned(),
1347                         ]),
1348                         deps: BTreeSet::from([proc_macro2_id, quote_id, unicode_ident_id.clone(),]),
1349                     }
1350                 ),
1351                 (
1352                     time_id,
1353                     CargoTreeEntry {
1354                         features: BTreeSet::new(),
1355                         deps: BTreeSet::from([libc_id]),
1356                     }
1357                 ),
1358                 (
1359                     tree_data_id,
1360                     CargoTreeEntry {
1361                         features: BTreeSet::new(),
1362                         deps: BTreeSet::from([chrono_id, cpufeatures_id, serde_derive_id,]),
1363                     }
1364                 ),
1365                 (
1366                     unicode_ident_id,
1367                     CargoTreeEntry {
1368                         features: BTreeSet::new(),
1369                         deps: BTreeSet::new()
1370                     }
1371                 )
1372             ]),
1373             output,
1374         );
1375     }
1376 
1377     #[test]
serde_cargo_tree_entry()1378     fn serde_cargo_tree_entry() {
1379         {
1380             let entry: CargoTreeEntry = serde_json::from_str("{}").unwrap();
1381             assert_eq!(CargoTreeEntry::new(), entry);
1382         }
1383         {
1384             let entry: CargoTreeEntry =
1385                 serde_json::from_str(r#"{"features": ["default"]}"#).unwrap();
1386             assert_eq!(
1387                 CargoTreeEntry {
1388                     features: BTreeSet::from(["default".to_owned()]),
1389                     deps: BTreeSet::new(),
1390                 },
1391                 entry
1392             );
1393         }
1394         {
1395             let entry: CargoTreeEntry =
1396                 serde_json::from_str(r#"{"deps": ["common 1.2.3"]}"#).unwrap();
1397             assert_eq!(
1398                 CargoTreeEntry {
1399                     features: BTreeSet::new(),
1400                     deps: BTreeSet::from([CrateId::new(
1401                         "common".to_owned(),
1402                         Version::new(1, 2, 3)
1403                     )]),
1404                 },
1405                 entry
1406             );
1407         }
1408         {
1409             let entry: CargoTreeEntry =
1410                 serde_json::from_str(r#"{"features": ["default"], "deps": ["common 1.2.3"]}"#)
1411                     .unwrap();
1412             assert_eq!(
1413                 CargoTreeEntry {
1414                     features: BTreeSet::from(["default".to_owned()]),
1415                     deps: BTreeSet::from([CrateId::new(
1416                         "common".to_owned(),
1417                         Version::new(1, 2, 3)
1418                     )]),
1419                 },
1420                 entry
1421             );
1422         }
1423     }
1424 
1425     #[test]
parse_registry_source()1426     fn parse_registry_source() {
1427         let source = Source::parse(
1428             "registry+https://github.com/rust-lang/crates.io-index",
1429             "1.0.1".to_owned(),
1430         )
1431         .unwrap();
1432         assert_eq!(
1433             source,
1434             Source::Registry {
1435                 registry: "https://github.com/rust-lang/crates.io-index".to_owned(),
1436                 version: "1.0.1".to_owned()
1437             }
1438         );
1439     }
1440 
1441     #[test]
parse_git_source()1442     fn parse_git_source() {
1443         let source = Source::parse("git+https://github.com/serde-rs/serde.git?rev=9b868ef831c95f50dd4bde51a7eb52e3b9ee265a#9b868ef831c95f50dd4bde51a7eb52e3b9ee265a", "unused".to_owned()).unwrap();
1444         assert_eq!(
1445             source,
1446             Source::Git {
1447                 git: "https://github.com/serde-rs/serde.git".to_owned(),
1448                 rev: Some("9b868ef831c95f50dd4bde51a7eb52e3b9ee265a".to_owned()),
1449                 branch: None,
1450                 tag: None,
1451             }
1452         );
1453     }
1454 }
1455