1
Fork 0
mirror of https://github.com/RGBCube/dix synced 2025-07-28 12:17:45 +00:00

main: separate logging channels via log crate

This commit is contained in:
NotAShelf 2025-05-05 02:30:46 +03:00
parent dde5723cee
commit f52281d691
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
3 changed files with 266 additions and 59 deletions

90
Cargo.lock generated
View file

@ -107,6 +107,29 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -119,6 +142,36 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "jiff"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -130,6 +183,8 @@ name = "nnpdt"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger",
"log",
"regex", "regex",
"thiserror", "thiserror",
"yansi", "yansi",
@ -141,6 +196,21 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.95"
@ -188,6 +258,26 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"

View file

@ -6,5 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
clap = { version = "4.5.37", features = ["derive"] } clap = { version = "4.5.37", features = ["derive"] }
regex = "1.11.1" regex = "1.11.1"
thiserror = "2.0.12"
yansi = "1.0.1" yansi = "1.0.1"
thiserror = "2.0.12"
log = "0.4.20"
env_logger = "0.11.3"

View file

@ -1,10 +1,12 @@
use clap::Parser; use clap::Parser;
use core::str; use core::str;
use env_logger;
use log::{debug, error, info, warn};
use regex::Regex; use regex::Regex;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
process::Command, process::Command,
string::String, string::{String, ToString},
sync::OnceLock, sync::OnceLock,
thread, thread,
}; };
@ -49,6 +51,14 @@ struct Args {
/// Print the closure size /// Print the closure size
#[arg(long, short)] #[arg(long, short)]
closure_size: bool, 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)] #[derive(Debug, Clone)]
@ -78,14 +88,35 @@ impl<'a> Package<'a> {
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
println!("Nix available: {}", check_nix_available()); // 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();
debug!("Nix available: {}", check_nix_available()); // XXX: is this supposed to be user-facing?
println!("<<< {}", args.path.to_string_lossy()); println!("<<< {}", args.path.to_string_lossy());
println!(">>> {}", args.path2.to_string_lossy()); println!(">>> {}", args.path2.to_string_lossy());
// Handles to the threads collecting closure size information // handles to the threads 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_handles = if args.closure_size {
debug!("Calculating closure sizes in background");
let path = args.path.clone(); let path = args.path.clone();
let path2 = args.path2.clone(); let path2 = args.path2.clone();
Some(( Some((
@ -98,17 +129,41 @@ fn main() {
// Get package lists and handle potential errors // Get package lists and handle potential errors
let package_list_pre = match get_packages(&args.path) { let package_list_pre = match get_packages(&args.path) {
Ok(packages) => packages, Ok(packages) => {
debug!("Found {} packages in first closure", packages.len());
packages
}
Err(e) => { Err(e) => {
eprintln!("Error getting packages from path {}: {}", args.path.display(), e); error!(
"Error getting packages from path {}: {}",
args.path.display(),
e
);
eprintln!(
"Error getting packages from path {}: {}",
args.path.display(),
e
);
Vec::new() Vec::new()
} }
}; };
let package_list_post = match get_packages(&args.path2) { let package_list_post = match get_packages(&args.path2) {
Ok(packages) => packages, Ok(packages) => {
debug!("Found {} packages in second closure", packages.len());
packages
}
Err(e) => { Err(e) => {
eprintln!("Error getting packages from path {}: {}", args.path2.display(), e); error!(
"Error getting packages from path {}: {}",
args.path2.display(),
e
);
eprintln!(
"Error getting packages from path {}: {}",
args.path2.display(),
e
);
Vec::new() Vec::new()
} }
}; };
@ -121,11 +176,9 @@ fn main() {
match get_version(&**p) { match get_version(&**p) {
Ok((name, version)) => { Ok((name, version)) => {
pre.entry(name).or_default().insert(version); pre.entry(name).or_default().insert(version);
},
Err(e) => {
if cfg!(debug_assertions) {
eprintln!("Error parsing package version: {e}");
} }
Err(e) => {
debug!("Error parsing package version: {}", e);
} }
} }
} }
@ -134,11 +187,9 @@ fn main() {
match get_version(&**p) { match get_version(&**p) {
Ok((name, version)) => { Ok((name, version)) => {
post.entry(name).or_default().insert(version); post.entry(name).or_default().insert(version);
},
Err(e) => {
if cfg!(debug_assertions) {
eprintln!("Error parsing package version: {e}");
} }
Err(e) => {
debug!("Error parsing package version: {}", e);
} }
} }
} }
@ -153,6 +204,20 @@ fn main() {
// Get the intersection of the package names for version changes // Get the intersection of the package names for version changes
let changed: HashSet<&str> = &pre_keys & &post_keys; let changed: HashSet<&str> = &pre_keys & &post_keys;
debug!("Added packages: {}", added.len());
debug!("Removed packages: {}", removed.len());
debug!(
"Changed packages: {}",
changed
.iter()
.filter(|p| !p.is_empty())
.filter_map(|p| match (pre.get(p), post.get(p)) {
(Some(ver_pre), Some(ver_post)) if ver_pre != ver_post => Some(p),
_ => None,
})
.count()
);
println!("Difference between the two generations:"); println!("Difference between the two generations:");
println!(); println!();
@ -165,15 +230,20 @@ fn main() {
if let Some((pre_handle, post_handle)) = closure_size_handles { if let Some((pre_handle, post_handle)) = closure_size_handles {
match (pre_handle.join(), post_handle.join()) { match (pre_handle.join(), post_handle.join()) {
(Ok(Ok(pre_size)), Ok(Ok(post_size))) => { (Ok(Ok(pre_size)), Ok(Ok(post_size))) => {
debug!("Pre closure size: {} MiB", pre_size);
debug!("Post closure size: {} MiB", post_size);
println!("{}", "Closure Size:".underline().bold()); println!("{}", "Closure Size:".underline().bold());
println!("Before: {pre_size} MiB"); println!("Before: {pre_size} MiB");
println!("After: {post_size} MiB"); println!("After: {post_size} MiB");
println!("Difference: {} MiB", post_size - pre_size); println!("Difference: {} MiB", post_size - pre_size);
} }
(Ok(Err(e)), _) | (_, Ok(Err(e))) => { (Ok(Err(e)), _) | (_, Ok(Err(e))) => {
error!("Error getting closure size: {}", e);
eprintln!("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"); eprintln!("Error: Failed to get closure size information due to a thread error");
} }
} }
@ -182,6 +252,8 @@ fn main() {
/// Gets the packages in a closure /// Gets the packages in a closure
fn get_packages(path: &std::path::Path) -> Result<Vec<String>> { fn get_packages(path: &std::path::Path) -> Result<Vec<String>> {
debug!("Getting packages from path: {}", path.display());
// Get the nix store paths using `nix-store --query --references <path>`` // Get the nix store paths using `nix-store --query --references <path>``
let output = Command::new("nix-store") let output = Command::new("nix-store")
.arg("--query") .arg("--query")
@ -191,18 +263,23 @@ fn get_packages(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!( return Err(AppError::CommandFailed(format!(
"nix-store command failed: {stderr}" "nix-store command failed: {stderr}"
))); )));
} }
let list = str::from_utf8(&output.stdout)?; let list = str::from_utf8(&output.stdout)?;
Ok(list.lines().map(str::to_owned).collect()) let packages: Vec<String> = list.lines().map(str::to_owned).collect();
debug!("Found {} packages", packages.len());
Ok(packages)
} }
/// Gets the dependencies of the packages in a closure /// Gets the dependencies of the packages in a closure
fn get_dependencies(path: &std::path::Path) -> Result<Vec<String>> { fn get_dependencies(path: &std::path::Path) -> Result<Vec<String>> {
// Get the nix store paths using `nix-store --query --requisites <path>`` debug!("Getting dependencies from path: {}", path.display());
// Get the nix store paths using `nix-store --query --requisites <path>`
let output = Command::new("nix-store") let output = Command::new("nix-store")
.arg("--query") .arg("--query")
.arg("--requisites") .arg("--requisites")
@ -211,13 +288,16 @@ fn get_dependencies(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!( return Err(AppError::CommandFailed(format!(
"nix-store command failed: {stderr}" "nix-store command failed: {stderr}"
))); )));
} }
let list = str::from_utf8(&output.stdout)?; let list = str::from_utf8(&output.stdout)?;
Ok(list.lines().map(str::to_owned).collect()) let dependencies: Vec<String> = list.lines().map(str::to_owned).collect();
debug!("Found {} dependencies", dependencies.len());
Ok(dependencies)
} }
// Returns a reference to the compiled regex pattern. // Returns a reference to the compiled regex pattern.
@ -256,13 +336,21 @@ fn get_version<'a>(pack: impl Into<&'a str>) -> Result<(&'a str, &'a str)> {
fn check_nix_available() -> bool { fn check_nix_available() -> bool {
// Check if nix is available on the host system. // Check if nix is available on the host system.
debug!("Checking nix command availability");
let nix_available = Command::new("nix").arg("--version").output().ok(); let nix_available = Command::new("nix").arg("--version").output().ok();
let nix_query_available = Command::new("nix-store").arg("--version").output().ok(); let nix_query_available = Command::new("nix-store").arg("--version").output().ok();
nix_available.is_some() && nix_query_available.is_some() let result = nix_available.is_some() && nix_query_available.is_some();
if !result {
warn!("Nix commands not available, functionality may be limited");
}
result
} }
fn get_closure_size(path: &std::path::Path) -> Result<i64> { fn get_closure_size(path: &std::path::Path) -> Result<i64> {
debug!("Calculating closure size for path: {}", path.display());
// Run nix path-info command to get closure size // Run nix path-info command to get closure size
let output = Command::new("nix") let output = Command::new("nix")
.arg("path-info") .arg("path-info")
@ -272,6 +360,7 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix path-info command failed: {}", stderr);
return Err(AppError::CommandFailed(format!( return Err(AppError::CommandFailed(format!(
"nix path-info command failed: {stderr}" "nix path-info command failed: {stderr}"
))); )));
@ -280,20 +369,33 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
let stdout = str::from_utf8(&output.stdout)?; let stdout = str::from_utf8(&output.stdout)?;
// Parse the last word in the output as an integer (in bytes) // Parse the last word in the output as an integer (in bytes)
stdout let size = stdout
.split_whitespace() .split_whitespace()
.last() .last()
.ok_or_else(|| AppError::ParseError("Unexpected output format from nix path-info".into()))? .ok_or_else(|| {
let err = "Unexpected output format from nix path-info";
error!("{}", err);
AppError::ParseError(err.into())
})?
.parse::<i64>() .parse::<i64>()
.map_err(|e| AppError::ParseError(format!("Failed to parse size value: {e}"))) .map_err(|e| {
.map(|size| size / 1024 / 1024) // Convert to MiB let err = format!("Failed to parse size value: {e}");
error!("{}", err);
AppError::ParseError(err)
})?;
// Convert to MiB
let size_mib = size / 1024 / 1024;
debug!("Closure size for {}: {} MiB", path.display(), size_mib);
Ok(size_mib)
} }
fn print_added(set: HashSet<&str>, post: &HashMap<&str, HashSet<&str>>) { fn print_added(set: HashSet<&str>, post: &HashMap<&str, HashSet<&str>>) {
println!("{}", "Packages added:".underline().bold()); println!("{}", "Packages added:".underline().bold());
// Use sorted output // Use sorted outpu
let mut sorted: Vec<_> = set.iter() let mut sorted: Vec<_> = set
.iter()
.filter_map(|p| post.get(p).map(|ver| (*p, ver))) .filter_map(|p| post.get(p).map(|ver| (*p, ver)))
.collect(); .collect();
@ -314,9 +416,17 @@ fn print_added(set: HashSet<&str>, post: &HashMap<&str, HashSet<&str>>) {
fn print_removed(set: HashSet<&str>, pre: &HashMap<&str, HashSet<&str>>) { fn print_removed(set: HashSet<&str>, pre: &HashMap<&str, HashSet<&str>>) {
println!("{}", "Packages removed:".underline().bold()); println!("{}", "Packages removed:".underline().bold());
set.iter()
// Use sorted output for more predictable and readable results
let mut sorted: Vec<_> = set
.iter()
.filter_map(|p| pre.get(p).map(|ver| (*p, ver))) .filter_map(|p| pre.get(p).map(|ver| (*p, ver)))
.for_each(|(p, ver)| { .collect();
// Sort by package name for consistent output
sorted.sort_by(|(a, _), (b, _)| a.cmp(b));
for (p, ver) in sorted {
let version_str = ver.iter().copied().collect::<Vec<_>>().join(" "); let version_str = ver.iter().copied().collect::<Vec<_>>().join(" ");
println!( println!(
"{} {} {} {}", "{} {} {} {}",
@ -325,7 +435,7 @@ fn print_removed(set: HashSet<&str>, pre: &HashMap<&str, HashSet<&str>>) {
"@".yellow(), "@".yellow(),
version_str.blue() version_str.blue()
); );
}); }
} }
fn print_changes( fn print_changes(
@ -334,17 +444,22 @@ fn print_changes(
post: &HashMap<&str, HashSet<&str>>, post: &HashMap<&str, HashSet<&str>>,
) { ) {
println!("{}", "Version changes:".underline().bold()); println!("{}", "Version changes:".underline().bold());
set.iter()
.filter(|p| !p.is_empty()) // Use sorted output for more predictable and readable results
.filter_map(|p| { let mut changes = Vec::new();
match (pre.get(p), post.get(p)) {
(Some(ver_pre), Some(ver_post)) if ver_pre != ver_post => { for p in set.iter().filter(|p| !p.is_empty()) {
Some((p, ver_pre, ver_post)) if let (Some(ver_pre), Some(ver_post)) = (pre.get(p), post.get(p)) {
if ver_pre != ver_post {
changes.push((*p, ver_pre, ver_post));
} }
_ => None,
} }
}) }
.for_each(|(p, ver_pre, ver_post)| {
// Sort by package name for consistent output
changes.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
for (p, ver_pre, ver_post) in changes {
let version_str_pre = ver_pre.iter().copied().collect::<Vec<_>>().join(" "); let version_str_pre = ver_pre.iter().copied().collect::<Vec<_>>().join(" ");
let version_str_post = ver_post.iter().copied().collect::<Vec<_>>().join(", "); let version_str_post = ver_post.iter().copied().collect::<Vec<_>>().join(", ");
@ -356,5 +471,5 @@ fn print_changes(
version_str_pre.magenta(), version_str_pre.magenta(),
version_str_post.blue() version_str_post.blue()
); );
}); }
} }