From 90a1298e2d6d3d38bcd6f727649634250784c2de Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Mon, 18 Dec 2023 12:54:22 +0100 Subject: [PATCH 01/25] init log rewrite --- Cargo.lock | 187 +++++++++++++++++++++++++++++++++++----------- Cargo.toml | 47 ++++++------ README.md | 5 ++ devshell.nix | 1 + src/clean.rs | 2 +- src/commands.rs | 2 +- src/completion.rs | 1 + src/home.rs | 2 +- src/logging.rs | 68 +++++++++++++++++ src/main.rs | 64 +--------------- src/nixos.rs | 2 +- src/search.rs | 2 +- 12 files changed, 252 insertions(+), 131 deletions(-) create mode 100644 src/logging.rs diff --git a/Cargo.lock b/Cargo.lock index 7b10766..f12a324 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,17 +202,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "colored" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" -dependencies = [ - "is-terminal", - "lazy_static", - "winapi", -] - [[package]] name = "console" version = "0.15.7" @@ -340,16 +329,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -[[package]] -name = "fern" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" -dependencies = [ - "colored", - "log", -] - [[package]] name = "fnv" version = "1.0.7" @@ -368,12 +347,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - [[package]] name = "hostname" version = "0.3.1" @@ -403,17 +376,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" version = "0.10.5" @@ -459,6 +421,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.6.4" @@ -486,12 +457,11 @@ dependencies = [ "color-eyre", "derive_builder", "dialoguer", - "fern", "hostname", "humantime", - "log", "nix", "once_cell", + "owo-colors", "regex", "serde", "serde_json", @@ -499,6 +469,8 @@ dependencies = [ "tempfile", "thiserror", "timeago", + "tracing", + "tracing-subscriber", ] [[package]] @@ -512,6 +484,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "object" version = "0.32.1" @@ -527,12 +509,24 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + [[package]] name = "proc-macro2" version = "1.0.70" @@ -568,8 +562,17 @@ checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -580,9 +583,15 @@ checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -645,12 +654,27 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + [[package]] name = "strsim" version = "0.10.0" @@ -722,12 +746,83 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "timeago" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1710e589de0a76aaf295cd47a6699f6405737dbfd3cf2b75c92d000b548d0e6" +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -746,6 +841,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 16263ad..620faa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,41 +3,44 @@ name = "nh" version = "3.4.15" edition = "2021" license = "EUPL-1.2" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ambassador = "0.3.5" +anstyle = "1.0.0" clap = { version = "4.0", features = [ + "cargo", + "color", "derive", "env", "unstable-styles", - "color", - "cargo", ] } clap_complete = "4.0" -fern = { version = "0.6", features = [ - "colored", -] } -log = "0.4" -hostname = "^0.3" clean-path = "0.2" -subprocess = "0.2" -dialoguer = { version = "0.10.2", default-features = false } -thiserror = "1.0" +color-eyre = { version = "0.6.2", default-features = false, features = [ + "track-caller", +] } derive_builder = "0.12.0" -tempfile = "3.5.0" +dialoguer = { version = "0.10.2", default-features = false } +hostname = "^0.3" +humantime = "2.1.0" nix = { version = "0.26.2", default-features = false, features = [ - "user", "fs", + "user", ] } -anstyle = "1.0.0" -color-eyre = { version = "0.6.2", default-features = false, features = [ - "track-caller", -] } -ambassador = "0.3.5" -regex = "1.8.4" once_cell = "1.18.0" -timeago = { version = "0.4.1", default-features = false } -humantime = "2.1.0" +owo-colors = "3.5.0" +regex = "1.8.4" +serde = { version = "1.0.166", features = [ + "derive", +] } serde_json = "1.0.100" -serde = { version = "1.0.166", features = ["derive"] } +subprocess = "0.2" +tempfile = "3.5.0" +thiserror = "1.0" +timeago = { version = "0.4.1", default-features = false } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = [ + "env-filter", + "registry", +] } diff --git a/README.md b/README.md index 90e22f3..6a16682 100644 --- a/README.md +++ b/README.md @@ -136,3 +136,8 @@ Just `nix develop` [^1]: At the time of this writing. [^2]: The toplevel package is what you can build with `nix build /flake#nixosConfiguration.HOSTNAME.config.system.build.toplevel`, and what sits on `/run/current-system`, `/run/booted-system` and `/nix/var/nix/profiles/system`. + +## FIXME + +https://hydra.nixos.org/job/nixos/trunk-combined/nixpkgs.nh.x86_64-linux +https://hydra.nixos.org/job/nixos/trunk-combined/nixpkgs.nh.aarch64-linux \ No newline at end of file diff --git a/devshell.nix b/devshell.nix index 2b553b0..0b0cebe 100644 --- a/devshell.nix +++ b/devshell.nix @@ -27,6 +27,7 @@ mkShell { env = { NH_NOM = "1"; + RUST_LOG = "trace"; RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; }; } diff --git a/src/clean.rs b/src/clean.rs index aa22f83..a7767e0 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -6,7 +6,7 @@ use std::time::SystemTime; use color_eyre::eyre::{bail, ensure, Context, ContextCompat}; use color_eyre::Result; -use log::{debug, info, trace, warn}; +use tracing::{debug, info, trace, warn}; use once_cell::sync::Lazy; use regex::Regex; diff --git a/src/commands.rs b/src/commands.rs index 097d9c2..53a0297 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,7 +6,7 @@ use color_eyre::{ use std::ffi::{OsStr, OsString}; use thiserror::Error; -use log::{debug, info}; +use tracing::{debug, info}; use subprocess::{Exec, ExitStatus, PopenError, Redirection}; use crate::*; diff --git a/src/completion.rs b/src/completion.rs index 03368ac..679559c 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,6 +1,7 @@ use crate::*; use clap_complete::generate; use color_eyre::Result; +use tracing::trace; impl NHRunnable for interface::CompletionArgs { fn run(&self) -> Result<()> { diff --git a/src/home.rs b/src/home.rs index 276a827..c39ffce 100644 --- a/src/home.rs +++ b/src/home.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use color_eyre::eyre::bail; use color_eyre::Result; -use log::{debug, info, trace}; +use tracing::{debug, info, trace}; use thiserror::Error; use crate::*; diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..157df93 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,68 @@ +use owo_colors::OwoColorize; +use tracing::Event; +use tracing::Level; +use tracing::Subscriber; +use tracing_subscriber::filter::filter_fn; +use tracing_subscriber::fmt; +use tracing_subscriber::fmt::format::Format; +use tracing_subscriber::fmt::FormatEvent; +use tracing_subscriber::fmt::FormatFields; +use tracing_subscriber::prelude::*; +use tracing_subscriber::registry::LookupSpan; +use tracing_subscriber::EnvFilter; +use crate::*; + +struct InfoFormatter; + +impl FormatEvent for InfoFormatter +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &fmt::FmtContext<'_, S, N>, + mut writer: fmt::format::Writer, + event: &Event, + ) -> std::fmt::Result { + // Based on https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/trait.FormatEvent.html#examples + // Without the unused parts + write!(writer, "{} ", ">".green())?; + ctx.field_format().format_fields(writer.by_ref(), event)?; + writeln!(writer)?; + Ok(()) + } +} + +pub(crate) fn setup_logging() -> Result<()> { + color_eyre::config::HookBuilder::default() + .display_location_section(false) + .panic_section("Please report the bug at https://github.com/viperML/nh/issues") + .display_env_section(false) + .install()?; + + let layer_debug = fmt::layer() + .with_writer(std::io::stderr) + .without_time() + .compact() + .with_line_number(true) + .with_filter(EnvFilter::from_default_env()) + .with_filter(filter_fn(|meta| *meta.level() != Level::INFO)); + + let layer_info = fmt::layer() + .with_writer(std::io::stderr) + .without_time() + .with_target(false) + .with_level(false) + .event_format(InfoFormatter) + .with_filter(filter_fn(|meta| *meta.level() == Level::INFO)); + + tracing_subscriber::registry() + .with(layer_debug) + .with(layer_info) + .init(); + + tracing::trace!("Logging setup!"); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 4e14eb0..afce362 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,76 +3,18 @@ mod commands; mod completion; mod home; mod interface; +mod logging; mod nixos; mod search; -use color_eyre::Result; -use fern::colors::Color; -use log::{error, trace, SetLoggerError}; - use crate::interface::NHParser; use crate::interface::NHRunnable; +use color_eyre::Result; fn main() -> Result<()> { - if cfg!(debug_assertions) { - std::env::set_var("RUST_BACKTRACE", "full"); - } - - color_eyre::config::HookBuilder::default() - .display_location_section(false) - .panic_section("consider reporting the bug at https://github.com/viperML/nh") - .display_env_section(false) - .install()?; + logging::setup_logging()?; let args = ::parse(); - setup_logging(args.verbose)?; - args.command.run() } - -fn setup_logging(verbose: bool) -> Result<(), SetLoggerError> { - let loglevel = if cfg!(debug_assertions) { - log::LevelFilter::Trace - } else if verbose { - log::LevelFilter::Debug - } else { - log::LevelFilter::Info - }; - - let color_text = fern::colors::ColoredLevelConfig::new() - .debug(Color::BrightBlack) - .error(Color::White) - .trace(Color::BrightBlue); - - let color_symbol = fern::colors::ColoredLevelConfig::new() - .debug(Color::BrightBlack) - .error(Color::Red) - .error(Color::Red) - .info(Color::Green) - .trace(Color::BrightBlue) - .warn(Color::Yellow); - - fern::Dispatch::new() - .format(move |out, message, record| { - let prefix = match record.level() { - log::Level::Info | log::Level::Warn | log::Level::Error => "\n", - _ => "", - }; - out.finish(format_args!( - "{prefix}{color_symbol}>\x1B[0m {color_line}{message}\x1B[0m", - color_symbol = format_args!( - "\x1B[{}m", - color_symbol.get_color(&record.level()).to_fg_str() - ), - color_line = format_args!( - "\x1B[{}m", - color_text.get_color(&record.level()).to_fg_str() - ), - message = message, - )); - }) - .level(loglevel) - .chain(std::io::stdout()) - .apply() -} diff --git a/src/nixos.rs b/src/nixos.rs index 3457d65..1b38518 100644 --- a/src/nixos.rs +++ b/src/nixos.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use color_eyre::eyre::{bail, Context}; use color_eyre::Result; -use log::{debug, info, trace}; +use tracing::{debug, info, trace}; use crate::interface::NHRunnable; use crate::interface::OsRebuildType::{self, Boot, Switch, Test}; diff --git a/src/search.rs b/src/search.rs index e6a6aa9..a808a6f 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, ops::Deref, process::Command}; - +use tracing::trace; use crate::*; use interface::SearchArgs; From fc3ea080c12715594f3016eaa9cce5af7480814c Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 22 Dec 2023 10:26:06 +0100 Subject: [PATCH 02/25] fixme --- devshell.nix | 2 +- src/clean.rs | 338 +++++------------------------------------------ src/interface.rs | 10 +- 3 files changed, 39 insertions(+), 311 deletions(-) diff --git a/devshell.nix b/devshell.nix index 0b0cebe..291f56e 100644 --- a/devshell.nix +++ b/devshell.nix @@ -27,7 +27,7 @@ mkShell { env = { NH_NOM = "1"; - RUST_LOG = "trace"; + RUST_LOG = "nh=trace"; RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; }; } diff --git a/src/clean.rs b/src/clean.rs index a7767e0..cf6e59c 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,331 +1,61 @@ -use std::collections::HashMap; -use std::io::ErrorKind; -use std::os::unix::process::CommandExt; -use std::path::{Component, Path, PathBuf}; -use std::time::SystemTime; - -use color_eyre::eyre::{bail, ensure, Context, ContextCompat}; -use color_eyre::Result; -use tracing::{debug, info, trace, warn}; use once_cell::sync::Lazy; use regex::Regex; +use tracing::{trace, debug}; -use crate::commands; -use crate::interface::NHRunnable; -use crate::interface::{CleanArgs, CleanMode}; +use crate::*; // Reference: https://github.com/NixOS/nix/blob/master/src/nix-collect-garbage/nix-collect-garbage.cc -impl NHRunnable for CleanMode { +impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { + match self { - CleanMode::Info => todo!(), - CleanMode::All(args) => { + interface::CleanMode::All(args) => { let uid = nix::unistd::Uid::effective(); + trace!(?uid); if !uid.is_root() { - let mut cmd = std::process::Command::new("sudo"); - cmd.args(std::env::args()); - debug!("{:?}", cmd); - let err = cmd.exec(); - bail!(err); - } - - let mut profiles = Vec::new(); - let mut gcroots = Vec::new(); + debug!("nh clean all called as root user, re-executing with sudo"); - gcroots.push(PathBuf::from("/nix/var/nix/gcroots/auto")); - profiles.push(PathBuf::from("/nix/var/nix/profiles")); - - for entry in std::fs::read_dir("/home")? { - let homedir = entry?.path(); - profiles.push(homedir.join(".local/state/nix/profiles")); - } - for entry in std::fs::read_dir("/nix/var/nix/profiles/per-user")? { - let path = entry?.path(); - profiles.push(path); - } - for entry in std::fs::read_dir("/nix/var/nix/gcroots/per-user")? { - let path = entry?.path(); - gcroots.push(path); } - clean(args, &profiles, &gcroots) - } - CleanMode::User(args) => { - let uid = nix::unistd::Uid::effective(); - if uid.is_root() { - bail!("nh clean user: don't run me as root!"); - } - let user = nix::unistd::User::from_uid(uid)?.unwrap(); - - let profiles = std::env::var("NIX_PROFILES") - .wrap_err("Reading NIX_PROFILES to detect the profiles locations")? - .split(' ') - .map(PathBuf::from) - .filter(|profile| { - use nix::unistd::AccessFlags; - let parent = match profile.parent() { - Some(p) => p, - None => { - return false; - } - }; - let access = nix::unistd::access( - parent, - AccessFlags::F_OK | AccessFlags::R_OK | AccessFlags::W_OK, - ); - trace!("eaccess {parent:?} -> {access:?}"); - - if let (Ok(_), true) = (access, profile.exists()) { - true - } else { - false - } - }) - .collect::>(); - - clean( - args, - &profiles, - // FIXME scan auto - &[PathBuf::from("/nix/var/nix/gcroots/per-user").join(user.name)], - ) - } - } - } -} -fn clean

(args: &CleanArgs, base_dirs: &[P], gcroots_dirs: &[P]) -> Result<()> -where - P: AsRef + std::fmt::Debug, -{ - info!("Calculating transaction"); - trace!("{:?}", gcroots_dirs); - let mut gc_roots_to_remove = Vec::new(); - if !args.nogcroots { - for dir in gcroots_dirs { - for entry in std::fs::read_dir(dir)? { - let entry = entry?.path(); - trace!("Checking entry {:?}", entry); - - let pointing_to = match std::fs::read_link(&entry) { - Ok(p) => p, - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => continue, - other => bail!(other), - }, - }; - - let last_modified = std::fs::symlink_metadata(&entry)?.modified()?; - let now = SystemTime::now(); - match now.duration_since(last_modified) { - Err(err) => { - warn!("Failed to compare time!: {entry:?} {now:?} , {last_modified:?}, err: {err:?}"); - warn!("Please file a bug on https://github.com/viperML/nh/issues"); - continue; - } - Ok(val) if val <= args.keep_since.into() => { - continue; - } - Ok(_) => {} - }; - - let delete = pointing_to.components().any(|comp| { - if let Component::Normal(s) = comp { - let s = s.to_str().expect("Couldn't convert OsStr to UTF-8 str"); - if s == ".direnv" { - return true; - } - if s.contains("result") { - return true; - } - }; - false - }); - - if delete { - eprintln!( - " 🗑 {} -> {}", - entry.to_str().unwrap(), - pointing_to.to_str().unwrap() - ); - gc_roots_to_remove.push(entry); - }; - } - } - } - - trace!("{:?}", base_dirs); - let mut profiles: HashMap> = HashMap::new(); - - for base_dir in base_dirs { - // let read = std::fs::read_dir(base_dir)?; - let read = match std::fs::read_dir(base_dir) { - Ok(inner) => inner, - Err(inner) => match inner.kind() { - ErrorKind::NotFound => { - debug!("Base dir not found!"); - continue; - } - _ => Err(inner) - .context(base_dir.as_ref().to_str().unwrap().to_string()) - .context("Reading base dir")?, }, - }; - - for entry in read { - // let x = x.await; - let path = entry?.path(); - let parent = path.parent().unwrap(); - let name = path.file_name().unwrap().to_str().unwrap().to_string(); - - if let Some((base_name, id)) = parse_profile(&name) { - let base_profile = parent.join(base_name); - let last_modified: SystemTime = std::fs::symlink_metadata(&path)?.modified()?; - - let profile = Generation { - id, - path, - last_modified, - marked_for_deletion: true, - }; - - match profiles.get_mut(&base_profile) { - None => { - profiles.insert(base_profile, vec![profile]); - } - Some(v) => { - v.push(profile); - } - }; - } - - if name.ends_with("-link") {} + interface::CleanMode::User(_) => todo!(), } - } - - trace!("{:?}", profiles); - - for (base_profile, generations) in &mut profiles { - generations.sort_by(|a, b| a.id.cmp(&b.id)); - trace!("generations: {:?}", generations); - - let last_id = generations.last().unwrap().id; - - let base_profile_link = base_profile.read_link()?; - let base_profile_link = base_profile_link.to_str().unwrap(); - let (_, base_profile_id) = - parse_profile(base_profile_link).wrap_err("Parsing base profile")?; - trace!("({base_profile_id:?}) {}", base_profile_link); - trace!( - "({last_id:?}) {}", - generations.last().unwrap().path.to_str().unwrap() - ); - ensure!( - base_profile_id == last_id, - "Profile doesn't point into the generation with highest number, aborting" - ); - - eprintln!(); - eprintln!("- {}", base_profile.as_os_str().to_str().unwrap()); - - for gen in generations.iter_mut() { - // Use relative numbering, 1,2,3,4 - gen.id = last_id - gen.id + 1; - - if gen.id <= args.keep { - gen.marked_for_deletion = false; - } - - let age = SystemTime::now().duration_since(gen.last_modified)?; - if age <= args.keep_since.into() { - gen.marked_for_deletion = false; - } - - if gen.marked_for_deletion { - eprintln!(" 🗑 {}", gen.path.to_str().unwrap()); - } else { - eprintln!(" ✅ {}", gen.path.to_str().unwrap()); - } - } - } - - if args.dry { - return Ok(()); - } - if args.ask { - info!("Confirm the cleanup plan?"); - let confirmation = dialoguer::Confirm::new().default(false).interact()?; - if !confirmation { - return Ok(()); - } - } - - for root in gc_roots_to_remove { - info!("Removing {:?}", root); - if let Err(e) = std::fs::remove_file(root) { - warn!("Failed to remove: {:?}", e); - } - } - - for (_, generations) in profiles { - for gen in generations { - if gen.marked_for_deletion { - info!("Removing {:?}", gen.path); - if let Err(e) = std::fs::remove_file(gen.path) { - warn!("Failed to remove: {:?}", e); - } - } - } - } - if !args.nogc { - commands::CommandBuilder::default() - .args(&["nix", "store", "gc"]) - .message("nix store gc") - .capture(false) - .build()? - .exec()?; + Ok(()) } - - Ok(()) } -#[derive(Debug, Clone)] -struct Generation { - id: u32, - path: PathBuf, - last_modified: SystemTime, - marked_for_deletion: bool, -} -static PROFILE_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^(.*)-(\d+)-link$").unwrap()); -fn parse_profile(s: &str) -> Option<(&str, u32)> { - let captures = PROFILE_PATTERN.captures(s)?; +// static PROFILE_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^(.*)-(\d+)-link$").unwrap()); - let base = captures.get(1)?.as_str(); - let number = captures.get(2)?.as_str().parse().ok()?; +// fn parse_profile(s: &str) -> Option<(&str, u32)> { +// let captures = PROFILE_PATTERN.captures(s)?; - Some((base, number)) -} +// let base = captures.get(1)?.as_str(); +// let number = captures.get(2)?.as_str().parse().ok()?; -#[test] -fn test_parse_profile() { - assert_eq!( - parse_profile("home-manager-3-link"), - Some(("home-manager", 3)) - ); - assert_eq!( - parse_profile("home-manager-30-link"), - Some(("home-manager", 30)) - ); - assert_eq!(parse_profile("home-manager"), None); - assert_eq!( - parse_profile("foo-bar-baz-0-link"), - Some(("foo-bar-baz", 0)) - ); - assert_eq!(parse_profile("foo-bar-baz-X-link"), None); -} +// Some((base, number)) +// } + +// #[test] +// fn test_parse_profile() { +// assert_eq!( +// parse_profile("home-manager-3-link"), +// Some(("home-manager", 3)) +// ); +// assert_eq!( +// parse_profile("home-manager-30-link"), +// Some(("home-manager", 30)) +// ); +// assert_eq!(parse_profile("home-manager"), None); +// assert_eq!( +// parse_profile("foo-bar-baz-0-link"), +// Some(("foo-bar-baz", 0)) +// ); +// assert_eq!(parse_profile("foo-bar-baz-X-link"), None); +// } diff --git a/src/interface.rs b/src/interface.rs index ff2f43f..11441c5 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -2,7 +2,7 @@ use ambassador::{delegatable_trait, Delegate}; use anstyle::Style; use clap::{builder::Styles, Args, Parser, Subcommand}; use color_eyre::Result; -use std::{ffi::OsString, ops::Deref}; +use std::{ffi::OsString, ops::Deref, path::PathBuf}; #[derive(Debug, Clone, Default)] pub struct FlakeRef(String); @@ -148,8 +148,9 @@ pub struct SearchArgs { pub query: String, - #[arg(default_value = "nixpkgs")] - pub flake: FlakeRef, + // #[arg(default_value = "nixpkgs")] + // pub flake: FlakeRef, + pub database: PathBuf } // Needed a struct to have multiple sub-subcommands @@ -167,9 +168,6 @@ pub enum CleanMode { All(CleanArgs), /// Clean your user's profiles and gcroots User(CleanArgs), - /// Print information about the store of the system - #[clap(hide = true)] - Info, } #[derive(Args, Clone, Debug)] From 839b4d00ced0bd18fe8044fe734f8e68360e85a8 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Sun, 24 Dec 2023 16:41:22 +0100 Subject: [PATCH 03/25] init ES client --- Cargo.lock | 692 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/interface.rs | 6 +- src/main.rs | 2 + src/search.rs | 239 ++++++++++++++-- 5 files changed, 919 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f12a324..161e297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "backtrace" version = "0.3.69" @@ -101,6 +107,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "bitflags" version = "1.3.2" @@ -113,6 +125,18 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + [[package]] name = "cc" version = "1.0.83" @@ -128,6 +152,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "clap" version = "4.4.10" @@ -215,6 +249,22 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "darling" version = "0.14.4" @@ -297,12 +347,39 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elasticsearch-dsl" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7066057f05cce0d1c6c7cdbd1e99a83f903bad35faf070cd86e34aa5bc504feb" +dependencies = [ + "chrono", + "num-traits", + "serde", + "serde_json", +] + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.8" @@ -335,18 +412,117 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "hostname" version = "0.3.1" @@ -358,24 +534,122 @@ dependencies = [ "winapi", ] +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indenter" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itertools" version = "0.10.5" @@ -391,6 +665,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -436,6 +719,12 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -445,6 +734,17 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "nh" version = "3.4.15" @@ -457,12 +757,14 @@ dependencies = [ "color-eyre", "derive_builder", "dialoguer", + "elasticsearch-dsl", "hostname", "humantime", "nix", "once_cell", "owo-colors", "regex", + "reqwest", "serde", "serde_json", "subprocess", @@ -494,6 +796,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.1" @@ -521,12 +842,24 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.70" @@ -598,6 +931,60 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -617,12 +1004,53 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "serde" version = "1.0.193" @@ -654,6 +1082,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -669,12 +1109,37 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.10.0" @@ -713,6 +1178,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.8.1" @@ -762,6 +1248,67 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1710e589de0a76aaf295cd47a6699f6405737dbfd3cf2b75c92d000b548d0e6" +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -823,18 +1370,56 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -847,6 +1432,103 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "winapi" version = "0.3.9" @@ -1066,3 +1748,13 @@ name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml index 620faa4..3b0908f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ color-eyre = { version = "0.6.2", default-features = false, features = [ ] } derive_builder = "0.12.0" dialoguer = { version = "0.10.2", default-features = false } +elasticsearch-dsl = "0.4.19" hostname = "^0.3" humantime = "2.1.0" nix = { version = "0.26.2", default-features = false, features = [ @@ -31,6 +32,7 @@ nix = { version = "0.26.2", default-features = false, features = [ once_cell = "1.18.0" owo-colors = "3.5.0" regex = "1.8.4" +reqwest = { version = "0.11.23", features = ["rustls-tls", "blocking", "json"], default-features = false } serde = { version = "1.0.166", features = [ "derive", ] } diff --git a/src/interface.rs b/src/interface.rs index 11441c5..acc928a 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -144,13 +144,9 @@ pub struct CommonRebuildArgs { /// Search a package pub struct SearchArgs { #[arg(long, short, default_value = "10")] - pub max_results: usize, + pub limit: usize, pub query: String, - - // #[arg(default_value = "nixpkgs")] - // pub flake: FlakeRef, - pub database: PathBuf } // Needed a struct to have multiple sub-subcommands diff --git a/src/main.rs b/src/main.rs index afce362..51dd2e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,8 @@ use crate::interface::NHParser; use crate::interface::NHRunnable; use color_eyre::Result; +const NH_VERSION: &'static str = env!("CARGO_PKG_VERSION"); + fn main() -> Result<()> { logging::setup_logging()?; diff --git a/src/search.rs b/src/search.rs index a808a6f..867d32a 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,32 +1,237 @@ -use std::{collections::HashMap, ops::Deref, process::Command}; -use tracing::trace; use crate::*; +use color_eyre::eyre::Context; use interface::SearchArgs; +use owo_colors::OwoColorize; +use serde_json::{json, Value}; +use std::{collections::HashMap, ops::Deref, process::Command, time::Instant}; +use tracing::{debug, info, trace}; -#[derive(Debug, serde::Deserialize)] -struct RawEntry<'a> { - description: &'a str, - pname: &'a str, - version: &'a str, +use elasticsearch_dsl::*; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[allow(non_snake_case, dead_code)] +struct SearchResult { + // r#type: String, + package_attr_name: String, + package_attr_set: String, + package_pname: String, + package_pversion: String, + package_platforms: Vec, + package_outputs: Vec, + package_default_output: Option, + package_programs: Vec, + // package_license: Vec, + package_license_set: Vec, + // package_maintainers: Vec>, + package_description: Option, + package_longDescription: Option, + package_hydra: (), + package_system: String, + package_homepage: Vec, + package_position: Option, } -type RawResults<'a> = HashMap<&'a str, RawEntry<'a>>; +#[derive(Debug, Deserialize)] +struct License { + url: String, + fullName: String, +} impl NHRunnable for SearchArgs { fn run(&self) -> Result<()> { trace!("args: {self:?}"); - let results = Command::new("nix") - .arg("search") - .arg(self.flake.deref()) - .arg(&self.query) - .arg("--json") - .output()?; + let query = json!({ + "from": 0, + "size": self.limit, + "sort": [ + { + "_score": "desc", + "package_attr_name": "desc", + "package_pversion": "desc" + } + ], + "aggs": { + "package_attr_set": { + "terms": { + "field": "package_attr_set", + "size": 20 + } + }, + "package_license_set": { + "terms": { + "field": "package_license_set", + "size": 20 + } + }, + "package_maintainers_set": { + "terms": { + "field": "package_maintainers_set", + "size": 20 + } + }, + "package_platforms": { + "terms": { + "field": "package_platforms", + "size": 20 + } + }, + "all": { + "global": {}, + "aggregations": { + "package_attr_set": { + "terms": { + "field": "package_attr_set", + "size": 20 + } + }, + "package_license_set": { + "terms": { + "field": "package_license_set", + "size": 20 + } + }, + "package_maintainers_set": { + "terms": { + "field": "package_maintainers_set", + "size": 20 + } + }, + "package_platforms": { + "terms": { + "field": "package_platforms", + "size": 20 + } + } + } + } + }, + "query": { + "bool": { + "filter": [ + { + "term": { + "type": { + "value": "package", + "_name": "filter_packages" + } + } + }, + { + "bool": { + "must": [ + { + "bool": { + "should": [] + } + }, + { + "bool": { + "should": [] + } + }, + { + "bool": { + "should": [] + } + }, + { + "bool": { + "should": [] + } + } + ] + } + } + ], + "must": [ + { + "dis_max": { + "tie_breaker": 0.7, + "queries": [ + { + "multi_match": { + "type": "cross_fields", + "query": self.query, + "analyzer": "whitespace", + "auto_generate_synonyms_phrase_query": false, + "operator": "and", + "_name": "multi_match_xd", + "fields": [ + "package_attr_name^9", + "package_attr_name.*^5.3999999999999995", + "package_programs^9", + "package_programs.*^5.3999999999999995", + "package_pname^6", + "package_pname.*^3.5999999999999996", + "package_description^1.3", + "package_description.*^0.78", + "package_longDescription^1", + "package_longDescription.*^0.6", + "flake_name^0.5", + "flake_name.*^0.3" + ] + } + }, + { + "wildcard": { + "package_attr_name": { + "value": "*xd*", + "case_insensitive": true + } + } + } + ] + } + } + ] + } + } + }); + + let client = reqwest::blocking::Client::new(); + + let req = client + .post("https://search.nixos.org/backend/latest-42-nixos-23.11/_search") + .json(&query) + .header("User-Agent", format!("nh/{}", crate::NH_VERSION)) + .basic_auth("aWVSALXpZv", Some("X8gPHnzL52wFEekuxsfQ9cSh")) + .build() + .context("building search query")?; + + debug!(?req); + + let then = Instant::now(); + let response = client + .execute(req) + .context("querying the elasticsearch API")?; + let elapsed = then.elapsed(); + debug!(?elapsed, "took"); + debug!(?response); + + let search: SearchResponse = response + .json() + .context("parsing response into the elasticsearch format")?; + debug!(?search); - let parsed: RawResults = serde_json::from_slice(&results.stdout)?; + let x = search + .documents::() + .context("parsing search document")?; - trace!("{:?}", parsed); + for elem in x.iter().rev() { + trace!("{elem:#?}"); + println!( + "{} ({})", + elem.package_attr_name.blue(), + elem.package_pversion.green() + ); + if let Some(ref description) = elem.package_description { + println!(" {}", description); + } + println!(); + } - todo!(); + Ok(()) } } From 70380024ffeeedf3f3b9344a13a02fced852e8d2 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Sun, 31 Dec 2023 18:58:35 +0100 Subject: [PATCH 04/25] tweak search --- Cargo.lock | 70 +++++++++++++- Cargo.toml | 1 + src/interface.rs | 16 +++- src/search.rs | 244 ++++++++++++++++------------------------------- 4 files changed, 162 insertions(+), 169 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 161e297..6476074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -644,6 +644,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -686,6 +697,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -769,6 +786,7 @@ dependencies = [ "serde_json", "subprocess", "tempfile", + "textwrap", "thiserror", "timeago", "tracing", @@ -991,6 +1009,20 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.26" @@ -1000,7 +1032,7 @@ dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.12", "windows-sys 0.52.0", ] @@ -1124,6 +1156,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.5.5" @@ -1208,10 +1246,32 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix", + "rustix 0.38.26", "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix 0.37.27", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "terminal_size", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -1388,6 +1448,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" diff --git a/Cargo.toml b/Cargo.toml index 3b0908f..ab1cd0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ serde = { version = "1.0.166", features = [ serde_json = "1.0.100" subprocess = "0.2" tempfile = "3.5.0" +textwrap = { version = "0.16.0", features = ["terminal_size"] } thiserror = "1.0" timeago = { version = "0.4.1", default-features = false } tracing = "0.1.40" diff --git a/src/interface.rs b/src/interface.rs index acc928a..4c25345 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -141,11 +141,21 @@ pub struct CommonRebuildArgs { } #[derive(Args, Debug)] -/// Search a package +/// Searches packages by querying search.nixos.org pub struct SearchArgs { - #[arg(long, short, default_value = "10")] - pub limit: usize, + #[arg(long, short, default_value = "30")] + /// Number of search results to display + pub limit: u64, + #[arg(long, short = 'L')] + /// Display more information about each result + pub long: bool, + + #[arg(long, short, default_value = "nixos-unstable")] + /// Name of the channel to query (e.g nixos-23.11, nixos-unstable) + pub channel: String, + + /// Name of the package to search pub query: String, } diff --git a/src/search.rs b/src/search.rs index 867d32a..0e2d24e 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,7 +1,6 @@ use crate::*; use color_eyre::eyre::Context; use interface::SearchArgs; -use owo_colors::OwoColorize; use serde_json::{json, Value}; use std::{collections::HashMap, ops::Deref, process::Command, time::Instant}; use tracing::{debug, info, trace}; @@ -42,194 +41,111 @@ impl NHRunnable for SearchArgs { fn run(&self) -> Result<()> { trace!("args: {self:?}"); - let query = json!({ - "from": 0, - "size": self.limit, - "sort": [ - { - "_score": "desc", - "package_attr_name": "desc", - "package_pversion": "desc" - } - ], - "aggs": { - "package_attr_set": { - "terms": { - "field": "package_attr_set", - "size": 20 - } - }, - "package_license_set": { - "terms": { - "field": "package_license_set", - "size": 20 - } - }, - "package_maintainers_set": { - "terms": { - "field": "package_maintainers_set", - "size": 20 - } - }, - "package_platforms": { - "terms": { - "field": "package_platforms", - "size": 20 - } - }, - "all": { - "global": {}, - "aggregations": { - "package_attr_set": { - "terms": { - "field": "package_attr_set", - "size": 20 - } - }, - "package_license_set": { - "terms": { - "field": "package_license_set", - "size": 20 - } - }, - "package_maintainers_set": { - "terms": { - "field": "package_maintainers_set", - "size": 20 - } - }, - "package_platforms": { - "terms": { - "field": "package_platforms", - "size": 20 - } - } - } - } - }, - "query": { - "bool": { - "filter": [ - { - "term": { - "type": { - "value": "package", - "_name": "filter_packages" - } - } - }, - { - "bool": { - "must": [ - { - "bool": { - "should": [] - } - }, - { - "bool": { - "should": [] - } - }, - { - "bool": { - "should": [] - } - }, - { - "bool": { - "should": [] - } - } - ] - } - } - ], - "must": [ - { - "dis_max": { - "tie_breaker": 0.7, - "queries": [ - { - "multi_match": { - "type": "cross_fields", - "query": self.query, - "analyzer": "whitespace", - "auto_generate_synonyms_phrase_query": false, - "operator": "and", - "_name": "multi_match_xd", - "fields": [ - "package_attr_name^9", - "package_attr_name.*^5.3999999999999995", - "package_programs^9", - "package_programs.*^5.3999999999999995", - "package_pname^6", - "package_pname.*^3.5999999999999996", - "package_description^1.3", - "package_description.*^0.78", - "package_longDescription^1", - "package_longDescription.*^0.6", - "flake_name^0.5", - "flake_name.*^0.3" - ] - } - }, - { - "wildcard": { - "package_attr_name": { - "value": "*xd*", - "case_insensitive": true - } - } - } - ] - } - } - ] - } - } - }); + let query = Search::new().from(0).size(self.limit).query( + Query::bool().filter(Query::term("type", "package")).must( + Query::dis_max() + .tie_breaker(0.7) + .query( + Query::multi_match( + [ + "package_attr_name^9", + "package_attr_name.*^5.3999999999999995", + "package_programs^9", + "package_programs.*^5.3999999999999995", + "package_pname^6", + "package_pname.*^3.5999999999999996", + "package_description^1.3", + "package_description.*^0.78", + "package_longDescription^1", + "package_longDescription.*^0.6", + "flake_name^0.5", + "flake_name.*^0.3", + ], + self.query.as_str(), + ) + .r#type(TextQueryType::CrossFields) + .analyzer("whitespace") + .auto_generate_synonyms_phrase_query(false) + .operator(Operator::And), + ) + .query( + Query::wildcard("package_attr_name", format!("*{}*", self.query)) + .case_insensitive(true), + ), + ), + ); - let client = reqwest::blocking::Client::new(); + println!( + "Querying search.nixos.org, with channel {}...", + self.channel + ); + let then = Instant::now(); + let client = reqwest::blocking::Client::new(); let req = client - .post("https://search.nixos.org/backend/latest-42-nixos-23.11/_search") + // I guess 42 is the version of the backend API + // TODO: have a GH action or something check if they updated this thing + .post(format!( + "https://search.nixos.org/backend/latest-42-{}/_search", + self.channel + )) .json(&query) .header("User-Agent", format!("nh/{}", crate::NH_VERSION)) + // Hardcoded upstream + // https://github.com/NixOS/nixos-search/blob/744ec58e082a3fcdd741b2c9b0654a0f7fda4603/frontend/src/index.js .basic_auth("aWVSALXpZv", Some("X8gPHnzL52wFEekuxsfQ9cSh")) .build() .context("building search query")?; debug!(?req); - let then = Instant::now(); let response = client .execute(req) .context("querying the elasticsearch API")?; let elapsed = then.elapsed(); - debug!(?elapsed, "took"); - debug!(?response); + debug!(?elapsed); + trace!(?response); + println!("Took {}ms", elapsed.as_millis()); + println!("Most relevant results at end"); + println!(); - let search: SearchResponse = response + let parsed_response: SearchResponse = response .json() .context("parsing response into the elasticsearch format")?; - debug!(?search); + trace!(?parsed_response); - let x = search + let documents = parsed_response .documents::() .context("parsing search document")?; - for elem in x.iter().rev() { + for elem in documents.iter().rev() { + println!(); + use owo_colors::OwoColorize; trace!("{elem:#?}"); - println!( - "{} ({})", - elem.package_attr_name.blue(), - elem.package_pversion.green() - ); - if let Some(ref description) = elem.package_description { - println!(" {}", description); + print!("{}", elem.package_attr_name.blue(),); + let v = &elem.package_pversion; + if !v.is_empty() { + print!(" ({})", v.green()); } + println!(); + + if let Some(ref desc) = elem.package_description { + let desc = desc.replace("\n", " "); + for line in textwrap::wrap(&desc, textwrap::Options::with_termwidth()) { + println!(" {}", line); + } + } + + if self.long { + for url in elem.package_homepage.iter() { + println!(" Homepage: {}", url); + } + + if !elem.package_license_set.is_empty() { + println!(" License: {}", elem.package_license_set.join(", ")); + } + } } Ok(()) From a05703885afabda9c3c194bdd280d0b762e9adf4 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Mon, 1 Jan 2024 20:22:47 +0100 Subject: [PATCH 05/25] re-add cleanable_generations --- src/clean.rs | 102 +++++++++++++++++++++++++++++++++++++++++------ src/interface.rs | 14 ++++++- 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index cf6e59c..e37c8b3 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,6 +1,13 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use color_eyre::eyre::{Context, ContextCompat}; use once_cell::sync::Lazy; use regex::Regex; -use tracing::{trace, debug}; +use tracing::{debug, instrument, trace, warn}; use crate::*; @@ -8,28 +15,97 @@ use crate::*; impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { + let uid = nix::unistd::Uid::effective(); match self { - interface::CleanMode::All(args) => { - let uid = nix::unistd::Uid::effective(); - trace!(?uid); - if !uid.is_root() { - debug!("nh clean all called as root user, re-executing with sudo"); - - } - + interface::CleanMode::Profile(args) => { + // cleanable_generations(args., keep, keep_size) + cleanable_generations(&args.profile, args.common.keep, args.common.keep_since)?; + } + interface::CleanMode::All(args) => todo!(), + interface::CleanMode::User(args) => todo!(), + } + Ok(()) + } +} - }, - interface::CleanMode::User(_) => todo!(), +type ToBeCleaned = bool; + +#[instrument(err, level = "debug")] +fn cleanable_generations( + profile: &Path, + keep: u32, + keep_since: humantime::Duration, +) -> Result> { + let name = profile + .file_name() + .context("Checking profile's name")? + .to_str() + .unwrap(); + + let generation_regex = Regex::new(&format!(r"{name}-(\d+)-link"))?; + + let mut generations = Vec::new(); + + for entry in profile + .parent() + .context("Reading profile's parent dir")? + .read_dir() + .context("Reading profile's generations")? + { + let path = entry?.path(); + let captures = generation_regex.captures(path.file_name().unwrap().to_str().unwrap()); + + if let Some(caps) = captures { + if let Some(number) = caps.get(1) { + let last_modified = std::fs::symlink_metadata(&path) + .context("Checking symlink metadata")? + .modified() + .context("Reading modified time")?; + + generations.push(( + Generation { + number: number.as_str().parse().unwrap(), + last_modified, + path: path.clone(), + }, + true, + )); + } } + } + // Sort generations because I don't know if the fs reports paths in any order + generations.sort_by(|a, b| b.0.number.cmp(&a.0.number)); + + let now = SystemTime::now(); + for gen in generations.iter_mut() { + match now.duration_since(gen.0.last_modified) { + Err(err) => { + warn!(?err, ?now, ?gen, "Failed to compare time!"); + } + Ok(val) if val <= keep_since.into() => { + gen.1 = false; + } + Ok(_) => {} + }; + } - Ok(()) + for gen in generations.iter_mut().take(keep as _) { + gen.1 = false; } -} + debug!("{:#?}", generations); + Ok(generations) +} +#[derive(Debug)] +struct Generation { + path: PathBuf, + number: u32, + last_modified: SystemTime, +} // static PROFILE_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^(.*)-(\d+)-link$").unwrap()); diff --git a/src/interface.rs b/src/interface.rs index 4c25345..8030ee4 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -170,10 +170,12 @@ pub struct CleanProxy { #[derive(Debug, Clone, Subcommand)] /// Enhanced nix cleanup pub enum CleanMode { - /// Elevate to root to clean all profiles and gcroots + /// Cleans root profiles and calls a store gc All(CleanArgs), - /// Clean your user's profiles and gcroots + /// Cleans the current user's profiles and calls a store gc User(CleanArgs), + /// Cleans a specific profile + Profile(CleanProfileArgs), } #[derive(Args, Clone, Debug)] @@ -207,6 +209,14 @@ pub struct CleanArgs { pub nogcroots: bool, } +#[derive(Debug, Clone, Args)] +pub struct CleanProfileArgs { + #[command(flatten)] + pub common: CleanArgs, + + pub profile: PathBuf +} + #[derive(Debug, Args)] /// Home-manager functionality pub struct HomeArgs { From ced290e7f69171d59cd64243b558f9833030b1fd Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Mon, 1 Jan 2024 22:07:15 +0100 Subject: [PATCH 06/25] remove files --- src/clean.rs | 76 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index e37c8b3..5d5fec5 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -7,7 +7,7 @@ use std::{ use color_eyre::eyre::{Context, ContextCompat}; use once_cell::sync::Lazy; use regex::Regex; -use tracing::{debug, instrument, trace, warn}; +use tracing::{debug, info, instrument, trace, warn}; use crate::*; @@ -20,7 +20,11 @@ impl NHRunnable for interface::CleanMode { match self { interface::CleanMode::Profile(args) => { // cleanable_generations(args., keep, keep_size) - cleanable_generations(&args.profile, args.common.keep, args.common.keep_since)?; + let res = + cleanable_generations(&args.profile, args.common.keep, args.common.keep_since)?; + let mut h = HashMap::new(); + h.insert(args.profile.clone(), res); + prompt_clean(h, args.common.ask, args.common.dry)?; } interface::CleanMode::All(args) => todo!(), interface::CleanMode::User(args) => todo!(), @@ -107,31 +111,43 @@ struct Generation { last_modified: SystemTime, } -// static PROFILE_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^(.*)-(\d+)-link$").unwrap()); - -// fn parse_profile(s: &str) -> Option<(&str, u32)> { -// let captures = PROFILE_PATTERN.captures(s)?; - -// let base = captures.get(1)?.as_str(); -// let number = captures.get(2)?.as_str().parse().ok()?; - -// Some((base, number)) -// } - -// #[test] -// fn test_parse_profile() { -// assert_eq!( -// parse_profile("home-manager-3-link"), -// Some(("home-manager", 3)) -// ); -// assert_eq!( -// parse_profile("home-manager-30-link"), -// Some(("home-manager", 30)) -// ); -// assert_eq!(parse_profile("home-manager"), None); -// assert_eq!( -// parse_profile("foo-bar-baz-0-link"), -// Some(("foo-bar-baz", 0)) -// ); -// assert_eq!(parse_profile("foo-bar-baz-X-link"), None); -// } +fn prompt_clean( + profiles: HashMap>, + ask: bool, + dry: bool, +) -> Result<()> { + use owo_colors::OwoColorize; + for (k, v) in profiles.iter() { + println!("{}", k.to_string_lossy().bold().blue()); + for (gen, toberemoved) in v { + if *toberemoved { + println!("- {} {}", "DEL".red(), gen.path.to_string_lossy()); + } else { + println!("- {} {}", "OK ".green(), gen.path.to_string_lossy()); + }; + } + println!(); + } + + if !dry { + if ask { + info!("Confirm the cleanup plan?"); + if !dialoguer::Confirm::new().default(false).interact()? { + return Ok(()); + } + } + + for (_, v) in profiles.iter() { + for (gen, toberemoved) in v { + if *toberemoved { + info!("Removing {}", gen.path.to_string_lossy()); + if let Err(err) = std::fs::remove_file(&gen.path) { + warn!(?err, "Failed to remove"); + } + } + } + } + } + + Ok(()) +} From b4eec9e6dc20d1bc6305a02629d17dd4dc7a2cda Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Tue, 2 Jan 2024 22:14:53 +0100 Subject: [PATCH 07/25] clean user --- src/clean.rs | 109 ++++++++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index 5d5fec5..e676d81 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,47 +1,75 @@ use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, path::{Path, PathBuf}, time::SystemTime, }; -use color_eyre::eyre::{Context, ContextCompat}; -use once_cell::sync::Lazy; +use color_eyre::eyre::{bail, Context, ContextCompat}; use regex::Regex; use tracing::{debug, info, instrument, trace, warn}; use crate::*; -// Reference: https://github.com/NixOS/nix/blob/master/src/nix-collect-garbage/nix-collect-garbage.cc +// Nix impl: +// https://github.com/NixOS/nix/blob/master/src/nix-collect-garbage/nix-collect-garbage.cc + +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +struct Generation { + number: u32, + last_modified: SystemTime, + path: PathBuf, +} + +type ToBeCleaned = bool; +// BTreeMap to automatically sort generations by id +type GenerationsTagged = BTreeMap; +type ProfilesTagged = HashMap; impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { let uid = nix::unistd::Uid::effective(); + let mut profiles = ProfilesTagged::new(); + match self { interface::CleanMode::Profile(args) => { - // cleanable_generations(args., keep, keep_size) - let res = - cleanable_generations(&args.profile, args.common.keep, args.common.keep_since)?; - let mut h = HashMap::new(); - h.insert(args.profile.clone(), res); - prompt_clean(h, args.common.ask, args.common.dry)?; + profiles.insert( + args.profile.clone(), + cleanable_generations(&args.profile, args.common.keep, args.common.keep_since)?, + ); + prompt_clean(profiles, args.common.ask, args.common.dry)?; + } + interface::CleanMode::All(args) => {} + interface::CleanMode::User(args) => { + if uid.is_root() { + bail!("nh clean user: don't run me as root!"); + } + + for p in std::env::var("NIX_PROFILES") + .wrap_err("Reading NIX_PROFILES to detect the profiles locations")? + .split(' ') + .map(PathBuf::from) + { + profiles.insert( + p.clone(), + cleanable_generations(&p, args.keep, args.keep_since)?, + ); + } + + prompt_clean(profiles, args.ask, args.dry)?; } - interface::CleanMode::All(args) => todo!(), - interface::CleanMode::User(args) => todo!(), } Ok(()) } } -type ToBeCleaned = bool; - #[instrument(err, level = "debug")] fn cleanable_generations( profile: &Path, keep: u32, keep_since: humantime::Duration, -) -> Result> { +) -> Result { let name = profile .file_name() .context("Checking profile's name")? @@ -50,7 +78,7 @@ fn cleanable_generations( let generation_regex = Regex::new(&format!(r"{name}-(\d+)-link"))?; - let mut generations = Vec::new(); + let mut result = GenerationsTagged::new(); for entry in profile .parent() @@ -68,59 +96,44 @@ fn cleanable_generations( .modified() .context("Reading modified time")?; - generations.push(( + result.insert( Generation { number: number.as_str().parse().unwrap(), last_modified, path: path.clone(), }, true, - )); + ); } } } - // Sort generations because I don't know if the fs reports paths in any order - generations.sort_by(|a, b| b.0.number.cmp(&a.0.number)); - let now = SystemTime::now(); - for gen in generations.iter_mut() { - match now.duration_since(gen.0.last_modified) { + for (gen, tbr) in result.iter_mut() { + match now.duration_since(gen.last_modified) { Err(err) => { warn!(?err, ?now, ?gen, "Failed to compare time!"); } Ok(val) if val <= keep_since.into() => { - gen.1 = false; + *tbr = false; } Ok(_) => {} - }; + } } - for gen in generations.iter_mut().take(keep as _) { - gen.1 = false; + for (_, tbr) in result.iter_mut().rev().take(keep as _) { + *tbr = false; } - debug!("{:#?}", generations); - Ok(generations) -} - -#[derive(Debug)] -struct Generation { - path: PathBuf, - number: u32, - last_modified: SystemTime, + debug!("{:#?}", result); + Ok(result) } -fn prompt_clean( - profiles: HashMap>, - ask: bool, - dry: bool, -) -> Result<()> { +fn prompt_clean(profiles: ProfilesTagged, ask: bool, dry: bool) -> Result<()> { use owo_colors::OwoColorize; - for (k, v) in profiles.iter() { - println!("{}", k.to_string_lossy().bold().blue()); - for (gen, toberemoved) in v { - if *toberemoved { + for (_, generations_tagged) in profiles.iter() { + for (gen, tbr) in generations_tagged.iter().rev() { + if *tbr { println!("- {} {}", "DEL".red(), gen.path.to_string_lossy()); } else { println!("- {} {}", "OK ".green(), gen.path.to_string_lossy()); @@ -137,9 +150,9 @@ fn prompt_clean( } } - for (_, v) in profiles.iter() { - for (gen, toberemoved) in v { - if *toberemoved { + for (_, generations_tagged) in profiles.iter() { + for (gen, tbr) in generations_tagged.iter().rev() { + if *tbr { info!("Removing {}", gen.path.to_string_lossy()); if let Err(err) = std::fs::remove_file(&gen.path) { warn!(?err, "Failed to remove"); From 50828ce5f6776c558eb83ebfd04675dae6f316bf Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Wed, 3 Jan 2024 22:13:59 +0100 Subject: [PATCH 08/25] read dir for profiles --- src/clean.rs | 78 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index e676d81..474cc75 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -29,41 +29,79 @@ impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { let uid = nix::unistd::Uid::effective(); - let mut profiles = ProfilesTagged::new(); + let mut profiles = Vec::new(); - match self { + let args = match self { interface::CleanMode::Profile(args) => { - profiles.insert( - args.profile.clone(), - cleanable_generations(&args.profile, args.common.keep, args.common.keep_since)?, - ); - prompt_clean(profiles, args.common.ask, args.common.dry)?; + profiles.push(args.profile.clone()); + &args.common + } + interface::CleanMode::All(args) => { + if !uid.is_root() { + bail!("nh clean all: root permissions required, rerun with sudo!"); + } + todo!(); } - interface::CleanMode::All(args) => {} interface::CleanMode::User(args) => { if uid.is_root() { bail!("nh clean user: don't run me as root!"); } - for p in std::env::var("NIX_PROFILES") - .wrap_err("Reading NIX_PROFILES to detect the profiles locations")? - .split(' ') - .map(PathBuf::from) - { - profiles.insert( - p.clone(), - cleanable_generations(&p, args.keep, args.keep_since)?, - ); - } + let user = nix::unistd::User::from_uid(uid)?.unwrap(); + + profiles.extend(profiles_in_dir( + &PathBuf::from(std::env::var("HOME")?).join(".local/state/nix/profiles"), + )?); - prompt_clean(profiles, args.ask, args.dry)?; + profiles.extend(profiles_in_dir( + &PathBuf::from("/nix/var/nix/profiles/per-user").join(user.name), + )?); + + args } + }; + + // Use mutation to raise errors as they come + let mut profiles_tagged = ProfilesTagged::new(); + for p in profiles { + profiles_tagged.insert( + p.clone(), + cleanable_generations(&p, args.keep, args.keep_since)?, + ); } + prompt_clean(profiles_tagged, args.ask, args.dry)?; + Ok(()) } } +#[instrument(ret, err, level = "trace")] +fn profiles_in_dir(dir: &Path) -> Result> { + let mut res = Vec::new(); + + if let Ok(read_dir) = dir.read_dir() { + for entry in read_dir { + let path = entry?.path(); + + if let Ok(dst) = path.read_link() { + let name = dst + .file_name() + .wrap_err("Reading file_name")? + .to_string_lossy(); + + let generation_regex = Regex::new(r"^(.*)-(\d+)-link$")?; + + if let Some(_) = generation_regex.captures(&name) { + res.push(path); + } + } + } + } + + Ok(res) +} + #[instrument(err, level = "debug")] fn cleanable_generations( profile: &Path, @@ -76,7 +114,7 @@ fn cleanable_generations( .to_str() .unwrap(); - let generation_regex = Regex::new(&format!(r"{name}-(\d+)-link"))?; + let generation_regex = Regex::new(&format!(r"^{name}-(\d+)-link"))?; let mut result = GenerationsTagged::new(); From 77e51bb765651dd6580d1a687f2f30a72de0a1bc Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Wed, 3 Jan 2024 22:18:52 +0100 Subject: [PATCH 09/25] print profile --- src/clean.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/clean.rs b/src/clean.rs index 474cc75..44f6962 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -169,7 +169,9 @@ fn cleanable_generations( fn prompt_clean(profiles: ProfilesTagged, ask: bool, dry: bool) -> Result<()> { use owo_colors::OwoColorize; - for (_, generations_tagged) in profiles.iter() { + + for (profile, generations_tagged) in profiles.iter() { + println!("{}", profile.to_string_lossy().blue().bold()); for (gen, tbr) in generations_tagged.iter().rev() { if *tbr { println!("- {} {}", "DEL".red(), gen.path.to_string_lossy()); From 84678105927dda6e900cb594f9643c9bd8e8d645 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Thu, 4 Jan 2024 20:54:56 +0100 Subject: [PATCH 10/25] nh clean all impl --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/clean.rs | 27 ++++++++++++++++++++------- src/main.rs | 14 +++++++++++++- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6476074..7d8b451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -791,6 +791,7 @@ dependencies = [ "timeago", "tracing", "tracing-subscriber", + "uzers", ] [[package]] @@ -1492,6 +1493,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uzers" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63" +dependencies = [ + "libc", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ab1cd0a..127b22c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,4 @@ tracing-subscriber = { version = "0.3.18", features = [ "env-filter", "registry", ] } +uzers = { version = "0.11.3", default-features = false } diff --git a/src/clean.rs b/src/clean.rs index 44f6962..1768fc8 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -4,11 +4,11 @@ use std::{ time::SystemTime, }; +use crate::*; use color_eyre::eyre::{bail, Context, ContextCompat}; use regex::Regex; use tracing::{debug, info, instrument, trace, warn}; - -use crate::*; +use uzers::os::unix::UserExt; // Nix impl: // https://github.com/NixOS/nix/blob/master/src/nix-collect-garbage/nix-collect-garbage.cc @@ -38,9 +38,23 @@ impl NHRunnable for interface::CleanMode { } interface::CleanMode::All(args) => { if !uid.is_root() { - bail!("nh clean all: root permissions required, rerun with sudo!"); + crate::self_elevate(); + } + + profiles.extend(profiles_in_dir("/nix/var/nix/profiles")?); + + for read_dir in PathBuf::from("/nix/var/nix/profiles/per-user").read_dir()? { + let path = read_dir?.path(); + profiles.extend(profiles_in_dir(path)?); } - todo!(); + + for user in unsafe { uzers::all_users() } { + profiles.extend(profiles_in_dir( + user.home_dir().join(".local/state/nix/profiles"), + )?); + } + + args } interface::CleanMode::User(args) => { if uid.is_root() { @@ -52,7 +66,6 @@ impl NHRunnable for interface::CleanMode { profiles.extend(profiles_in_dir( &PathBuf::from(std::env::var("HOME")?).join(".local/state/nix/profiles"), )?); - profiles.extend(profiles_in_dir( &PathBuf::from("/nix/var/nix/profiles/per-user").join(user.name), )?); @@ -77,10 +90,10 @@ impl NHRunnable for interface::CleanMode { } #[instrument(ret, err, level = "trace")] -fn profiles_in_dir(dir: &Path) -> Result> { +fn profiles_in_dir + std::fmt::Debug>(dir: P) -> Result> { let mut res = Vec::new(); - if let Ok(read_dir) = dir.read_dir() { + if let Ok(read_dir) = dir.as_ref().read_dir() { for entry in read_dir { let path = entry?.path(); diff --git a/src/main.rs b/src/main.rs index 51dd2e3..1571d8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,13 @@ mod logging; mod nixos; mod search; + use crate::interface::NHParser; use crate::interface::NHRunnable; use color_eyre::Result; +use tracing::debug; -const NH_VERSION: &'static str = env!("CARGO_PKG_VERSION"); +const NH_VERSION: &'static str = env!("CARGO_PKG_VERSION"); fn main() -> Result<()> { logging::setup_logging()?; @@ -20,3 +22,13 @@ fn main() -> Result<()> { args.command.run() } + +fn self_elevate() -> ! { + use std::os::unix::process::CommandExt; + + let mut cmd = std::process::Command::new("sudo"); + cmd.args(std::env::args()); + debug!("{:?}", cmd); + let err = cmd.exec(); + panic!("{}", err); +} From 812a175ab4b871ec54b261d12b864c38ba0ec9d4 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 18:06:37 +0100 Subject: [PATCH 11/25] properly search in profiles without failing --- Cargo.toml | 1 + src/clean.rs | 67 +++++++++++++++++++++++++++++++------------------- src/logging.rs | 13 ++++++---- src/main.rs | 3 +-- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 127b22c..843a7bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,5 +46,6 @@ tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = [ "env-filter", "registry", + "std" ] } uzers = { version = "0.11.3", default-features = false } diff --git a/src/clean.rs b/src/clean.rs index 1768fc8..2fbaa7a 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,5 +1,6 @@ use std::{ collections::{BTreeMap, HashMap}, + fmt, path::{Path, PathBuf}, time::SystemTime, }; @@ -41,17 +42,20 @@ impl NHRunnable for interface::CleanMode { crate::self_elevate(); } - profiles.extend(profiles_in_dir("/nix/var/nix/profiles")?); + profiles.extend(profiles_in_dir("/nix/var/nix/profiles")); for read_dir in PathBuf::from("/nix/var/nix/profiles/per-user").read_dir()? { let path = read_dir?.path(); - profiles.extend(profiles_in_dir(path)?); + profiles.extend(profiles_in_dir(path)); } for user in unsafe { uzers::all_users() } { - profiles.extend(profiles_in_dir( - user.home_dir().join(".local/state/nix/profiles"), - )?); + if user.uid() >= 1000 || user.uid() == 0 { + debug!(?user, "Adding XDG profiles for user"); + profiles.extend(profiles_in_dir( + user.home_dir().join(".local/state/nix/profiles"), + )); + } } args @@ -65,10 +69,10 @@ impl NHRunnable for interface::CleanMode { profiles.extend(profiles_in_dir( &PathBuf::from(std::env::var("HOME")?).join(".local/state/nix/profiles"), - )?); + )); profiles.extend(profiles_in_dir( &PathBuf::from("/nix/var/nix/profiles/per-user").join(user.name), - )?); + )); args } @@ -89,30 +93,43 @@ impl NHRunnable for interface::CleanMode { } } -#[instrument(ret, err, level = "trace")] -fn profiles_in_dir + std::fmt::Debug>(dir: P) -> Result> { +#[instrument(ret, level = "trace")] +fn profiles_in_dir + fmt::Debug>(dir: P) -> Vec { let mut res = Vec::new(); - - if let Ok(read_dir) = dir.as_ref().read_dir() { - for entry in read_dir { - let path = entry?.path(); - - if let Ok(dst) = path.read_link() { - let name = dst - .file_name() - .wrap_err("Reading file_name")? - .to_string_lossy(); - - let generation_regex = Regex::new(r"^(.*)-(\d+)-link$")?; - - if let Some(_) = generation_regex.captures(&name) { - res.push(path); + let dir = dir.as_ref(); + + match dir.read_dir() { + Ok(read_dir) => { + for entry in read_dir { + match entry { + Ok(e) => { + let path = e.path(); + + if let Ok(dst) = path.read_link() { + let name = dst + .file_name() + .expect("Failed to get filename") + .to_string_lossy(); + + let generation_regex = Regex::new(r"^(.*)-(\d+)-link$").unwrap(); + + if let Some(_) = generation_regex.captures(&name) { + res.push(path); + } + } + } + Err(error) => { + warn!(?dir, ?error, "Failed to read folder element"); + } } } } + Err(error) => { + warn!(?dir, ?error, "Failed to read profiles directory"); + } } - Ok(res) + res } #[instrument(err, level = "debug")] diff --git a/src/logging.rs b/src/logging.rs index 157df93..1ae8b0e 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use owo_colors::OwoColorize; use tracing::Event; use tracing::Level; @@ -10,6 +12,7 @@ use tracing_subscriber::fmt::FormatFields; use tracing_subscriber::prelude::*; use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::EnvFilter; +use tracing_subscriber::filter::FilterExt; use crate::*; struct InfoFormatter; @@ -34,7 +37,7 @@ where } } -pub(crate) fn setup_logging() -> Result<()> { +pub(crate) fn setup_logging(verbose: bool) -> Result<()> { color_eyre::config::HookBuilder::default() .display_location_section(false) .panic_section("Please report the bug at https://github.com/viperML/nh/issues") @@ -46,8 +49,8 @@ pub(crate) fn setup_logging() -> Result<()> { .without_time() .compact() .with_line_number(true) - .with_filter(EnvFilter::from_default_env()) - .with_filter(filter_fn(|meta| *meta.level() != Level::INFO)); + .with_filter(EnvFilter::from_default_env().or(filter_fn(move |_| verbose))) + .with_filter(filter_fn(|meta| *meta.level() > Level::INFO)); let layer_info = fmt::layer() .with_writer(std::io::stderr) @@ -55,14 +58,14 @@ pub(crate) fn setup_logging() -> Result<()> { .with_target(false) .with_level(false) .event_format(InfoFormatter) - .with_filter(filter_fn(|meta| *meta.level() == Level::INFO)); + .with_filter(filter_fn(|meta| *meta.level() <= Level::INFO)); tracing_subscriber::registry() .with(layer_debug) .with(layer_info) .init(); - tracing::trace!("Logging setup!"); + tracing::trace!("Logging OK"); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 1571d8e..0f4c18e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,9 +16,8 @@ use tracing::debug; const NH_VERSION: &'static str = env!("CARGO_PKG_VERSION"); fn main() -> Result<()> { - logging::setup_logging()?; - let args = ::parse(); + crate::logging::setup_logging(args.verbose)?; args.command.run() } From 8b6863d78ee9ccac2af158210d21c0ad968f2069 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 21:23:21 +0100 Subject: [PATCH 12/25] tweak warn logging --- src/logging.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/logging.rs b/src/logging.rs index 1ae8b0e..852f8e3 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,10 +1,12 @@ use std::str::FromStr; +use crate::*; use owo_colors::OwoColorize; use tracing::Event; use tracing::Level; use tracing::Subscriber; use tracing_subscriber::filter::filter_fn; +use tracing_subscriber::filter::FilterExt; use tracing_subscriber::fmt; use tracing_subscriber::fmt::format::Format; use tracing_subscriber::fmt::FormatEvent; @@ -12,8 +14,6 @@ use tracing_subscriber::fmt::FormatFields; use tracing_subscriber::prelude::*; use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::EnvFilter; -use tracing_subscriber::filter::FilterExt; -use crate::*; struct InfoFormatter; @@ -30,7 +30,16 @@ where ) -> std::fmt::Result { // Based on https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/trait.FormatEvent.html#examples // Without the unused parts - write!(writer, "{} ", ">".green())?; + let level = event.metadata().level(); + + if *level == Level::ERROR { + write!(writer, "{} ", "!".red())?; + } else if *level == Level::WARN { + write!(writer, "{} ", "!".yellow())?; + } else { + write!(writer, "{} ", ">".green())?; + } + ctx.field_format().format_fields(writer.by_ref(), event)?; writeln!(writer)?; Ok(()) From d69c28e8653d6b16ee7f12ee4816d6a6939c3504 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 21:24:40 +0100 Subject: [PATCH 13/25] clippy --- src/clean.rs | 2 +- src/commands.rs | 4 +--- src/home.rs | 9 ++------- src/interface.rs | 2 +- src/logging.rs | 4 +--- src/main.rs | 3 +-- src/search.rs | 8 ++++---- 7 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index 2fbaa7a..9e0b231 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -8,7 +8,7 @@ use std::{ use crate::*; use color_eyre::eyre::{bail, Context, ContextCompat}; use regex::Regex; -use tracing::{debug, info, instrument, trace, warn}; +use tracing::{debug, info, instrument, warn}; use uzers::os::unix::UserExt; // Nix impl: diff --git a/src/commands.rs b/src/commands.rs index 53a0297..97df76d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,10 +6,8 @@ use color_eyre::{ use std::ffi::{OsStr, OsString}; use thiserror::Error; -use tracing::{debug, info}; use subprocess::{Exec, ExitStatus, PopenError, Redirection}; - -use crate::*; +use tracing::{debug, info}; #[derive(Debug, derive_builder::Builder, Default)] #[builder(derive(Debug), setter(into), default)] diff --git a/src/home.rs b/src/home.rs index c39ffce..00fbea6 100644 --- a/src/home.rs +++ b/src/home.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use color_eyre::eyre::bail; use color_eyre::Result; -use tracing::{debug, info, trace}; use thiserror::Error; +use tracing::{debug, info, trace}; use crate::*; use crate::{ @@ -91,12 +91,7 @@ impl HomeRebuildArgs { // just do nothing for None case (fresh installs) if let Some(prev_gen) = prev_generation { commands::CommandBuilder::default() - .args(&[ - "nvd", - "diff", - (prev_gen.to_str().unwrap()), - out_link_str, - ]) + .args(&["nvd", "diff", (prev_gen.to_str().unwrap()), out_link_str]) .message("Comparing changes") .build()? .exec()?; diff --git a/src/interface.rs b/src/interface.rs index 8030ee4..1206b79 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -214,7 +214,7 @@ pub struct CleanProfileArgs { #[command(flatten)] pub common: CleanArgs, - pub profile: PathBuf + pub profile: PathBuf, } #[derive(Debug, Args)] diff --git a/src/logging.rs b/src/logging.rs index 852f8e3..e6668ac 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use crate::*; use owo_colors::OwoColorize; use tracing::Event; @@ -8,7 +6,7 @@ use tracing::Subscriber; use tracing_subscriber::filter::filter_fn; use tracing_subscriber::filter::FilterExt; use tracing_subscriber::fmt; -use tracing_subscriber::fmt::format::Format; + use tracing_subscriber::fmt::FormatEvent; use tracing_subscriber::fmt::FormatFields; use tracing_subscriber::prelude::*; diff --git a/src/main.rs b/src/main.rs index 0f4c18e..76dfa5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,12 @@ mod logging; mod nixos; mod search; - use crate::interface::NHParser; use crate::interface::NHRunnable; use color_eyre::Result; use tracing::debug; -const NH_VERSION: &'static str = env!("CARGO_PKG_VERSION"); +const NH_VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() -> Result<()> { let args = ::parse(); diff --git a/src/search.rs b/src/search.rs index 0e2d24e..0dac7b6 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,9 +1,9 @@ use crate::*; use color_eyre::eyre::Context; use interface::SearchArgs; -use serde_json::{json, Value}; -use std::{collections::HashMap, ops::Deref, process::Command, time::Instant}; -use tracing::{debug, info, trace}; + +use std::time::Instant; +use tracing::{debug, trace}; use elasticsearch_dsl::*; use serde::Deserialize; @@ -131,7 +131,7 @@ impl NHRunnable for SearchArgs { println!(); if let Some(ref desc) = elem.package_description { - let desc = desc.replace("\n", " "); + let desc = desc.replace('\n', " "); for line in textwrap::wrap(&desc, textwrap::Options::with_termwidth()) { println!(" {}", line); } From cf8f114b51e816cb55f5f4000db792844d0b50e7 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 21:26:07 +0100 Subject: [PATCH 14/25] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d8b451..d2a7a07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -764,7 +764,7 @@ dependencies = [ [[package]] name = "nh" -version = "3.4.15" +version = "3.5.0" dependencies = [ "ambassador", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 843a7bd..e7e1f17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nh" -version = "3.4.15" +version = "3.5.0" edition = "2021" license = "EUPL-1.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 2f2a19527883dfc901161f0613475567c4585227 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 21:38:15 +0100 Subject: [PATCH 15/25] prompt clean other paths --- src/clean.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index 9e0b231..322b9dd 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -31,6 +31,7 @@ impl NHRunnable for interface::CleanMode { let uid = nix::unistd::Uid::effective(); let mut profiles = Vec::new(); + let mut other_paths = Vec::new(); let args = match self { interface::CleanMode::Profile(args) => { @@ -87,7 +88,7 @@ impl NHRunnable for interface::CleanMode { ); } - prompt_clean(profiles_tagged, args.ask, args.dry)?; + prompt_clean(profiles_tagged, other_paths, args.ask, args.dry)?; Ok(()) } @@ -197,9 +198,16 @@ fn cleanable_generations( Ok(result) } -fn prompt_clean(profiles: ProfilesTagged, ask: bool, dry: bool) -> Result<()> { +fn prompt_clean(profiles: ProfilesTagged, other_paths: Vec, ask: bool, dry: bool) -> Result<()> { use owo_colors::OwoColorize; + if !other_paths.is_empty() { + println!("{}", "gcroots".blue().bold()); + } + for path in &other_paths { + println!("- {} {}", "DEL".red(), path.to_string_lossy()); + } + for (profile, generations_tagged) in profiles.iter() { println!("{}", profile.to_string_lossy().blue().bold()); for (gen, tbr) in generations_tagged.iter().rev() { @@ -220,13 +228,14 @@ fn prompt_clean(profiles: ProfilesTagged, ask: bool, dry: bool) -> Result<()> { } } + for path in &other_paths { + remove_path_nofail(path); + } + for (_, generations_tagged) in profiles.iter() { for (gen, tbr) in generations_tagged.iter().rev() { if *tbr { - info!("Removing {}", gen.path.to_string_lossy()); - if let Err(err) = std::fs::remove_file(&gen.path) { - warn!(?err, "Failed to remove"); - } + remove_path_nofail(&gen.path); } } } @@ -234,3 +243,10 @@ fn prompt_clean(profiles: ProfilesTagged, ask: bool, dry: bool) -> Result<()> { Ok(()) } + +fn remove_path_nofail(path: &Path) { + info!("Removing {}", path.to_string_lossy()); + if let Err(err) = std::fs::remove_file(path) { + warn!(?path, ?err, "Failed to remove path"); + } +} From b75d6638f26d1882a783516e455e0d072ec35167 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 21:42:57 +0100 Subject: [PATCH 16/25] inline code --- src/clean.rs | 103 +++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index 322b9dd..f8a2227 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -28,11 +28,11 @@ type ProfilesTagged = HashMap; impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { - let uid = nix::unistd::Uid::effective(); - let mut profiles = Vec::new(); - let mut other_paths = Vec::new(); + let mut other_paths: Vec = Vec::new(); + // What profiles to clean depending on the call mode + let uid = nix::unistd::Uid::effective(); let args = match self { interface::CleanMode::Profile(args) => { profiles.push(args.profile.clone()); @@ -42,14 +42,11 @@ impl NHRunnable for interface::CleanMode { if !uid.is_root() { crate::self_elevate(); } - profiles.extend(profiles_in_dir("/nix/var/nix/profiles")); - for read_dir in PathBuf::from("/nix/var/nix/profiles/per-user").read_dir()? { let path = read_dir?.path(); profiles.extend(profiles_in_dir(path)); } - for user in unsafe { uzers::all_users() } { if user.uid() >= 1000 || user.uid() == 0 { debug!(?user, "Adding XDG profiles for user"); @@ -58,23 +55,19 @@ impl NHRunnable for interface::CleanMode { )); } } - args } interface::CleanMode::User(args) => { if uid.is_root() { bail!("nh clean user: don't run me as root!"); } - let user = nix::unistd::User::from_uid(uid)?.unwrap(); - profiles.extend(profiles_in_dir( &PathBuf::from(std::env::var("HOME")?).join(".local/state/nix/profiles"), )); profiles.extend(profiles_in_dir( &PathBuf::from("/nix/var/nix/profiles/per-user").join(user.name), )); - args } }; @@ -88,7 +81,49 @@ impl NHRunnable for interface::CleanMode { ); } - prompt_clean(profiles_tagged, other_paths, args.ask, args.dry)?; + // Preset the user the information about the paths to clean + use owo_colors::OwoColorize; + + if !other_paths.is_empty() { + println!("{}", "gcroots".blue().bold()); + } + for path in &other_paths { + println!("- {} {}", "DEL".red(), path.to_string_lossy()); + } + + for (profile, generations_tagged) in profiles_tagged.iter() { + println!("{}", profile.to_string_lossy().blue().bold()); + for (gen, tbr) in generations_tagged.iter().rev() { + if *tbr { + println!("- {} {}", "DEL".red(), gen.path.to_string_lossy()); + } else { + println!("- {} {}", "OK ".green(), gen.path.to_string_lossy()); + }; + } + println!(); + } + + // Clean the paths + if !args.dry { + if args.ask { + info!("Confirm the cleanup plan?"); + if !dialoguer::Confirm::new().default(false).interact()? { + return Ok(()); + } + } + + for path in &other_paths { + remove_path_nofail(path); + } + + for (_, generations_tagged) in profiles_tagged.iter() { + for (gen, tbr) in generations_tagged.iter().rev() { + if *tbr { + remove_path_nofail(&gen.path); + } + } + } + } Ok(()) } @@ -198,52 +233,6 @@ fn cleanable_generations( Ok(result) } -fn prompt_clean(profiles: ProfilesTagged, other_paths: Vec, ask: bool, dry: bool) -> Result<()> { - use owo_colors::OwoColorize; - - if !other_paths.is_empty() { - println!("{}", "gcroots".blue().bold()); - } - for path in &other_paths { - println!("- {} {}", "DEL".red(), path.to_string_lossy()); - } - - for (profile, generations_tagged) in profiles.iter() { - println!("{}", profile.to_string_lossy().blue().bold()); - for (gen, tbr) in generations_tagged.iter().rev() { - if *tbr { - println!("- {} {}", "DEL".red(), gen.path.to_string_lossy()); - } else { - println!("- {} {}", "OK ".green(), gen.path.to_string_lossy()); - }; - } - println!(); - } - - if !dry { - if ask { - info!("Confirm the cleanup plan?"); - if !dialoguer::Confirm::new().default(false).interact()? { - return Ok(()); - } - } - - for path in &other_paths { - remove_path_nofail(path); - } - - for (_, generations_tagged) in profiles.iter() { - for (gen, tbr) in generations_tagged.iter().rev() { - if *tbr { - remove_path_nofail(&gen.path); - } - } - } - } - - Ok(()) -} - fn remove_path_nofail(path: &Path) { info!("Removing {}", path.to_string_lossy()); if let Err(err) = std::fs::remove_file(path) { From 7bd098e35cac8303ae6ba59a4d6e8f01eeb5da32 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 23:00:23 +0100 Subject: [PATCH 17/25] gcroots handling --- src/clean.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index f8a2227..e1cbc59 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,14 +1,21 @@ use std::{ collections::{BTreeMap, HashMap}, fmt, + os::unix::fs::PermissionsExt, path::{Path, PathBuf}, time::SystemTime, }; use crate::*; -use color_eyre::eyre::{bail, Context, ContextCompat}; +use color_eyre::eyre::{bail, eyre, Context, ContextCompat}; +use nix::errno::Errno; +use nix::{ + fcntl::AtFlags, + unistd::{faccessat, AccessFlags}, +}; use regex::Regex; -use tracing::{debug, info, instrument, warn}; +use std::os::unix::fs::MetadataExt; +use tracing::{debug, info, instrument, trace, warn}; use uzers::os::unix::UserExt; // Nix impl: @@ -30,6 +37,7 @@ impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { let mut profiles = Vec::new(); let mut other_paths: Vec = Vec::new(); + let now = SystemTime::now(); // What profiles to clean depending on the call mode let uid = nix::unistd::Uid::effective(); @@ -81,16 +89,60 @@ impl NHRunnable for interface::CleanMode { ); } - // Preset the user the information about the paths to clean - use owo_colors::OwoColorize; + // Query gcroots + for elem in PathBuf::from("/nix/var/nix/gcroots/auto") + .read_dir() + .wrap_err("Reading auto gcroots dir")? + { + let src = elem.wrap_err("Reading auto gcroots element")?.path(); + let dst = src.read_link().wrap_err("Reading symlink destination")?; + debug!(?src, ?dst); + + // Use .exists to not travel symlinks + if dst.exists() { + let meta = dst.metadata().wrap_err("Reading gcroot metadata")?; + let last_modified = meta.modified()?; + let access = match faccessat( + None, + &dst, + AccessFlags::F_OK | AccessFlags::W_OK, + AtFlags::AT_SYMLINK_NOFOLLOW, + ) { + Ok(_) => true, + Err(errno) => match errno { + Errno::EACCES => false, + _ => bail!(eyre!("Checking gcroot access, unknown error").wrap_err(errno)), + }, + }; + + debug!(?access); + + // filter gcroots by filename + + if access { + match now.duration_since(last_modified) { + Err(err) => { + warn!(?err, ?now, ?dst, "Failed to compare time!"); + } + Ok(val) if val <= args.keep_since.into() => {} + Ok(_) => { + other_paths.push(dst); + } + } + } + } + } + trace!("other_paths: {:#?}", other_paths); + + // Present the user the information about the paths to clean + use owo_colors::OwoColorize; if !other_paths.is_empty() { println!("{}", "gcroots".blue().bold()); } for path in &other_paths { println!("- {} {}", "DEL".red(), path.to_string_lossy()); } - for (profile, generations_tagged) in profiles_tagged.iter() { println!("{}", profile.to_string_lossy().blue().bold()); for (gen, tbr) in generations_tagged.iter().rev() { From 8ab6bae2f122aaea7b6673385f28d395fa251c04 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Fri, 5 Jan 2024 23:01:17 +0100 Subject: [PATCH 18/25] tweak format --- src/clean.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index e1cbc59..d706c03 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -139,9 +139,10 @@ impl NHRunnable for interface::CleanMode { use owo_colors::OwoColorize; if !other_paths.is_empty() { println!("{}", "gcroots".blue().bold()); - } - for path in &other_paths { - println!("- {} {}", "DEL".red(), path.to_string_lossy()); + for path in &other_paths { + println!("- {} {}", "DEL".red(), path.to_string_lossy()); + } + println!(); } for (profile, generations_tagged) in profiles_tagged.iter() { println!("{}", profile.to_string_lossy().blue().bold()); From fc80c10dde226fa809690855f11eae72ef5d4df9 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Sun, 7 Jan 2024 18:14:34 +0100 Subject: [PATCH 19/25] gcroots UX improvements --- src/clean.rs | 141 +++++++++++++++++++++++++++++++---------------- src/interface.rs | 2 +- 2 files changed, 95 insertions(+), 48 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index d706c03..3f13f33 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -28,22 +28,24 @@ struct Generation { path: PathBuf, } -type ToBeCleaned = bool; +type ToBeRemoved = bool; // BTreeMap to automatically sort generations by id -type GenerationsTagged = BTreeMap; +type GenerationsTagged = BTreeMap; type ProfilesTagged = HashMap; impl NHRunnable for interface::CleanMode { fn run(&self) -> Result<()> { let mut profiles = Vec::new(); - let mut other_paths: Vec = Vec::new(); + let mut gcroots_tagged: HashMap = HashMap::new(); let now = SystemTime::now(); + let mut is_profile_clean = false; // What profiles to clean depending on the call mode let uid = nix::unistd::Uid::effective(); let args = match self { interface::CleanMode::Profile(args) => { profiles.push(args.profile.clone()); + is_profile_clean = true; &args.common } interface::CleanMode::All(args) => { @@ -90,57 +92,99 @@ impl NHRunnable for interface::CleanMode { } // Query gcroots - for elem in PathBuf::from("/nix/var/nix/gcroots/auto") - .read_dir() - .wrap_err("Reading auto gcroots dir")? - { - let src = elem.wrap_err("Reading auto gcroots element")?.path(); - let dst = src.read_link().wrap_err("Reading symlink destination")?; - debug!(?src, ?dst); - - // Use .exists to not travel symlinks - if dst.exists() { - let meta = dst.metadata().wrap_err("Reading gcroot metadata")?; - let last_modified = meta.modified()?; - - let access = match faccessat( - None, - &dst, - AccessFlags::F_OK | AccessFlags::W_OK, - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - Ok(_) => true, - Err(errno) => match errno { - Errno::EACCES => false, - _ => bail!(eyre!("Checking gcroot access, unknown error").wrap_err(errno)), - }, + let filename_tests = [r".*/.direnv/.*", r".*result.*"]; + let regexes = filename_tests + .into_iter() + .map(Regex::new) + .collect::, regex::Error>>()?; + + if !is_profile_clean { + for elem in PathBuf::from("/nix/var/nix/gcroots/auto") + .read_dir() + .wrap_err("Reading auto gcroots dir")? + { + let src = elem.wrap_err("Reading auto gcroots element")?.path(); + let dst = src.read_link().wrap_err("Reading symlink destination")?; + debug!(?src, ?dst); + + if !regexes.iter().fold(false, |acc, next| { + acc || next.is_match(&dst.to_string_lossy()) + }) { + trace!(?dst, "dst doesn't match any gcroot regex, skipping"); + continue; }; - debug!(?access); - - // filter gcroots by filename - - if access { - match now.duration_since(last_modified) { - Err(err) => { - warn!(?err, ?now, ?dst, "Failed to compare time!"); - } - Ok(val) if val <= args.keep_since.into() => {} - Ok(_) => { - other_paths.push(dst); + // Use .exists to not travel symlinks + if dst.exists() { + let access = match faccessat( + None, + &dst, + AccessFlags::F_OK | AccessFlags::W_OK, + AtFlags::AT_SYMLINK_NOFOLLOW, + ) { + Ok(_) => true, + Err(errno) => match errno { + Errno::EACCES => false, + _ => { + bail!(eyre!("Checking gcroot access, unknown error").wrap_err(errno)) + } + }, + }; + + debug!(?access); + + let dur = now.duration_since( + dst.symlink_metadata() + .wrap_err("Reading gcroot metadata")? + .modified()?, + ); + trace!(?dur, ?dst); + if access { + match dur { + Err(err) => { + warn!(?err, ?now, ?dst, "Failed to compare time!"); + } + Ok(val) if val <= args.keep_since.into() => { + trace!(?dst, "gcroot old"); + gcroots_tagged.insert(dst, false); + } + Ok(_) => { + trace!(?dst, "gcroot new"); + gcroots_tagged.insert(dst, true); + } } } } } } - trace!("other_paths: {:#?}", other_paths); // Present the user the information about the paths to clean use owo_colors::OwoColorize; - if !other_paths.is_empty() { - println!("{}", "gcroots".blue().bold()); - for path in &other_paths { - println!("- {} {}", "DEL".red(), path.to_string_lossy()); + println!(); + println!("{}", "Welcome to nh clean".bold()); + println!("Keeping {} generation(s)", args.keep.green()); + println!("Keeping paths newer than {}", args.keep_since.green()); + println!(); + println!("legend:"); + println!("{}: path to be kept", "OK".green()); + println!("{}: path to be removed", "DEL".red()); + println!(); + if !gcroots_tagged.is_empty() { + println!( + "{}", + "gcroots (matching the following regex patterns)" + .blue() + .bold() + ); + for re in regexes { + println!("- {} {}", "RE".purple(), re); + } + for (path, tbr) in &gcroots_tagged { + if *tbr { + println!("- {} {}", "DEL".red(), path.to_string_lossy()); + } else { + println!("- {} {}", "OK ".green(), path.to_string_lossy()); + } } println!(); } @@ -165,8 +209,10 @@ impl NHRunnable for interface::CleanMode { } } - for path in &other_paths { - remove_path_nofail(path); + for (path, tbr) in &gcroots_tagged { + if *tbr { + remove_path_nofail(path); + } } for (_, generations_tagged) in profiles_tagged.iter() { @@ -248,7 +294,8 @@ fn cleanable_generations( if let Some(caps) = captures { if let Some(number) = caps.get(1) { - let last_modified = std::fs::symlink_metadata(&path) + let last_modified = path + .symlink_metadata() .context("Checking symlink metadata")? .modified() .context("Reading modified time")?; diff --git a/src/interface.rs b/src/interface.rs index 1206b79..201a4bc 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -188,7 +188,7 @@ pub struct CleanArgs { /// At least keep this number of generations pub keep: u32, - #[arg(long, short = 'K', default_value = "0s")] + #[arg(long, short = 'K', default_value = "0h")] /// At least keep gcroots and generations in this time range since now. pub keep_since: humantime::Duration, From 8b147fa444205caf616a691999293d5ae4fc2c78 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Sun, 7 Jan 2024 21:13:16 +0100 Subject: [PATCH 20/25] cleanup logging --- src/clean.rs | 68 +++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index 3f13f33..306b7a9 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -15,7 +15,7 @@ use nix::{ }; use regex::Regex; use std::os::unix::fs::MetadataExt; -use tracing::{debug, info, instrument, trace, warn}; +use tracing::{debug, info, instrument, span, trace, warn, Level}; use uzers::os::unix::UserExt; // Nix impl: @@ -105,55 +105,51 @@ impl NHRunnable for interface::CleanMode { { let src = elem.wrap_err("Reading auto gcroots element")?.path(); let dst = src.read_link().wrap_err("Reading symlink destination")?; - debug!(?src, ?dst); + let span = span!(Level::TRACE, "gcroot detection", ?dst); + let _entered = span.enter(); + debug!(?src); if !regexes.iter().fold(false, |acc, next| { acc || next.is_match(&dst.to_string_lossy()) }) { - trace!(?dst, "dst doesn't match any gcroot regex, skipping"); + debug!("dst doesn't match any gcroot regex, skipping"); continue; }; // Use .exists to not travel symlinks - if dst.exists() { - let access = match faccessat( - None, - &dst, - AccessFlags::F_OK | AccessFlags::W_OK, - AtFlags::AT_SYMLINK_NOFOLLOW, - ) { - Ok(_) => true, - Err(errno) => match errno { - Errno::EACCES => false, - _ => { - bail!(eyre!("Checking gcroot access, unknown error").wrap_err(errno)) - } - }, - }; - - debug!(?access); - + if match faccessat( + None, + &dst, + AccessFlags::F_OK | AccessFlags::W_OK, + AtFlags::AT_SYMLINK_NOFOLLOW, + ) { + Ok(_) => true, + Err(errno) => match errno { + Errno::EACCES | Errno::ENOENT => false, + _ => { + bail!(eyre!("Checking access for gcroot {:?}, unknown error", dst).wrap_err(errno)) + } + }, + } { let dur = now.duration_since( dst.symlink_metadata() .wrap_err("Reading gcroot metadata")? .modified()?, ); - trace!(?dur, ?dst); - if access { - match dur { - Err(err) => { - warn!(?err, ?now, ?dst, "Failed to compare time!"); - } - Ok(val) if val <= args.keep_since.into() => { - trace!(?dst, "gcroot old"); - gcroots_tagged.insert(dst, false); - } - Ok(_) => { - trace!(?dst, "gcroot new"); - gcroots_tagged.insert(dst, true); - } + debug!(?dur); + match dur { + Err(err) => { + warn!(?err, ?now, "Failed to compare time!"); + } + Ok(val) if val <= args.keep_since.into() => { + gcroots_tagged.insert(dst, false); + } + Ok(_) => { + gcroots_tagged.insert(dst, true); } } + } else { + debug!("dst doesn't exist or is not writable, skipping"); } } } @@ -228,7 +224,7 @@ impl NHRunnable for interface::CleanMode { } } -#[instrument(ret, level = "trace")] +#[instrument(ret, level = "debug")] fn profiles_in_dir + fmt::Debug>(dir: P) -> Vec { let mut res = Vec::new(); let dir = dir.as_ref(); From 4a4b1f159efcb87db92fa318281dea332ab74b5c Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Sun, 7 Jan 2024 21:16:24 +0100 Subject: [PATCH 21/25] completions logging --- src/completion.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 679559c..e85d89e 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,11 +1,11 @@ use crate::*; use clap_complete::generate; use color_eyre::Result; -use tracing::trace; +use tracing::{instrument, trace}; impl NHRunnable for interface::CompletionArgs { + #[instrument(ret, level = "trace")] fn run(&self) -> Result<()> { - trace!("{:?}", self); let mut cmd = ::command(); generate(self.shell, &mut cmd, "nh", &mut std::io::stdout()); Ok(()) From 2d319dd6710aa974fd21757fbcdb5ed5eefd1b68 Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Mon, 8 Jan 2024 14:47:40 +0100 Subject: [PATCH 22/25] store clean --- src/clean.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index 306b7a9..f038dd9 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -127,7 +127,8 @@ impl NHRunnable for interface::CleanMode { Err(errno) => match errno { Errno::EACCES | Errno::ENOENT => false, _ => { - bail!(eyre!("Checking access for gcroot {:?}, unknown error", dst).wrap_err(errno)) + bail!(eyre!("Checking access for gcroot {:?}, unknown error", dst) + .wrap_err(errno)) } }, } { @@ -197,14 +198,14 @@ impl NHRunnable for interface::CleanMode { } // Clean the paths - if !args.dry { - if args.ask { - info!("Confirm the cleanup plan?"); - if !dialoguer::Confirm::new().default(false).interact()? { - return Ok(()); - } + if args.ask { + info!("Confirm the cleanup plan?"); + if !dialoguer::Confirm::new().default(false).interact()? { + return Ok(()); } + } + if !args.dry { for (path, tbr) in &gcroots_tagged { if *tbr { remove_path_nofail(path); @@ -220,6 +221,14 @@ impl NHRunnable for interface::CleanMode { } } + crate::commands::CommandBuilder::default() + .args(&["nix", "store", "gc"]) + .dry(args.dry) + .message("Running nix store gc") + .capture(false) + .build()? + .exec()?; + Ok(()) } } From a403423b7db3f164538d85b6bd4b4163285494ae Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Wed, 10 Jan 2024 20:48:03 +0100 Subject: [PATCH 23/25] tweak command runners --- src/clean.rs | 5 +- src/commands.rs | 140 ++++++++++++++++++++++-------------------------- src/home.rs | 13 +++-- src/logging.rs | 17 ++++-- src/main.rs | 1 + src/nixos.rs | 2 - 6 files changed, 88 insertions(+), 90 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index f038dd9..e8d3156 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -221,11 +221,10 @@ impl NHRunnable for interface::CleanMode { } } - crate::commands::CommandBuilder::default() + commands::CommandBuilder::default() .args(&["nix", "store", "gc"]) .dry(args.dry) - .message("Running nix store gc") - .capture(false) + .message("Performing garbage collection on the nix store") .build()? .exec()?; diff --git a/src/commands.rs b/src/commands.rs index 97df76d..f0d928d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -9,83 +9,90 @@ use thiserror::Error; use subprocess::{Exec, ExitStatus, PopenError, Redirection}; use tracing::{debug, info}; -#[derive(Debug, derive_builder::Builder, Default)] -#[builder(derive(Debug), setter(into), default)] +#[derive(Debug, derive_builder::Builder)] +#[builder(derive(Debug), setter(into))] pub struct Command { /// Whether to actually run the command or just log it + #[builder(default = "false")] dry: bool, /// Human-readable message regarding what the command does - #[builder(setter(strip_option))] + #[builder(setter(strip_option), default = "None")] message: Option, - /// Whether to capture the stdout or let it inherit the parent - capture: bool, /// Arguments 0..N - #[builder(setter(custom), default = "vec![]")] + #[builder(setter(custom))] args: Vec, } impl CommandBuilder { - pub fn args(&mut self, input: &[impl AsRef]) -> &mut Self { - if let Some(args) = &mut self.args { - args.extend(input.iter().map(|elem| elem.as_ref().to_owned())); - self - } else { - self.args = Some(Vec::new()); - self.args(input) - } + pub fn args(&mut self, input: I) -> &mut Self + where + S: AsRef, + I: IntoIterator, + { + self.args + .get_or_insert_with(Default::default) + .extend(input.into_iter().map(|s| s.as_ref().to_owned())); + self } } impl Command { - fn exec_inner(&self) -> Result, PopenError> { + pub fn exec(&self) -> Result<()> { let [head, tail @ ..] = &*self.args else { - panic!("Args was length 0"); + bail!("Args was length 0"); }; - let cmd = if self.capture { - Exec::cmd(head) - .args(tail) - .stderr(Redirection::None) - .stdout(Redirection::Pipe) - } else { - Exec::cmd(head) - .args(tail) - .stderr(Redirection::None) - .stdout(Redirection::None) - }; + let cmd = Exec::cmd(head) + .args(tail) + .stderr(Redirection::None) + .stdout(Redirection::None); + if let Some(m) = &self.message { info!("{}", m); } - debug!("{:?}", cmd); + debug!(?cmd); - let result = if self.capture { - Some(cmd.capture()?.stdout_str()) - } else { - cmd.join()?; - None - }; + if !self.dry { + if let Some(m) = &self.message { + cmd.join().wrap_err(m.clone())?; + } else { + cmd.join()?; + } + } - Ok(result) + + Ok(()) } - pub fn exec(self) -> Result> { - let result = self.exec_inner(); + pub fn exec_capture(&self) -> Result> { + let [head, tail @ ..] = &*self.args else { + bail!("Args was length 0"); + }; - if let Some(m) = self.message { - Ok(result.context(m)?) + let cmd = Exec::cmd(head) + .args(tail) + .stderr(Redirection::None) + .stdout(Redirection::Pipe); + + if let Some(m) = &self.message { + info!("{}", m); + } + debug!(?cmd); + + if !self.dry { + Ok(Some(cmd.capture()?.stdout_str())) } else { - Ok(result?) + Ok(None) } } } -#[derive(Debug, Default, derive_builder::Builder)] -#[builder(setter(into), default)] +#[derive(Debug, derive_builder::Builder)] +#[builder(setter(into))] pub struct BuildCommand { /// Human-readable message regarding what the command does - #[builder(setter(strip_option))] - message: Option, + message: String, // Flakeref to build flakeref: String, // Extra arguments passed to nix build @@ -96,22 +103,21 @@ pub struct BuildCommand { } impl BuildCommandBuilder { - pub fn extra_args(&mut self, input: &[impl AsRef]) -> &mut Self { - if let Some(args) = &mut self.extra_args { - args.extend(input.iter().map(|elem| elem.as_ref().to_owned())); - self - } else { - self.extra_args = Some(Vec::new()); - self.extra_args(input) - } + pub fn extra_args(&mut self, input: I) -> &mut Self + where + S: AsRef, + I: IntoIterator, + { + self.extra_args + .get_or_insert_with(Default::default) + .extend(input.into_iter().map(|s| s.as_ref().to_owned())); + self } } impl BuildCommand { - fn exec_inner(&self) -> Result<()> { - if let Some(m) = &self.message { - info!("{}", m); - } + pub fn exec(&self) -> Result<()> { + info!("{}", self.message); let exit = if self.nom { let cmd = { @@ -129,7 +135,7 @@ impl BuildCommand { | Exec::cmd("nom").args(&["--json"]) } .stdout(Redirection::None); - debug!("{:?}", cmd); + debug!(?cmd); cmd.join() } else { let cmd = Exec::cmd("nix") @@ -138,33 +144,17 @@ impl BuildCommand { .stdout(Redirection::None) .stderr(Redirection::Merge); - debug!("{:?}", cmd); + debug!(?cmd); cmd.join() }; - let exit: ExitStatus = if let Some(ref m) = self.message { - exit.context(m.clone())? - } else { - exit? - }; - - match exit { + match exit.wrap_err(self.message.clone())? { ExitStatus::Exited(0) => (), other => bail!(ExitError(other)), } Ok(()) } - - pub fn exec(self) -> Result<()> { - let result = self.exec_inner(); - - if let Some(m) = self.message { - Ok(result.context(m)?) - } else { - Ok(result?) - } - } } #[derive(Debug, Error)] diff --git a/src/home.rs b/src/home.rs index 00fbea6..24fb112 100644 --- a/src/home.rs +++ b/src/home.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use color_eyre::eyre::bail; use color_eyre::Result; use thiserror::Error; -use tracing::{debug, info, trace}; +use tracing::{debug, info, instrument, trace}; use crate::*; use crate::{ @@ -68,7 +68,7 @@ impl HomeRebuildArgs { commands::BuildCommandBuilder::default() .flakeref(&flakeref) - .extra_args(&["--out-link", out_link_str]) + .extra_args(["--out-link", out_link_str]) .extra_args(&self.extra_args) .message("Building home configuration") .nom(self.common.nom) @@ -153,19 +153,18 @@ fn get_home_output + std::fmt::Display>( } } +#[instrument(ret, err, level = "debug")] fn configuration_exists(flakeref: &FlakeRef, configuration: &str) -> Result { let output = format!("{}#homeConfigurations", flakeref.deref()); let filter = format!(r#" x: x ? "{}" "#, configuration); let result = commands::CommandBuilder::default() .args(&["nix", "eval", &output, "--apply", &filter]) - .capture(true) - .build() - .unwrap() - .exec()? + .build()? + .exec_capture()? .unwrap(); - trace!("{:?}", result); + debug!(?result); match result.as_str().trim() { "true" => Ok(true), diff --git a/src/logging.rs b/src/logging.rs index e6668ac..df64b4e 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -28,7 +28,8 @@ where ) -> std::fmt::Result { // Based on https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/trait.FormatEvent.html#examples // Without the unused parts - let level = event.metadata().level(); + let metadata = event.metadata(); + let level = metadata.level(); if *level == Level::ERROR { write!(writer, "{} ", "!".red())?; @@ -39,6 +40,13 @@ where } ctx.field_format().format_fields(writer.by_ref(), event)?; + + if *level != Level::INFO { + if let (Some(file), Some(line)) = (metadata.file(), metadata.line()) { + write!(writer, " @ {}:{}", file, line)?; + } + } + writeln!(writer)?; Ok(()) } @@ -46,7 +54,7 @@ where pub(crate) fn setup_logging(verbose: bool) -> Result<()> { color_eyre::config::HookBuilder::default() - .display_location_section(false) + .display_location_section(true) .panic_section("Please report the bug at https://github.com/viperML/nh/issues") .display_env_section(false) .install()?; @@ -65,7 +73,10 @@ pub(crate) fn setup_logging(verbose: bool) -> Result<()> { .with_target(false) .with_level(false) .event_format(InfoFormatter) - .with_filter(filter_fn(|meta| *meta.level() <= Level::INFO)); + .with_filter(filter_fn(|meta| { + let level = *meta.level(); + (level == Level::INFO) || (level == Level::WARN) + })); tracing_subscriber::registry() .with(layer_debug) diff --git a/src/main.rs b/src/main.rs index 76dfa5b..c2394cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ const NH_VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() -> Result<()> { let args = ::parse(); crate::logging::setup_logging(args.verbose)?; + tracing::debug!(?args); args.command.run() } diff --git a/src/nixos.rs b/src/nixos.rs index 1b38518..a8db730 100644 --- a/src/nixos.rs +++ b/src/nixos.rs @@ -17,8 +17,6 @@ const SPEC_LOCATION: &str = "/etc/specialisation"; impl NHRunnable for interface::OsArgs { fn run(&self) -> Result<()> { - trace!("{:?}", self); - match &self.action { Switch(args) | Boot(args) | Test(args) => args.rebuild(&self.action), s => bail!("Subcommand {:?} not yet implemented", s), From bde5048c05a09a1299c0fa9962f9b7706b32eeee Mon Sep 17 00:00:00 2001 From: Fernando Ayats Date: Wed, 10 Jan 2024 20:48:48 +0100 Subject: [PATCH 24/25] clippy --- src/clean.rs | 12 ++++++------ src/commands.rs | 4 +--- src/completion.rs | 2 +- src/home.rs | 10 +++++----- src/nixos.rs | 14 +++++++------- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/clean.rs b/src/clean.rs index e8d3156..81bdb9a 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,7 +1,6 @@ use std::{ collections::{BTreeMap, HashMap}, fmt, - os::unix::fs::PermissionsExt, path::{Path, PathBuf}, time::SystemTime, }; @@ -15,7 +14,7 @@ use nix::{ }; use regex::Regex; use std::os::unix::fs::MetadataExt; -use tracing::{debug, info, instrument, span, trace, warn, Level}; +use tracing::{debug, info, instrument, span, warn, Level}; use uzers::os::unix::UserExt; // Nix impl: @@ -109,9 +108,10 @@ impl NHRunnable for interface::CleanMode { let _entered = span.enter(); debug!(?src); - if !regexes.iter().fold(false, |acc, next| { - acc || next.is_match(&dst.to_string_lossy()) - }) { + if !regexes + .iter() + .any(|next| next.is_match(&dst.to_string_lossy())) + { debug!("dst doesn't match any gcroot regex, skipping"); continue; }; @@ -222,7 +222,7 @@ impl NHRunnable for interface::CleanMode { } commands::CommandBuilder::default() - .args(&["nix", "store", "gc"]) + .args(["nix", "store", "gc"]) .dry(args.dry) .message("Performing garbage collection on the nix store") .build()? diff --git a/src/commands.rs b/src/commands.rs index f0d928d..176a1c3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,7 +6,7 @@ use color_eyre::{ use std::ffi::{OsStr, OsString}; use thiserror::Error; -use subprocess::{Exec, ExitStatus, PopenError, Redirection}; +use subprocess::{Exec, ExitStatus, Redirection}; use tracing::{debug, info}; #[derive(Debug, derive_builder::Builder)] @@ -47,7 +47,6 @@ impl Command { .stderr(Redirection::None) .stdout(Redirection::None); - if let Some(m) = &self.message { info!("{}", m); } @@ -61,7 +60,6 @@ impl Command { } } - Ok(()) } diff --git a/src/completion.rs b/src/completion.rs index e85d89e..4e6a883 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,7 +1,7 @@ use crate::*; use clap_complete::generate; use color_eyre::Result; -use tracing::{instrument, trace}; +use tracing::instrument; impl NHRunnable for interface::CompletionArgs { #[instrument(ret, level = "trace")] diff --git a/src/home.rs b/src/home.rs index 24fb112..8c14c1c 100644 --- a/src/home.rs +++ b/src/home.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use color_eyre::eyre::bail; use color_eyre::Result; use thiserror::Error; -use tracing::{debug, info, instrument, trace}; +use tracing::{debug, info, instrument}; use crate::*; use crate::{ @@ -60,7 +60,7 @@ impl HomeRebuildArgs { if self.common.update { commands::CommandBuilder::default() - .args(&["nix", "flake", "update", &self.common.flakeref]) + .args(["nix", "flake", "update", &self.common.flakeref]) .message("Updating flake") .build()? .exec()?; @@ -91,7 +91,7 @@ impl HomeRebuildArgs { // just do nothing for None case (fresh installs) if let Some(prev_gen) = prev_generation { commands::CommandBuilder::default() - .args(&["nvd", "diff", (prev_gen.to_str().unwrap()), out_link_str]) + .args(["nvd", "diff", (prev_gen.to_str().unwrap()), out_link_str]) .message("Comparing changes") .build()? .exec()?; @@ -111,7 +111,7 @@ impl HomeRebuildArgs { } commands::CommandBuilder::default() - .args(&[&format!("{}/activate", out_link_str)]) + .args([&format!("{}/activate", out_link_str)]) .message("Activating configuration") .build()? .exec()?; @@ -159,7 +159,7 @@ fn configuration_exists(flakeref: &FlakeRef, configuration: &str) -> Result Date: Wed, 10 Jan 2024 20:51:58 +0100 Subject: [PATCH 25/25] tweak format --- src/search.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/search.rs b/src/search.rs index 0dac7b6..811d699 100644 --- a/src/search.rs +++ b/src/search.rs @@ -31,12 +31,6 @@ struct SearchResult { package_position: Option, } -#[derive(Debug, Deserialize)] -struct License { - url: String, - fullName: String, -} - impl NHRunnable for SearchArgs { fn run(&self) -> Result<()> { trace!("args: {self:?}"); @@ -139,11 +133,11 @@ impl NHRunnable for SearchArgs { if self.long { for url in elem.package_homepage.iter() { - println!(" Homepage: {}", url); + println!(" Homepage: {}", url); } if !elem.package_license_set.is_empty() { - println!(" License: {}", elem.package_license_set.join(", ")); + println!(" License: {}", elem.package_license_set.join(", ")); } } }