From a3fd89f1bbb03bb580fd47f1ee7daa9018bba7cb Mon Sep 17 00:00:00 2001 From: Malo Bourgon Date: Mon, 9 Feb 2026 19:36:07 -0800 Subject: [PATCH] modules/homebrew: add `link: :overwrite` support Homebrew supports `link: :overwrite` which runs `brew link --overwrite`, force-overwriting existing symlinks. Extract the existing `restart_service` special-case logic into a reusable helper (`mkBrewfileLineBoolOrSymbolString`) for options that can be either a bool or a Ruby symbol in the Brewfile. --- modules/homebrew.nix | 34 ++++++++++++++++++++-------------- tests/homebrew.nix | 7 ++++--- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/modules/homebrew.nix b/modules/homebrew.nix index eba43c6..61fb5c8 100644 --- a/modules/homebrew.nix +++ b/modules/homebrew.nix @@ -28,6 +28,15 @@ let mkBrewfileLineOptionsListString = attrs: concatStringsSep ", " (mapAttrsToList (n: v: "${n}: ${v}") attrs); + # Renders a Brewfile option that can be either a bool or a Ruby symbol (e.g. `:overwrite`). + mkBrewfileLineBoolOrSymbolString = name: config: sCfg: + optionalString (hasAttr name sCfg) ( + ", ${name}: " + ( + if isBool config.${name} then sCfg.${name} + else ":${config.${name}}" + ) + ); + # Option and submodule helper functions ---------------------------------------------------------- @@ -456,12 +465,15 @@ let Homebrew's default is `false`. ''; }; - link = mkNullOrBoolOption { + link = mkOption { + type = with types; nullOr (either bool (enum [ "overwrite" ])); + default = null; description = '' - Whether to link the formula to the Homebrew prefix. When this option is - `null`, Homebrew will use it's default behavior which is to link the - formula if it's currently unlinked and not keg-only, and to unlink the formula if it's - currently linked and keg-only. + Whether to link the formula to the Homebrew prefix. When set to `"overwrite"`, + existing symlinks will be overwritten ({command}`brew link --overwrite`). When this + option is `null`, Homebrew will use its default behavior which is to link the formula + if it's currently unlinked and not keg-only, and to unlink the formula if it's currently + linked and keg-only. ''; }; @@ -471,20 +483,14 @@ let config = let sCfg = mkProcessedSubmodConfig config; - sCfgSubset = removeAttrs sCfg [ "name" "restart_service" ]; + sCfgSubset = removeAttrs sCfg [ "name" "restart_service" "link" ]; in { brewfileLine = "brew ${sCfg.name}" + optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}" - # We need to handle the `restart_service` option seperately since it can be either a bool - # or `:changed` in the Brewfile. - + optionalString (sCfg ? restart_service) ( - ", restart_service: " + ( - if isBool config.restart_service then sCfg.restart_service - else ":${config.restart_service}" - ) - ); + + mkBrewfileLineBoolOrSymbolString "link" config sCfg + + mkBrewfileLineBoolOrSymbolString "restart_service" config sCfg; }; }; diff --git a/tests/homebrew.nix b/tests/homebrew.nix index 64e52c4..7815158 100644 --- a/tests/homebrew.nix +++ b/tests/homebrew.nix @@ -17,7 +17,7 @@ in homebrew.user = "test-homebrew-user"; - # Examples taken from https://github.com/Homebrew/homebrew-bundle + # Examples adapted from https://docs.brew.sh/Brew-Bundle-and-Brewfile homebrew.taps = [ "homebrew/cask" { @@ -41,7 +41,8 @@ in { name = "denji/nginx/nginx-full"; args = [ "with-rmtp" ]; - restart_service = "changed"; + link = "overwrite"; + restart_service = "always"; } { name = "mysql@5.6"; @@ -89,7 +90,7 @@ in echo "checking brew entries in Brewfile" >&2 ${mkTest "imagemagick" ''brew "imagemagick"''} - ${mkTest "denji/nginx/nginx-full" ''brew "denji/nginx/nginx-full", args: ["with-rmtp"], restart_service: :changed''} + ${mkTest "denji/nginx/nginx-full" ''brew "denji/nginx/nginx-full", args: ["with-rmtp"], link: :overwrite, restart_service: :always''} ${mkTest "mysql@5.6" ''brew "mysql@5.6", conflicts_with: ["mysql"], link: true, restart_service: true''} echo "checking cask entries in Brewfile" >&2