summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-08-30 10:18:06 +0000
committerLars Wirzenius <liw@liw.fi>2022-08-30 10:18:06 +0000
commitde289ba77c50b29b8e3bc351cdbdb85a0c5a9c99 (patch)
tree04e81929d656bc9364e795b48af26657044e000b
parent678c408b1a911a2a452ea31624ef595649b4acf7 (diff)
parent0488ee4432cdc43556fa4bc7de235fd1913f5ff4 (diff)
downloadmissing-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.lock340
-rw-r--r--Cargo.toml6
-rw-r--r--README.md6
-rwxr-xr-xdebian-crate-packages18
-rw-r--r--src/main.rs182
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"
diff --git a/Cargo.toml b/Cargo.toml
index 17ea22b..cd5f77e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index 9681380..b9df5b3 100644
--- a/README.md
+++ b/README.md
@@ -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()
+ }
}