diff options
author | Lars Wirzenius <liw@liw.fi> | 2022-09-22 07:21:08 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2022-09-22 07:21:08 +0000 |
commit | 4b895e19b9e0167236b5dac46f438e43d8a74616 (patch) | |
tree | 8eec1ded60e1a54e638f6a926b72451ed3eeb0d1 | |
parent | fd61004c38bbd5d97fb2c36fedab014f3d6c36d9 (diff) | |
parent | 885c3bb497cbb1fd106b5d96ff406399c80ce75f (diff) | |
download | missing-dependencies-4b895e19b9e0167236b5dac46f438e43d8a74616.tar.gz |
Merge branch 'improvements' into 'main'
fix problems, improve reporting, produce HTML report
See merge request sequoia-pgp/missing-dependencies!3
-rw-r--r-- | Cargo.lock | 54 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rwxr-xr-x | debian-crate-packages | 6 | ||||
-rwxr-xr-x | report.sh | 9 | ||||
-rw-r--r-- | src/main.rs | 193 | ||||
-rw-r--r-- | style.css | 18 |
6 files changed, 235 insertions, 46 deletions
@@ -41,6 +41,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] name = "camino" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -130,6 +136,12 @@ dependencies = [ ] [[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -207,6 +219,7 @@ dependencies = [ "pretty_env_logger", "regex", "semver", + "tabled", ] [[package]] @@ -222,6 +235,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" [[package]] +name = "papergrid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453cf71f2a37af495a1a124bf30d4d7469cfbea58e9f2479be9d222396a518a2" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] name = "pretty_env_logger" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -360,6 +384,30 @@ dependencies = [ ] [[package]] +name = "tabled" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5b2f8c37d26d87d2252187b0a45ea3cbf42baca10377c7e7eaaa2800fa9bf97" +dependencies = [ + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ee618502f497abf593e1c5c9577f34775b111480009ffccd7ad70d23fcaba8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -381,6 +429,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" [[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -13,3 +13,4 @@ log = "0.4.17" pretty_env_logger = "0.4.0" regex = "1.6.0" semver = "1.0.12" +tabled = "0.8.0" diff --git a/debian-crate-packages b/debian-crate-packages index 89f28ae..3f74fe1 100755 --- a/debian-crate-packages +++ b/debian-crate-packages @@ -6,8 +6,11 @@ import subprocess import sys +verbose = False + + def log(msg): - if True: + if verbose: sys.stderr.write(f"{msg}\n") sys.stderr.flush() @@ -49,6 +52,7 @@ def crate_version(version): if "+really" in version: version = version.split("really")[-1] version = strip_prefix(version, ".") + version = "-".join(version.split("~")) parts = version.split("-") if len(parts) == 1: return parts[0] diff --git a/report.sh b/report.sh new file mode 100755 index 0000000..aa8ccee --- /dev/null +++ b/report.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -euo pipefail + +sq_dir="$1" + +./debian-crate-packages >crates-in-debian +cargo run -q -- --style=markdown crates-in-debian "$sq_dir" -f crypto-nettle | + pandoc -H style.css -f markdown /dev/stdin -o problems.html --metadata title="Problems packaging sq for Debian" diff --git a/src/main.rs b/src/main.rs index af31ec4..0c99d2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ use anyhow::anyhow; -use cargo_metadata::MetadataCommand; +use cargo_metadata::{CargoOpt, DependencyKind, Metadata, MetadataCommand}; use clap::Parser; -use log::{debug, error, info}; +use log::{debug, error, info, trace}; use semver::{Version, VersionReq}; use std::collections::HashMap; -use std::fmt; use std::fs::read; use std::path::{Path, PathBuf}; +use tabled::{Style, Table, Tabled}; fn main() { if let Err(e) = fallible_main() { @@ -23,56 +23,134 @@ fn fallible_main() -> anyhow::Result<()> { let packaged = Crates::from_file(&args.packaged)?; info!("load dependencies with version requirements"); - let metadata = MetadataCommand::new() - .manifest_path(&args.dirname.join("Cargo.toml")) - .exec()?; + let metadata = load_cargo_metadata(&args)?; + let dependencies = collect_dependencies(&metadata); + info!("found {} dependencies", dependencies.len()); + + info!("find dependencies that are not packaged"); + let problems = find_problems(&packaged, &dependencies); + let count = problems.len(); + + if count == 0 { + info!("all good"); + Ok(()) + } else { + report_problems(problems, &args.style); + Err(anyhow!("there were {} missing dependencies", count)) + } +} + +fn load_cargo_metadata(args: &Args) -> anyhow::Result<Metadata> { + let mut metadata = MetadataCommand::new(); + metadata.other_options(vec!["--filter-platform=x86_64-unknown-linux-gnu".into()]); + metadata.manifest_path(&args.dirname.join("Cargo.toml")); + + if args.all_features { + metadata.features(CargoOpt::AllFeatures); + } else if !args.features.is_empty() { + metadata.features(CargoOpt::NoDefaultFeatures); + metadata.features(CargoOpt::SomeFeatures(args.features.clone())); + }; + + info!("run cargo command: {:?}", metadata.cargo_command()); + Ok(metadata.exec()?) +} + +fn collect_dependencies(metadata: &Metadata) -> HashMap<String, VersionReq> { let mut dependencies: HashMap<String, VersionReq> = HashMap::new(); - for package in metadata.packages { + for package in metadata.packages.iter() { for dep in &package.dependencies { - let name = canonicalize_crate_name(&package.name); - dependencies.insert(name, dep.req.clone()); + if dep.target.is_none() + && matches!(dep.kind, DependencyKind::Build | DependencyKind::Normal) + { + trace!("package {} platform {:?}", package.name, dep.target); + let name = canonicalize_crate_name(&dep.name); + trace!("dependency on {} {}", name, dep.req); + dependencies.insert(name, dep.req.clone()); + } } } - info!("found {} dependencies", dependencies.len()); + dependencies +} - info!("find dependencies that are not packaged"); +fn find_problems(packaged: &Crates, dependencies: &HashMap<String, VersionReq>) -> Vec<Problem> { let mut problems = Problems::default(); for (name, req) in dependencies.iter() { debug!("consider {}, version {} required", name, req); if let Some(c) = packaged.get(name) { if !req.matches(&c.version) { - info!("crate {} is packaged, but version doesn't satisfy {}", c.orig_name, c.version); - problems.push(Problem::MissingVersion( - c.orig_name.clone(), - req.clone(), - c.version.clone(), - )); + info!( + "crate {} is packaged, but version doesn't satisfy {}", + c.orig_name, c.version + ); + problems.push(Problem::MissingVersion(MissingVersion { + name: c.orig_name.clone(), + req: req.clone(), + version: c.version.clone(), + })); } else { - info!("crate {} is packaged and version satisfies {}", c.orig_name, c.version); + info!( + "crate {} is packaged and version satisfies {}", + c.orig_name, c.version + ); } } else { info!("crate {} is not packaged at all", name); - problems.push(Problem::MissingCrate(name.clone(), req.clone())); + problems.push(Problem::MissingCrate(MissingCrate { + name: name.clone(), + req: req.clone(), + })); } } + problems.to_vec() +} - let mut problems = problems.to_vec(); - if problems.is_empty() { - info!("all good"); - Ok(()) - } else { - problems.sort_by_key(|p| match p { - Problem::MissingCrate(name, _) => name.clone(), - Problem::MissingVersion(name, _, _) => name.clone(), - }); - for p in problems.iter() { - println!("{}", p); +fn report_problems(mut problems: Vec<Problem>, style: &str) { + problems.sort_by_key(|p| match p { + Problem::MissingCrate(x) => x.name.clone(), + Problem::MissingVersion(x) => x.name.clone(), + }); + + let versions: Vec<&MissingVersion> = problems + .iter() + .filter_map(|p| match p { + Problem::MissingVersion(x) => Some(x), + _ => None, + }) + .collect(); + + let crates: Vec<&MissingCrate> = problems + .iter() + .filter_map(|p| match p { + Problem::MissingCrate(x) => Some(x), + _ => None, + }) + .collect(); + + let got_versions = !versions.is_empty(); + if got_versions { + + let table = match style { + "markdown" => Table::new(versions).with(Style::markdown()), + _ => Table::new(versions).with(Style::modern()), + }; + println!("Table: Packaged version is not what is required"); + println!(); + println!("{}", table.to_string()); + } + + if !crates.is_empty() { + if got_versions { + println!(); } - Err(anyhow!( - "there were {} missing dependencies", - problems.len() - )) + let table = match style { + "markdown" => Table::new(crates).with(Style::markdown()), + _ => Table::new(crates).with(Style::modern()), + }; + println!("Table: Required crate is not packaged at all"); + println!(); + println!("{}", table.to_string()); } } @@ -84,6 +162,18 @@ fn fallible_main() -> anyhow::Result<()> { /// to "cargo tree" to select features you need. #[derive(Parser)] struct Args { + /// Ask cargo for all features. + #[clap(long)] + all_features: bool, + + /// Ask cargo for specified features (use once per feature). + #[clap(short, long)] + features: Vec<String>, + + /// Style of tables to use for reporting problems. + #[clap(long, possible_values = ["modern", "markdown"], default_value = "modern")] + style: String, + /// List of crates and versions packaged in the target operating system. packaged: PathBuf, @@ -99,6 +189,7 @@ struct Crate { impl Crate { fn new(name: &str, version: Version) -> Self { + trace!("found {} {}", name, version); Self { canonical_name: canonicalize_crate_name(name), orig_name: name.into(), @@ -123,6 +214,7 @@ impl Crates { let mut crates = Crates::default(); for (lineno, line) in text.lines().enumerate() { + trace!("parsing line {}: {:?}", lineno, line); let mut words = line.split(' '); if let Some(name) = words.next() { if let Some(version) = words.next() { @@ -164,19 +256,30 @@ impl Crates { #[derive(Debug, Clone, Eq, PartialEq)] enum Problem { - MissingCrate(String, VersionReq), - MissingVersion(String, VersionReq, Version), + MissingCrate(MissingCrate), + MissingVersion(MissingVersion), } -impl fmt::Display for Problem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::MissingCrate(name, req) => write!(f, "missing-entirely {} {}", name, req), - Self::MissingVersion(name, req, got) => { - write!(f, "missing-version {} {} got {}", name, req, got) - } - } - } +#[derive(Debug, Clone, Eq, PartialEq, Tabled)] +struct MissingCrate { + #[tabled(rename = "Crate")] + name: String, + + #[tabled(rename = "Required version")] + req: VersionReq, +} + +#[derive(Debug, Clone, Eq, PartialEq, Tabled)] +#[tabled(rename = "Crates for which a packaged version is too old")] +struct MissingVersion { + #[tabled(rename = "Crate")] + name: String, + + #[tabled(rename = "Required version")] + req: VersionReq, + + #[tabled(rename = "Packaged version")] + version: Version, } #[derive(Default, Debug)] diff --git a/style.css b/style.css new file mode 100644 index 0000000..83eacc7 --- /dev/null +++ b/style.css @@ -0,0 +1,18 @@ +<style> +table { + margin-top: 5em; + width: 100%; +} +caption { + margin-bottom: 2em; +} +th, td { + text-align: left; +} +tr.even { + background: #f0f0f0; +} +tbody tr td:first-child { + font-family: monospace; +} +</style> |