Skip to content

Commit cacd6de

Browse files
committed
darwin: show homebrew diff if available
1 parent 8d69029 commit cacd6de

File tree

5 files changed

+107
-1
lines changed

5 files changed

+107
-1
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ functionality, under the "Removed" section.
1616

1717
## Unreleased
1818

19+
### Added
20+
21+
- `nh darwin` now displays Homebrew package changes alongside Nix package
22+
changes when using nix-darwin configurations with Homebrew management enabled.
23+
This shows formulae, casks, taps, and Mac App Store apps that will be
24+
added or removed during a configuration switch.
25+
1926
### Changed
2027

2128
- Nh checks are now more robust in the sense that unnecessary features will not

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ path = "src/lib.rs"
2727

2828
[dependencies]
2929
anstyle = "1.0.0"
30+
brewdiff = "0.2.0"
3031
chrono = "0.4.39"
3132
clap.workspace = true
3233
clap-verbosity-flag = { version = "3.0.3", features = [ "tracing" ], default-features = false }

src/darwin.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::installable::Installable;
1111
use crate::interface::{DarwinArgs, DarwinRebuildArgs, DarwinReplArgs, DarwinSubcommand, DiffType};
1212
use crate::nixos::toplevel_for;
1313
use crate::update::update;
14-
use crate::util::{get_hostname, print_dix_diff};
14+
use crate::util::{get_hostname, print_dix_diff, print_homebrew_diff};
1515

1616
const SYSTEM_PROFILE: &str = "/nix/var/nix/profiles/system";
1717
const CURRENT_PROFILE: &str = "/run/current-system";
@@ -130,6 +130,7 @@ impl DarwinRebuildArgs {
130130
target_profile.display()
131131
);
132132
let _ = print_dix_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile);
133+
let _ = print_homebrew_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile);
133134
}
134135

135136
if self.common.ask && !self.common.dry && !matches!(variant, Build) {

src/util.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ impl<W: io::Write> fmt::Write for WriteFmt<W> {
3131
self.0.write_all(string.as_bytes()).map_err(|_| fmt::Error)
3232
}
3333
}
34+
3435
/// Get the Nix variant (cached)
3536
pub fn get_nix_variant() -> &'static NixVariant {
3637
NIX_VARIANT.get_or_init(|| {
@@ -320,3 +321,85 @@ pub fn print_dix_diff(old_generation: &Path, new_generation: &Path) -> Result<()
320321
}
321322
Ok(())
322323
}
324+
325+
/// Prints the difference between Homebrew packages in darwin generations.
326+
///
327+
/// # Arguments
328+
///
329+
/// * `old_generation` - A reference to the path of the old/current generation.
330+
/// * `new_generation` - A reference to the path of the new generation.
331+
///
332+
/// # Returns
333+
///
334+
/// Returns `Ok(())` if the operation completed successfully, or an error wrapped in `eyre::Result` if something went wrong.
335+
/// Silently returns Ok(()) if Homebrew is not available or not configured.
336+
#[cfg(target_os = "macos")]
337+
pub fn print_homebrew_diff(_old_generation: &Path, new_generation: &Path) -> Result<()> {
338+
if !homebrew_available() {
339+
debug!("Homebrew not found, skipping Homebrew diff");
340+
return Ok(());
341+
}
342+
343+
// Try to extract the nix-darwin Homebrew intent from the new profile
344+
// If this fails, it likely means Homebrew isn't configured in the profile
345+
let nix_intent = match brewdiff::extract_nix_darwin_intent(new_generation) {
346+
Ok(intent) => intent,
347+
Err(e) => {
348+
debug!("Could not extract Homebrew intent from profile: {}", e);
349+
return Ok(());
350+
}
351+
};
352+
353+
if !nix_intent.has_packages() {
354+
debug!("No Homebrew packages configured in profile, skipping diff");
355+
return Ok(());
356+
}
357+
358+
let mut out = WriteFmt(io::stdout());
359+
360+
let diff_handle = brewdiff::spawn_homebrew_diff(new_generation.to_path_buf());
361+
let diff_data = match diff_handle.join() {
362+
Ok(Ok(data)) => data,
363+
Ok(Err(e)) => {
364+
debug!("Failed to compute Homebrew diff: {}", e);
365+
return Ok(());
366+
}
367+
Err(_) => {
368+
debug!("Homebrew diff thread panicked");
369+
return Ok(());
370+
}
371+
};
372+
373+
if diff_data.has_changes() {
374+
// Separator from dix' output
375+
println!();
376+
377+
// Print statistics first to make it clear the details below belong to Homebrew
378+
brewdiff::write_homebrew_stats(&mut out, &diff_data)?;
379+
brewdiff::display::write_diff(&mut out, &diff_data)?;
380+
} else {
381+
debug!("No Homebrew package changes detected");
382+
}
383+
384+
Ok(())
385+
}
386+
387+
/// Checks if Homebrew is available on the system
388+
#[cfg(target_os = "macos")]
389+
fn homebrew_available() -> bool {
390+
use std::process::Command as StdCommand;
391+
392+
StdCommand::new("which")
393+
.arg("brew")
394+
.stdout(Stdio::null())
395+
.stderr(Stdio::null())
396+
.status()
397+
.map(|s| s.success())
398+
.unwrap_or(false)
399+
}
400+
401+
/// Stub for non-macOS platforms
402+
#[cfg(not(target_os = "macos"))]
403+
pub fn print_homebrew_diff(_old_generation: &Path, _new_generation: &Path) -> Result<()> {
404+
Ok(())
405+
}

0 commit comments

Comments
 (0)