1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use cfg_expr::{
16 targets::{Arch, Family, Os},
17 Predicate, TargetPredicate,
18 };
19 use crates_index::{Crate, Dependency, DependencyKind, Version};
20 use semver::VersionReq;
21 use std::collections::HashMap;
22
23 /// Filter versions by those that are "safe", meaning not yanked or pre-release.
24 pub trait SafeVersions {
25 // Versions of the crate that aren't yanked or pre-release.
safe_versions(&self) -> impl DoubleEndedIterator<Item = &Version>26 fn safe_versions(&self) -> impl DoubleEndedIterator<Item = &Version>;
27 // Versions of the crate greater than 'version'.
versions_gt(&self, version: &semver::Version) -> impl DoubleEndedIterator<Item = &Version>28 fn versions_gt(&self, version: &semver::Version) -> impl DoubleEndedIterator<Item = &Version> {
29 self.safe_versions().filter(|v| {
30 semver::Version::parse(v.version()).map_or(false, |parsed| parsed.gt(version))
31 })
32 }
33 // Get a specific version of a crate.
get_version(&self, version: &semver::Version) -> Option<&Version>34 fn get_version(&self, version: &semver::Version) -> Option<&Version> {
35 self.safe_versions().find(|v| {
36 semver::Version::parse(v.version()).map_or(false, |parsed| parsed.eq(version))
37 })
38 }
39 }
40 impl SafeVersions for Crate {
safe_versions(&self) -> impl DoubleEndedIterator<Item = &Version>41 fn safe_versions(&self) -> impl DoubleEndedIterator<Item = &Version> {
42 self.versions().iter().filter(|v| {
43 !v.is_yanked()
44 && semver::Version::parse(v.version()).map_or(false, |parsed| parsed.pre.is_empty())
45 })
46 }
47 }
48
49 /// Filter dependencies for those likely to be relevant to Android.
50 pub trait AndroidDependencies {
android_deps(&self) -> impl DoubleEndedIterator<Item = &Dependency>51 fn android_deps(&self) -> impl DoubleEndedIterator<Item = &Dependency>;
android_version_reqs_by_name(&self) -> HashMap<&str, &str>52 fn android_version_reqs_by_name(&self) -> HashMap<&str, &str> {
53 self.android_deps().map(|dep| (dep.crate_name(), dep.requirement())).collect()
54 }
android_deps_with_version_reqs( &self, ) -> impl DoubleEndedIterator<Item = (&Dependency, VersionReq)>55 fn android_deps_with_version_reqs(
56 &self,
57 ) -> impl DoubleEndedIterator<Item = (&Dependency, VersionReq)> {
58 self.android_deps().filter_map(|dep| {
59 VersionReq::parse(dep.requirement()).map_or(None, |req| Some((dep, req)))
60 })
61 }
62 }
63 impl AndroidDependencies for Version {
android_deps(&self) -> impl DoubleEndedIterator<Item = &Dependency>64 fn android_deps(&self) -> impl DoubleEndedIterator<Item = &Dependency> {
65 self.dependencies().iter().filter(|dep| {
66 dep.kind() == DependencyKind::Normal && !dep.is_optional() && dep.is_android()
67 })
68 }
69 }
70
71 /// Dependencies that are likely to be relevant to Android.
72 /// Unconditional dependencies (without a target cfg string) are always relevant.
73 /// Conditional dependencies are relevant if they are for Unix, Android, or Linux, and for an architecture we care about (Arm, RISC-V, or X86)
74 pub trait IsAndroid {
75 /// Returns true if this dependency is likely to be relevant to Android.
is_android(&self) -> bool76 fn is_android(&self) -> bool;
77 }
78 impl IsAndroid for Dependency {
is_android(&self) -> bool79 fn is_android(&self) -> bool {
80 self.target().map_or(true, is_android)
81 }
82 }
is_android(target: &str) -> bool83 fn is_android(target: &str) -> bool {
84 let expr = cfg_expr::Expression::parse(target);
85 if expr.is_err() {
86 return false;
87 }
88 let expr = expr.unwrap();
89 expr.eval(|pred| match pred {
90 Predicate::Target(target_predicate) => match target_predicate {
91 TargetPredicate::Family(family) => *family == Family::unix,
92 TargetPredicate::Os(os) => *os == Os::android || *os == Os::linux,
93 TargetPredicate::Arch(arch) => {
94 [Arch::arm, Arch::aarch64, Arch::riscv32, Arch::riscv64, Arch::x86, Arch::x86_64]
95 .contains(arch)
96 }
97 _ => true,
98 },
99 _ => true,
100 })
101 }
102
103 pub trait DependencyChanges {
is_new_dep(&self, base_deps: &HashMap<&str, &str>) -> bool104 fn is_new_dep(&self, base_deps: &HashMap<&str, &str>) -> bool;
is_changed_dep(&self, base_deps: &HashMap<&str, &str>) -> bool105 fn is_changed_dep(&self, base_deps: &HashMap<&str, &str>) -> bool;
106 }
107
108 impl DependencyChanges for Dependency {
is_new_dep(&self, base_deps: &HashMap<&str, &str>) -> bool109 fn is_new_dep(&self, base_deps: &HashMap<&str, &str>) -> bool {
110 !base_deps.contains_key(self.crate_name())
111 }
112
is_changed_dep(&self, base_deps: &HashMap<&str, &str>) -> bool113 fn is_changed_dep(&self, base_deps: &HashMap<&str, &str>) -> bool {
114 let base_dep = base_deps.get(self.crate_name());
115 base_dep.is_none() || base_dep.is_some_and(|base_req| *base_req != self.requirement())
116 }
117 }
118
119 #[cfg(test)]
120 mod tests {
121 use super::*;
122 #[test]
test_android_cfgs()123 fn test_android_cfgs() {
124 assert!(!is_android("asmjs-unknown-emscripten"), "Parse error");
125 assert!(!is_android("cfg(windows)"));
126 assert!(is_android("cfg(unix)"));
127 assert!(!is_android(r#"cfg(target_os = "redox")"#));
128 assert!(!is_android(r#"cfg(target_arch = "wasm32")"#));
129 assert!(is_android(r#"cfg(any(target_os = "linux", target_os = "android"))"#));
130 assert!(is_android(
131 r#"cfg(any(all(target_arch = "arm", target_pointer_width = "32"), target_arch = "mips", target_arch = "powerpc"))"#
132 ));
133 assert!(!is_android(
134 r#"cfg(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown"))"#
135 ));
136 assert!(is_android("cfg(tracing_unstable)"));
137 assert!(is_android(r#"cfg(any(unix, target_os = "wasi"))"#));
138 assert!(is_android(r#"cfg(not(all(target_arch = "arm", target_os = "none")))"#))
139 }
140 }
141