modules/homebrew: comprehensive module refresh (#1692)
This commit is contained in:
commit
6c5a56295d
7 changed files with 341 additions and 117 deletions
29
CHANGELOG
29
CHANGELOG
|
|
@ -1,3 +1,32 @@
|
|||
2026-02-10
|
||||
- Major changes to `homebrew` module
|
||||
|
||||
`homebrew.brewPrefix` was renamed to `homebrew.prefix`, and its semantics
|
||||
changed: the old option pointed to the bin directory (e.g.,
|
||||
`/opt/homebrew/bin`), while the new option points to the Homebrew prefix
|
||||
(e.g., `/opt/homebrew`), matching `brew --prefix`.
|
||||
|
||||
`homebrew.whalebrews` was removed. Whalebrew support was removed from
|
||||
Homebrew Bundle in Homebrew 4.7.0 (Nov 2025), so `whalebrew` entries in a
|
||||
Brewfile now cause `brew bundle` to fail.
|
||||
|
||||
`homebrew.global.lockfiles` and `homebrew.global.noLock` no longer have any
|
||||
effect. Homebrew Bundle removed lockfile support in Homebrew 4.4.0 (Oct 2024).
|
||||
|
||||
`homebrew.onActivation.cleanup` now supports a `"check"` mode, which checks
|
||||
for unlisted packages and aborts activation if any are found, without
|
||||
removing them.
|
||||
|
||||
Shell integration options were added: `homebrew.enableBashIntegration`,
|
||||
`homebrew.enableFishIntegration`, and `homebrew.enableZshIntegration`.
|
||||
|
||||
New Brewfile entry types were added: `homebrew.goPackages`,
|
||||
`homebrew.cargoPackages`, and `homebrew.vscode`.
|
||||
|
||||
New options were added for brews: `postinstall`, `link = "overwrite"`, and
|
||||
`restart_service = "always"`. The `postinstall` option was also added for
|
||||
casks.
|
||||
|
||||
2025-01-30
|
||||
- Previously, some nix-darwin options applied to the user running
|
||||
`darwin-rebuild`. As part of a long‐term migration to make
|
||||
|
|
|
|||
|
|
@ -28,9 +28,23 @@ 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 ----------------------------------------------------------
|
||||
|
||||
mkShellIntegrationOption = shell: mkEnableOption ''
|
||||
Homebrew ${shell} shell integration, which sets up Homebrew's environment
|
||||
and shell completions
|
||||
'';
|
||||
|
||||
mkNullOrBoolOption = args: mkOption (args // {
|
||||
type = types.nullOr types.bool;
|
||||
default = null;
|
||||
|
|
@ -62,29 +76,36 @@ let
|
|||
onActivationOptions = { config, ... }: {
|
||||
options = {
|
||||
cleanup = mkOption {
|
||||
type = types.enum [ "none" "uninstall" "zap" ];
|
||||
type = types.enum [ "none" "check" "uninstall" "zap" ];
|
||||
default = "none";
|
||||
example = "uninstall";
|
||||
description = ''
|
||||
This option manages what happens to formulae installed by Homebrew, that aren't present in
|
||||
This option manages what happens to packages installed by Homebrew that aren't present in
|
||||
the Brewfile generated by this module, during {command}`nix-darwin` system
|
||||
activation.
|
||||
|
||||
When set to `"none"` (the default), formulae not present in the generated
|
||||
When set to `"none"` (the default), packages not present in the generated
|
||||
Brewfile are left installed.
|
||||
|
||||
When set to `"check"`, {command}`nix-darwin` verifies during system activation that no
|
||||
Homebrew packages (taps, formulae, casks, etc.) are installed that aren't present in the
|
||||
generated Brewfile. If extra packages are found, activation fails with a list of them.
|
||||
Note that when this check fails during {command}`darwin-rebuild switch`, the entire
|
||||
system activation is aborted and no other configuration changes will be applied until
|
||||
the issue is resolved.
|
||||
|
||||
When set to `"uninstall"`, {command}`nix-darwin` invokes
|
||||
{command}`brew bundle [install]` with the {command}`--cleanup` flag. This
|
||||
uninstalls all formulae not listed in generated Brewfile, i.e.,
|
||||
{command}`brew uninstall` is run for those formulae.
|
||||
uninstalls all packages not listed in the generated Brewfile, i.e.,
|
||||
{command}`brew uninstall` is run for those packages.
|
||||
|
||||
When set to `"zap"`, {command}`nix-darwin` invokes
|
||||
{command}`brew bundle [install]` with the {command}`--cleanup --zap`
|
||||
flags. This uninstalls all formulae not listed in the generated Brewfile, and if the
|
||||
formula is a cask, removes all files associated with that cask. In other words,
|
||||
{command}`brew uninstall --zap` is run for all those formulae.
|
||||
flags. This uninstalls all packages not listed in the generated Brewfile, and if the
|
||||
package is a cask, removes all files associated with that cask. In other words,
|
||||
{command}`brew uninstall --zap` is run for all those packages.
|
||||
|
||||
If you plan on exclusively using {command}`nix-darwin` to manage formulae
|
||||
If you plan on exclusively using {command}`nix-darwin` to manage packages
|
||||
installed by Homebrew, you probably want to set this option to
|
||||
`"uninstall"` or `"zap"`.
|
||||
'';
|
||||
|
|
@ -97,7 +118,7 @@ let
|
|||
{command}`nix-darwin` system activation. The default is `false`
|
||||
so that repeated invocations of {command}`darwin-rebuild switch` are idempotent.
|
||||
|
||||
Note that Homebrew auto-updates when it's been more then 5 minutes since it last updated.
|
||||
Note that Homebrew auto-updates when it's been more than 5 minutes since it last updated.
|
||||
|
||||
Although auto-updating is disabled by default during system activation, note that Homebrew
|
||||
will auto-update when you manually invoke certain Homebrew commands. To modify this
|
||||
|
|
@ -155,14 +176,6 @@ let
|
|||
Whether to enable Homebrew to automatically use the Brewfile that this module generates in
|
||||
the Nix store, when you manually invoke {command}`brew bundle`.
|
||||
|
||||
Enabling this option will change the default value of
|
||||
[](#opt-homebrew.global.lockfiles) to `false` since, with
|
||||
this option enabled, {command}`brew bundle [install]` will default to using the
|
||||
Brewfile that this module generates in the Nix store, unless you explicitly point it at
|
||||
another Brewfile using the `--file` flag. As a result, it will try to
|
||||
write the lockfile in the Nix store, and complain that it can't (though the command will
|
||||
run successfully regardless).
|
||||
|
||||
Implementation note: when enabled, this option sets the
|
||||
`HOMEBREW_BUNDLE_FILE` environment variable to the path of the Brewfile
|
||||
that this module generates in the Nix store, by adding it to
|
||||
|
|
@ -178,7 +191,7 @@ let
|
|||
{command}`brew tap`, and {command}`brew bundle [install]`.
|
||||
|
||||
Note that Homebrew auto-updates when you manually invoke commands like the ones mentioned
|
||||
above if it's been more then 5 minutes since it last updated.
|
||||
above if it's been more than 5 minutes since it last updated.
|
||||
|
||||
You may want to consider disabling this option if you have
|
||||
[](#opt-homebrew.onActivation.upgrade) enabled, and
|
||||
|
|
@ -191,31 +204,13 @@ let
|
|||
[](#opt-environment.variables).
|
||||
'';
|
||||
};
|
||||
lockfiles = mkOption {
|
||||
type = types.bool;
|
||||
default = !config.brewfile;
|
||||
defaultText = literalExpression "!config.homebrew.global.brewfile";
|
||||
description = ''
|
||||
Whether to enable Homebrew to generate lockfiles when you manually invoke
|
||||
{command}`brew bundle [install]`.
|
||||
|
||||
This option will default to `false` if
|
||||
[](#opt-homebrew.global.brewfile) is enabled since, with that option enabled,
|
||||
{command}`brew bundle [install]` will default to using the Brewfile that this
|
||||
module generates in the Nix store, unless you explicitly point it at another Brewfile
|
||||
using the `--file` flag. As a result, it will try to write the
|
||||
lockfile in the Nix store, and complain that it can't (though the command will run
|
||||
successfully regardless).
|
||||
|
||||
Implementation note: when disabled, this option sets the
|
||||
`HOMEBREW_BUNDLE_NO_LOCK` environment variable, by adding it to
|
||||
[](#opt-environment.variables).
|
||||
'';
|
||||
};
|
||||
|
||||
# The `noLock` option was replaced by `lockfiles`. Due to `homebrew.global` being a submodule,
|
||||
# we can't use `mkRemovedOptionModule`, so we leave this option definition here, and trigger
|
||||
# and error message with an assertion below if it's set by the user.
|
||||
# `noLock` was the original option; `lockfiles` replaced it (with inverted semantics).
|
||||
# Both are now dead: Homebrew Bundle removed lockfile support in Homebrew 4.4.0
|
||||
# (Oct 2024), so the `HOMEBREW_BUNDLE_NO_LOCK` env var and `--no-lock` CLI flag are
|
||||
# ignored. We keep both definitions with null defaults to detect explicit user
|
||||
# configuration and emit a warning below. We can't use `mkRemovedOptionModule` because
|
||||
# `homebrew.global` is a submodule.
|
||||
lockfiles = mkOption { visible = false; default = null; };
|
||||
noLock = mkOption { visible = false; default = null; };
|
||||
|
||||
homebrewEnvironmentVariables = mkInternalOption { type = types.attrs; };
|
||||
|
|
@ -225,7 +220,6 @@ let
|
|||
homebrewEnvironmentVariables = {
|
||||
HOMEBREW_BUNDLE_FILE = mkIf config.brewfile "${brewfileFile}";
|
||||
HOMEBREW_NO_AUTO_UPDATE = mkIf (!config.autoUpdate) "1";
|
||||
HOMEBREW_BUNDLE_NO_LOCK = mkIf (!config.lockfiles) "1";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -253,6 +247,10 @@ let
|
|||
description = ''
|
||||
Whether to auto-update the tap even if it is not hosted on GitHub. By default, only taps
|
||||
hosted on GitHub are auto-updated (for performance reasons).
|
||||
|
||||
Note: Homebrew Bundle accepts this option in Brewfile syntax but may silently ignore it
|
||||
during installation. See [the Homebrew Bundle source](https://github.com/Homebrew/brew/tree/master/Library/Homebrew/bundle)
|
||||
for current behavior.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -385,19 +383,34 @@ let
|
|||
};
|
||||
require_sha = mkNullOrBoolOption {
|
||||
description = ''
|
||||
Whether to require cask(s) to have a checksum.
|
||||
Whether to require casks to have a checksum.
|
||||
|
||||
Homebrew's default is `false`.
|
||||
'';
|
||||
};
|
||||
no_quarantine = mkNullOrBoolOption {
|
||||
description = "Whether to disable quarantining of downloads.";
|
||||
description = ''
|
||||
Whether to disable quarantining of downloads.
|
||||
|
||||
Note: this option is deprecated in Homebrew and may be removed in a
|
||||
future release. See [Homebrew/brew#20755](https://github.com/Homebrew/brew/issues/20755).
|
||||
|
||||
Homebrew's default is `false`.
|
||||
'';
|
||||
};
|
||||
no_binaries = mkNullOrBoolOption {
|
||||
description = "Whether to disable linking of helper executables.";
|
||||
description = ''
|
||||
Whether to disable linking of helper executables.
|
||||
|
||||
Homebrew's default is `false`.
|
||||
'';
|
||||
};
|
||||
ignore_dependencies = mkNullOrBoolOption {
|
||||
description = "Ignore casks dependencies in case you manage them extrenally";
|
||||
description = ''
|
||||
Whether to ignore cask dependencies, e.g., when you manage them externally.
|
||||
|
||||
Homebrew's default is `false`.
|
||||
'';
|
||||
};
|
||||
|
||||
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
|
||||
|
|
@ -424,7 +437,7 @@ let
|
|||
type = with types; nullOr (listOf str);
|
||||
default = null;
|
||||
description = ''
|
||||
Arguments flags to pass to {command}`brew install`. Values should not include the
|
||||
Argument flags to pass to {command}`brew install`. Values should not include the
|
||||
leading `"--"`.
|
||||
'';
|
||||
};
|
||||
|
|
@ -437,12 +450,14 @@ let
|
|||
'';
|
||||
};
|
||||
restart_service = mkOption {
|
||||
type = with types; nullOr (either bool (enum [ "changed" ]));
|
||||
type = with types; nullOr (either bool (enum [ "changed" "always" ]));
|
||||
default = null;
|
||||
description = ''
|
||||
Whether to run {command}`brew services restart` for the formula and register it to
|
||||
launch at login (or boot). If set to `"changed"`, the service will only
|
||||
be restarted on version changes.
|
||||
be restarted when the formula is newly installed or upgraded. If set to
|
||||
`"always"`, the service will be restarted on every {command}`brew bundle`
|
||||
run, even if nothing changed.
|
||||
|
||||
Homebrew's default is `false`.
|
||||
'';
|
||||
|
|
@ -450,19 +465,34 @@ let
|
|||
start_service = mkNullOrBoolOption {
|
||||
description = ''
|
||||
Whether to run {command}`brew services start` for the formula and register it to
|
||||
launch at login (or boot).
|
||||
launch at login (or boot). Unlike {option}`restart_service`, this only starts
|
||||
the service if it is not currently running, without restarting an already-running
|
||||
service.
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
postinstall = mkNullOrStrOption {
|
||||
description = ''
|
||||
A shell command to run after the formula is installed or upgraded. The command is passed
|
||||
to the system shell and only executes when the formula actually changed (was freshly
|
||||
installed or upgraded), not on every {command}`brew bundle` run.
|
||||
'';
|
||||
};
|
||||
# `version_file` is intentionally not exposed: it writes the installed version to a file
|
||||
# path relative to the `brew bundle` working directory, which is not meaningful during
|
||||
# nix-darwin system activation.
|
||||
|
||||
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
|
||||
};
|
||||
|
|
@ -470,20 +500,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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -510,6 +534,13 @@ let
|
|||
itself.
|
||||
'';
|
||||
};
|
||||
postinstall = mkNullOrStrOption {
|
||||
description = ''
|
||||
A shell command to run after the cask is installed or upgraded. The command is passed to
|
||||
the system shell and only executes when the cask was actually installed or upgraded, not
|
||||
on every {command}`brew bundle` run.
|
||||
'';
|
||||
};
|
||||
|
||||
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
|
||||
};
|
||||
|
|
@ -533,21 +564,26 @@ in
|
|||
imports = [
|
||||
(mkRenamedOptionModule [ "homebrew" "autoUpdate" ] [ "homebrew" "onActivation" "autoUpdate" ])
|
||||
(mkRenamedOptionModule [ "homebrew" "cleanup" ] [ "homebrew" "onActivation" "cleanup" ])
|
||||
(mkRemovedOptionModule [ "homebrew" "brewPrefix" ] "`homebrew.brewPrefix` has been renamed to `homebrew.prefix` and its semantics changed: the old option pointed to the bin directory (e.g., `/opt/homebrew/bin`), while the new option points to the Homebrew prefix (e.g., `/opt/homebrew`), matching `brew --prefix`. Please replace `homebrew.brewPrefix` with `homebrew.prefix`, removing the trailing `/bin` if present.")
|
||||
(mkRemovedOptionModule [ "homebrew" "whalebrews" ] "Whalebrew support was removed from Homebrew Bundle in Homebrew 4.7.0 (Nov 2025). `whalebrew` entries in a Brewfile now cause `brew bundle` to fail. Please manage Whalebrew images directly using the `whalebrew` CLI.")
|
||||
];
|
||||
|
||||
options.homebrew = {
|
||||
enable = mkEnableOption ''
|
||||
{command}`nix-darwin` to manage installing/updating/upgrading Homebrew taps, formulae,
|
||||
and casks, as well as Mac App Store apps and Docker containers, using Homebrew Bundle.
|
||||
casks, Mac App Store apps, Visual Studio Code extensions, Go packages, and Cargo
|
||||
crates using Homebrew Bundle.
|
||||
|
||||
Note that enabling this option does not install Homebrew, see the Homebrew
|
||||
[website](https://brew.sh) for installation instructions.
|
||||
|
||||
Use the [](#opt-homebrew.brews), [](#opt-homebrew.casks),
|
||||
[](#opt-homebrew.masApps), [](#opt-homebrew.whalebrews), [](#opt-homebrew.vscode) options
|
||||
to list the Homebrew formulae, casks, Mac App Store apps, Docker containers and Visual Studio
|
||||
Code Extensions you'd like to install. Use the [](#opt-homebrew.taps) option, to make additional
|
||||
formula repositories available to Homebrew. This module uses those options (along with the
|
||||
[](#opt-homebrew.masApps), [](#opt-homebrew.vscode),
|
||||
[](#opt-homebrew.goPackages), and [](#opt-homebrew.cargoPackages) options to list
|
||||
the Homebrew formulae, casks, Mac App Store apps, Visual Studio Code extensions,
|
||||
Go packages, and Cargo crates you'd like to install. Use the
|
||||
[](#opt-homebrew.taps) option, to make additional formula repositories available to
|
||||
Homebrew. This module uses those options (along with the
|
||||
[](#opt-homebrew.caskArgs) options) to generate a Brewfile that
|
||||
{command}`nix-darwin` passes to the {command}`brew bundle` command during
|
||||
system activation.
|
||||
|
|
@ -572,20 +608,27 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
brewPrefix = mkOption {
|
||||
prefix = mkOption {
|
||||
type = types.str;
|
||||
default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin" else "/usr/local/bin";
|
||||
default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew" else "/usr/local";
|
||||
defaultText = literalExpression ''
|
||||
if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew/bin"
|
||||
else "/usr/local/bin"
|
||||
if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew"
|
||||
else "/usr/local"
|
||||
'';
|
||||
description = ''
|
||||
The path prefix where the {command}`brew` executable is located. This will be set to
|
||||
the correct value based on your system's platform, and should only need to be changed if you
|
||||
manually installed Homebrew in a non-standard location.
|
||||
The Homebrew prefix directory, i.e., the value that {command}`brew --prefix` returns.
|
||||
The default is automatically set based on your system's platform, and should only need
|
||||
to be changed if you manually installed Homebrew in a non-standard location.
|
||||
'';
|
||||
};
|
||||
|
||||
# These default to `false` (unlike direnv, which defaults to `true`) because existing users
|
||||
# likely already have `brew shellenv` in their dotfiles, and enabling by default would cause
|
||||
# duplicate evaluation.
|
||||
enableBashIntegration = mkShellIntegrationOption "Bash";
|
||||
enableFishIntegration = mkShellIntegrationOption "Fish";
|
||||
enableZshIntegration = mkShellIntegrationOption "Zsh";
|
||||
|
||||
onActivation = mkOption {
|
||||
type = types.submodule onActivationOptions;
|
||||
default = { };
|
||||
|
|
@ -648,6 +691,8 @@ in
|
|||
description = ''
|
||||
Whether to always upgrade casks listed in [](#opt-homebrew.casks) regardless
|
||||
of whether it's unversioned or it updates itself.
|
||||
|
||||
Homebrew's default is `false`.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -674,6 +719,12 @@ in
|
|||
link = true;
|
||||
conflicts_with = [ "mysql" ];
|
||||
}
|
||||
|
||||
# `brew install`, run a post-install command on version changes
|
||||
{
|
||||
name = "postgresql@16";
|
||||
postinstall = "\''${HOMEBREW_PREFIX}/opt/postgresql@16/bin/postgres -D \''${HOMEBREW_PREFIX}/var/postgresql@16";
|
||||
}
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
|
|
@ -705,6 +756,12 @@ in
|
|||
name = "opera";
|
||||
greedy = true;
|
||||
}
|
||||
|
||||
# `brew install --cask`, run a post-install command on install or upgrade
|
||||
{
|
||||
name = "google-cloud-sdk";
|
||||
postinstall = "\''${HOMEBREW_PREFIX}/bin/gcloud components update";
|
||||
}
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
|
|
@ -739,21 +796,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
whalebrews = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = [ "whalebrew/wget" ];
|
||||
description = ''
|
||||
List of Docker images to install using {command}`whalebrew`.
|
||||
|
||||
When this option is used, `"whalebrew"` is automatically added to
|
||||
[](#opt-homebrew.brews).
|
||||
|
||||
For more information on {command}`whalebrew` see:
|
||||
[github.com/whalebrew/whalebrew](https://github.com/whalebrew/whalebrew).
|
||||
'';
|
||||
};
|
||||
|
||||
vscode = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
|
|
@ -770,6 +812,31 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
goPackages = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = [ "github.com/charmbracelet/crush" ];
|
||||
description = ''
|
||||
List of Go packages to install using {command}`go install`.
|
||||
|
||||
Homebrew will automatically install the {command}`go` formula if it is not already
|
||||
installed.
|
||||
'';
|
||||
};
|
||||
|
||||
cargoPackages = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = [ "ripgrep" ];
|
||||
description = ''
|
||||
List of Rust packages to install using {command}`cargo install`.
|
||||
|
||||
Homebrew will automatically install the {command}`rust` formula if it is not already
|
||||
installed.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
|
|
@ -782,7 +849,7 @@ in
|
|||
|
||||
brewfile = mkInternalOption {
|
||||
type = types.str;
|
||||
description = "String reprensentation of the generated Brewfile useful for debugging.";
|
||||
description = "String representation of the generated Brewfile useful for debugging.";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -791,22 +858,16 @@ in
|
|||
|
||||
config = {
|
||||
|
||||
assertions = [
|
||||
# See comment above `homebrew.global.noLock` option declaration for why this is required.
|
||||
{ assertion = cfg.global.noLock == null; message = "The option `homebrew.global.noLock' was removed, use `homebrew.global.lockfiles' in it's place."; }
|
||||
];
|
||||
|
||||
warnings = [
|
||||
(mkIf (options.homebrew.autoUpdate.isDefined || options.homebrew.cleanup.isDefined) "The `homebrew' module no longer upgrades outdated formulae and apps by default during `nix-darwin' system activation. To enable upgrading, set `homebrew.onActivation.upgrade = true'.")
|
||||
(mkIf (cfg.global.noLock != null || cfg.global.lockfiles != null) "The options `homebrew.global.noLock' and `homebrew.global.lockfiles' have been deprecated. Homebrew Bundle removed lockfile support in Homebrew 4.4.0 (Oct 2024), so these options no longer have any effect. Please remove them from your configuration.")
|
||||
(mkIf (hasSuffix "/bin" cfg.prefix) "`homebrew.prefix` should be the Homebrew prefix directory (e.g., `/opt/homebrew`), not the bin directory. The value should match what `brew --prefix` returns. Did you mean to remove the trailing `/bin`?")
|
||||
];
|
||||
|
||||
system.requiresPrimaryUser = mkIf (cfg.enable && options.homebrew.user.highestPrio == (mkOptionDefault {}).priority) [
|
||||
"homebrew.enable"
|
||||
];
|
||||
|
||||
homebrew.brews =
|
||||
optional (cfg.whalebrews != [ ]) "whalebrew";
|
||||
|
||||
homebrew.brewfile =
|
||||
"# Created by `nix-darwin`'s `homebrew` module\n\n"
|
||||
+ mkBrewfileSectionString "Taps" cfg.taps
|
||||
|
|
@ -816,17 +877,72 @@ in
|
|||
+ mkBrewfileSectionString "Casks" cfg.casks
|
||||
+ mkBrewfileSectionString "Mac App Store apps"
|
||||
(mapAttrsToList (n: id: ''mas "${n}", id: ${toString id}'') cfg.masApps)
|
||||
+ mkBrewfileSectionString "Docker containers" (map (v: ''whalebrew "${v}"'') cfg.whalebrews)
|
||||
+ mkBrewfileSectionString "Visual Studio Code extensions" (map (v: ''vscode "${v}"'') cfg.vscode)
|
||||
+ mkBrewfileSectionString "Go packages" (map (v: ''go "${v}"'') cfg.goPackages)
|
||||
+ mkBrewfileSectionString "Cargo packages" (map (v: ''cargo "${v}"'') cfg.cargoPackages)
|
||||
+ optionalString (cfg.extraConfig != "") ("# Extra config\n" + cfg.extraConfig);
|
||||
|
||||
environment.variables = mkIf cfg.enable cfg.global.homebrewEnvironmentVariables;
|
||||
|
||||
programs = mkIf cfg.enable {
|
||||
bash.interactiveShellInit = mkIf cfg.enableBashIntegration ''
|
||||
eval "$(${cfg.prefix}/bin/brew shellenv bash)"
|
||||
if [[ -r "${cfg.prefix}/etc/profile.d/bash_completion.sh" ]]; then
|
||||
source "${cfg.prefix}/etc/profile.d/bash_completion.sh"
|
||||
else
|
||||
for COMPLETION in "${cfg.prefix}/etc/bash_completion.d/"*; do
|
||||
[[ -r "$COMPLETION" ]] && source "$COMPLETION"
|
||||
done
|
||||
fi
|
||||
'';
|
||||
|
||||
zsh.interactiveShellInit = mkIf cfg.enableZshIntegration ''
|
||||
eval "$(${cfg.prefix}/bin/brew shellenv zsh)"
|
||||
'';
|
||||
|
||||
fish.interactiveShellInit = mkIf cfg.enableFishIntegration ''
|
||||
eval (${cfg.prefix}/bin/brew shellenv fish)
|
||||
if test -d "${cfg.prefix}/share/fish/completions"
|
||||
set -p fish_complete_path "${cfg.prefix}/share/fish/completions"
|
||||
end
|
||||
if test -d "${cfg.prefix}/share/fish/vendor_completions.d"
|
||||
set -p fish_complete_path "${cfg.prefix}/share/fish/vendor_completions.d"
|
||||
end
|
||||
'';
|
||||
};
|
||||
|
||||
system.checks.text = mkIf (cfg.enable && cfg.onActivation.cleanup == "check") ''
|
||||
if [ -f "${cfg.prefix}/bin/brew" ]; then
|
||||
homebrewCleanupExitCode=0
|
||||
homebrewCleanupResult=$(PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
|
||||
sudo \
|
||||
--preserve-env=PATH \
|
||||
--user=${escapeShellArg cfg.user} \
|
||||
--set-home \
|
||||
env HOMEBREW_NO_AUTO_UPDATE=1 \
|
||||
brew bundle cleanup --file='${brewfileFile}' 2>&1) || homebrewCleanupExitCode=$?
|
||||
if [ "$homebrewCleanupExitCode" -eq 1 ]; then
|
||||
printf >&2 '\e[1;31merror: found Homebrew packages not listed in the Brewfile, aborting activation\e[0m\n'
|
||||
printf >&2 '%s\n' "$homebrewCleanupResult"
|
||||
printf >&2 '\n'
|
||||
printf >&2 'To fix this, either:\n'
|
||||
printf >&2 ' - Add the listed packages to your nix-darwin Homebrew configuration\n'
|
||||
printf >&2 ' - Remove them by running: brew bundle cleanup --force\n'
|
||||
printf >&2 ' - Set homebrew.onActivation.cleanup to "uninstall" or "zap"\n'
|
||||
exit 2
|
||||
elif [ "$homebrewCleanupExitCode" -ne 0 ]; then
|
||||
printf >&2 '\e[1;31merror: brew bundle cleanup failed, aborting activation\e[0m\n'
|
||||
printf >&2 '%s\n' "$homebrewCleanupResult"
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
||||
system.activationScripts.homebrew.text = mkIf cfg.enable ''
|
||||
# Homebrew Bundle
|
||||
echo >&2 "Homebrew bundle..."
|
||||
if [ -f "${cfg.brewPrefix}/brew" ]; then
|
||||
PATH="${cfg.brewPrefix}:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
|
||||
if [ -f "${cfg.prefix}/bin/brew" ]; then
|
||||
PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
|
||||
sudo \
|
||||
--preserve-env=PATH \
|
||||
--user=${escapeShellArg cfg.user} \
|
||||
|
|
@ -838,4 +954,8 @@ in
|
|||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
meta.maintainers = [
|
||||
lib.maintainers.malo or "malo"
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ let
|
|||
'';
|
||||
|
||||
homebrewInstalled = ''
|
||||
if [[ ! -f ${escapeShellArg config.homebrew.brewPrefix}/brew && -z "''${INSTALLING_HOMEBREW:-}" ]]; then
|
||||
if [[ ! -f ${escapeShellArg config.homebrew.prefix}/bin/brew && -z "''${INSTALLING_HOMEBREW:-}" ]]; then
|
||||
echo "[1;31merror: Using the homebrew module requires homebrew installed, aborting activation[0m" >&2
|
||||
echo "Homebrew doesn't seem to be installed. Please install homebrew separately." >&2
|
||||
echo "You can install homebrew using the following command:" >&2
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ in {
|
|||
tests.environment-path = makeTest ./tests/environment-path.nix;
|
||||
tests.environment-terminfo = makeTest ./tests/environment-terminfo.nix;
|
||||
tests.homebrew = makeTest ./tests/homebrew.nix;
|
||||
tests.homebrew-cleanup-check = makeTest ./tests/homebrew-cleanup-check.nix;
|
||||
tests.homebrew-shell-integration = makeTest ./tests/homebrew-shell-integration.nix;
|
||||
tests.launchd-daemons = makeTest ./tests/launchd-daemons.nix;
|
||||
tests.launchd-setenv = makeTest ./tests/launchd-setenv.nix;
|
||||
tests.networking-firewall = makeTest ./tests/networking-firewall.nix;
|
||||
|
|
|
|||
19
tests/homebrew-cleanup-check.nix
Normal file
19
tests/homebrew-cleanup-check.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
homebrew.enable = true;
|
||||
homebrew.user = "test-homebrew-user";
|
||||
homebrew.onActivation.cleanup = "check";
|
||||
|
||||
test = ''
|
||||
echo "checking that cleanup check is present in system checks" >&2
|
||||
grep 'brew bundle cleanup --file=' ${config.out}/activate
|
||||
|
||||
echo "checking that brew bundle command does not have --cleanup flag" >&2
|
||||
if echo "${config.homebrew.onActivation.brewBundleCmd}" | grep -F -- '--cleanup' > /dev/null; then
|
||||
echo "Expected no --cleanup flag in brewBundleCmd"
|
||||
echo "Actual: ${config.homebrew.onActivation.brewBundleCmd}"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
}
|
||||
29
tests/homebrew-shell-integration.nix
Normal file
29
tests/homebrew-shell-integration.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
homebrew.enable = true;
|
||||
programs.bash.enable = true;
|
||||
programs.zsh.enable = true;
|
||||
programs.fish.enable = true;
|
||||
|
||||
homebrew.user = "test-homebrew-user";
|
||||
|
||||
homebrew.enableBashIntegration = true;
|
||||
homebrew.enableFishIntegration = true;
|
||||
homebrew.enableZshIntegration = true;
|
||||
|
||||
test = ''
|
||||
echo >&2 "checking bash shell integration in /etc/bashrc"
|
||||
grep 'brew shellenv bash' ${config.out}/etc/bashrc
|
||||
echo >&2 "checking bash completions in /etc/bashrc"
|
||||
grep 'bash_completion' ${config.out}/etc/bashrc
|
||||
|
||||
echo >&2 "checking zsh shell integration in /etc/zshrc"
|
||||
grep 'brew shellenv zsh' ${config.out}/etc/zshrc
|
||||
|
||||
echo >&2 "checking fish shell integration in /etc/fish/config.fish"
|
||||
grep 'brew shellenv fish' ${config.out}/etc/fish/config.fish
|
||||
echo >&2 "checking fish completions in /etc/fish/config.fish"
|
||||
grep 'fish_complete_path' ${config.out}/etc/fish/config.fish
|
||||
'';
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -49,6 +50,10 @@ in
|
|||
link = true;
|
||||
conflicts_with = [ "mysql" ];
|
||||
}
|
||||
{
|
||||
name = "postgresql@16";
|
||||
postinstall = "\${HOMEBREW_PREFIX}/opt/postgresql@16/bin/postgres -D \${HOMEBREW_PREFIX}/var/postgresql@16";
|
||||
}
|
||||
];
|
||||
|
||||
homebrew.casks = [
|
||||
|
|
@ -61,6 +66,10 @@ in
|
|||
name = "opera";
|
||||
greedy = true;
|
||||
}
|
||||
{
|
||||
name = "google-cloud-sdk";
|
||||
postinstall = "\${HOMEBREW_PREFIX}/bin/gcloud components update";
|
||||
}
|
||||
];
|
||||
|
||||
homebrew.masApps = {
|
||||
|
|
@ -68,14 +77,19 @@ in
|
|||
Xcode = 497799835;
|
||||
};
|
||||
|
||||
homebrew.whalebrews = [
|
||||
"whalebrew/wget"
|
||||
];
|
||||
|
||||
homebrew.vscode = [
|
||||
"golang.go"
|
||||
];
|
||||
|
||||
homebrew.goPackages = [
|
||||
"github.com/charmbracelet/crush"
|
||||
];
|
||||
|
||||
homebrew.cargoPackages = [
|
||||
"ripgrep"
|
||||
];
|
||||
|
||||
|
||||
test = ''
|
||||
bf=${lib.escapeShellArg config.homebrew.brewfile}
|
||||
|
||||
|
|
@ -89,22 +103,33 @@ 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''}
|
||||
${mkTest "postgresql@16" ''brew "postgresql@16", postinstall: "''${HOMEBREW_PREFIX}/opt/postgresql@16/bin/postgres -D ''${HOMEBREW_PREFIX}/var/postgresql@16"''}
|
||||
|
||||
echo "checking cask entries in Brewfile" >&2
|
||||
${mkTest "google-chrome" ''cask "google-chrome"''}
|
||||
${mkTest "firefox" ''cask "firefox", args: { appdir: "~/my-apps/Applications" }''}
|
||||
${mkTest "opera" ''cask "opera", greedy: true''}
|
||||
${mkTest "google-cloud-sdk" ''cask "google-cloud-sdk", postinstall: "''${HOMEBREW_PREFIX}/bin/gcloud components update"''}
|
||||
|
||||
echo "checking mas entries in Brewfile" >&2
|
||||
${mkTest "1Password for Safari" ''mas "1Password for Safari", id: 1569813296''}
|
||||
${mkTest "Xcode" ''mas "Xcode", id: 497799835''}
|
||||
|
||||
echo "checking whalebrew entries in Brewfile" >&2
|
||||
${mkTest "whalebrew/wget" ''whalebrew "whalebrew/wget"''}
|
||||
|
||||
echo "checking vscode entries in Brewfile" >&2
|
||||
${mkTest "golang.go" ''vscode "golang.go"''}
|
||||
|
||||
echo "checking go entries in Brewfile" >&2
|
||||
${mkTest "github.com/charmbracelet/crush" ''go "github.com/charmbracelet/crush"''}
|
||||
|
||||
echo "checking cargo entries in Brewfile" >&2
|
||||
${mkTest "ripgrep" ''cargo "ripgrep"''}
|
||||
|
||||
echo "checking that shell integration is absent by default" >&2
|
||||
(! grep 'brew shellenv' ${config.out}/etc/zshrc)
|
||||
|
||||
echo "checking that cleanup check is absent by default" >&2
|
||||
(! grep 'brew bundle cleanup --file=' ${config.out}/activate)
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue