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 `` 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.