Allow user to specify semver compatibility logic for suggested updates.
Test: treehugger, ran crate updater.
Change-Id: Ide337e60bdae7c835242477a36d939f6ec0da2bf
diff --git a/tools/external_crates/crate_tool/src/lib.rs b/tools/external_crates/crate_tool/src/lib.rs
index e0ca11f..d6f3238 100644
--- a/tools/external_crates/crate_tool/src/lib.rs
+++ b/tools/external_crates/crate_tool/src/lib.rs
@@ -35,6 +35,7 @@
pub use self::android_bp::maybe_build_cargo_embargo;
pub use self::managed_repo::ManagedRepo;
+pub use self::upgradable::SemverCompatibilityRule;
#[derive(Error, Debug)]
pub enum CrateError {
diff --git a/tools/external_crates/crate_tool/src/main.rs b/tools/external_crates/crate_tool/src/main.rs
index 5f8bb1c..bd9a617 100644
--- a/tools/external_crates/crate_tool/src/main.rs
+++ b/tools/external_crates/crate_tool/src/main.rs
@@ -19,7 +19,9 @@
use anyhow::Result;
use clap::{Args, Parser, Subcommand};
-use crate_tool::{default_repo_root, maybe_build_cargo_embargo, ManagedRepo};
+use crate_tool::{
+ default_repo_root, maybe_build_cargo_embargo, ManagedRepo, SemverCompatibilityRule,
+};
use rooted_path::RootedPath;
use semver::Version;
@@ -114,6 +116,10 @@
/// Don't exclude crates that have patches.
#[arg(long, default_value_t = false)]
patches: bool,
+
+ /// How strict to be about enforcing semver compatibility.
+ #[arg(long, value_enum, default_value_t = SemverCompatibilityRule::Loose)]
+ semver_compatibility: SemverCompatibilityRule,
},
/// Update a crate to the specified version.
Update {
@@ -238,7 +244,9 @@
}
Cmd::UpdatableCrates {} => managed_repo.updatable_crates(),
Cmd::AnalyzeUpdates { crate_name } => managed_repo.analyze_updates(crate_name),
- Cmd::SuggestUpdates { patches } => managed_repo.suggest_updates(patches).map(|_x| ()),
+ Cmd::SuggestUpdates { patches, semver_compatibility } => {
+ managed_repo.suggest_updates(patches, semver_compatibility).map(|_x| ())
+ }
Cmd::Update { crate_name, version } => managed_repo.update(crate_name, version),
Cmd::Init {} => managed_repo.init(),
Cmd::TestMapping { crates } => {
diff --git a/tools/external_crates/crate_tool/src/managed_repo.rs b/tools/external_crates/crate_tool/src/managed_repo.rs
index cebbde9..6935988 100644
--- a/tools/external_crates/crate_tool/src/managed_repo.rs
+++ b/tools/external_crates/crate_tool/src/managed_repo.rs
@@ -42,7 +42,7 @@
license::{most_restrictive_type, update_module_license_files},
managed_crate::ManagedCrate,
pseudo_crate::{CargoVendorDirty, PseudoCrate},
- upgradable::{IsUpgradableTo, MatchesRelaxed},
+ upgradable::{IsUpgradableTo, MatchesWithCompatibilityRule, SemverCompatibilityRule},
SuccessOrError,
};
@@ -416,8 +416,12 @@
continue;
}
let versions = cc.get_versions(dep.crate_name()).collect::<Vec<_>>();
- let has_matching_version =
- versions.iter().any(|(nv, _)| req.matches_relaxed(nv.version()));
+ let has_matching_version = versions.iter().any(|(nv, _)| {
+ req.matches_with_compatibility_rule(
+ nv.version(),
+ SemverCompatibilityRule::Loose,
+ )
+ });
if !has_matching_version {
found_problems = true;
}
@@ -430,7 +434,14 @@
" Dep {} {} is {}satisfied by v{} at {}",
dep.crate_name(),
dep.requirement(),
- if req.matches_relaxed(dep_crate.version()) { "" } else { "not " },
+ if req.matches_with_compatibility_rule(
+ dep_crate.version(),
+ SemverCompatibilityRule::Loose
+ ) {
+ ""
+ } else {
+ "not "
+ },
dep_crate.version(),
dep_crate.path()
);
@@ -698,9 +709,15 @@
println!("Version {}", version.version());
let mut found_problems = false;
let parsed_version = semver::Version::parse(version.version())?;
- if !krate.android_version().is_upgradable_to(&parsed_version) {
+ if !krate
+ .android_version()
+ .is_upgradable_to(&parsed_version, SemverCompatibilityRule::Strict)
+ {
found_problems = true;
- if !krate.android_version().is_upgradable_to_relaxed(&parsed_version) {
+ if !krate
+ .android_version()
+ .is_upgradable_to(&parsed_version, SemverCompatibilityRule::Loose)
+ {
println!(" Not semver-compatible, even by relaxed standards");
} else {
println!(" Semver-compatible, but only by relaxed standards since major version is 0");
@@ -731,7 +748,10 @@
}
}
for (_, dep_crate) in cc.get_versions(dep.crate_name()) {
- if !req.matches_relaxed(dep_crate.version()) {
+ if !req.matches_with_compatibility_rule(
+ dep_crate.version(),
+ SemverCompatibilityRule::Loose,
+ ) {
found_problems = true;
println!(
" Dep {} {} is not satisfied by v{} at {}",
@@ -753,7 +773,11 @@
Ok(())
}
- pub fn suggest_updates(&self, consider_patched_crates: bool) -> Result<Vec<(String, String)>> {
+ pub fn suggest_updates(
+ &self,
+ consider_patched_crates: bool,
+ semver_compatibility: SemverCompatibilityRule,
+ ) -> Result<Vec<(String, String)>> {
let mut suggestions = Vec::new();
let mut managed_crates = self.new_cc();
managed_crates.add_from(self.managed_dir().rel())?;
@@ -786,7 +810,7 @@
for version in cio_crate.versions_gt(krate.version()).rev() {
let parsed_version = semver::Version::parse(version.version())?;
- if !krate.version().is_upgradable_to_relaxed(&parsed_version) {
+ if !krate.version().is_upgradable_to(&parsed_version, semver_compatibility) {
continue;
}
if !version.android_deps_with_version_reqs().any(|(dep, req)| {
@@ -799,7 +823,10 @@
&legacy_crates
};
for (_, dep_crate) in cc.get_versions(dep.crate_name()) {
- if req.matches_relaxed(dep_crate.version()) {
+ if req.matches_with_compatibility_rule(
+ dep_crate.version(),
+ SemverCompatibilityRule::Loose,
+ ) {
return false;
}
}
diff --git a/tools/external_crates/crate_tool/src/upgradable.rs b/tools/external_crates/crate_tool/src/upgradable.rs
index 1cf441c..2803bfb 100644
--- a/tools/external_crates/crate_tool/src/upgradable.rs
+++ b/tools/external_crates/crate_tool/src/upgradable.rs
@@ -12,45 +12,76 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use clap::ValueEnum;
use semver::{Version, VersionReq};
+/// How strictly to enforce semver compatibility.
+#[derive(Copy, Clone, ValueEnum)]
+pub enum SemverCompatibilityRule {
+ /// Ignore semantic version. Consider any two versions compatible.
+ Ignore,
+ /// Consider 0.x and 0.y to be compatible, but otherwise follow standard rules.
+ Loose,
+ /// Follow standard semantic version rules, under which 0.x and 0.y are incompatible.
+ Strict,
+}
/// A trait for determining semver compatibility.
pub trait IsUpgradableTo {
- /// Returns true if the object version is semver-compatible with 'other'.
- fn is_upgradable_to(&self, other: &Version) -> bool;
- /// Returns true if the object version is semver-compatible with 'other', or if
- /// both have a major version of 0 and the other version is greater.
- fn is_upgradable_to_relaxed(&self, other: &Version) -> bool;
+ /// Returns true if the object version is upgradable to 'other', according to
+ /// the specified semantic version compatibility strictness.
+ fn is_upgradable_to(
+ &self,
+ other: &Version,
+ semver_compatibility: SemverCompatibilityRule,
+ ) -> bool;
}
impl IsUpgradableTo for semver::Version {
- fn is_upgradable_to(&self, other: &Version) -> bool {
- VersionReq::parse(&self.to_string()).is_ok_and(|req| req.matches(other))
- }
- fn is_upgradable_to_relaxed(&self, other: &Version) -> bool {
- VersionReq::parse(&self.to_string()).is_ok_and(|req| req.matches_relaxed(other))
+ fn is_upgradable_to(
+ &self,
+ other: &Version,
+ semver_compatibility: SemverCompatibilityRule,
+ ) -> bool {
+ other > self
+ && VersionReq::parse(&self.to_string())
+ .is_ok_and(|req| req.matches_with_compatibility_rule(other, semver_compatibility))
}
}
-/// A trait for relaxed semver compatibility. Major versions of 0 are treated as if they were non-zero.
-pub trait MatchesRelaxed {
- /// Returns true if the version matches the req, but treats
- /// major version of zero as if it were non-zero.
- fn matches_relaxed(&self, version: &Version) -> bool;
+/// A trait for custom semver compatibility logic, allowing it to be ignored or relaxed.
+pub trait MatchesWithCompatibilityRule {
+ /// Returns true if the version matches the req, according to the
+ /// custom compatibility requirements of 'semver_compatibility'.
+ fn matches_with_compatibility_rule(
+ &self,
+ version: &Version,
+ semver_compatibility: SemverCompatibilityRule,
+ ) -> bool;
}
-impl MatchesRelaxed for VersionReq {
- fn matches_relaxed(&self, version: &Version) -> bool {
- if self.matches(version) {
- return true;
+impl MatchesWithCompatibilityRule for VersionReq {
+ fn matches_with_compatibility_rule(
+ &self,
+ version: &Version,
+ semver_compatibility: SemverCompatibilityRule,
+ ) -> bool {
+ match semver_compatibility {
+ SemverCompatibilityRule::Ignore => true,
+ SemverCompatibilityRule::Loose => {
+ if self.comparators.len() == 1
+ && self.comparators[0].major == 0
+ && version.major == 0
+ {
+ let mut fake_v = version.clone();
+ fake_v.major = 1;
+ let mut fake_req = self.clone();
+ fake_req.comparators[0].major = 1;
+ fake_req.matches(&fake_v)
+ } else {
+ self.matches(version)
+ }
+ }
+ SemverCompatibilityRule::Strict => self.matches(version),
}
- if self.comparators.len() == 1 && self.comparators[0].major == 0 && version.major == 0 {
- let mut fake_v = version.clone();
- fake_v.major = 1;
- let mut fake_req = self.clone();
- fake_req.comparators[0].major = 1;
- return fake_req.matches(&fake_v);
- }
- false
}
}
@@ -68,18 +99,58 @@
let major = Version::parse("3.0.0")?;
let older = Version::parse("2.3.3")?;
- // All have same behavior for is_upgradable_to_relaxed
- assert!(version.is_upgradable_to(&patch), "Patch update");
- assert!(version.is_upgradable_to_relaxed(&patch), "Patch update");
+ // All have same behavior for SemverCompatibility::LOOSE.
+ assert!(
+ version.is_upgradable_to(&patch, SemverCompatibilityRule::Strict),
+ "Patch update, strict"
+ );
+ assert!(
+ version.is_upgradable_to(&patch, SemverCompatibilityRule::Loose),
+ "Patch update, loose"
+ );
+ assert!(
+ version.is_upgradable_to(&patch, SemverCompatibilityRule::Ignore),
+ "Patch update, ignore"
+ );
- assert!(version.is_upgradable_to(&minor), "Minor version update");
- assert!(version.is_upgradable_to_relaxed(&minor), "Minor version update");
+ assert!(
+ version.is_upgradable_to(&minor, SemverCompatibilityRule::Strict),
+ "Minor version update, strict"
+ );
+ assert!(
+ version.is_upgradable_to(&minor, SemverCompatibilityRule::Loose),
+ "Minor version update, loose"
+ );
+ assert!(
+ version.is_upgradable_to(&minor, SemverCompatibilityRule::Ignore),
+ "Minor version update, ignore"
+ );
- assert!(!version.is_upgradable_to(&major), "Incompatible (major version) update");
- assert!(!version.is_upgradable_to_relaxed(&major), "Incompatible (major version) update");
+ assert!(
+ !version.is_upgradable_to(&major, SemverCompatibilityRule::Strict),
+ "Incompatible (major version) update, strict"
+ );
+ assert!(
+ !version.is_upgradable_to(&major, SemverCompatibilityRule::Loose),
+ "Incompatible (major version) update, loose"
+ );
+ assert!(
+ version.is_upgradable_to(&major, SemverCompatibilityRule::Ignore),
+ "Incompatible (major version) update, ignore"
+ );
- assert!(!version.is_upgradable_to(&older), "Downgrade");
- assert!(!version.is_upgradable_to_relaxed(&older), "Downgrade");
+ assert!(
+ !version.is_upgradable_to(&older, SemverCompatibilityRule::Strict),
+ "Downgrade, strict"
+ );
+ assert!(
+ !version.is_upgradable_to(&older, SemverCompatibilityRule::Loose),
+ "Downgrade, loose"
+ );
+ assert!(
+ !version.is_upgradable_to(&older, SemverCompatibilityRule::Ignore),
+ "Downgrade, ignore"
+ );
Ok(())
}
@@ -92,18 +163,58 @@
let major = Version::parse("1.0.0")?;
let older = Version::parse("0.3.3")?;
- assert!(version.is_upgradable_to(&patch), "Patch update");
- assert!(version.is_upgradable_to_relaxed(&patch), "Patch update");
+ assert!(
+ version.is_upgradable_to(&patch, SemverCompatibilityRule::Strict),
+ "Patch update, strict"
+ );
+ assert!(
+ version.is_upgradable_to(&patch, SemverCompatibilityRule::Loose),
+ "Patch update, loose"
+ );
+ assert!(
+ version.is_upgradable_to(&patch, SemverCompatibilityRule::Ignore),
+ "Patch update, ignore"
+ );
// Different behavior for minor version changes.
- assert!(!version.is_upgradable_to(&minor), "Minor version update");
- assert!(version.is_upgradable_to_relaxed(&minor), "Minor version update");
+ assert!(
+ !version.is_upgradable_to(&minor, SemverCompatibilityRule::Strict),
+ "Minor version update, strict"
+ );
+ assert!(
+ version.is_upgradable_to(&minor, SemverCompatibilityRule::Loose),
+ "Minor version update, loose"
+ );
+ assert!(
+ version.is_upgradable_to(&minor, SemverCompatibilityRule::Ignore),
+ "Minor version update, ignore"
+ );
- assert!(!version.is_upgradable_to(&major), "Incompatible (major version) update");
- assert!(!version.is_upgradable_to_relaxed(&major), "Incompatible (major version) update");
+ assert!(
+ !version.is_upgradable_to(&major, SemverCompatibilityRule::Strict),
+ "Incompatible (major version) update, strict"
+ );
+ assert!(
+ !version.is_upgradable_to(&major, SemverCompatibilityRule::Loose),
+ "Incompatible (major version) update, loose"
+ );
+ assert!(
+ version.is_upgradable_to(&major, SemverCompatibilityRule::Ignore),
+ "Incompatible (major version) update, ignore"
+ );
- assert!(!version.is_upgradable_to(&older), "Downgrade");
- assert!(!version.is_upgradable_to_relaxed(&older), "Downgrade");
+ assert!(
+ !version.is_upgradable_to(&older, SemverCompatibilityRule::Strict),
+ "Downgrade, strict"
+ );
+ assert!(
+ !version.is_upgradable_to(&older, SemverCompatibilityRule::Loose),
+ "Downgrade, loose"
+ );
+ assert!(
+ !version.is_upgradable_to(&older, SemverCompatibilityRule::Ignore),
+ "Downgrade, ignore"
+ );
Ok(())
}