mirror of
https://github.com/RGBCube/dix
synced 2025-07-28 12:17:45 +00:00
wip: compiles
This commit is contained in:
parent
fc73fa9722
commit
faa6634142
8 changed files with 389 additions and 381 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -73,7 +73,7 @@ version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.1.19",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
@ -144,6 +144,16 @@ dependencies = [
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap-verbosity-flag"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84"
|
||||||
|
dependencies = [
|
||||||
|
"clap 4.5.37",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.37"
|
version = "4.5.37"
|
||||||
|
@ -305,6 +315,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap 4.5.37",
|
"clap 4.5.37",
|
||||||
|
"clap-verbosity-flag",
|
||||||
"criterion",
|
"criterion",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"diff",
|
"diff",
|
||||||
|
@ -405,6 +416,23 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.5.1",
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
|
@ -1026,3 +1054,6 @@ name = "yansi"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
dependencies = [
|
||||||
|
"is-terminal",
|
||||||
|
]
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "dix"
|
name = "dix"
|
||||||
|
description = "Diff Nix"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
clap = { version = "4.5.37", features = [ "derive" ] }
|
clap = { version = "4.5.37", features = [ "derive" ] }
|
||||||
derive_more = { version = "2.0.1", features = ["full"] }
|
clap-verbosity-flag = "3.0.2"
|
||||||
|
derive_more = { version = "2.0.1", features = [ "full" ] }
|
||||||
diff = "0.1.13"
|
diff = "0.1.13"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
@ -15,7 +17,7 @@ regex = "1.11.1"
|
||||||
rusqlite = { version = "0.35.0", features = [ "bundled" ] }
|
rusqlite = { version = "0.35.0", features = [ "bundled" ] }
|
||||||
rustc-hash = "2.1.1"
|
rustc-hash = "2.1.1"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
yansi = "1.0.1"
|
yansi = { version = "1.0.1", features = [ "detect-env", "detect-tty" ] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
|
101
src/diff.rs
Normal file
101
src/diff.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
io,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rustc_hash::{
|
||||||
|
FxBuildHasher,
|
||||||
|
FxHashMap,
|
||||||
|
};
|
||||||
|
use yansi::Paint as _;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
StorePath,
|
||||||
|
Version,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Diff<T> {
|
||||||
|
old: T,
|
||||||
|
new: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum DiffStatus {
|
||||||
|
Added,
|
||||||
|
Removed,
|
||||||
|
Changed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DiffStatus {
|
||||||
|
fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
writer,
|
||||||
|
"[{letter}]",
|
||||||
|
letter = match *self {
|
||||||
|
DiffStatus::Added => "A".green(),
|
||||||
|
DiffStatus::Removed => "R".red(),
|
||||||
|
DiffStatus::Changed => "C".yellow(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diff<'a>(
|
||||||
|
writer: &mut dyn io::Write,
|
||||||
|
paths_old: impl Iterator<Item = &'a StorePath>,
|
||||||
|
paths_new: impl Iterator<Item = &'a StorePath>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let mut paths =
|
||||||
|
FxHashMap::<&str, Diff<Vec<Option<&Version>>>>::with_hasher(FxBuildHasher);
|
||||||
|
|
||||||
|
for path in paths_old {
|
||||||
|
match path.parse_name_and_version() {
|
||||||
|
Ok((name, version)) => {
|
||||||
|
paths.entry(name).or_default().old.push(version);
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
log::info!("error parsing old path name and version: {error}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in paths_new {
|
||||||
|
match path.parse_name_and_version() {
|
||||||
|
Ok((name, version)) => {
|
||||||
|
paths.entry(name).or_default().new.push(version);
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
log::info!("error parsing new path name and version: {error}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut diffs = paths
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(name, versions)| {
|
||||||
|
let status = match (versions.old.len(), versions.new.len()) {
|
||||||
|
(0, 0) => unreachable!(),
|
||||||
|
(0, _) => DiffStatus::Removed,
|
||||||
|
(_, 0) => DiffStatus::Added,
|
||||||
|
(..) if versions.old != versions.new => DiffStatus::Changed,
|
||||||
|
(..) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((name, versions, status))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
diffs.sort_by(|&(a_name, _, a_status), &(b_name, _, b_status)| {
|
||||||
|
a_status.cmp(&b_status).then_with(|| a_name.cmp(b_name))
|
||||||
|
});
|
||||||
|
|
||||||
|
for (name, _versions, status) in diffs {
|
||||||
|
write!(writer, "{status} {name}")?;
|
||||||
|
writeln!(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
133
src/error.rs
133
src/error.rs
|
@ -1,133 +0,0 @@
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Application errors with thiserror
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum AppError {
|
|
||||||
#[error("Command failed: {command} {args:?} - {message}")]
|
|
||||||
CommandFailed {
|
|
||||||
command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Failed to decode command output from {context}: {source}")]
|
|
||||||
CommandOutputError {
|
|
||||||
source: std::str::Utf8Error,
|
|
||||||
context: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Failed to parse data in {context}: {message}")]
|
|
||||||
ParseError {
|
|
||||||
message: String,
|
|
||||||
context: String,
|
|
||||||
#[source]
|
|
||||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Regex error in {context}: {source}")]
|
|
||||||
RegexError {
|
|
||||||
source: regex::Error,
|
|
||||||
context: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("IO error in {context}: {source}")]
|
|
||||||
IoError {
|
|
||||||
source: std::io::Error,
|
|
||||||
context: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Database error: {source}")]
|
|
||||||
DatabaseError { source: rusqlite::Error },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement From traits to support the ? operator
|
|
||||||
impl From<std::io::Error> for AppError {
|
|
||||||
fn from(source: std::io::Error) -> Self {
|
|
||||||
Self::IoError {
|
|
||||||
source,
|
|
||||||
context: "unknown context".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::str::Utf8Error> for AppError {
|
|
||||||
fn from(source: std::str::Utf8Error) -> Self {
|
|
||||||
Self::CommandOutputError {
|
|
||||||
source,
|
|
||||||
context: "command output".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rusqlite::Error> for AppError {
|
|
||||||
fn from(source: rusqlite::Error) -> Self {
|
|
||||||
Self::DatabaseError { source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<regex::Error> for AppError {
|
|
||||||
fn from(source: regex::Error) -> Self {
|
|
||||||
Self::RegexError {
|
|
||||||
source,
|
|
||||||
context: "regex operation".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppError {
|
|
||||||
/// Create a command failure error with context
|
|
||||||
pub fn command_failed<S: Into<String>>(
|
|
||||||
command: S,
|
|
||||||
args: &[&str],
|
|
||||||
message: S,
|
|
||||||
) -> Self {
|
|
||||||
Self::CommandFailed {
|
|
||||||
command: command.into(),
|
|
||||||
args: args.iter().map(|&s| s.to_string()).collect(),
|
|
||||||
message: message.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a parse error with context
|
|
||||||
pub fn parse_error<S: Into<String>, C: Into<String>>(
|
|
||||||
message: S,
|
|
||||||
context: C,
|
|
||||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
|
||||||
) -> Self {
|
|
||||||
Self::ParseError {
|
|
||||||
message: message.into(),
|
|
||||||
context: context.into(),
|
|
||||||
source,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an IO error with context
|
|
||||||
pub fn io_error<C: Into<String>>(source: std::io::Error, context: C) -> Self {
|
|
||||||
Self::IoError {
|
|
||||||
source,
|
|
||||||
context: context.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a regex error with context
|
|
||||||
pub fn regex_error<C: Into<String>>(
|
|
||||||
source: regex::Error,
|
|
||||||
context: C,
|
|
||||||
) -> Self {
|
|
||||||
Self::RegexError {
|
|
||||||
source,
|
|
||||||
context: context.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a command output error with context
|
|
||||||
pub fn command_output_error<C: Into<String>>(
|
|
||||||
source: std::str::Utf8Error,
|
|
||||||
context: C,
|
|
||||||
) -> Self {
|
|
||||||
Self::CommandOutputError {
|
|
||||||
source,
|
|
||||||
context: context.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,8 +12,8 @@ use anyhow::{
|
||||||
};
|
};
|
||||||
use derive_more::Deref;
|
use derive_more::Deref;
|
||||||
|
|
||||||
// pub mod diff;
|
mod diff;
|
||||||
// pub mod print;
|
pub use diff::diff;
|
||||||
|
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ pub use version::Version;
|
||||||
#[derive(Deref, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Deref, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct DerivationId(i64);
|
pub struct DerivationId(i64);
|
||||||
|
|
||||||
#[derive(Deref, Debug, Clone, PartialEq, Eq)]
|
#[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct StorePath(PathBuf);
|
pub struct StorePath(PathBuf);
|
||||||
|
|
||||||
impl TryFrom<PathBuf> for StorePath {
|
impl TryFrom<PathBuf> for StorePath {
|
||||||
|
|
412
src/main.rs
412
src/main.rs
|
@ -1,225 +1,229 @@
|
||||||
use core::str;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
fmt::Write as _,
|
||||||
|
io::{
|
||||||
|
self,
|
||||||
|
Write as _,
|
||||||
|
},
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
use anyhow::{
|
||||||
use dixlib::{
|
Context as _,
|
||||||
print,
|
Error,
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
use clap::Parser as _;
|
||||||
|
use dix::{
|
||||||
|
diff,
|
||||||
store,
|
store,
|
||||||
util::PackageDiff,
|
|
||||||
};
|
};
|
||||||
use log::{
|
use yansi::Paint as _;
|
||||||
debug,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
use yansi::Paint;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
#[command(name = "dix")]
|
#[command(version, about)]
|
||||||
#[command(version = "1.0")]
|
struct Cli {
|
||||||
#[command(about = "Diff Nix stuff", long_about = None)]
|
old_path: PathBuf,
|
||||||
#[command(version, about, long_about = None)]
|
new_path: PathBuf,
|
||||||
struct Args {
|
|
||||||
path: std::path::PathBuf,
|
|
||||||
path2: std::path::PathBuf,
|
|
||||||
|
|
||||||
/// Print the whole store paths
|
#[command(flatten)]
|
||||||
#[arg(short, long)]
|
verbose: clap_verbosity_flag::Verbosity,
|
||||||
paths: bool,
|
|
||||||
|
|
||||||
/// Print the closure size
|
|
||||||
#[arg(long, short)]
|
|
||||||
closure_size: bool,
|
|
||||||
|
|
||||||
/// Verbosity level: -v for debug, -vv for trace
|
|
||||||
#[arg(short, long, action = clap::ArgAction::Count)]
|
|
||||||
verbose: u8,
|
|
||||||
|
|
||||||
/// Silence all output except errors
|
|
||||||
#[arg(short, long)]
|
|
||||||
quiet: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
fn real_main() -> Result<()> {
|
||||||
struct Package<'a> {
|
let Cli {
|
||||||
name: &'a str,
|
old_path,
|
||||||
versions: HashSet<&'a str>,
|
new_path,
|
||||||
/// Save if a package is a dependency of another package
|
verbose,
|
||||||
is_dep: bool,
|
} = Cli::parse();
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Package<'a> {
|
yansi::whenever(yansi::Condition::TTY_AND_COLOR);
|
||||||
fn new(name: &'a str, version: &'a str, is_dep: bool) -> Self {
|
|
||||||
let mut versions = HashSet::new();
|
|
||||||
versions.insert(version);
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
versions,
|
|
||||||
is_dep,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_version(&mut self, version: &'a str) {
|
env_logger::Builder::new()
|
||||||
self.versions.insert(version);
|
.filter_level(verbose.log_level_filter())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
|
|
||||||
fn main() {
|
|
||||||
let args = Args::parse();
|
|
||||||
|
|
||||||
// Configure logger based on verbosity flags and environment variables
|
|
||||||
// Respects RUST_LOG environment variable if present.
|
|
||||||
// XXX:We can also dedicate a specific env variable for this tool, if we want
|
|
||||||
// to.
|
|
||||||
let env = env_logger::Env::default().filter_or(
|
|
||||||
"RUST_LOG",
|
|
||||||
if args.quiet {
|
|
||||||
"error"
|
|
||||||
} else {
|
|
||||||
match args.verbose {
|
|
||||||
0 => "info",
|
|
||||||
1 => "debug",
|
|
||||||
_ => "trace",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build and initialize the logger
|
|
||||||
env_logger::Builder::from_env(env)
|
|
||||||
.format_timestamp(Some(env_logger::fmt::TimestampPrecision::Seconds))
|
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// handles to the threads collecting closure size information
|
// Handle to the thread collecting closure size information.
|
||||||
// We do this as early as possible because nix is slow.
|
// We do this as early as possible because Nix is slow.
|
||||||
let closure_size_handles = if args.closure_size {
|
let _closure_size_handle = {
|
||||||
debug!("Calculating closure sizes in background");
|
log::debug!("calculating closure sizes in background");
|
||||||
let path = args.path.clone();
|
|
||||||
let path2 = args.path2.clone();
|
let mut connection = store::connect()?;
|
||||||
Some((
|
|
||||||
thread::spawn(move || store::get_closure_size(&path)),
|
let old_path = old_path.clone();
|
||||||
thread::spawn(move || store::get_closure_size(&path2)),
|
let new_path = new_path.clone();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
Ok::<_, Error>((
|
||||||
|
connection.query_closure_size(&old_path)?,
|
||||||
|
connection.query_closure_size(&new_path)?,
|
||||||
))
|
))
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get package lists and handle potential errors
|
|
||||||
let package_list_pre = match store::query_packages(&args.path) {
|
|
||||||
Ok(packages) => {
|
|
||||||
debug!("Found {} packages in first closure", packages.len());
|
|
||||||
packages.into_iter().map(|(_, path)| path).collect()
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let package_list_post = match store::query_packages(&args.path2) {
|
|
||||||
Ok(packages) => {
|
|
||||||
debug!("Found {} packages in second closure", packages.len());
|
|
||||||
packages.into_iter().map(|(_, path)| path).collect()
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path2.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"Error getting packages from path {}: {}",
|
|
||||||
args.path2.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let PackageDiff {
|
|
||||||
pkg_to_versions_pre: pre,
|
|
||||||
pkg_to_versions_post: post,
|
|
||||||
pre_keys: _,
|
|
||||||
post_keys: _,
|
|
||||||
added,
|
|
||||||
removed,
|
|
||||||
changed,
|
|
||||||
} = PackageDiff::new(&package_list_pre, &package_list_post);
|
|
||||||
|
|
||||||
debug!("Added packages: {}", added.len());
|
|
||||||
debug!("Removed packages: {}", removed.len());
|
|
||||||
debug!(
|
|
||||||
"Changed packages: {}",
|
|
||||||
changed
|
|
||||||
.iter()
|
|
||||||
.filter(|p| {
|
|
||||||
!p.is_empty()
|
|
||||||
&& match (pre.get(*p), post.get(*p)) {
|
|
||||||
(Some(ver_pre), Some(ver_post)) => ver_pre != ver_post,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.count()
|
};
|
||||||
|
|
||||||
|
let mut connection = store::connect()?;
|
||||||
|
|
||||||
|
let paths_old =
|
||||||
|
connection.query_depdendents(&old_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to query dependencies of path '{path}'",
|
||||||
|
path = old_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"found {count} packages in old closure",
|
||||||
|
count = paths_old.len(),
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("Difference between the two generations:");
|
let paths_new =
|
||||||
println!();
|
connection.query_depdendents(&new_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to query dependencies of path '{path}'",
|
||||||
|
path = new_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let width_changes = changed.iter().filter(|&&p| {
|
log::debug!(
|
||||||
match (pre.get(p), post.get(p)) {
|
"found {count} packages in new closure",
|
||||||
(Some(version_pre), Some(version_post)) => version_pre != version_post,
|
count = paths_new.len(),
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let col_width = added
|
|
||||||
.iter()
|
|
||||||
.chain(removed.iter())
|
|
||||||
.chain(width_changes)
|
|
||||||
.map(|p| p.len())
|
|
||||||
.max()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
println!("<<< {}", args.path.to_string_lossy());
|
|
||||||
println!(">>> {}", args.path2.to_string_lossy());
|
|
||||||
print::print_added(&added, &post, col_width);
|
|
||||||
print::print_removed(&removed, &pre, col_width);
|
|
||||||
print::print_changes(&changed, &pre, &post, col_width);
|
|
||||||
|
|
||||||
if let Some((pre_handle, post_handle)) = closure_size_handles {
|
|
||||||
match (pre_handle.join(), post_handle.join()) {
|
|
||||||
(Ok(Ok(pre_size)), Ok(Ok(post_size))) => {
|
|
||||||
let pre_size = pre_size / 1024 / 1024;
|
|
||||||
let post_size = post_size / 1024 / 1024;
|
|
||||||
debug!("Pre closure size: {pre_size} MiB");
|
|
||||||
debug!("Post closure size: {post_size} MiB");
|
|
||||||
|
|
||||||
println!("{}", "Closure Size:".underline().bold());
|
|
||||||
println!("Before: {pre_size} MiB");
|
|
||||||
println!("After: {post_size} MiB");
|
|
||||||
println!("Difference: {} MiB", post_size - pre_size);
|
|
||||||
},
|
|
||||||
(Ok(Err(e)), _) | (_, Ok(Err(e))) => {
|
|
||||||
error!("Error getting closure size: {e}");
|
|
||||||
eprintln!("Error getting closure size: {e}");
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
error!("Failed to get closure size information due to a thread error");
|
|
||||||
eprintln!(
|
|
||||||
"Error: Failed to get closure size information due to a thread error"
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
}
|
drop(connection);
|
||||||
}
|
|
||||||
|
let mut out = io::stdout();
|
||||||
|
|
||||||
|
#[expect(clippy::pattern_type_mismatch)]
|
||||||
|
diff(
|
||||||
|
&mut out,
|
||||||
|
paths_old.iter().map(|(_, path)| path),
|
||||||
|
paths_new.iter().map(|(_, path)| path),
|
||||||
|
)?;
|
||||||
|
// let PackageDiff {
|
||||||
|
// pkg_to_versions_pre: pre,
|
||||||
|
// pkg_to_versions_post: post,
|
||||||
|
// pre_keys: _,
|
||||||
|
// post_keys: _,
|
||||||
|
// added,
|
||||||
|
// removed,
|
||||||
|
// changed,
|
||||||
|
// } = PackageDiff::new(&packages_old, &packages_after);
|
||||||
|
|
||||||
|
// log::debug!("Added packages: {}", added.len());
|
||||||
|
// log::debug!("Removed packages: {}", removed.len());
|
||||||
|
// log::debug!(
|
||||||
|
// "Changed packages: {}",
|
||||||
|
// changed
|
||||||
|
// .iter()
|
||||||
|
// .filter(|p| {
|
||||||
|
// !p.is_empty()
|
||||||
|
// && match (pre.get(*p), post.get(*p)) {
|
||||||
|
// (Some(ver_pre), Some(ver_post)) => ver_pre != ver_post,
|
||||||
|
// _ => false,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .count()
|
||||||
|
// );
|
||||||
|
|
||||||
|
// println!("Difference between the two generations:");
|
||||||
|
// println!();
|
||||||
|
|
||||||
|
// let width_changes = changed.iter().filter(|&&p| {
|
||||||
|
// match (pre.get(p), post.get(p)) {
|
||||||
|
// (Some(version_pre), Some(version_post)) => version_pre != version_post,
|
||||||
|
// _ => false,
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// let col_width = added
|
||||||
|
// .iter()
|
||||||
|
// .chain(removed.iter())
|
||||||
|
// .chain(width_changes)
|
||||||
|
// .map(|p| p.len())
|
||||||
|
// .max()
|
||||||
|
// .unwrap_or_default();
|
||||||
|
|
||||||
|
// println!("<<< {}", cli.path.to_string_lossy());
|
||||||
|
// println!(">>> {}", cli.path2.to_string_lossy());
|
||||||
|
// print::print_added(&added, &post, col_width);
|
||||||
|
// print::print_removed(&removed, &pre, col_width);
|
||||||
|
// print::print_changes(&changed, &pre, &post, col_width);
|
||||||
|
|
||||||
|
// if let Some((pre_handle, post_handle)) = closure_size_handles {
|
||||||
|
// match (pre_handle.join(), post_handle.join()) {
|
||||||
|
// (Ok(Ok(pre_size)), Ok(Ok(post_size))) => {
|
||||||
|
// let pre_size = pre_size / 1024 / 1024;
|
||||||
|
// let post_size = post_size / 1024 / 1024;
|
||||||
|
// log::debug!("Pre closure size: {pre_size} MiB");
|
||||||
|
// log::debug!("Post closure size: {post_size} MiB");
|
||||||
|
|
||||||
|
// println!("{}", "Closure Size:".underline().bold());
|
||||||
|
// println!("Before: {pre_size} MiB");
|
||||||
|
// println!("After: {post_size} MiB");
|
||||||
|
// println!("Difference: {} MiB", post_size - pre_size);
|
||||||
|
// },
|
||||||
|
// (Ok(Err(e)), _) | (_, Ok(Err(e))) => {
|
||||||
|
// log::error!("Error getting closure size: {e}");
|
||||||
|
// eprintln!("Error getting closure size: {e}");
|
||||||
|
// },
|
||||||
|
// _ => {
|
||||||
|
// log::error!(
|
||||||
|
// "Failed to get closure size information due to a thread error"
|
||||||
|
// );
|
||||||
|
// eprintln!(
|
||||||
|
// "Error: Failed to get closure size information due to a thread
|
||||||
|
// error" );
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::allow_attributes, clippy::exit)]
|
||||||
|
fn main() {
|
||||||
|
let Err(error) = real_main() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut err = io::stderr();
|
||||||
|
|
||||||
|
let mut message = String::new();
|
||||||
|
let mut chain = error.chain().rev().peekable();
|
||||||
|
|
||||||
|
while let Some(error) = chain.next() {
|
||||||
|
let _ = write!(
|
||||||
|
err,
|
||||||
|
"{header} ",
|
||||||
|
header = if chain.peek().is_none() {
|
||||||
|
"error:"
|
||||||
|
} else {
|
||||||
|
"cause:"
|
||||||
|
}
|
||||||
|
.red()
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
|
||||||
|
String::clear(&mut message);
|
||||||
|
let _ = write!(message, "{error}");
|
||||||
|
|
||||||
|
let mut chars = message.char_indices();
|
||||||
|
|
||||||
|
let _ = if let Some((_, first)) = chars.next()
|
||||||
|
&& let Some((second_start, second)) = chars.next()
|
||||||
|
&& second.is_lowercase()
|
||||||
|
{
|
||||||
|
writeln!(
|
||||||
|
err,
|
||||||
|
"{first_lowercase}{rest}",
|
||||||
|
first_lowercase = first.to_lowercase(),
|
||||||
|
rest = &message[second_start..],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeln!(err, "{message}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@ pub fn print_changes(
|
||||||
fn name_regex() -> &'static Regex {
|
fn name_regex() -> &'static Regex {
|
||||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
static REGEX: OnceLock<Regex> = OnceLock::new();
|
||||||
REGEX.get_or_init(|| {
|
REGEX.get_or_init(|| {
|
||||||
Regex::new(r"(-man|-lib|-doc|-dev|-out|-terminfo)")
|
Regex::new("(-man|-lib|-doc|-dev|-out|-terminfo)")
|
||||||
.expect("Failed to compile regex pattern for name")
|
.expect("Failed to compile regex pattern for name")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
61
src/store.rs
61
src/store.rs
|
@ -1,4 +1,7 @@
|
||||||
use std::result;
|
use std::{
|
||||||
|
path::Path,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{
|
use anyhow::{
|
||||||
Context as _,
|
Context as _,
|
||||||
|
@ -48,10 +51,36 @@ pub fn connect() -> Result<Connection> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Gathers all derivations that the given store path depends on.
|
/// Gets the total closure size of the given store path by summing up the nar
|
||||||
|
/// size of all depdendent derivations.
|
||||||
|
pub fn query_closure_size(&mut self, path: &Path) -> Result<usize> {
|
||||||
|
const QUERY: &str = "
|
||||||
|
WITH RECURSIVE
|
||||||
|
graph(p) AS (
|
||||||
|
SELECT id
|
||||||
|
FROM ValidPaths
|
||||||
|
WHERE path = ?
|
||||||
|
UNION
|
||||||
|
SELECT reference FROM Refs
|
||||||
|
JOIN graph ON referrer = p
|
||||||
|
)
|
||||||
|
SELECT SUM(narSize) as sum from graph
|
||||||
|
JOIN ValidPaths ON p = id;
|
||||||
|
";
|
||||||
|
|
||||||
|
path_to_str!(path);
|
||||||
|
|
||||||
|
let closure_size = self
|
||||||
|
.prepare_cached(QUERY)?
|
||||||
|
.query_row([path], |row| row.get(0))?;
|
||||||
|
|
||||||
|
Ok(closure_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gathers all derivations that the given profile path depends on.
|
||||||
pub fn query_depdendents(
|
pub fn query_depdendents(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &StorePath,
|
path: &Path,
|
||||||
) -> Result<Vec<(DerivationId, StorePath)>> {
|
) -> Result<Vec<(DerivationId, StorePath)>> {
|
||||||
const QUERY: &str = "
|
const QUERY: &str = "
|
||||||
WITH RECURSIVE
|
WITH RECURSIVE
|
||||||
|
@ -82,32 +111,6 @@ impl Connection {
|
||||||
Ok(packages?)
|
Ok(packages?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the total closure size of the given store path by summing up the nar
|
|
||||||
/// size of all depdendent derivations.
|
|
||||||
pub fn query_closure_size(&mut self, path: &StorePath) -> Result<usize> {
|
|
||||||
const QUERY: &str = "
|
|
||||||
WITH RECURSIVE
|
|
||||||
graph(p) AS (
|
|
||||||
SELECT id
|
|
||||||
FROM ValidPaths
|
|
||||||
WHERE path = ?
|
|
||||||
UNION
|
|
||||||
SELECT reference FROM Refs
|
|
||||||
JOIN graph ON referrer = p
|
|
||||||
)
|
|
||||||
SELECT SUM(narSize) as sum from graph
|
|
||||||
JOIN ValidPaths ON p = id;
|
|
||||||
";
|
|
||||||
|
|
||||||
path_to_str!(path);
|
|
||||||
|
|
||||||
let closure_size = self
|
|
||||||
.prepare_cached(QUERY)?
|
|
||||||
.query_row([path], |row| row.get(0))?;
|
|
||||||
|
|
||||||
Ok(closure_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gathers the complete dependency graph of of the store path as an adjacency
|
/// Gathers the complete dependency graph of of the store path as an adjacency
|
||||||
/// list.
|
/// list.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue