Enable devShell as a function to take packages

Before, devShell could be set to a submodule config, a package def, or a
function taking module args and returning a submodule config. This
changes the last form to take the package set instead.

This enables cleaner configuration by not needing each option to
individually be a function of pkgs. Passing pkgs also gives more
flexibility as all the module args are available under the `moduleArgs`
attr.

Code that relied on module args not directly in the package set will now
have to access them from the `moduleArgs` attr.
This commit is contained in:
Archit Gupta 2024-02-06 21:52:22 -08:00
parent 1fa95e0d84
commit 9fe4cb1994
3 changed files with 82 additions and 37 deletions

View File

@ -439,40 +439,43 @@ To use the first example, but manually specify the package name:
``` ```
Type: Type:
devShell: Cfg | (ModuleArgs -> Cfg) | PackageDef devShell: Cfg | (Pkgs -> Cfg) | PackageDef
Cfg.packages: Pkgs -> [Derivation] Cfg.packages: [Derivation] | (Pkgs -> [Derivation])
Cfg.inputsFrom: Pkgs -> [Derivation] Cfg.inputsFrom: [Derivation] | (Pkgs -> [Derivation])
Cfg.shellHook: Str | (Pkgs -> Str) Cfg.shellHook: Str | (Pkgs -> Str)
Cfg.env: (AttrsOf Str) | (Pkgs -> (AttrsOf Str)) Cfg.env: (AttrsOf Str) | (Pkgs -> (AttrsOf Str))
Cfg.stdenv: Pkgs -> Stdenv Cfg.stdenv: Stdenv | (Pkgs -> Stdenv)
``` ```
The devshell options allow you to configure `devShells.${system}.default`. It is The devshell options allow you to configure `devShells.${system}.default`. It is
split up into options in order to enable multiple modules to contribute to its split up into options in order to enable multiple modules to contribute to its
configuration. configuration.
The first three correspond to `mkShell` arguments. `devShell` can alternatively be set to a package definition, which is then used
as the default shell, overriding other options.
`devShell.packages` is a function that takes the package set and returns a list `devShell` can also be set to a function that takes the package set and returns
of packages to add to the shell. an attrSet of the devShell configuration options.
`devShell.inputsFrom` is a function that takes the package set and returns a The options available are as follows:
list of packages whose deps should be in the shell.
`devShell.packages` is a list of packages to add to the shell. It can optionally
be a function taking the package set and returning such a list.
`devShell.inputsFrom` is a list of packages whose deps should be in the shell.
It can optionally be a function taking the package set and returning such a
list.
`devShell.shellHook` is a string that provides bash code to run in shell `devShell.shellHook` is a string that provides bash code to run in shell
initialization. It can optionally be a function to such a string in order to initialization. It can optionally be a function taking the package set and
access packages. returning such a string.
`devShell.env` is for setting environment variables in the shell. It is an `devShell.env` is for setting environment variables in the shell. It is an
attribute set mapping variables to values. It can optionally be a function to attribute set mapping variables to values. It can optionally be a function
such an attribute set in order to access packages. taking the package set and returning such an attribute set.
`devShell.stdenv` allows changing the stdenv used for the shell. It is a `devShell.stdenv` is the stdenv package used for the shell. It can optionally be
function that takes the package set and returns the stdenv to use. a function takeing the package set and returning the stdenv to use.
`devShell` can alternatively be set to a package definition, which is then used
as the default shell, overriding the above options. It can also be set to a
function that takes `moduleArgs` and returns an attrSet of the above options.
For example, these can be configured as follows: For example, these can be configured as follows:
@ -481,18 +484,18 @@ For example, these can be configured as follows:
inputs.flakelight.url = "github:nix-community/flakelight"; inputs.flakelight.url = "github:nix-community/flakelight";
outputs = { flakelight, ... }: outputs = { flakelight, ... }:
flakelight ./. { flakelight ./. {
devShell = { devShell = pkgs: {
# Include build deps of emacs # Include build deps of emacs
inputsFrom = pkgs: [ pkgs.emacs ]; inputsFrom = [ pkgs.emacs ];
# Add coreutils to the shell # Add coreutils to the shell
packages = pkgs: [ pkgs.coreutils ]; packages = [ pkgs.coreutils ];
# Add shell hook. Can be a function if you need packages # Add shell hook. Can be a function if you need packages
shellHook = '' shellHook = ''
echo Welcome to example shell! echo Welcome to example shell!
''; '';
# Set an environment var. `env` can be an be a function # Set an environment var. `env` can be an be a function
env.TEST_VAR = "test value"; env.TEST_VAR = "test value";
stdenv = pkgs: pkgs.clangStdenv; stdenv = pkgs.clangStdenv;
}; };
}; };
} }
@ -521,6 +524,20 @@ To add the build inputs of one of your packages, you can do as follows:
} }
``` ```
To override the devShell, you can use a package definition as such:
```nix
{
inputs.flakelight.url = "github:nix-community/flakelight";
outputs = { flakelight, ... }:
flakelight ./. {
devShell = { mkShell, hello }: mkShell {
pacakges = [ hello ];
};
};
}
```
### devShells ### devShells
``` ```

View File

@ -4,23 +4,22 @@
{ config, lib, flakelight, genSystems, moduleArgs, ... }: { config, lib, flakelight, genSystems, moduleArgs, ... }:
let let
inherit (builtins) attrNames hasAttr; inherit (lib) filterAttrs functionArgs mapAttrs mkDefault mkIf mkMerge
inherit (lib) all filterAttrs functionArgs mapAttrs mkDefault mkIf mkMerge
mkOption; mkOption;
inherit (lib.types) coercedTo functionTo lazyAttrsOf lines listOf nullOr inherit (lib.types) attrs coercedTo functionTo lazyAttrsOf lines listOf nullOr
package str submodule; package str submodule;
inherit (flakelight) supportedSystem; inherit (flakelight) supportedSystem;
inherit (flakelight.types) function optCallWith optFunctionTo packageDef; inherit (flakelight.types) function optCallWith optFunctionTo packageDef;
devShellModule.options = { devShellModule.options = {
inputsFrom = mkOption { inputsFrom = mkOption {
type = functionTo (listOf package); type = optFunctionTo (listOf package);
default = _: [ ]; default = [ ];
}; };
packages = mkOption { packages = mkOption {
type = functionTo (listOf package); type = optFunctionTo (listOf package);
default = _: [ ]; default = [ ];
}; };
shellHook = mkOption { shellHook = mkOption {
@ -34,7 +33,7 @@ let
}; };
stdenv = mkOption { stdenv = mkOption {
type = functionTo package; type = optFunctionTo package;
default = pkgs: pkgs.stdenv; default = pkgs: pkgs.stdenv;
}; };
@ -45,17 +44,18 @@ let
}; };
}; };
moduleFromFn = fn: wrapFn = fn: pkgs:
if all (a: hasAttr a moduleArgs) (attrNames (functionArgs fn)) if (functionArgs fn == { }) || !(package.check (pkgs.callPackage fn { }))
then fn moduleArgs then fn pkgs
else { overrideShell = fn; }; else { overrideShell = fn; };
in in
{ {
options = { options = {
devShell = mkOption { devShell = mkOption {
default = null; default = null;
type = nullOr (coercedTo function moduleFromFn type = nullOr (coercedTo function wrapFn
(submodule devShellModule)); (coercedTo attrs (x: _: x)
(functionTo (submodule devShellModule))));
}; };
devShells = mkOption { devShells = mkOption {
@ -67,7 +67,7 @@ in
config = mkMerge [ config = mkMerge [
(mkIf (config.devShell != null) { (mkIf (config.devShell != null) {
devShells.default = mkDefault ({ pkgs, mkShell }: devShells.default = mkDefault ({ pkgs, mkShell }:
let cfg = mapAttrs (_: v: v pkgs) config.devShell; in let cfg = mapAttrs (_: v: v pkgs) (config.devShell pkgs); in
mkShell.override { inherit (cfg) stdenv; } mkShell.override { inherit (cfg) stdenv; }
(cfg.env // { inherit (cfg) inputsFrom packages shellHook; })); (cfg.env // { inherit (cfg) inputsFrom packages shellHook; }));
}) })

View File

@ -337,6 +337,34 @@ in
}) })
(f: lib.isDerivation f.devShells.x86_64-linux.default); (f: lib.isDerivation f.devShells.x86_64-linux.default);
devShell-pkgs-arg = test
(flakelight ./empty {
devShell = pkgs: {
inputsFrom = [ pkgs.emacs ];
packages = [ pkgs.coreutils ];
shellHook = ''
echo Welcome to example shell!
'';
env.TEST_VAR = "test value";
stdenv = pkgs.clangStdenv;
};
})
(f: lib.isDerivation f.devShells.x86_64-linux.default);
devShell-pkgs-arg-set = test
(flakelight ./empty {
devShell = { emacs, coreutils, clangStdenv, ... }: {
inputsFrom = [ emacs ];
packages = [ coreutils ];
shellHook = ''
echo Welcome to example shell!
'';
env.TEST_VAR = "test value";
stdenv = clangStdenv;
};
})
(f: lib.isDerivation f.devShells.x86_64-linux.default);
devShells = test devShells = test
(flakelight ./empty { (flakelight ./empty {
devShell.inputsFrom = pkgs: [ pkgs.emacs ]; devShell.inputsFrom = pkgs: [ pkgs.emacs ];