# Created by: https://github.com/malob { config, lib, options, pkgs, ... }: with lib; let cfg = config.homebrew; brewfileFile = pkgs.writeText "Brewfile" cfg.brewfile; # Brewfile creation helper functions ------------------------------------------------------------- mkBrewfileSectionString = heading: entries: optionalString (entries != [ ]) '' # ${heading} ${concatStringsSep "\n" (unique (map (v: v.brewfileLine or v) entries))} ''; mkBrewfileLineValueString = v: if isInt v then toString v else if isFloat v then strings.floatToString v else if isBool v then boolToString v else if isString v then ''"${v}"'' else if isAttrs v then "{ ${concatStringsSep ", " (mapAttrsToList (n: v': "${n}: ${mkBrewfileLineValueString v'}") v)} }" else if isList v then "[${concatMapStringsSep ", " mkBrewfileLineValueString v}]" else abort "The value: ${generators.toPretty v} is not a valid Brewfile value."; 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; }); mkNullOrStrOption = args: mkOption (args // { type = types.nullOr types.str; default = null; }); mkInternalOption = args: mkOption (args // { visible = false; internal = true; readOnly = true; }); mkProcessedSubmodConfig = attrs: mapAttrs (_: mkBrewfileLineValueString) (filterAttrsRecursive (n: v: n != "_module" && n != "brewfileLine" && v != null) attrs); # Submodules ------------------------------------------------------------------------------------- # Option values and descriptions of Brewfile entries are sourced/derived from: # * `brew` manpage: https://docs.brew.sh/Manpage # * `brew bundle` source files (at https://github.com/Homebrew/brew/tree/master/Library/Homebrew/bundle): # * dsl.rb # * {brew,cask,tap}_installer.rb # * ../test/bundle/{brew,cask,tap}_installer_spec.rb onActivationOptions = { config, ... }: { options = { cleanup = mkOption { type = types.enum [ "none" "check" "uninstall" "zap" ]; default = "none"; example = "uninstall"; description = '' 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), 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 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 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 packages installed by Homebrew, you probably want to set this option to `"uninstall"` or `"zap"`. ''; }; autoUpdate = mkOption { type = types.bool; default = false; description = '' Whether to enable Homebrew to auto-update itself and all formulae during {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 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 behavior see [](#opt-homebrew.global.autoUpdate). Implementation note: when disabled, this option sets the `HOMEBREW_NO_AUTO_UPDATE` environment variable when {command}`nix-darwin` invokes {command}`brew bundle [install]` during system activation. ''; }; upgrade = mkOption { type = types.bool; default = false; description = '' Whether to enable Homebrew to upgrade outdated formulae and Mac App Store apps during {command}`nix-darwin` system activation. The default is `false` so that repeated invocations of {command}`darwin-rebuild switch` are idempotent. Implementation note: when disabled, {command}`nix-darwin` invokes {command}`brew bundle [install]` with the {command}`--no-upgrade` flag during system activation. ''; }; extraFlags = mkOption { type = types.listOf types.str; default = [ ]; example = [ "--verbose" ]; description = '' Extra flags to pass to {command}`brew bundle [install]` during {command}`nix-darwin` system activation. ''; }; brewBundleCmd = mkInternalOption { type = types.str; }; }; config = { brewBundleCmd = concatStringsSep " " ( optional (!config.autoUpdate) "HOMEBREW_NO_AUTO_UPDATE=1" ++ [ "brew bundle --file='${brewfileFile}'" ] ++ optional (!config.upgrade) "--no-upgrade" ++ optional (config.cleanup == "uninstall") "--cleanup" ++ optional (config.cleanup == "zap") "--cleanup --zap" ++ config.extraFlags ); }; }; globalOptions = { config, ... }: { options = { brewfile = mkOption { type = types.bool; default = false; description = '' Whether to enable Homebrew to automatically use the Brewfile that this module generates in the Nix store, when you manually invoke {command}`brew bundle`. 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 [](#opt-environment.variables). ''; }; autoUpdate = mkOption { type = types.bool; default = true; description = '' Whether to enable Homebrew to auto-update itself and all formulae when you manually invoke commands like {command}`brew install`, {command}`brew upgrade`, {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 than 5 minutes since it last updated. You may want to consider disabling this option if you have [](#opt-homebrew.onActivation.upgrade) enabled, and [](#opt-homebrew.onActivation.autoUpdate) disabled, if you want to ensure that your installed formulae will only be upgraded during {command}`nix-darwin` system activation, after you've explicitly run {command}`brew update`. Implementation note: when disabled, this option sets the `HOMEBREW_NO_AUTO_UPDATE` environment variable, by adding it to [](#opt-environment.variables). ''; }; # `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; }; }; config = { homebrewEnvironmentVariables = { HOMEBREW_BUNDLE_FILE = mkIf config.brewfile "${brewfileFile}"; HOMEBREW_NO_AUTO_UPDATE = mkIf (!config.autoUpdate) "1"; }; }; }; tapOptions = { config, ... }: { options = { name = mkOption { type = types.str; example = "apple/apple"; description = '' When {option}`clone_target` is unspecified, this is the name of a formula repository to tap from GitHub using HTTPS. For example, `"user/repo"` will tap https://github.com/user/homebrew-repo. ''; }; clone_target = mkNullOrStrOption { description = '' Use this option to tap a formula repository from anywhere, using any transport protocol that {command}`git` handles. When {option}`clone_target` is specified, taps can be cloned from places other than GitHub and using protocols other than HTTPS, e.g., SSH, git, HTTP, FTP(S), rsync. ''; }; force_auto_update = mkNullOrBoolOption { 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. ''; }; brewfileLine = mkInternalOption { type = types.nullOr types.str; }; }; config = let sCfg = mkProcessedSubmodConfig config; in { brewfileLine = "tap ${sCfg.name}" + optionalString (sCfg ? clone_target) ", ${sCfg.clone_target}" + optionalString (sCfg ? force_auto_update) ", force_auto_update: ${sCfg.force_auto_update}"; }; }; # Sourced from https://docs.brew.sh/Manpage#global-cask-options # and valid values for `HOMEBREW_CASK_OPTS`. caskArgsOptions = { config, ... }: { options = { appdir = mkNullOrStrOption { description = '' Target location for Applications. Homebrew's default is {file}`/Applications`. ''; }; colorpickerdir = mkNullOrStrOption { description = '' Target location for Color Pickers. Homebrew's default is {file}`~/Library/ColorPickers`. ''; }; prefpanedir = mkNullOrStrOption { description = '' Target location for Preference Panes. Homebrew's default is {file}`~/Library/PreferencePanes`. ''; }; qlplugindir = mkNullOrStrOption { description = '' Target location for QuickLook Plugins. Homebrew's default is {file}`~/Library/QuickLook`. ''; }; mdimporterdir = mkNullOrStrOption { description = '' Target location for Spotlight Plugins. Homebrew's default is {file}`~/Library/Spotlight`. ''; }; dictionarydir = mkNullOrStrOption { description = '' Target location for Dictionaries. Homebrew's default is {file}`~/Library/Dictionaries`. ''; }; fontdir = mkNullOrStrOption { description = '' Target location for Fonts. Homebrew's default is {file}`~/Library/Fonts`. ''; }; servicedir = mkNullOrStrOption { description = '' Target location for Services. Homebrew's default is {file}`~/Library/Services`. ''; }; input_methoddir = mkNullOrStrOption { description = '' Target location for Input Methods. Homebrew's default is {file}`~/Library/Input Methods`. ''; }; internet_plugindir = mkNullOrStrOption { description = '' Target location for Internet Plugins. Homebrew's default is {file}`~/Library/Internet Plug-Ins`. ''; }; audio_unit_plugindir = mkNullOrStrOption { description = '' Target location for Audio Unit Plugins. Homebrew's default is {file}`~/Library/Audio/Plug-Ins/Components`. ''; }; vst_plugindir = mkNullOrStrOption { description = '' Target location for VST Plugins. Homebrew's default is {file}`~/Library/Audio/Plug-Ins/VST`. ''; }; vst3_plugindir = mkNullOrStrOption { description = '' Target location for VST3 Plugins. Homebrew's default is {file}`~/Library/Audio/Plug-Ins/VST3`. ''; }; screen_saverdir = mkNullOrStrOption { description = '' Target location for Screen Savers. Homebrew's default is {file}`~/Library/Screen Savers`. ''; }; language = mkNullOrStrOption { description = '' Comma-separated list of language codes to prefer for cask installation. The first matching language is used, otherwise it reverts to the cask’s default language. The default value is the language of your system. ''; example = "zh-TW"; }; require_sha = mkNullOrBoolOption { description = '' Whether to require casks to have a checksum. Homebrew's default is `false`. ''; }; no_quarantine = mkNullOrBoolOption { 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. Homebrew's default is `false`. ''; }; ignore_dependencies = mkNullOrBoolOption { 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; }; }; config = let sCfg = mkProcessedSubmodConfig config; in { brewfileLine = if sCfg == { } then null else "cask_args ${mkBrewfileLineOptionsListString sCfg}"; }; }; brewOptions = { config, ... }: { options = { name = mkOption { type = types.str; description = "The name of the formula to install."; }; args = mkOption { type = with types; nullOr (listOf str); default = null; description = '' Argument flags to pass to {command}`brew install`. Values should not include the leading `"--"`. ''; }; conflicts_with = mkOption { type = with types; nullOr (listOf str); default = null; description = '' List of formulae that should be unlinked and their services stopped (if they are installed). ''; }; restart_service = mkOption { 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 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`. ''; }; start_service = mkNullOrBoolOption { description = '' Whether to run {command}`brew services start` for the formula and register it to 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 = mkOption { type = with types; nullOr (either bool (enum [ "overwrite" ])); default = null; description = '' 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; }; }; config = let sCfg = mkProcessedSubmodConfig config; sCfgSubset = removeAttrs sCfg [ "name" "restart_service" "link" ]; in { brewfileLine = "brew ${sCfg.name}" + optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}" + mkBrewfileLineBoolOrSymbolString "link" config sCfg + mkBrewfileLineBoolOrSymbolString "restart_service" config sCfg; }; }; caskOptions = { config, ... }: { options = { name = mkOption { type = types.str; description = "The name of the cask to install."; }; args = mkOption { type = types.nullOr (types.submodule caskArgsOptions); default = null; visible = "shallow"; # so that options from `homebrew.caskArgs` aren't repeated. description = '' Arguments passed to {command}`brew install --cask` when installing this cask. See [](#opt-homebrew.caskArgs) for the available options. ''; }; greedy = mkOption { type = types.nullOr types.bool; default = cfg.greedyCasks; description = '' Whether to always upgrade this cask regardless of whether it's unversioned or it updates 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; }; }; config = let sCfg = mkProcessedSubmodConfig config; sCfgSubset = removeAttrs sCfg [ "name" ]; in { brewfileLine = "cask ${sCfg.name}" + optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}"; }; }; in { # Interface -------------------------------------------------------------------------------------- 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, 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.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. The default configuration of this module prevents Homebrew Bundle from auto-updating Homebrew and all formulae, as well as upgrading anything that's already installed, so that repeated invocations of {command}`darwin-rebuild switch` (without any change to the configuration) are idempotent. You can modify this behavior using the options under [](#opt-homebrew.onActivation). This module also provides a few options for modifying how Homebrew commands behave when you manually invoke them, under [](#opt-homebrew.global)''; user = mkOption { type = types.str; default = config.system.primaryUser; defaultText = literalExpression "config.system.primaryUser"; description = '' The user that owns the Homebrew installation. In most cases this should be the normal user account that you installed Homebrew as. ''; }; prefix = mkOption { type = types.str; default = if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew" else "/usr/local"; defaultText = literalExpression '' if pkgs.stdenv.hostPlatform.isAarch64 then "/opt/homebrew" else "/usr/local" ''; description = '' 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 = { }; description = '' Options for configuring the behavior of the {command}`brew bundle` command that {command}`nix-darwin` runs during system activation. ''; }; global = mkOption { type = types.submodule globalOptions; default = { }; description = '' Options for configuring the behavior of Homebrew commands when you manually invoke them. ''; }; taps = mkOption { type = with types; listOf (coercedTo str (name: { inherit name; }) (submodule tapOptions)); default = [ ]; example = literalExpression '' # Adapted from https://docs.brew.sh/Brew-Bundle-and-Brewfile [ # `brew tap` "apple/apple" # `brew tap` with custom Git URL and arguments { name = "user/tap-repo"; clone_target = "https://user@bitbucket.org/user/homebrew-tap-repo.git"; force_auto_update = true; } ] ''; description = '' List of Homebrew formula repositories to tap. Taps defined as strings, e.g., `"user/repo"`, are a shorthand for: `{ name = "user/repo"; }` ''; }; caskArgs = mkOption { type = types.submodule caskArgsOptions; default = { }; example = literalExpression '' { appdir = "~/Applications"; require_sha = true; } ''; description = '' Arguments passed to {command}`brew install --cask` for all casks listed in [](#opt-homebrew.casks). ''; }; greedyCasks = mkNullOrBoolOption { 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`. ''; }; brews = mkOption { type = with types; listOf (coercedTo str (name: { inherit name; }) (submodule brewOptions)); default = [ ]; example = literalExpression '' # Adapted from https://docs.brew.sh/Brew-Bundle-and-Brewfile [ # `brew install` "imagemagick" # `brew install --with-rmtp`, `brew services restart` on version changes { name = "denji/nginx/nginx-full"; args = [ "with-rmtp" ]; restart_service = "changed"; } # `brew install`, always `brew services restart`, `brew link`, `brew unlink mysql` (if it is installed) { name = "mysql@5.6"; restart_service = true; 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 = '' List of Homebrew formulae to install. Formulae defined as strings, e.g., `"imagemagick"`, are a shorthand for: `{ name = "imagemagick"; }` ''; }; casks = mkOption { type = with types; listOf (coercedTo str (name: { inherit name; }) (submodule caskOptions)); default = [ ]; example = literalExpression '' # Adapted from https://docs.brew.sh/Brew-Bundle-and-Brewfile [ # `brew install --cask` "google-chrome" # `brew install --cask --appdir=~/my-apps/Applications` { name = "firefox"; args = { appdir = "~/my-apps/Applications"; }; } # always upgrade auto-updated or unversioned cask to latest version even if already installed { 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 = '' List of Homebrew casks to install. Casks defined as strings, e.g., `"google-chrome"`, are a shorthand for: `{ name = "google-chrome"; }` ''; }; masApps = mkOption { type = types.attrsOf types.ints.positive; default = { }; example = literalExpression '' { "1Password for Safari" = 1569813296; Xcode = 497799835; } ''; description = '' Applications to install from Mac App Store using {command}`mas`. Note that you need to be signed into the Mac App Store for {command}`mas` to successfully install and upgrade applications, and that unfortunately apps removed from this option will not be uninstalled automatically even if [](#opt-homebrew.onActivation.cleanup) is set to `"uninstall"` or `"zap"` (this is currently a limitation of Homebrew Bundle). For more information on {command}`mas` see: [github.com/mas-cli/mas](https://github.com/mas-cli/mas). ''; }; vscode = mkOption { type = with types; listOf str; default = [ ]; example = [ "golang.go" ]; description = '' List of Visual Studio Code extensions to install using Homebrew Bundle. A compatible editor (Visual Studio Code, VSCodium, Cursor, or VS Code Insiders) must be available. If none is found, Homebrew will attempt to install `visual-studio-code` automatically. For more information on {command}`code` see: [VSCode Extension Marketplace](https://code.visualstudio.com/docs/editor/extension-marketplace). ''; }; 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 = ""; example = '' # 'brew cask install' only if '/usr/libexec/java_home --failfast' fails cask "java" unless system "/usr/libexec/java_home --failfast" ''; description = "Extra lines to be added verbatim to the bottom of the generated Brewfile."; }; brewfile = mkInternalOption { type = types.str; description = "String representation of the generated Brewfile useful for debugging."; }; }; # Implementation --------------------------------------------------------------------------------- config = { 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.brewfile = "# Created by `nix-darwin`'s `homebrew` module\n\n" + mkBrewfileSectionString "Taps" cfg.taps + mkBrewfileSectionString "Arguments for all casks" (optional (cfg.caskArgs.brewfileLine != null) cfg.caskArgs) + mkBrewfileSectionString "Brews" cfg.brews + mkBrewfileSectionString "Casks" cfg.casks + mkBrewfileSectionString "Mac App Store apps" (mapAttrsToList (n: id: ''mas "${n}", id: ${toString id}'') cfg.masApps) + 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.prefix}/bin/brew" ]; then PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \ sudo \ --preserve-env=PATH \ --user=${escapeShellArg cfg.user} \ --set-home \ env \ ${cfg.onActivation.brewBundleCmd} else echo -e "\e[1;31merror: Homebrew is not installed, skipping...\e[0m" >&2 fi ''; }; meta.maintainers = [ lib.maintainers.malo or "malo" ]; }