Use the feature resolver for imports and updates. Also rework and improve the dependency differ to use the fact that the Dependency type is hashable. Get rid of crate_tool/src/crates_io.rs because all functionality is now superseded by crates_io_util. Test: treehugger, cargo test, ./crate_tool analyze-updates ahash Change-Id: If7f5a7985afe032f966d13686de791500fafe6fc
diff --git a/tools/external_crates/crate_tool/src/crates_io.rs b/tools/external_crates/crate_tool/src/crates_io.rs deleted file mode 100644 index 0b7fda5..0000000 --- a/tools/external_crates/crate_tool/src/crates_io.rs +++ /dev/null
@@ -1,56 +0,0 @@ -// Copyright (C) 2024 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crates_index::{Dependency, DependencyKind, Version}; -use crates_io_util::AndroidTarget; -use semver::VersionReq; -use std::collections::HashMap; - -/// Filter dependencies for those likely to be relevant to Android. -pub trait AndroidDependencies { - fn android_deps(&self) -> impl DoubleEndedIterator<Item = &Dependency>; - fn android_version_reqs_by_name(&self) -> HashMap<&str, &str> { - self.android_deps().map(|dep| (dep.crate_name(), dep.requirement())).collect() - } - fn android_deps_with_version_reqs( - &self, - ) -> impl DoubleEndedIterator<Item = (&Dependency, VersionReq)> { - self.android_deps().filter_map(|dep| { - VersionReq::parse(dep.requirement()).map_or(None, |req| Some((dep, req))) - }) - } -} -impl AndroidDependencies for Version { - fn android_deps(&self) -> impl DoubleEndedIterator<Item = &Dependency> { - self.dependencies().iter().filter(|dep| { - dep.kind() == DependencyKind::Normal && !dep.is_optional() && dep.is_android_target() - }) - } -} - -pub trait DependencyChanges { - fn is_new_dep(&self, base_deps: &HashMap<&str, &str>) -> bool; - fn is_changed_dep(&self, base_deps: &HashMap<&str, &str>) -> bool; -} - -impl DependencyChanges for Dependency { - fn is_new_dep(&self, base_deps: &HashMap<&str, &str>) -> bool { - !base_deps.contains_key(self.crate_name()) - } - - fn is_changed_dep(&self, base_deps: &HashMap<&str, &str>) -> bool { - let base_dep = base_deps.get(self.crate_name()); - base_dep.is_none() || base_dep.is_some_and(|base_req| *base_req != self.requirement()) - } -}
diff --git a/tools/external_crates/crate_tool/src/lib.rs b/tools/external_crates/crate_tool/src/lib.rs index 20ca22a..037b9b0 100644 --- a/tools/external_crates/crate_tool/src/lib.rs +++ b/tools/external_crates/crate_tool/src/lib.rs
@@ -27,7 +27,6 @@ mod android_bp; mod crate_collection; mod crate_type; -mod crates_io; mod managed_crate; mod managed_repo; mod patch;
diff --git a/tools/external_crates/crate_tool/src/managed_repo.rs b/tools/external_crates/crate_tool/src/managed_repo.rs index 3c62a55..6e44974 100644 --- a/tools/external_crates/crate_tool/src/managed_repo.rs +++ b/tools/external_crates/crate_tool/src/managed_repo.rs
@@ -22,7 +22,10 @@ use anyhow::{anyhow, bail, Context, Result}; use crates_index::DependencyKind; -use crates_io_util::{CratesIoIndex, GetVersion, SafeVersions}; +use crates_io_util::{ + CratesIoIndex, DependencyDiffer, FeatureResolver, GetVersion, ParsedVersion, ParsedVersionReq, + SafeVersions, +}; use google_metadata::GoogleMetadata; use itertools::Itertools; use license_checker::find_licenses; @@ -38,7 +41,6 @@ copy_dir, crate_collection::CrateCollection, crate_type::Crate, - crates_io::{AndroidDependencies, DependencyChanges}, managed_crate::ManagedCrate, pseudo_crate::{CargoVendorDirty, PseudoCrate}, upgradable::{IsUpgradableTo, MatchesWithCompatibilityRule, SemverCompatibilityRule}, @@ -178,8 +180,10 @@ for version in cio_crate.safe_versions() { println!("Version {}", version.version()); + let resolver = FeatureResolver::new(version); let mut found_problems = false; - for (dep, req) in version.android_deps_with_version_reqs() { + for dep in resolver.resolve(None as Option<Box<dyn Iterator<Item = &str>>>)? { + let req = dep.parsed_version_req()?; let cc = if managed_crates.contains_crate(dep.crate_name()) { &managed_crates } else { @@ -473,7 +477,7 @@ krate.name(), krate.android_version() ))?; - let base_deps = base_version.android_version_reqs_by_name(); + let dep_differ = DependencyDiffer::new(base_version); let mut newer_versions = cio_crate.safe_versions_gt(krate.android_version()).peekable(); if newer_versions.peek().is_none() { @@ -482,7 +486,8 @@ for version in newer_versions { println!("Version {}", version.version()); let mut found_problems = false; - let parsed_version = semver::Version::parse(version.version())?; + let parsed_version = version.parsed_version()?; + let resolver = FeatureResolver::new(version); if !krate .android_version() .is_upgradable_to(&parsed_version, SemverCompatibilityRule::Strict) @@ -497,12 +502,13 @@ println!(" Semver-compatible, but only by relaxed standards since major version is 0"); } } + let diff = dep_differ.diff(version); // Check to see if the update has any missing dependencies. // We try to be a little clever about this in the following ways: // * Only consider deps that are likely to be relevant to Android. For example, ignore Windows-only deps. // * If a dep is missing, but the same dep exists for the current version of the crate, it's probably not actually necessary. // * Use relaxed version requirements, treating 0.x and 0.y as compatible, even though they aren't according to semver rules. - for (dep, req) in version.android_deps_with_version_reqs() { + for dep in resolver.resolve(None as Option<Box<dyn Iterator<Item = &str>>>)? { let cc = if managed_crates.contains_crate(dep.crate_name()) { &managed_crates } else { @@ -515,14 +521,14 @@ dep.crate_name(), dep.requirement() ); - if !dep.is_new_dep(&base_deps) { + if !diff.is_added(dep) { println!(" But the current version has the same dependency, and it seems to work"); } else { continue; } } for (_, dep_crate) in cc.get_versions(dep.crate_name()) { - if !req.matches_with_compatibility_rule( + if !dep.parsed_version_req()?.matches_with_compatibility_rule( dep_crate.version(), SemverCompatibilityRule::Loose, ) { @@ -534,7 +540,7 @@ dep_crate.version(), dep_crate.path() ); - if !dep.is_changed_dep(&base_deps) { + if !diff.is_changed(dep) { println!(" But the current version has the same dependency and it seems to work.") } } @@ -575,7 +581,7 @@ continue; } let base_version = base_version.unwrap(); - let base_deps = base_version.android_version_reqs_by_name(); + let dep_differ = DependencyDiffer::new(base_version); let patch_dir = krate.path().join("patches").unwrap(); if patch_dir.abs().exists() && !consider_patched_crates { @@ -594,8 +600,10 @@ if !krate.version().is_upgradable_to(&parsed_version, semver_compatibility) { continue; } - if !version.android_deps_with_version_reqs().any(|(dep, req)| { - if !dep.is_changed_dep(&base_deps) { + let resolver = FeatureResolver::new(version); + if !resolver.resolve(None as Option<Box<dyn Iterator<Item = &str>>>)?.any(|dep| { + let diff = dep_differ.diff(version); + if !diff.is_changed(dep) { return false; } let cc = if managed_crates.contains_crate(dep.crate_name()) { @@ -603,6 +611,9 @@ } else { &legacy_crates }; + let Ok(req) = dep.parsed_version_req() else { + return false; + }; for (_, dep_crate) in cc.get_versions(dep.crate_name()) { if req.matches_with_compatibility_rule( dep_crate.version(),
diff --git a/tools/external_crates/crates_io_util/src/android_target.rs b/tools/external_crates/crates_io_util/src/android_target.rs index 9bc542a..5c6fe40 100644 --- a/tools/external_crates/crates_io_util/src/android_target.rs +++ b/tools/external_crates/crates_io_util/src/android_target.rs
@@ -16,7 +16,7 @@ targets::{Arch, Family, Os}, Predicate, TargetPredicate, }; -use crates_index::Dependency; +use crates_index::{Dependency, Version}; /// Parse cfg expressions in dependencies and determine if they refer to a target relevant to Android. /// Dependencies are relevant if they are for Unix, Android, or Linux, and for an architecture we care about (Arm, RISC-V, or X86) @@ -51,9 +51,32 @@ }) } +/// Get the required Android dependencies of a crate. +pub trait RequiredAndroidDeps { + /// Returns the required Android dependencies of a crate. + /// That is, dependencies that are: + /// * Non-optional + /// * Normal (not Build or Dev) + /// * If they have a target cfg expression, it applies to Android. + fn required_android_deps(&self) -> impl DoubleEndedIterator<Item = &crates_index::Dependency>; +} + +impl RequiredAndroidDeps for Version { + fn required_android_deps(&self) -> impl DoubleEndedIterator<Item = &crates_index::Dependency> { + self.dependencies().iter().filter(|dep| { + dep.kind() == crates_index::DependencyKind::Normal + && !dep.is_optional() + && dep.is_android_target() + }) + } +} + #[cfg(test)] mod tests { + use itertools::assert_equal; + use super::*; + #[test] fn test_android_cfgs() { assert!(!is_android("asmjs-unknown-emscripten"), "Parse error"); @@ -83,4 +106,19 @@ "cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))" ), "rustix 0.38.31"); } + + #[test] + fn test_required_android_deps() { + let aarch64_paging_0_7_1: Version = + serde_json::from_str(include_str!("testdata/aarch64-paging-0.7.1")) + .expect("Failed to parse JSON testdata"); + assert_equal( + aarch64_paging_0_7_1.required_android_deps(), + [aarch64_paging_0_7_1 + .dependencies() + .iter() + .find(|dep| dep.name() == "bitflags") + .unwrap()], + ); + } }
diff --git a/tools/external_crates/crates_io_util/src/dependency.rs b/tools/external_crates/crates_io_util/src/dependency.rs new file mode 100644 index 0000000..d020e04 --- /dev/null +++ b/tools/external_crates/crates_io_util/src/dependency.rs
@@ -0,0 +1,42 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crates_index::Dependency; +use semver::VersionReq; + +/// Trait for parsing version requirement strings of dependencies. +pub trait ParsedVersionReq { + /// Parses a version requirement, returning an error if unsuccessful. + fn parsed_version_req(&self) -> Result<VersionReq, semver::Error>; +} + +impl ParsedVersionReq for Dependency { + fn parsed_version_req(&self) -> Result<VersionReq, semver::Error> { + VersionReq::parse(self.requirement()) + } +} + +/// Trait for determining if dependencies in different versions of +/// a crate are the same. +pub trait SameDep { + /// Returns true if the two dependencies from different versions are + /// (probably) the same. That is, they have the same name and kind. + fn same_name_and_kind(&self, other: &Dependency) -> bool; +} + +impl SameDep for Dependency { + fn same_name_and_kind(&self, other: &Dependency) -> bool { + self.name() == other.name() && self.kind() == other.kind() + } +}
diff --git a/tools/external_crates/crates_io_util/src/dependency_diff.rs b/tools/external_crates/crates_io_util/src/dependency_diff.rs index 6e580c4..2992812 100644 --- a/tools/external_crates/crates_io_util/src/dependency_diff.rs +++ b/tools/external_crates/crates_io_util/src/dependency_diff.rs
@@ -12,74 +12,116 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; +use std::collections::{HashMap, HashSet}; use crates_index::{Dependency, Version}; +use itertools::Itertools; +use log::debug; -use crate::DepSet; +use crate::dependency::SameDep; /// Diff dependencies between two versions of a crate. /// Holds a reference to the current (base) dependencies of a crate, /// against which multiple newer versions can be compared. #[derive(Debug)] pub struct DependencyDiffer<'a> { - base_deps: DepSet<'a>, + base_version: &'a Version, } -/// A pair of dependencies that differ between two versions of a crate. -#[derive(Debug)] -pub struct ChangedDep<'base, 'other> { - /// The dependencies of the base version of the crate. - pub base: &'base Dependency, - /// The dependencies of the other version of the crate. - pub other: &'other Dependency, -} +impl<'a> DependencyDiffer<'a> { + /// Constructs a DependencyDiffer with the specified base version. + pub fn new(base_version: &'a Version) -> DependencyDiffer<'a> { + DependencyDiffer { base_version } + } + /// Compares the base version with `other` and returns the differences. + pub fn diff<'other>(&'a self, other: &'other Version) -> DependencyDiff<'a, 'other> { + debug!( + "diff base:\n{}", + self.base_version + .dependencies() + .iter() + .map(|d| format!("{} {:?}", d.name(), d.kind())) + .join("\n") + ); + debug!( + "diff other:\n{}", + other + .dependencies() + .iter() + .map(|d| format!("{} {:?}", d.name(), d.kind())) + .sorted() + .join("\n") + ); + let added: HashSet<&Dependency> = other + .dependencies() + .iter() + .filter(|other_dep| { + !self + .base_version + .dependencies() + .iter() + .any(|base_dep| base_dep.same_name_and_kind(other_dep)) + }) + .collect(); + debug!( + "diff added:\n{}", + added.iter().map(|d| format!("{} {:?}", d.name(), d.kind())).sorted().join("\n") + ); + let deleted: HashSet<&Dependency> = self + .base_version + .dependencies() + .iter() + .filter(|base_dep| { + !other.dependencies().iter().any(|other_dep| base_dep.same_name_and_kind(other_dep)) + }) + .collect(); + debug!( + "diff deleted:\n{}", + deleted.iter().map(|d| format!("{} {:?}", d.name(), d.kind())).join("\n") + ); + let mut changed = HashMap::new(); + for base_dep in self.base_version.dependencies() { + if let Some(other_dep) = + other.dependencies().iter().find(|other_dep| base_dep.same_name_and_kind(other_dep)) + { + if base_dep != other_dep { + changed.insert(other_dep, base_dep); + } + } + } + debug!( + "diff changed:\n{}", + changed.keys().map(|d| format!("{} {:?}", d.name(), d.kind())).sorted().join("\n") + ); -type ChangedDeps<'base, 'other> = BTreeMap<&'base str, ChangedDep<'base, 'other>>; + DependencyDiff { added, deleted, changed } + } +} /// The difference between the dependencies of two versions of a crate. #[derive(Debug)] pub struct DependencyDiff<'base, 'other> { /// Newly added dependencies. - pub added: DepSet<'other>, + added: HashSet<&'other Dependency>, /// Dependencies that were deleted. - pub deleted: DepSet<'base>, + deleted: HashSet<&'base Dependency>, /// Dependencies that differ between the two versions. For example, /// they could have different version requirements. - pub changed: ChangedDeps<'base, 'other>, + changed: HashMap<&'other Dependency, &'base Dependency>, } -impl<'a> DependencyDiffer<'a> { - /// Constructs a DependencyDiffer with the specified base version. - pub fn new(base: &'a Version) -> DependencyDiffer<'a> { - let base_deps = BTreeMap::from_iter(base.dependencies().iter().map(|d| (d.name(), d))); - DependencyDiffer { base_deps } +impl<'base, 'other> DependencyDiff<'base, 'other> { + /// Returns true if the dependency is added in the new version. + pub fn is_added(&self, other: &'other Dependency) -> bool { + self.added.contains(other) } - /// Compares the base version with `other` and returns the differences. - pub fn diff<'other>(&'a self, other: &'other Version) -> DependencyDiff<'a, 'other> { - let other_deps = BTreeMap::from_iter(other.dependencies().iter().map(|d| (d.name(), d))); - - let added = other_deps - .iter() - .filter(|(name, _)| !self.base_deps.contains_key(**name)) - .map(|(name, dep)| (*name, *dep)) - .collect(); - let deleted = self - .base_deps - .iter() - .filter(|(name, _)| !other_deps.contains_key(**name)) - .map(|(name, dep)| (*name, *dep)) - .collect(); - let mut changed = ChangedDeps::new(); - for (name, base_dep) in &self.base_deps { - if let Some(other_dep) = other_deps.get(name) { - if base_dep != other_dep { - changed.insert(*name, ChangedDep { base: base_dep, other: other_dep }); - } - } - } - - DependencyDiff { added, deleted, changed } + /// Returns true if the dependency is deleted in the new version. + pub fn is_deleted(&self, base: &'base Dependency) -> bool { + self.deleted.contains(base) + } + /// Returns true if the dependency is changed. + pub fn is_changed(&self, other: &'other Dependency) -> bool { + self.changed.contains_key(other) } } @@ -87,10 +129,17 @@ mod tests { use super::*; - use itertools::assert_equal; + use crates_index::DependencyKind; + use itertools::{assert_equal, Itertools}; + + fn init_logger() { + let _ = + env_logger::builder().filter_level(log::LevelFilter::max()).is_test(true).try_init(); + } #[test] fn trivial() { + init_logger(); let hashbrown_0_12_3: Version = serde_json::from_str(include_str!("testdata/hashbrown-0.12.3")) .expect("Failed to parse JSON testdata"); @@ -103,6 +152,7 @@ #[test] fn hashbrown() { + init_logger(); let hashbrown_0_12_3: Version = serde_json::from_str(include_str!("testdata/hashbrown-0.12.3")) .expect("Failed to parse JSON testdata"); @@ -111,21 +161,49 @@ .expect("Failed to parse JSON testdata"); let differ = DependencyDiffer::new(&hashbrown_0_12_3); let diff = differ.diff(&hashbrown_0_14_5); - assert_equal(diff.added.keys(), ["allocator-api2", "equivalent", "rkyv"].iter()); - assert!(diff.deleted.is_empty()); - assert_equal(diff.changed.keys(), ["ahash", "bumpalo"].iter()); + assert_equal( + diff.added + .iter() + .filter_map( + |d| if d.kind() == DependencyKind::Normal { Some(d.name()) } else { None }, + ) + .sorted(), + ["allocator-api2", "equivalent", "rkyv"], + ); + assert_equal( + diff.deleted + .iter() + .filter_map( + |d| if d.kind() == DependencyKind::Normal { Some(d.name()) } else { None }, + ) + .sorted(), + ["bumpalo"], + ); + assert_equal( + diff.changed + .keys() + .filter_map( + |d| if d.kind() == DependencyKind::Normal { Some(d.name()) } else { None }, + ) + .sorted(), + ["ahash"], + ); } #[test] fn winnow() { + init_logger(); let winnow_0_5_37: Version = serde_json::from_str(include_str!("testdata/winnow-0.5.37")) .expect("Failed to parse JSON testdata"); let winnow_0_6_20: Version = serde_json::from_str(include_str!("testdata/winnow-0.6.20")) .expect("Failed to parse JSON testdata"); let differ = DependencyDiffer::new(&winnow_0_5_37); let diff = differ.diff(&winnow_0_6_20); - assert_equal(diff.added.keys(), ["annotate-snippets", "anyhow", "automod"].iter()); - assert_equal(diff.deleted.keys(), ["escargot"].iter()); - assert_equal(diff.changed.keys(), ["snapbox", "terminal_size"].iter()); + assert_equal( + diff.added.iter().map(|d| d.name()).sorted(), + ["annotate-snippets", "anyhow", "automod"], + ); + assert_equal(diff.deleted.iter().map(|d| d.name()).sorted(), ["escargot"]); + assert_equal(diff.changed.keys().map(|d| d.name()).sorted(), ["snapbox", "terminal_size"]); } }
diff --git a/tools/external_crates/crates_io_util/src/feature_resolver.rs b/tools/external_crates/crates_io_util/src/feature_resolver.rs index 0ee9d29..b79fab8 100644 --- a/tools/external_crates/crates_io_util/src/feature_resolver.rs +++ b/tools/external_crates/crates_io_util/src/feature_resolver.rs
@@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use cargo_toml::DependencyDetail; -use crates_index::Version; +use crates_index::{Dependency, Version}; use itertools::Itertools; use log::debug; -use crate::{AndroidTarget, DepSet, Error}; +use crate::{AndroidTarget, Error, RequiredAndroidDeps}; /// Resolves a list of enabled features to a set of optional dependencies that these features switch on. pub struct FeatureResolver<'a> { @@ -31,11 +31,18 @@ pub fn new(version: &'a Version) -> FeatureResolver<'a> { FeatureResolver { version } } - /// Resolves a list of enabled features into a set of optional dependencies. + /// Resolves a list of enabled features into a set of required and optional dependencies. pub fn resolve( + &'a self, + features: Option<impl Iterator<Item = impl AsRef<str>>>, + ) -> Result<impl Iterator<Item = &'a crates_index::Dependency>, Error> { + Ok(self.version.required_android_deps().chain(self.resolve_optional(features)?)) + } + /// Resolves a list of enabled features into a set of optional dependencies. + pub fn resolve_optional( &self, features: Option<impl Iterator<Item = impl AsRef<str>>>, - ) -> Result<DepSet<'a>, Error> { + ) -> Result<HashSet<&Dependency>, Error> { debug!("Resolving {}", self.version.name()); let resolver = cargo_toml::features::Resolver::new(); @@ -92,7 +99,7 @@ let parsed_features = resolver.parse_custom(self.version.features(), parse_deps.into_iter()); - let mut resolved = DepSet::new(); + let mut resolved = HashSet::new(); let mut resolved_features = BTreeSet::new(); let mut frontier = BTreeSet::new(); if let Some(features) = features { @@ -124,7 +131,7 @@ && dep.is_optional() && dep.is_android_target() { - resolved.insert(dep.crate_name(), dep); + resolved.insert(*dep); } else { debug!(" skipping. is_conditional = {}, optional = {}, android_target = {}", dep_action.is_conditional, dep.is_optional(), dep.is_android_target()); } @@ -161,15 +168,24 @@ serde_json::from_str(include_str!("testdata/hashbrown-0.12.3")) .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&hashbrown_0_12_3); - assert_equal(resolver.resolve(Some(["bumpalo"].iter()))?.keys(), ["bumpalo"].iter()); - assert_equal(resolver.resolve(Some(["default"].iter()))?.keys(), ["ahash"].iter()); + assert_equal( + resolver.resolve_optional(Some(["bumpalo"].iter()))?.into_iter().map(|d| d.name()), + ["bumpalo"], + ); + assert_equal( + resolver.resolve_optional(Some(["default"].iter()))?.into_iter().map(|d| d.name()), + ["ahash"], + ); // ahash-compile-time-rng depends on ahash/compile-time-rng assert_equal( - resolver.resolve(Some(["ahash-compile-time-rng"].iter()))?.keys(), - ["ahash"].iter(), + resolver + .resolve_optional(Some(["ahash-compile-time-rng"].iter()))? + .into_iter() + .map(|d| d.name()), + ["ahash"], ); assert!( - resolver.resolve(Some(["inline-more"].iter()))?.is_empty(), + resolver.resolve_optional(Some(["inline-more"].iter()))?.is_empty(), "inline-more has no deps associated with it" ); @@ -178,12 +194,16 @@ .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&hashbrown_0_14_5); assert!( - resolver.resolve(Some(["bumpalo"].iter())).is_err(), + resolver.resolve_optional(Some(["bumpalo"].iter())).is_err(), "bumpalo is no longer an optional dep in hashbrown-0.14.5" ); assert_equal( - resolver.resolve(Some(["default"].iter()))?.keys(), - ["ahash", "allocator-api2"].iter(), + resolver + .resolve_optional(Some(["default"].iter()))? + .into_iter() + .map(|d| d.name()) + .sorted(), + ["ahash", "allocator-api2"], ); Ok(()) } @@ -195,8 +215,11 @@ .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&hashbrown_0_12_3); assert_equal( - resolver.resolve(Some(["ahash", "default", "inline-more", "raw"].iter()))?.keys(), - ["ahash"].iter(), + resolver + .resolve_optional(Some(["ahash", "default", "inline-more", "raw"].iter()))? + .into_iter() + .map(|d| d.name()), + ["ahash"], ); Ok(()) } @@ -208,28 +231,40 @@ .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&hashbrown_0_12_3); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; - assert_equal(resolver.resolve(empty), resolver.resolve(Some(["default"].iter()))); + assert_equal( + resolver.resolve_optional(empty), + resolver.resolve_optional(Some(["default"].iter())), + ); let hashbrown_0_14_5: Version = serde_json::from_str(include_str!("testdata/hashbrown-0.14.5")) .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&hashbrown_0_14_5); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; - assert_equal(resolver.resolve(empty), resolver.resolve(Some(["default"].iter()))); + assert_equal( + resolver.resolve_optional(empty), + resolver.resolve_optional(Some(["default"].iter())), + ); let winnow_0_5_37: Version = serde_json::from_str(include_str!("testdata/winnow-0.5.37")) .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&winnow_0_5_37); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; - assert_equal(resolver.resolve(empty), resolver.resolve(Some(["default"].iter()))); + assert_equal( + resolver.resolve_optional(empty), + resolver.resolve_optional(Some(["default"].iter())), + ); let cfg_if_1_0_0: Version = serde_json::from_str(include_str!("testdata/cfg-if-1.0.0")) .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&cfg_if_1_0_0); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; - assert!(resolver.resolve(empty)?.is_empty(), "cfg-if has no explicit 'default' feature"); assert!( - resolver.resolve(Some(["default"].iter())).is_err(), + resolver.resolve_optional(empty)?.is_empty(), + "cfg-if has no explicit 'default' feature" + ); + assert!( + resolver.resolve_optional(Some(["default"].iter())).is_err(), "cfg-if has no explicit 'default' feature" ); @@ -242,9 +277,12 @@ .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&winnow_0_5_37); // "simd" depends on "dep:memchr" - assert_equal(resolver.resolve(Some(["simd"].iter()))?.keys(), ["memchr"].iter()); + assert_equal( + resolver.resolve_optional(Some(["simd"].iter()))?.into_iter().map(|d| d.name()), + ["memchr"], + ); assert!( - resolver.resolve(Some(["std"].iter()))?.is_empty(), + resolver.resolve_optional(Some(["std"].iter()))?.is_empty(), "'std' depends on 'memchr?/std', which should be omitted since it's optional." ); @@ -258,7 +296,7 @@ .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&aarch64_paging_0_7_1); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; - assert_equal(resolver.resolve(empty)?.keys(), ["zerocopy"].iter()); + assert_equal(resolver.resolve_optional(empty)?.into_iter().map(|d| d.name()), ["zerocopy"]); Ok(()) } @@ -270,7 +308,7 @@ let resolver = FeatureResolver::new(&axum_0_7_0); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; assert_equal( - resolver.resolve(empty)?.keys(), + resolver.resolve_optional(empty)?.into_iter().map(|d| d.name()).sorted(), [ "hyper", "hyper-util", @@ -278,8 +316,7 @@ "serde_path_to_error", "serde_urlencoded", "tokio", - ] - .iter(), + ], ); Ok(()) @@ -291,7 +328,7 @@ .expect("Failed to parse JSON testdata"); let resolver = FeatureResolver::new(&chrono_0_4_39); let empty: Option<Box<dyn Iterator<Item = &str>>> = None; - assert!(resolver.resolve(empty)?.contains_key("android-tzdata")); + assert!(resolver.resolve_optional(empty)?.iter().any(|d| d.name() == "android-tzdata")); Ok(()) } }
diff --git a/tools/external_crates/crates_io_util/src/lib.rs b/tools/external_crates/crates_io_util/src/lib.rs index 13d63af..a439490 100644 --- a/tools/external_crates/crates_io_util/src/lib.rs +++ b/tools/external_crates/crates_io_util/src/lib.rs
@@ -16,13 +16,11 @@ //! of crates_index, with Android-specific helpers for dealing with features, //! dependencies, etc. -use std::{collections::BTreeMap, io}; - -use crates_index::Dependency; -use semver::VersionReq; - mod android_target; -pub use android_target::AndroidTarget; +pub use android_target::{AndroidTarget, RequiredAndroidDeps}; + +mod dependency; +pub use dependency::{ParsedVersionReq, SameDep}; mod dependency_diff; pub use dependency_diff::DependencyDiffer; @@ -42,20 +40,6 @@ mod versions; pub use versions::{GetVersion, IsSafe, ParsedVersion, SafeVersions}; -type DepSet<'a> = BTreeMap<&'a str, &'a Dependency>; - -/// Trait for parsing version requirement strings of dependencies. -pub trait ParsedVersionReq { - /// Parses a version requirement, returning an error if unsuccessful. - fn parsed_version_req(&self) -> Result<VersionReq, semver::Error>; -} - -impl ParsedVersionReq for Dependency { - fn parsed_version_req(&self) -> Result<VersionReq, semver::Error> { - VersionReq::parse(self.requirement()) - } -} - /// Error types for the 'crates_io_util' crate. #[derive(thiserror::Error, Debug)] pub enum Error { @@ -93,7 +77,7 @@ CratesIndexHttp(#[from] crates_index::http::Error), /// Propagated io::Error #[error(transparent)] - IoError(#[from] io::Error), + IoError(#[from] std::io::Error), /// Error running cargo #[error(transparent)] CargoError(#[from] success_or_error::Error),
diff --git a/tools/external_crates/crates_io_util/tests/resolver_test.rs b/tools/external_crates/crates_io_util/tests/resolver_test.rs index 415096c..e8853b6 100644 --- a/tools/external_crates/crates_io_util/tests/resolver_test.rs +++ b/tools/external_crates/crates_io_util/tests/resolver_test.rs
@@ -177,10 +177,9 @@ .collect::<BTreeSet<String>>(); let resolver = FeatureResolver::new(version); let optional = resolver - // .resolve(None as Option<Box<dyn Iterator<Item = &str>>>)? - .resolve(features.as_ref().map(|f| f.iter()))? - .into_keys() - .map(String::from) + .resolve_optional(features.as_ref().map(|f| f.iter()))? + .into_iter() + .map(|d| d.name().to_string()) .collect::<BTreeSet<String>>(); Ok(Deps { required, optional }) }