Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example of dogfooding (fix #121) #197

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
- a check with runCommand
'';
};
dogfood = {
path = ./template/dogfood;
description = ''
A minimal flake using flake-parts creating flake modules to build its own outputs.
'';
};
};
flakeModules = {
easyOverlay = ./extras/easyOverlay.nix;
Expand Down
27 changes: 27 additions & 0 deletions template/dogfood/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
description = "Description for the project";

inputs = {
nixpkgs_24_05.url = "github:NixOS/nixpkgs/nixos-24.05";
};

outputs = { flake-parts, self, nixpkgs, ... }@inputs:
flake-parts.lib.mkFlake
{
inherit inputs;
}
(topLevel: {
imports = [
flake-parts.flakeModules.partitions
./modules/dev.nix
];

systems = [ "x86_64-linux" "aarch64-darwin" ];

partitionedAttrs.devShells = "dogfood";
partitionedAttrs.packages = "dogfood";
Comment on lines +21 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having to list these is not great UX.

I think I'd prefer to add a parameter to mkFlake.
I think we could have a modules specialArg to make that nice:

mkFlake {
  inherit inputs;
  publicModules.foo = ./foo.nix;
}
({ modules, ... }: {
  imports = [
    modules.self.foo  # based on the publicModules parameter
    modules.git-hooks-nix.default  # just a mapAttrs of inputs; lazy and cheap
  ];
})

It's a bit more custom, but it avoids the complexity of having partitions, probably improving error messages as well, and slightly less boilerplate.

It does put the module in a different scope, but I think this can be worked around, with techniques similar to withSystem, and it'd look like:

  publicModules = local@{ config, withSystem, ... }: { # not defining a module; just a function
  
    /** A module that adds a check using a package built in the local way,
        with no interference except `follows` */
    foo = userFlake@{ self, ... }: {
      perSystem = { system, pkgs, ... }:
        withSystem system (localPerSystem@{ ... }: {
          checks.foo = pkgs.runCommand "check-foo" {
            nativeBuildInputs = [ localPerSystem.config.packages.foo ];
          } "foo ${userFlake.self} && touch $out";
        });
    };
  };

However, actually needing access to the local flake seems quite rare for flake-parts modules, so we might not even need it for now.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a flake module that defines a flake module is a good abstract, because it distinguishes the module author's inputs from the module user's inputs. I like the idea to make publicModules a partition of preparatory step by default before the "main" partition. This design has precedent in other languages, e.g. sbt.

Copy link
Contributor Author

@Atry Atry Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why we cannot put bootstrap modules to flakeModules is that flakeModules is a submodule of the mkFlake's top level module, and the mkFlake's top level module cannot depend on its own submodule.

Can we make mkFlake a submodule instead of a function, then we can create a "meta-module" as the new top-level module to set up special args for the mkFlake submodule. Eventually the user could simply use nixpkgs's evalModules to evaluate the meta-module to build the flake:

(lib.evalModules {
  modules = [
    inputs.flake-parts.modules.metaModule
    {
      inherit inputs;
      publicModules.myPublicModule = {};
      mkFlake = {modules, flake-parts-lib, lib, inputs, ...}: {
        imports = [ modules.myPublicModule ];
        perSystem = { ... };
      };
    }
  ];
}).config.mkFlake.flake

partitions.dogfood = {
module = topLevel.config.flake.flakeModules.dev;
};
});
}
33 changes: 33 additions & 0 deletions template/dogfood/modules/custom-hello.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{ inputs, flake-parts-lib, lib, ... }:
{
imports = [
inputs.flake-parts.flakeModules.flakeModules
];

flake.flakeModules.customHello = flakeModule:
let
cfg = flakeModule.config.customHello;
in
{
options.customHello.enableUserStdenv = lib.mkEnableOption "stdenv from `flakeModules.customHello` user's nixpkgs";
options.perSystem = flake-parts-lib.mkPerSystemOption ({ pkgs, system, ... }: {
packages.hello =
(inputs.nixpkgs_24_05.legacyPackages.${system}.hello.override {
stdenv =
if cfg.enableUserStdenv then
pkgs.stdenv
else
inputs.nixpkgs_24_05.legacyPackages.${system}.stdenv;
}).overrideAttrs (oldAttrs: {
meta = oldAttrs.meta // {
description = "A hello package from the `flakeModules.customHello` author's nixpkgs 24.05, built with stdenv from ${
if cfg.enableUserStdenv then
"the `flakeModules.customHello` user's nixpkgs"
else
"the `flakeModules.customHello` author's nixpkgs 24.05"
}";
};
});
});
};
}
27 changes: 27 additions & 0 deletions template/dogfood/modules/dev.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{ flake-parts-lib, inputs, ... }@topLevel:
{
imports = [
inputs.flake-parts.flakeModules.flakeModules

# For `topLevel.config.flake.flakeModules.customHello`
./custom-hello.nix
];

flake.flakeModules.dev = {
imports = [
# For `perSystem.config.packages.hello
topLevel.config.flake.flakeModules.customHello
];

config.customHello.enableUserStdenv = true;

options.perSystem = flake-parts-lib.mkPerSystemOption ({ pkgs, ... }@perSystem: {
devShells.default = pkgs.mkShell {
buildInputs = [ perSystem.config.packages.hello ];
shellHook = ''
hello
'';
};
});
};
}