Separating default.nix into default.nix and shell.nix

I have some nix I’m trying to refactor. It’s a defautl.nix that is a lambda with a ton of arguments that all have defaults. All of them them are used with nix-build --arg ... to get different behaviour. The reason I’m refactoring is because default.nix relies on lib.inNixShell to either serve a build or a shell, and this is causing problems. I thought it would be really easy to separate it into a default.nix and a shell.nix but this has turned out not to be the case due to how nix works. The best structure I can currently come up with is

  • nix/default.nix: this is where the old default.nix now lives, with a couple changes
    • all the default argument values have been removed
    • the if statement has been separated into a set { build = ...; shell = ...; }
  • default.nix: is a lambda with all the arguments and their defaults, that passes them into nix/default.nix and returns the build attribute.
  • shell.nix: same as default.nix but returns the shell attributes.

This is really bad because we now have to manually make sure that the defaults stay in sync between default.nix and shell.nix. not to mention all of the many arguments are duplicated across three files and in two of them they’re duplicated within the file, as they simply just get forwarded into nix/default.nix.

What I can’t do

  • declare the defaults in nix/default.nix and use { ... }@args: (import ./nix args).build
    • the --arg flag won’t pass in anything
  • store all the defaults in a single set so at least they don’t have to be duplicated across default.nix and shell.nix
    • the defaults depend on each other. I don’t think I can replicate this with a normal set.

I’ve been thinking about it and I’m at a loss. I’m also open to a different approach that doesn’t rely on --arg but still allows us to configure builds easily.

1 Like

I have come up with something that might be the best we can do.

  • change the if statement in default.nix to a set { build = ...; shell = ...; } and move it to nix/default.nix
  • make the new default.nix
    { args ? {} }: (import ./nix args).build
    
    similarly for shell.nix
  • instead of using nix-build --arg attr1 val1 --arg attr2 val2 ... you now use nix-build --arg args '{ attr1 = val1; attr2 = val2; ... }'

Okay, I think this is the answer I’m looking for:

build-or-shell:
{ /*all args with defaults*/ }:
...
{ build = ...;
  shell = ...;
}.${build-or-shell}

then for default.nix you use import ./nix "build", and similarly for shell.nix.

Slight improvement:

let
  f = build-or-shell: { /* all args with defaults*/ }:
    ...
in
{ build = f "build";
  shell = f "shell";
}

Then use (import ./nix).build and (import ./nix).shell