diff options
author | Lars Wirzenius <liw@liw.fi> | 2022-08-30 10:18:06 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2022-08-30 10:18:06 +0000 |
commit | de289ba77c50b29b8e3bc351cdbdb85a0c5a9c99 (patch) | |
tree | 04e81929d656bc9364e795b48af26657044e000b | |
parent | 678c408b1a911a2a452ea31624ef595649b4acf7 (diff) | |
parent | 0488ee4432cdc43556fa4bc7de235fd1913f5ff4 (diff) | |
download | missing-dependencies-de289ba77c50b29b8e3bc351cdbdb85a0c5a9c99.tar.gz |
Merge branch 'impl' into 'main'
feat: add scripts to list Debian packages and Rust dependencies
See merge request sequoia-pgp/missing-dependencies!1
-rw-r--r-- | Cargo.lock | 340 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rwxr-xr-x | debian-crate-packages | 18 | ||||
-rw-r--r-- | src/main.rs | 182 |
5 files changed, 546 insertions, 6 deletions
diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6aa6f1a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,340 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "missing-dependencies" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "log", + "pretty_env_logger", + "regex", + "semver", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "os_str_bytes" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "semver" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" @@ -6,3 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.58" +clap = { version = "3.2.16", features = ["derive"] } +log = "0.4.17" +pretty_env_logger = "0.4.0" +regex = "1.6.0" +semver = "1.0.12" @@ -12,9 +12,6 @@ There are three parts: * `debian-crate-packages` is a simple shell script that fetches the package list for the Debian "unstable" version, and produces a list of Rust crates and versions that have been packaged. -* `rust-dependencies` is a shell script that reads the output of - `cargo tree` (possibly with options like `--all-features`) and - produces a list of crates and versions. * The Rust program in this crate reads the outputs of the two scripts and produces a list of missing crates or versions. @@ -34,8 +31,7 @@ about dependencies: ```sh $ ./debian-crate-packages > crates-in-debian -$ (cd /where/rust/program/is && cargo tree) | ./rust-dependencies > crates-wanted -$ cargo run -q -- crates-in-debian crates-wanted +$ (cd /where/rust/program/is && cargo tree) | cargo run -q -- crates-in-debian missing-version aho-corasick 0.7.18 missing-entirely ansi_term ``` diff --git a/debian-crate-packages b/debian-crate-packages new file mode 100755 index 0000000..c4c8a5a --- /dev/null +++ b/debian-crate-packages @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euo pipefail + +if [ ! -e Packages ]; then + curl -s http://deb.debian.org/debian/dists/unstable/main/binary-amd64/Packages.xz | + unxz >Packages +fi +grep-dctrl -s Package,Version librust- Packages | + awk ' +/^Package:/ { + name = $2 + gsub(/^librust-/, "", name) + gsub(/-dev$/, "", name) + gsub(/-/, "_", name) +} +/^Version:/ { gsub(/-[^-]*$/, "", $2); print name, $2 } +' diff --git a/src/main.rs b/src/main.rs index e7a11a9..88e7f45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,183 @@ +use anyhow::anyhow; +use clap::Parser; +use log::debug; +use regex::Regex; +use semver::{Version, VersionReq}; +use std::fmt; +use std::fs::read; +use std::path::{Path, PathBuf}; + fn main() { - println!("Hello, world!"); + if let Err(e) = fallible_main() { + eprintln!("ERROR: {}", e); + std::process::exit(1); + } +} + +fn fallible_main() -> anyhow::Result<()> { + pretty_env_logger::init(); + let args = Args::parse(); + debug!("loading packages"); + let packages = Crates::from_file(&args.packaged)?; + debug!("loading dependencies"); + let dependencies = get_dependencies()?; + let mut problems = Problems::default(); + + debug!("finding problems"); + for c in dependencies.iter() { + debug!(".. {}", c.name); + let got = packages.versions(&c.name); + if got.is_empty() { + problems.push(Problem::Crate(c.clone())); + } else { + let wanted = VersionReq::parse(&format!("{}", c.version))?; + if !satisfied_by(&wanted, &got) { + problems.push(Problem::Version(c.clone())); + } + } + } + + let mut problems = problems.to_vec(); + if problems.is_empty() { + debug!("all good"); + Ok(()) + } else { + problems.sort_by_key(|p| match p { + Problem::Crate(c) => (c.name.clone(), c.version.clone()), + Problem::Version(c) => (c.name.clone(), c.version.clone()), + }); + for p in problems.iter() { + println!("{}", p); + } + Err(anyhow!("there were {} missing dependencies", problems.len())) + } +} + +/// List Rust dependeencies that haven't been been packaged in a target +/// operating system. +/// +/// The list of dependencies needed is read from the standard input, +/// and should be in the form of output of "cargo tree". Add any flags +/// to "cargo tree" to select features you need. +#[derive(Parser)] +struct Args { + /// List of crates and versions packaged in the target operating system. + packaged: PathBuf, +} + +fn get_dependencies() -> anyhow::Result<Crates> { + let pat = Regex::new(r#"(?P<name>\S+) v(?P<version>\d+\.\d+\.\d+)"#)?; + let lines: Result<Vec<String>, std::io::Error> = std::io::stdin().lines().collect(); + let lines = lines?; + let dependencies: Result<Vec<Crate>, semver::Error> = lines.iter() + .filter_map(|line| { + if let Some(caps) = pat.captures(line) { + let name = caps.name("name").unwrap().as_str(); + let version = caps.name("version").unwrap().as_str(); + Some(Crate::new(name, version)) + } else { + None + } + }) + .collect(); + let mut crates = Crates::default(); + for c in dependencies? { + crates.push(c) + } + Ok(crates) +} + +fn satisfied_by(wanted: &VersionReq, got: &[&Version]) -> bool { + got.iter().any(|v| wanted.matches(v)) +} + +#[derive(Default)] +struct Crates { + crates: Vec<Crate>, +} + +impl Crates { + fn from_file(filename: &Path) -> anyhow::Result<Self> { + let crates: Result<Vec<Crate>, semver::Error> = String::from_utf8_lossy(&read(filename)?) + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()) + .map(|line| { + let mut words = line.split(' '); + let name = words.next().unwrap(); + let version = words.next().unwrap(); + Crate::new(name, version) + }) + .collect(); + let crates = crates?; + Ok(Crates { crates }) + } + + fn push(&mut self, c: Crate) { + self.crates.push(c); + } + + fn iter(&self) -> impl Iterator<Item = &Crate> { + self.crates.iter() + } + + fn versions(&self, name: &str) -> Vec<&Version> { + self.crates + .iter() + .filter_map(|c| { + if c.name == name { + Some(&c.version) + } else { + None + } + }) + .collect() + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +struct Crate { + name: String, + version: Version, +} + +impl Crate { + fn new(name: &str, version: &str) -> Result<Self, semver::Error> { + Ok(Self { + name: name.into(), + version: Version::parse(version)?, + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum Problem { + Crate(Crate), + Version(Crate), +} + +impl fmt::Display for Problem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Crate(c) => write!(f, "missing-entirely {}", c.name), + Self::Version(c) => write!(f, "missing-version {} {}", c.name, c.version), + } + } +} + +#[derive(Default, Debug)] +struct Problems { + problems: Vec<Problem>, +} + +impl Problems { + fn push(&mut self, new: Problem) { + if !self.problems.iter().any(|p| *p == new) { + self.problems.push(new); + } + } + + fn to_vec(&self) -> Vec<Problem> { + self.problems.clone() + } } |