• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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