1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
use cargo_metadata::CargoOpt; use cargo_metadata::Dependency; use cargo_metadata::MetadataCommand; use cargo_metadata::Package; use cargo_metadata::PackageId; use crates_index::Index; use quick_error::quick_error; use semver::Version; use std::collections::HashMap; pub use cargo_metadata::Error as MetadataError; pub use crates_index::Error as IndexError; quick_error! { #[derive(Debug)] pub enum Error { Index(err: IndexError) { from() display("Can't fetch index: {}", err) } Metadata(err: MetadataError) { from() display("Can't get crate metadata: {}", err) } } } pub struct UpgradesChecker { workspace: Workspace, index: Index, } struct Workspace { packages: HashMap<PackageId, Package>, members: Vec<PackageId>, } impl UpgradesChecker { pub fn new(manifest_path: Option<&str>) -> Result<Self, Error> { let crates = std::thread::spawn(|| { let index = Index::new_cargo_default(); if !index.exists() { index.retrieve()?; } else { let needs_update = index.path().join(".git").metadata() .and_then(|m| m.modified()).ok() .map_or(true, |modified| { modified < std::time::SystemTime::now() - std::time::Duration::from_secs(3600*24) }); if needs_update { index.update()?; } } Ok(index) }); let workspace = Workspace::new(manifest_path)?; let index: Result<_, IndexError> = crates.join().unwrap(); Ok(Self { workspace, index: index?, }) } } pub struct Match<'a> { pub dependency: &'a Dependency, pub matches: Option<Version>, pub latest: Version, } impl Workspace { pub fn new(manifest_path: Option<&str>) -> Result<Self, MetadataError> { let mut cmd = Self::new_metadata(manifest_path, CargoOpt::AllFeatures); let metadata = cmd.exec().or_else(|_| { Self::new_metadata(manifest_path, CargoOpt::NoDefaultFeatures) }.exec())?; Ok(Self { packages: metadata.packages.into_iter().map(|p| (p.id.clone(), p)).collect(), members: metadata.workspace_members, }) } fn new_metadata(manifest_path: Option<&str>, features: CargoOpt) -> MetadataCommand { let mut cmd = MetadataCommand::new(); if let Some(path) = manifest_path { cmd.manifest_path(path); } cmd.features(features); cmd } pub fn check_package(&self, id: &PackageId, index: &Index) -> Option<(&Package, Vec<Match>)> { let package = self.packages.get(id)?; let deps = package.dependencies.iter().filter_map(|dep| { let is_from_crates_io = dep.source.as_ref().map_or(false, |d| d == "registry+https://github.com/rust-lang/crates.io-index"); if !is_from_crates_io { return None; } let c = index.crate_(dep.name.as_str())?; let versions: Vec<_> = c.versions().iter() .filter(|v| !v.is_yanked()) .filter_map(|v| Version::parse(v.version()).ok()) .collect(); let latest_stable = versions.iter().filter(|v| v.pre.is_empty()).max(); let latest_unstable = versions.iter().filter(|v| !v.pre.is_empty()).max(); let latest_usable = latest_stable.or(latest_unstable)?; let latest_matching = versions.iter().filter(|v| dep.req.matches(v)).max(); if latest_matching >= Some(latest_usable) { return None; } Some(Match { dependency: dep, matches: latest_matching.cloned(), latest: latest_usable.clone(), }) }) .collect(); Some((package, deps)) } } impl UpgradesChecker { pub fn outdated_dependencies<'a>(&'a self) -> impl Iterator<Item=(&Package, Vec<Match>)> + 'a { self.workspace.members.iter().filter_map(move |id| { self.workspace.check_package(id, &self.index) }) .filter(|(_, deps)| !deps.is_empty()) } } #[test] fn beta_vs_stable() { let beta11 = Version::parse("1.0.1-beta.1").unwrap(); let beta1 = Version::parse("1.0.0-beta.1").unwrap(); let v100 = Version::parse("1.0.0").unwrap(); assert!(v100 > beta1); assert!(beta11 > beta1); assert!(beta11 > v100); } #[test] fn test_self() { let u = UpgradesChecker::new(None).unwrap(); assert_eq!(0, u.outdated_dependencies().count()); }