summaryrefslogtreecommitdiffstats
path: root/front_end/src/crate_page.rs
diff options
context:
space:
mode:
authorKornel <kornel@geekhood.net>2020-01-25 18:05:48 +0000
committerKornel <kornel@geekhood.net>2020-01-25 18:06:01 +0000
commitb5da3d19ab96a17ea3b3662c6b70b434f5388bb4 (patch)
treed2d56f2f2d24760d653848490041c7bba3af34ae /front_end/src/crate_page.rs
parentb716c35bb04b879562b8ea7f84f9f3d342aa7c19 (diff)
Detect crates with copyleft deps
Diffstat (limited to 'front_end/src/crate_page.rs')
-rw-r--r--front_end/src/crate_page.rs94
1 files changed, 90 insertions, 4 deletions
diff --git a/front_end/src/crate_page.rs b/front_end/src/crate_page.rs
index 3bfb4cb..f869ede 100644
--- a/front_end/src/crate_page.rs
+++ b/front_end/src/crate_page.rs
@@ -29,6 +29,13 @@ use std::hash::Hash;
use udedokei::LanguageExt;
use udedokei::{Language, Lines, Stats};
use url::Url;
+use std::cmp::Ordering;
+
+pub struct CrateLicense {
+ pub origin: Origin,
+ pub license: Box<str>,
+ pub optional: bool,
+}
pub struct CrateSizes {
pub tarball: usize,
@@ -48,6 +55,7 @@ pub struct CratePage<'a> {
/// own, deps (tarball, uncompressed source); last one is sloc
pub sizes: Option<CrateSizes>,
pub lang_stats: Option<(usize, Stats)>,
+ pub viral_license: Option<CrateLicense>,
}
/// Helper used to find most "interesting" versions
@@ -103,9 +111,11 @@ impl<'a> CratePage<'a> {
markup,
sizes: None,
lang_stats: None,
+ viral_license: None,
};
- let (sizes, lang_stats) = page.crate_size(deps?)?;
+ let (sizes, lang_stats, viral_license) = page.crate_size_and_viral_license(deps?)?;
page.sizes = Some(sizes);
+ page.viral_license = viral_license;
let total = lang_stats.langs.iter().filter(|(lang, _)| lang.is_code()).map(|(_, lines)| lines.code).sum::<u32>();
page.lang_stats = Some((total as usize, lang_stats));
@@ -710,7 +720,9 @@ impl<'a> CratePage<'a> {
)
}
- fn crate_size(&self, deps: DepInfMap) -> CResult<(CrateSizes, Stats)> {
+ /// analyze dependencies checking their weight and their license
+ fn crate_size_and_viral_license(&self, deps: DepInfMap) -> CResult<(CrateSizes, Stats, Option<CrateLicense>)> {
+ let mut viral_license: Option<CrateLicense> = None;
let tmp: Vec<_> = deps
.into_par_iter()
.filter_map(|(name, (depinf, semver))| {
@@ -729,7 +741,7 @@ impl<'a> CratePage<'a> {
},
};
- let commonality = self.kitchen_sink.index.version_commonality(&name, &semver).expect("depsstats").unwrap_or(0.);
+ let commonality = self.kitchen_sink.index.version_global_popularity(&name, &semver).expect("depsstats").unwrap_or(0.);
let is_heavy_build_dep = match &*name {
"bindgen" | "clang-sys" | "cmake" | "cc" if depinf.default => true, // you deserve full weight of it
_ => false,
@@ -762,6 +774,28 @@ impl<'a> CratePage<'a> {
let mut deps_size_minimal = DepsSize::default();
tmp.into_iter().for_each(|(depinf, krate, weight, weight_minimal)| {
+ if let Some(dep_license) = krate.license() {
+ // if the parent crate itself is copyleft
+ // then there's no need to raise alerts about copyleft dependencies.
+ if viral_license.is_some() || compare_virality(dep_license, self.ver.license()) == Ordering::Greater {
+ let prev_license = viral_license.as_ref().map(|c| &*c.license);
+ let virality = compare_virality(dep_license, prev_license);
+ let existing_is_optional = viral_license.as_ref().map_or(false, |d| d.optional);
+ let new_is_optional = !depinf.default || depinf.ty == DepTy::Build;
+ // Prefer showing non-optional, direct dependency
+ if virality
+ .then(if !new_is_optional && existing_is_optional {Ordering::Greater} else {Ordering::Equal})
+ .then(if depinf.direct {Ordering::Greater} else {Ordering::Equal})
+ == Ordering::Greater {
+ viral_license = Some(CrateLicense {
+ origin: krate.origin().clone(),
+ optional: new_is_optional,
+ license: dep_license.into(),
+ });
+ }
+ }
+ }
+
let crate_size = krate.crate_size();
let tarball_weighed = (crate_size.0 as f32 * weight) as usize;
let uncompr_weighed = (crate_size.1 as f32 * weight) as usize;
@@ -795,7 +829,7 @@ impl<'a> CratePage<'a> {
uncompressed: main_crate_size.1,
minimal: deps_size_minimal,
typical: deps_size_typical,
- }, main_lang_stats))
+ }, main_lang_stats, viral_license))
}
fn get_crate_of_dependency(&self, name: &str, _semver: ()) -> CResult<RichCrateVersion> {
@@ -866,6 +900,58 @@ impl ReleaseCounts {
}
}
+fn is_permissive_license(l: &str) -> bool {
+ l.starts_with("MIT") || l.starts_with("Apache") || l.starts_with("BSD") || l.starts_with("Zlib") ||
+ l.starts_with("IJG") || l.starts_with("CC0") || l.starts_with("ISC") || l.starts_with("FTL")
+}
+
+// FIXME: this is very lousy parsing, but crates-io allows non-SPDX syntax, so I can't use a proper SPDX parser.
+fn virality_score(license: &str) -> u8 {
+ license.split("AND").filter_map(|l| {
+ l.split('/').flat_map(|l| l.split("OR"))
+ .filter_map(|l| {
+ let l = l.trim_start();
+ if is_permissive_license(l) {
+ Some(0)
+ } else if l.starts_with("AGPL") {
+ Some(6)
+ } else if l.starts_with("GPL") {
+ Some(5)
+ } else if l.starts_with("CC-") {
+ Some(4)
+ } else if l.starts_with("MPL") {
+ Some(3)
+ } else if l.starts_with("LGPL") {
+ Some(2)
+ } else if l.starts_with("GFDL") {
+ Some(1)
+ } else {
+ None
+ }
+ }).min()
+ }).max().unwrap_or(0)
+}
+
+#[test]
+fn test_vir() {
+ assert_eq!(6, virality_score("AGPL AND MIT"));
+ assert_eq!(0, virality_score("FTL / GPL-2.0"));
+ assert_eq!(0, virality_score("AGPL OR MIT"));
+ assert_eq!(0, virality_score("MIT/Apache/LGPL"));
+ assert_eq!(2, virality_score("MPL/LGPL"));
+ assert_eq!(2, virality_score("Apache/MIT AND LGPL"));
+ assert_eq!(Ordering::Greater, compare_virality("LGPL", Some("MIT")));
+ assert_eq!(Ordering::Greater, compare_virality("AGPL", Some("LGPL")));
+ assert_eq!(Ordering::Equal, compare_virality("GPL-3.0", Some("GPL-2.0")));
+ assert_eq!(Ordering::Less, compare_virality("CC0-1.0 OR GPL", Some("MPL-2.0")));
+}
+
+fn compare_virality(license: &str, other_license: Option<&str>) -> Ordering {
+ let score = virality_score(license);
+ let other_score = other_license.map(virality_score).unwrap_or(0);
+ score.cmp(&other_score)
+}
+
#[test]
fn counts() {
let r = ReleaseCounts {total: 10, stable: 0, major: 0, major_recent: 0, patch: 0, unstable: 9, breaking: 9, breaking_recent: 0};