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