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:
devShell: Cfg | (ModuleArgs -> Cfg) | PackageDef
Cfg.packages: Pkgs -> [Derivation]
Cfg.inputsFrom: Pkgs -> [Derivation]
devShell: Cfg | (Pkgs -> Cfg) | PackageDef
Cfg.packages: [Derivation] | (Pkgs -> [Derivation])
Cfg.inputsFrom: [Derivation] | (Pkgs -> [Derivation])
Cfg.shellHook: Str | (Pkgs -> 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
split up into options in order to enable multiple modules to contribute to its
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
of packages to add to the shell.
`devShell` can also be set to a function that takes the package set and returns
an attrSet of the devShell configuration options.
`devShell.inputsFrom` is a function that takes the package set and returns a
list of packages whose deps should be in the shell.
The options available are as follows:
`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
initialization. It can optionally be a function to such a string in order to
access packages.
initialization. It can optionally be a function taking the package set and
returning such a string.
`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
such an attribute set in order to access packages.
attribute set mapping variables to values. It can optionally be a function
taking the package set and returning such an attribute set.
`devShell.stdenv` allows changing the stdenv used for the shell. It is a
function that takes the package set and returns 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.
`devShell.stdenv` is the stdenv package used for the shell. It can optionally be
a function takeing the package set and returning the stdenv to use.
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";
outputs = { flakelight, ... }:
flakelight ./. {
devShell = {
devShell = pkgs: {
# Include build deps of emacs
inputsFrom = pkgs: [ pkgs.emacs ];
inputsFrom = [ pkgs.emacs ];
# Add coreutils to the shell
packages = pkgs: [ pkgs.coreutils ];
packages = [ pkgs.coreutils ];
# Add shell hook. Can be a function if you need packages
shellHook = ''
echo Welcome to example shell!
'';
# Set an environment var. `env` can be an be a function
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
```

View File

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

View File

@ -337,6 +337,34 @@ in
})
(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
(flakelight ./empty {
devShell.inputsFrom = pkgs: [ pkgs.emacs ];