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