modular-flake/README.md

218 lines
4.8 KiB
Markdown

This is my take on a module system used to simplify the definition of a Nix
flake. [flake-parts](https://github.com/hercules-ci/flake-parts) exists to do basically
the same thing, and you should check it out, before using this flake.
# Quick start
Add to your flake input:
```nix
# flake.nix
inputs.modularFlake.url = "https://git.dzuchun.ing/dzu/modular-flake.git";
# make it follow your nixpkgs, if you want
```
Use in your flake output:
```nix
# flake.nix
outputs = { modularFlake, ... }: modularFlake.mkFlake { };
```
Well, the code above actually produces an empty flake. For most use-cases, you would
have to _at least_ provide it with one system to use:
```nix
# flake.nix
outputs = { modularFlake, ... }: modularFlake.mkFlake { systems = [ "x86_64-linux" ] };
```
# Defining stuff
You can export things from your flake, using _nix modules_. Nix module is
- an attrset of a specific form, or
- a lambda evaluating to such attrset, or
- a path to a `.nix` file, containing either of those
You should emit your outputs in the `config` attribute, and as-if `<system>` component of
the path does not exist.
Here is a module, defining a check called `mathWorks`:
```nix
# math-check.nix
{
config.checks.mathWorks =
assert 2 + 2 == 4;
null;
}
```
Note, that unlike in flake output `checks` attribute, you may emit any kind of expressions -- for `checks`, they will be force-evaluated/built.
Here is how you can define an app:
```nix
# meow-app.nix
{ lib, hasSystem, pkgs }:
lib.optionalAttrs hasSystem {
config.apps.meow.program = pkgs.writeShellScript "${pkgs.coreutils}/bin/echo 'meow'";
}
```
And here is an overlay:
```nix
# my-overlay.nix
_: {
config.overlays.myOverlay = final: prev: { /* whatever you like */ };
}
```
To apply your flake modules to your flake output, put them into `modules` argument of `mkFlake`:
```nix
# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
modules = [
./math-check.nix
./meow-app.nix
./my-overlay.nix
];
};
```
# Defining ways to define stuff
But what if you don't want to use standard flake outputs? What if you want to define your own outputs, with their own schemas and stuff?
But what do you do, if your outputs conflict with the defaults?
You define your own outputs, of course!
```nix
# cats-output.nix
{ lib, ... }:
let
petCats =
{
lib,
pkgs,
cats,
}:
pkgs.writeShellScript "pet-cats" (
lib.concatStringsSep "\n" (
map (
cat:
if cat == "niko" then
builtins.warn "niko is not a cat!!!" ""
else
"${pkgs.coreutils}/bin/printf 'cat %s has been petted!' ${lib.escapeShellArg cat}"
) (lib.unique (lib.sort (a: b: a < b) cats))
)
);
in
{
config.outputKinds = [
(_: {
options = with lib; {
cats = mkOption {
type = with types; listOf str;
description = "a list of cats to pet";
};
};
outputs =
{
lib,
hasSystem,
config,
system,
pkgs,
...
}:
lib.optionalAttrs hasSystem {
apps.${system}.catPetters = {
type = "app";
program = petCats {
cats = config.cats;
inherit lib pkgs;
};
};
};
})
];
};
```
You can now pet cats, in the modular way:
```nix
# cat-definitions.nix
{
config = {
cats = [ "orange" "pink" ];
};
}
```
```nix
# niko-definition.nix
{
config = {
# TODO: add cuteness check
cats = [ "niko" ]; # uh oh
};
}
```
```nix
# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
outputs = [
./cats-output.nix
];
modules = [
./cat-definitions.nix
./niko-definition.nix
];
};
```
Note, that `outputs` parameter defines a complete set of outputs you use. So (for example)
any `checks` you emmit with the code above will be ignored.
## Using default outputs
Default output definitions are available under `modularFlake.defaultOutputs`. Here is how
you would subset your flake outputs to "apps only":
```nix
# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
outputs = with modularFlake.defaultOutputs; [ apps ];
...
};
```
Of course, you can combine default outputs with your own:
```nix
# flake.nix
outputs = { modularFlake, ... }: modularFlake.lib.mkFlake {
outputs = with modularFlake.defaultOutputs; [
apps
./cats-output.nix
];
...
};
```
## Using other flake inputs
In case you need to use some other flake input in your module definition, and want to keep
the module system ergonomics, here is the way:
```nix
# flake.nix
outputs = { modularFlake, ... }@flakeInputs: modularFlake.lib.mkFlake {
inherit flakeInputs;
...
};
```
All of your modules will now see your flake inputs at `flakeInputs` argument.