1
Fork 0
mirror of https://github.com/RGBCube/dix synced 2025-07-28 04:07:46 +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"
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]]
name = "heck"
version = "0.5.0"
@ -119,6 +142,36 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "memchr"
version = "2.7.4"
@ -130,6 +183,8 @@ name = "nnpdt"
version = "0.1.0"
dependencies = [
"clap",
"env_logger",
"log",
"regex",
"thiserror",
"yansi",
@ -141,6 +196,21 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro2"
version = "1.0.95"
@ -188,6 +258,26 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "strsim"
version = "0.11.1"

View file

@ -6,5 +6,7 @@ edition = "2024"
[dependencies]
clap = { version = "4.5.37", features = ["derive"] }
regex = "1.11.1"
thiserror = "2.0.12"
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 core::str;
use env_logger;
use log::{debug, error, info, warn};
use regex::Regex;
use std::{
collections::{HashMap, HashSet},
process::Command,
string::String,
string::{String, ToString},
sync::OnceLock,
thread,
};
@ -49,6 +51,14 @@ struct Args {
/// 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)]
@ -78,14 +88,35 @@ impl<'a> Package<'a> {
fn main() {
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.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.
let closure_size_handles = if args.closure_size {
debug!("Calculating closure sizes in background");
let path = args.path.clone();
let path2 = args.path2.clone();
Some((
@ -98,17 +129,41 @@ fn main() {
// Get package lists and handle potential errors
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) => {
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()
}
};
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) => {
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()
}
};
@ -121,11 +176,9 @@ fn main() {
match get_version(&**p) {
Ok((name, version)) => {
pre.entry(name).or_default().insert(version);
},
}
Err(e) => {
if cfg!(debug_assertions) {
eprintln!("Error parsing package version: {e}");
}
debug!("Error parsing package version: {}", e);
}
}
}
@ -134,11 +187,9 @@ fn main() {
match get_version(&**p) {
Ok((name, version)) => {
post.entry(name).or_default().insert(version);
},
}
Err(e) => {
if cfg!(debug_assertions) {
eprintln!("Error parsing package version: {e}");
}
debug!("Error parsing package version: {}", e);
}
}
}
@ -153,6 +204,20 @@ fn main() {
// Get the intersection of the package names for version changes
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!();
@ -165,15 +230,20 @@ fn main() {
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))) => {
debug!("Pre closure size: {} MiB", pre_size);
debug!("Post closure size: {} MiB", post_size);
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");
}
}
@ -182,6 +252,8 @@ fn main() {
/// Gets the packages in a closure
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>``
let output = Command::new("nix-store")
.arg("--query")
@ -191,18 +263,23 @@ fn get_packages(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!(
"nix-store command failed: {stderr}"
)));
}
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
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")
.arg("--query")
.arg("--requisites")
@ -211,13 +288,16 @@ fn get_dependencies(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!(
"nix-store command failed: {stderr}"
)));
}
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.
@ -256,13 +336,21 @@ fn get_version<'a>(pack: impl Into<&'a str>) -> Result<(&'a str, &'a str)> {
fn check_nix_available() -> bool {
// 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_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> {
debug!("Calculating closure size for path: {}", path.display());
// Run nix path-info command to get closure size
let output = Command::new("nix")
.arg("path-info")
@ -272,6 +360,7 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix path-info command failed: {}", stderr);
return Err(AppError::CommandFailed(format!(
"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)?;
// Parse the last word in the output as an integer (in bytes)
stdout
let size = stdout
.split_whitespace()
.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>()
.map_err(|e| AppError::ParseError(format!("Failed to parse size value: {e}")))
.map(|size| size / 1024 / 1024) // Convert to MiB
.map_err(|e| {
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>>) {
println!("{}", "Packages added:".underline().bold());
// Use sorted output
let mut sorted: Vec<_> = set.iter()
// Use sorted outpu
let mut sorted: Vec<_> = set
.iter()
.filter_map(|p| post.get(p).map(|ver| (*p, ver)))
.collect();
@ -314,18 +416,26 @@ fn print_added(set: HashSet<&str>, post: &HashMap<&str, HashSet<&str>>) {
fn print_removed(set: HashSet<&str>, pre: &HashMap<&str, HashSet<&str>>) {
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)))
.for_each(|(p, ver)| {
let version_str = ver.iter().copied().collect::<Vec<_>>().join(" ");
println!(
"{} {} {} {}",
"[R:]".red().bold(),
p,
"@".yellow(),
version_str.blue()
);
});
.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(" ");
println!(
"{} {} {} {}",
"[R:]".red().bold(),
p,
"@".yellow(),
version_str.blue()
);
}
}
fn print_changes(
@ -334,27 +444,32 @@ fn print_changes(
post: &HashMap<&str, HashSet<&str>>,
) {
println!("{}", "Version changes:".underline().bold());
set.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, ver_pre, ver_post))
}
_ => None,
}
})
.for_each(|(p, ver_pre, ver_post)| {
let version_str_pre = ver_pre.iter().copied().collect::<Vec<_>>().join(" ");
let version_str_post = ver_post.iter().copied().collect::<Vec<_>>().join(", ");
println!(
"{} {} {} {} ~> {}",
"[C:]".bold().bright_yellow(),
p,
"@".yellow(),
version_str_pre.magenta(),
version_str_post.blue()
);
});
// Use sorted output for more predictable and readable results
let mut changes = Vec::new();
for p in set.iter().filter(|p| !p.is_empty()) {
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));
}
}
}
// 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_post = ver_post.iter().copied().collect::<Vec<_>>().join(", ");
println!(
"{} {} {} {} ~> {}",
"[C:]".bold().bright_yellow(),
p,
"@".yellow(),
version_str_pre.magenta(),
version_str_post.blue()
);
}
}