diff --git a/CHANGELOG.md b/CHANGELOG.md index e08542aa..c7d318ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ functionality, under the "Removed" section. ## Unreleased +### Added + +- `nh darwin` now displays Homebrew package changes alongside Nix package + changes when using nix-darwin configurations with Homebrew management enabled. + This shows formulae, casks, taps, and Mac App Store apps that will be + added or removed during a configuration switch. + ### Changed - Nh checks are now more robust in the sense that unnecessary features will not diff --git a/Cargo.lock b/Cargo.lock index 86f8a042..f8136b99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,6 +163,19 @@ version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +[[package]] +name = "brewdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877eb05cf54889e3ac31b427ffe8d95f6c109cd7e7c5abc0004896dd168937ae" +dependencies = [ + "owo-colors", + "regex", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -1222,6 +1235,7 @@ name = "nh" version = "4.2.0-beta5" dependencies = [ "anstyle", + "brewdiff", "chrono", "clap", "clap-verbosity-flag", diff --git a/Cargo.toml b/Cargo.toml index edcde569..ba2b2748 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ tracing-subscriber = { features = [ "env-filter", "registry", "std" ], version = which = "8.0.0" [target.'cfg(target_os="macos")'.dependencies] +brewdiff = "0.2.0" system-configuration = "0.6.1" [dev-dependencies] diff --git a/src/darwin.rs b/src/darwin.rs index dbf00352..6f55c274 100644 --- a/src/darwin.rs +++ b/src/darwin.rs @@ -17,7 +17,7 @@ use crate::{ }, nixos::toplevel_for, update::update, - util::{get_hostname, print_dix_diff}, + util::{get_hostname, print_dix_diff, print_homebrew_diff}, }; const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system"; @@ -142,6 +142,8 @@ impl DarwinRebuildArgs { target_profile.display() ); let _ = print_dix_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile); + let _ = + print_homebrew_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile); } if self.common.ask && !self.common.dry && !matches!(variant, Build) { diff --git a/src/util.rs b/src/util.rs index 1652a598..0b73e225 100644 --- a/src/util.rs +++ b/src/util.rs @@ -30,6 +30,7 @@ impl fmt::Write for WriteFmt { self.0.write_all(string.as_bytes()).map_err(|_| fmt::Error) } } + /// Get the Nix variant (cached) pub fn get_nix_variant() -> &'static NixVariant { NIX_VARIANT.get_or_init(|| { @@ -336,3 +337,85 @@ pub fn print_dix_diff( } Ok(()) } + +/// Prints the difference between Homebrew packages in darwin generations. +/// +/// # Arguments +/// +/// * `old_generation` - A reference to the path of the old/current generation. +/// * `new_generation` - A reference to the path of the new generation. +/// +/// # Returns +/// +/// Returns `Ok(())` if the operation completed successfully, or an error +/// wrapped in `eyre::Result` if something went wrong. Silently returns Ok(()) +/// if Homebrew is not available or not configured. +#[cfg(target_os = "macos")] +pub fn print_homebrew_diff( + _old_generation: &Path, + new_generation: &Path, +) -> Result<()> { + if !homebrew_available() { + debug!("Homebrew not found, skipping Homebrew diff"); + return Ok(()); + } + + // Try to extract the nix-darwin Homebrew intent from the new profile + // If this fails, it likely means Homebrew isn't configured in the profile + let nix_intent = match brewdiff::extract_nix_darwin_intent(new_generation) { + Ok(intent) => intent, + Err(e) => { + debug!("Could not extract Homebrew intent from profile: {}", e); + return Ok(()); + }, + }; + + if !nix_intent.has_packages() { + debug!("No Homebrew packages configured in profile, skipping diff"); + return Ok(()); + } + + let mut out = WriteFmt(io::stdout()); + + let diff_handle = brewdiff::spawn_homebrew_diff(new_generation.to_path_buf()); + let diff_data = match diff_handle.join() { + Ok(Ok(data)) => data, + Ok(Err(e)) => { + debug!("Failed to compute Homebrew diff: {}", e); + return Ok(()); + }, + Err(_) => { + debug!("Homebrew diff thread panicked"); + return Ok(()); + }, + }; + + if diff_data.has_changes() { + // Separator from dix' output + println!(); + + // Print statistics first to make it clear the details below belong to + // Homebrew + brewdiff::write_homebrew_stats(&mut out, &diff_data)?; + brewdiff::display::write_diff(&mut out, &diff_data)?; + } else { + info!("No Homebrew package changes."); + } + + Ok(()) +} + +/// Checks if Homebrew is available on the system +#[cfg(target_os = "macos")] +fn homebrew_available() -> bool { + which::which("brew").is_ok() +} + +/// Stub for non-macOS platforms +#[cfg(not(target_os = "macos"))] +pub fn print_homebrew_diff( + _old_generation: &Path, + _new_generation: &Path, +) -> Result<()> { + Ok(()) +}