diff --git a/flake-modules/legacy-packages.nix b/flake-modules/legacy-packages.nix index 3e8555c583..323decc7a6 100644 --- a/flake-modules/legacy-packages.nix +++ b/flake-modules/legacy-packages.nix @@ -2,9 +2,8 @@ { perSystem = { - pkgs, - lib, makeNixvimWithModule, + system, ... }: { @@ -13,12 +12,7 @@ makeNixvim = module: makeNixvimWithModule { inherit module; }; nixvimConfiguration = helpers.modules.evalNixvim { - modules = [ - { - _file = ./legacy-packages.nix; - nixpkgs.pkgs = lib.mkDefault pkgs; - } - ]; + inherit system; }; }; }; diff --git a/flake-modules/lib.nix b/flake-modules/lib.nix index c905482b2e..2e606f5128 100644 --- a/flake-modules/lib.nix +++ b/flake-modules/lib.nix @@ -27,7 +27,9 @@ { pkgs, system, ... }: { # NOTE: this is the publicly documented flake output we've had for a while - check = pkgs.callPackage ../lib/tests.nix { inherit self; }; + check = pkgs.callPackage ../lib/tests.nix { + inherit lib self system; + }; # NOTE: no longer needs to be per-system helpers = lib.warn "nixvim: `.lib.${system}.helpers` has been moved to `.lib.nixvim` and no longer depends on a specific system" self.lib.nixvim; diff --git a/flake-modules/wrappers.nix b/flake-modules/wrappers.nix index 30fa85be2a..084f5ce0ab 100644 --- a/flake-modules/wrappers.nix +++ b/flake-modules/wrappers.nix @@ -1,10 +1,18 @@ -{ inputs, self, ... }: +{ + inputs, + self, + lib, + ... +}: { perSystem = { system, pkgs, ... }: { _module.args = { - makeNixvimWithModule = import ../wrappers/standalone.nix pkgs self; + makeNixvimWithModule = import ../wrappers/standalone.nix { + inherit lib self; + defaultSystem = system; + }; }; checks = diff --git a/lib/modules.nix b/lib/modules.nix index d9c39f1242..cbffe1d4a5 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -21,12 +21,16 @@ in { modules ? [ ], extraSpecialArgs ? { }, + system ? null, # Can also be defined using the `nixpkgs.hostPlatform` option }: # Ensure a suitable `lib` is used assert lib.assertMsg (extraSpecialArgs ? lib -> extraSpecialArgs.lib ? nixvim) '' Nixvim requires a lib that includes some custom extensions, however the `lib` from `specialArgs` does not have a `nixvim` attr. Remove `lib` from nixvim's `specialArgs` or ensure you apply nixvim's extensions to your `lib`. See https://nix-community.github.io/nixvim/user-guide/helpers.html#using-a-custom-lib-with-nixvim''; + assert lib.assertMsg (system != null -> lib.isString system) '' + When `system` is supplied to `evalNixvim`, it must be a string. + To define a more complex system, please use nixvim's `nixpkgs.hostPlatform` option.''; lib.evalModules { modules = modules ++ [ ../modules/top-level @@ -34,6 +38,10 @@ in _file = ""; flake = lib.mkOptionDefault flake; } + (lib.optionalAttrs (system != null) { + _file = "evalNixvim"; + nixpkgs.hostPlatform = lib.mkOptionDefault { inherit system; }; + }) ]; specialArgs = { modulesPath = ../modules; diff --git a/lib/tests.nix b/lib/tests.nix index 2363750514..b4179631eb 100644 --- a/lib/tests.nix +++ b/lib/tests.nix @@ -1,11 +1,10 @@ { self, - pkgs, - lib ? pkgs.lib, - ... + system, + lib, }: let - defaultPkgs = pkgs; + defaultSystem = system; # Create a nix derivation from a nixvim executable. # The build phase simply consists in running the provided nvim binary. @@ -30,7 +29,8 @@ let mkTestDerivationFromNixvimModule = { name ? null, - pkgs ? defaultPkgs, + pkgs ? null, + system ? defaultSystem, module, extraSpecialArgs ? { }, }: @@ -42,14 +42,18 @@ let _nixvimTests = true; }; + systemMod = + if pkgs == null then + { nixpkgs.hostPlatform = lib.mkDefault { inherit system; }; } + else + { nixpkgs.pkgs = lib.mkDefault pkgs; }; + result = helpers.modules.evalNixvim { modules = [ module (lib.optionalAttrs (name != null) { test.name = name; }) { wrapRc = true; } - # TODO: Only do this when `args?pkgs` - # Consider deprecating the `pkgs` arg too... - { nixpkgs.pkgs = lib.mkDefault pkgs; } + systemMod ]; inherit extraSpecialArgs; }; diff --git a/modules/top-level/nixpkgs.nix b/modules/top-level/nixpkgs.nix index dd7e0d11b3..61366d761f 100644 --- a/modules/top-level/nixpkgs.nix +++ b/modules/top-level/nixpkgs.nix @@ -2,28 +2,38 @@ config, options, lib, + pkgs, ... }: let cfg = config.nixpkgs; opt = options.nixpkgs; + + isConfig = x: builtins.isAttrs x || lib.isFunction x; + + mergeConfig = + lhs_: rhs_: + let + optCall = maybeFn: x: if lib.isFunction maybeFn then maybeFn x else maybeFn; + lhs = optCall lhs_ { inherit pkgs; }; + rhs = optCall rhs_ { inherit pkgs; }; + in + lib.recursiveUpdate lhs rhs + // lib.optionalAttrs (lhs ? packageOverrides) { + packageOverrides = + pkgs: + optCall lhs.packageOverrides pkgs // optCall (lib.attrByPath [ "packageOverrides" ] { } rhs) pkgs; + } + // lib.optionalAttrs (lhs ? perlPackageOverrides) { + perlPackageOverrides = + pkgs: + optCall lhs.perlPackageOverrides pkgs + // optCall (lib.attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs; + }; in { options.nixpkgs = { pkgs = lib.mkOption { - # TODO: - # defaultText = lib.literalExpression '' - # import "''${nixos}/.." { - # inherit (cfg) config overlays localSystem crossSystem; - # } - # ''; - defaultText = lib.literalMD '' - The `pkgs` inherited from your host config (i.e. NixOS, home-manager, or nix-darwin), - or the `pkgs` supplied to `makeNixvimWithModule` when building a standalone nixvim. - - > [!CAUTION] - > This default will be removed in a future version of nixvim - ''; type = lib.types.pkgs // { description = "An evaluation of Nixpkgs; the top level attribute set of packages"; }; @@ -31,17 +41,7 @@ in description = '' If set, the `pkgs` argument to all Nixvim modules is the value of this option. - - If unset, an assertion will trigger. In the future a `pkgs` instance will be constructed. - - + If unset, the `pkgs` argument is determined by importing `nixpkgs.source`. This option can be used by external applications to increase the performance of evaluation, or to create packages that depend on a container that should be built with the exact same @@ -56,6 +56,29 @@ in ''; }; + config = lib.mkOption { + default = { }; + example = { + allowBroken = true; + allowUnfree = true; + }; + type = lib.mkOptionType { + name = "nixpkgs-config"; + description = "nixpkgs config"; + check = x: isConfig x || lib.traceSeqN 1 x false; + merge = loc: lib.foldr (def: mergeConfig def.value) { }; + }; + description = '' + Global configuration for Nixpkgs. + The complete list of [Nixpkgs configuration options] is in the [Nixpkgs manual section on global configuration]. + + Ignored when {option}`nixpkgs.pkgs` is set. + + [Nixpkgs configuration options]: https://nixos.org/manual/nixpkgs/unstable/#sec-config-options-reference + [Nixpkgs manual section on global configuration]: https://nixos.org/manual/nixpkgs/unstable/#chap-packageconfig + ''; + }; + overlays = lib.mkOption { type = let @@ -114,15 +137,55 @@ in For details, see the [Overlays chapter in the Nixpkgs manual](https://nixos.org/manual/nixpkgs/stable/#chap-overlays). - - Overlays specified using the {option}`nixpkgs.overlays` option will be - applied after the overlays that were already included in `nixpkgs.pkgs`. + If the {option}`nixpkgs.pkgs` option is set, overlays specified using `nixpkgs.overlays` + will be applied after the overlays that were already included in `nixpkgs.pkgs`. + ''; + }; + + hostPlatform = lib.mkOption { + type = with lib.types; either str attrs; + example = { + system = "aarch64-linux"; + }; + apply = lib.systems.elaborate; + defaultText = lib.literalMD '' + - Inherited from the "host" configuration's `pkgs` + - Or `evalNixvim`'s `system` argument + - Otherwise, must be specified manually + ''; + description = '' + Specifies the platform where the Nixvim configuration will run. + + To cross-compile, also set `nixpkgs.buildPlatform`. - + Ignored when `nixpkgs.pkgs` is set. + ''; + }; + + buildPlatform = lib.mkOption { + type = with lib.types; either str attrs; + default = cfg.hostPlatform; + example = { + system = "x86_64-linux"; + }; + apply = + value: + let + elaborated = lib.systems.elaborate value; + in + # If equivalent to `hostPlatform`, make it actually identical so that `==` can be used + # See https://github.com/NixOS/nixpkgs/issues/278001 + if lib.systems.equals elaborated cfg.hostPlatform then cfg.hostPlatform else elaborated; + defaultText = lib.literalMD '' + Inherited from the "host" configuration's `pkgs`. + Or `config.nixpkgs.hostPlatform` when building a standalone nixvim. + ''; + description = '' + Specifies the platform on which Nixvim should be built. + By default, Nixvim is built on the system where it runs, but you can change where it's built. + Setting this option will cause Nixvim to be cross-compiled. + + Ignored when `nixpkgs.pkgs` is set. ''; }; @@ -136,26 +199,33 @@ in Ignored when `nixpkgs.pkgs` is set. ''; - - # FIXME: This is a stub option for now - internal = true; }; }; config = let - # TODO: construct a default pkgs instance from pkgsPath and cfg options - # https://github.com/nix-community/nixvim/issues/1784 - finalPkgs = if opt.pkgs.isDefined then cfg.pkgs.appendOverlays cfg.overlays else - # TODO: Remove once pkgs can be constructed internally - throw '' - nixvim: `nixpkgs.pkgs` is not defined. In the future, this option will be optional. - Currently a pkgs instance must be evaluated externally and assigned to `nixpkgs.pkgs` option. - ''; + let + args = { + inherit (cfg) config overlays; + }; + + # Configure `localSystem` and `crossSystem` as required + systemArgs = + if cfg.buildPlatform == cfg.hostPlatform then + { + localSystem = cfg.hostPlatform; + } + else + { + localSystem = cfg.buildPlatform; + crossSystem = cfg.hostPlatform; + }; + in + import cfg.source (args // systemArgs); in { # We explicitly set the default override priority, so that we do not need @@ -166,9 +236,20 @@ in # don't need to evaluate `finalPkgs`. _module.args.pkgs = lib.mkOverride lib.modules.defaultOverridePriority finalPkgs.__splicedPackages; - # FIXME: This is a stub option for now - warnings = lib.optional ( - opt.source.isDefined && opt.source.highestPrio < (lib.mkOptionDefault null).priority - ) "Defining the option `nixpkgs.source` currently has no effect"; + assertions = [ + { + assertion = opt.pkgs.isDefined -> cfg.config == { }; + message = '' + Your system configures nixpkgs with an externally created instance. + `nixpkgs.config` options should be passed when creating the instance instead. + + Current value: + ${lib.generators.toPretty { multiline = true; } cfg.config} + + Defined in: + ${lib.concatMapStringsSep "\n" (file: " - ${file}") opt.config.files} + ''; + } + ]; }; } diff --git a/templates/simple/flake.nix b/templates/simple/flake.nix index 8e80918683..ef20dfedac 100644 --- a/templates/simple/flake.nix +++ b/templates/simple/flake.nix @@ -18,12 +18,12 @@ ]; perSystem = - { pkgs, system, ... }: + { system, ... }: let nixvimLib = nixvim.lib.${system}; nixvim' = nixvim.legacyPackages.${system}; nixvimModule = { - inherit pkgs; + inherit system; # or alternatively, set `pkgs` module = import ./config; # import the module directly # You can use `extraSpecialArgs` to pass additional arguments to your module files extraSpecialArgs = { diff --git a/tests/main.nix b/tests/main.nix index 3b26069865..3801e0cbb6 100644 --- a/tests/main.nix +++ b/tests/main.nix @@ -8,10 +8,13 @@ pkgs, pkgsUnfree, self, + system, }: let fetchTests = callTest ./fetch-tests.nix { }; - test-derivation = callPackage ../lib/tests.nix { inherit self; }; + test-derivation = callPackage ../lib/tests.nix { + inherit lib self system; + }; inherit (test-derivation) mkTestDerivationFromNixvimModule; moduleToTest = diff --git a/tests/nixpkgs-mock.nix b/tests/nixpkgs-mock.nix new file mode 100644 index 0000000000..174ff07b6b --- /dev/null +++ b/tests/nixpkgs-mock.nix @@ -0,0 +1,15 @@ +# This mock nixpkgs can be used as `nixpkgs.source` in nixpkgs-module-test +# if we want/need to avoid importing & instantiating a real nixpkgs +{ + config ? { }, + ... +}: +let + pkgs = { + _type = "pkgs"; + __splicedPackages = pkgs; + inherit config pkgs; + mock = true; + }; +in +pkgs diff --git a/tests/nixpkgs-module.nix b/tests/nixpkgs-module.nix index ba04381b8a..031ce209da 100644 --- a/tests/nixpkgs-module.nix +++ b/tests/nixpkgs-module.nix @@ -33,10 +33,97 @@ let testModule = name: module: (evalModule name module).config.build.test; + # Unlike above, this imports the full nixvimConfiguration, + # allowing us to integration test the wrapper module + # + # This means `pkgs` probably gets used "for real", e.g. in the `files` module + testWrappers = + name: pkgs: module: + linkFarmFromDrvs name ( + lib.mapAttrsToList + ( + name': wrapper: + let + wrapperConfiguration = lib.evalModules { + modules = lib.toList module ++ [ + wrapper + { _module.check = false; } + { _module.args.pkgs = pkgs; } + { + # Stub `lib` option, required for bootstrapping wrapper module + options.lib = lib.mkOption { + type = with lib.types; attrsOf attrs; + default = { }; + }; + } + { + programs.nixvim.test = { + name = "${name}-${name'}"; + buildNixvim = false; + runNvim = false; + runCommand = runCommandLocal; + }; + } + ]; + }; + in + wrapperConfiguration.config.programs.nixvim.build.test + ) + { + nixos = self.nixosModules.default; + hm = self.homeManagerModules.default; + nix-darwin = self.nixDarwinModules.default; + } + ); + in linkFarmFromDrvs "nixpkgs-module-test" [ - # TODO: expect not setting `nixpkgs.pkgs` to throw + # Test that pkgs-config is affected by `nixpkgs.config` + (testModule "nixpkgs-config" ( + { pkgs, ... }: + { + nixpkgs.config = { + permittedInsecurePackages = [ + "foobar123" + ]; + }; + + nixpkgs.hostPlatform = { + inherit (stdenv.hostPlatform) system; + }; + + assertions = [ + { + assertion = pkgs.config.permittedInsecurePackages == [ "foobar123" ]; + message = '' + Expected `pkgs.config.permittedInsecurePackages` to match [ "foobar123" ], but found: + ${lib.generators.toPretty { } pkgs.config.permittedInsecurePackages}''; + } + ]; + } + )) + + # Test that a nixpkgs revision can be specified using `nixpkgs.source` + (testModule "nixpkgs-source" ( + { pkgs, ... }: + { + nixpkgs.source = ./nixpkgs-mock.nix; + + nixpkgs.hostPlatform = { + inherit (stdenv.hostPlatform) system; + }; + + assertions = [ + { + assertion = pkgs.mock or false; + message = "Expected `pkgs.mock` to be true, but ${ + if pkgs ? mock then "found " + lib.generators.toPretty { } pkgs.mock else "isn't present" + }"; + } + ]; + } + )) (testModule "nixpkgs-overlays" ( { pkgs, ... }: @@ -104,4 +191,37 @@ linkFarmFromDrvs "nixpkgs-module-test" [ } )) + (testWrappers "useGlobalPackages-empty" pkgs { }) + (testWrappers "useGlobalPackages-true" pkgs { + programs.nixvim.nixpkgs.useGlobalPackages = true; + }) + (testWrappers "useGlobalPackages-false" pkgs { + programs.nixvim.nixpkgs.useGlobalPackages = false; + }) + (testWrappers "useGlobalPackages-with-pkgs" pkgs { + _file = "test-file"; + programs.nixvim = { + nixpkgs.useGlobalPackages = true; + nixpkgs.pkgs = pkgs; + test.assertions = expect: [ + (expect "count" 1) + (expect "any" "`programs.nixvim.nixpkgs.useGlobalPackages' is enabled, but `programs.nixvim.nixpkgs.pkgs' is overridden.") + (expect "any" "- In `test-file'") + ]; + }; + }) + (testWrappers "useGlobalPackages-with-pkgs-arg" pkgs { + _file = "test-file"; + programs.nixvim = { + _module.args.pkgs = lib.mkForce pkgs; + nixpkgs.useGlobalPackages = true; + test.assertions = expect: [ + (expect "count" 1) + (expect "any" "`programs.nixvim.nixpkgs.useGlobalPackages' is enabled, but `programs.nixvim._module.args.pkgs' is overridden.") + # FIXME: can't showDefs for an attrOf an option + # (expect "any" "- In `test-file'") + ]; + }; + }) + ] diff --git a/wrappers/_shared.nix b/wrappers/_shared.nix index 33c002b624..858434186e 100644 --- a/wrappers/_shared.nix +++ b/wrappers/_shared.nix @@ -23,21 +23,33 @@ let map mkIf mkMerge - mkOption optionalAttrs setAttrByPath ; cfg = config.programs.nixvim; + + # FIXME: buildPlatform can't use mkOptionDefault because it already defaults to hostPlatform + buildPlatformPrio = (lib.mkOptionDefault null).priority - 1; + + nixpkgsModule = + { config, ... }: + { + _file = ./_shared.nix; + nixpkgs = { + # Use global packages in nixvim's submodule + pkgs = lib.mkIf config.nixpkgs.useGlobalPackages (lib.mkDefault pkgs); + + # Inherit platform spec + hostPlatform = lib.mkOptionDefault pkgs.stdenv.hostPlatform; + buildPlatform = lib.mkOverride buildPlatformPrio pkgs.stdenv.buildPlatform; + }; + }; + nixvimConfiguration = config.lib.nixvim.modules.evalNixvim ( evalArgs // { modules = evalArgs.modules or [ ] ++ [ - # Use global packages by default in nixvim's submodule - # TODO: `useGlobalPackages` option and/or deprecate using host packages? - { - _file = ./_shared.nix; - nixpkgs.pkgs = lib.mkDefault pkgs; - } + nixpkgsModule ]; } ); diff --git a/wrappers/modules/nixpkgs.nix b/wrappers/modules/nixpkgs.nix new file mode 100644 index 0000000000..898713a38b --- /dev/null +++ b/wrappers/modules/nixpkgs.nix @@ -0,0 +1,83 @@ +{ + lib, + config, + options, + ... +}: +let + cfg = config.nixpkgs; + opts = options.nixpkgs; + argOpts = lib.modules.mergeAttrDefinitionsWithPrio options._module.args; + + normalPrio = lib.modules.defaultOverridePriority; + defaultPrio = (lib.mkDefault null).priority; + optionDefaultPrio = (lib.mkOptionDefault null).priority; + # FIXME: buildPlatform can't use mkOptionDefault because it already defaults to hostPlatform + buildPlatformPrio = optionDefaultPrio - 1; + + mkGlobalPackagesAssertion = + { + assertion, + option ? null, + loc ? option.loc, + issue ? "is overridden", + }: + { + assertion = cfg.useGlobalPackages -> assertion; + message = + "`${lib.showOption opts.useGlobalPackages.loc}' is enabled, " + + "but `${lib.showOption loc}' ${issue}. " + + lib.optionalString ( + option != null + ) "Definition values:${lib.options.showDefs option.definitionsWithLocations}"; + }; +in +{ + options = { + nixpkgs.useGlobalPackages = lib.mkOption { + type = lib.types.bool; + default = true; # TODO: Added 2025-01-15; switch to false one release after adding a deprecation warning + defaultText = lib.literalMD ''`true`, but will change to `false` in a future version.''; + description = '' + Whether Nixvim should use the ${config.meta.wrapper.name} configuration's `pkgs`, + instead of constructing its own instance. + ''; + }; + }; + + config = { + assertions = map mkGlobalPackagesAssertion [ + { + assertion = opts.pkgs.highestPrio == defaultPrio; + option = opts.pkgs; + issue = "is overridden"; + } + { + assertion = argOpts.pkgs.highestPrio == normalPrio; + # FIXME: can't showDefs for an attrOf an option + loc = options._module.args.loc ++ [ "pkgs" ]; + issue = "is overridden"; + } + { + assertion = opts.hostPlatform.highestPrio == optionDefaultPrio; + option = opts.hostPlatform; + issue = "is overridden"; + } + { + assertion = opts.buildPlatform.highestPrio == buildPlatformPrio; + option = opts.buildPlatform; + issue = "is overridden"; + } + { + assertion = cfg.config == { }; + option = opts.config; + issue = "is not empty"; + } + { + assertion = cfg.overlays == [ ]; + option = opts.overlays; + issue = "is not empty"; + } + ]; + }; +} diff --git a/wrappers/modules/shared.nix b/wrappers/modules/shared.nix index 3aa9f7c557..cd580ed59b 100644 --- a/wrappers/modules/shared.nix +++ b/wrappers/modules/shared.nix @@ -11,4 +11,8 @@ }; }; }; + + imports = [ + ./nixpkgs.nix + ]; } diff --git a/wrappers/standalone.nix b/wrappers/standalone.nix index 494074a5b1..a7eac7ce71 100644 --- a/wrappers/standalone.nix +++ b/wrappers/standalone.nix @@ -1,8 +1,13 @@ -default_pkgs: self: +{ + self, + lib, + defaultSystem, +}: { # TODO: Deprecate this arg in favour of using module options - pkgs ? default_pkgs, - lib ? pkgs.lib, + pkgs ? null, + # NOTE: `defaultSystem` is the only reason this function can't go in `.lib` + system ? defaultSystem, extraSpecialArgs ? { }, _nixvimTests ? false, module, @@ -12,23 +17,32 @@ let helpers = self.lib.nixvim.override { inherit _nixvimTests; }; inherit (helpers.modules) evalNixvim; + systemMod = + if pkgs == null then + { + _file = ./standalone.nix; + nixpkgs.hostPlatform = lib.mkDefault { inherit system; }; + } + else + { + _file = ./standalone.nix; + nixpkgs.pkgs = lib.mkDefault pkgs; + }; + mkNvim = mod: let nixvimConfig = evalNixvim { modules = [ mod - # TODO: only include this when `args?pkgs`: - { - _file = ./standalone.nix; - nixpkgs.pkgs = lib.mkDefault pkgs; - } + systemMod ]; inherit extraSpecialArgs; }; inherit (nixvimConfig.config) enableMan build; + inherit (nixvimConfig._module.args.pkgs) symlinkJoin; in - (pkgs.symlinkJoin { + (symlinkJoin { name = "nixvim"; paths = [ build.package