Flakes and --args

I have a shell.nix which starts with

{
  py ? "39" # To override the default python version:  nix-shell shell.nix --argstr py 37
}:

and later contains

  python = builtins.getAttr ("python" + py) pkgs;

in order to allow overriding the default python version from the CLI.

I’m trying to upgrade this shell.nix to a flake.nix and an accompanying shell.nix which contains a shim around the flake. How can the override-version-on-CLI feature be implemented in these?

1 Like

I think this is sadly not solved right now
Here you can find some stuff on the topic: Passing options to flakes

2 Likes

since the python version is a finite enumeration, you can simply use the nix expression language to build N devShells, one for each python version

1 Like

What would be a not too verbose way of doing this?

{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
  outputs =
    inputs:
    let
      nixpkgs = import inputs.nixpkgs { inherit system; };
      system = "x86_64-linux";
    in
    {
      devShells.${ system } =
        builtins.listToAttrs
          (
            builtins.map
              (
                pythonVersion:
                {
                  name = pythonVersion;
                  value = nixpkgs.mkShell { packages = [ nixpkgs.${ pythonVersion } ]; };
                }
              )
              [ "python37" "python38" "python39" ]
          );
    };
}
$ nix3 flake show
warning: Git tree '/data/example' is dirty
evaluating ''...
git+file:///data/example
evaluating 'devShells'...
└───devShells
evaluating 'devShells.x86_64-linux'...
    └───x86_64-linux
evaluating 'devShells.x86_64-linux.python37'...
        ├───python37: development environment 'nix-shell'
evaluating 'devShells.x86_64-linux.python38'...
        ├───python38: development environment 'nix-shell'
evaluating 'devShells.x86_64-linux.python39'...
        └───python39: development environment 'nix-shell'

[kamadorueda@nixos:/data/example]$ nix develop .#python38

[kamadorueda@nixos:/data/example]$ python --version
Python 3.8.12

[kamadorueda@nixos:/data/example]$ exit

[kamadorueda@nixos:/data/example]$ nix develop .#python39

[kamadorueda@nixos:/data/example]$ python --version
Python 3.9.9

and so on

1 Like

I’m struggling to adapt this idea to multiple systems, which I have in my flake via flake-utils.each{Default,}System. Any hints?

Edit: OK, I think I’ve got it.

1 Like
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flakeUtils.url = "github:numtide/flake-utils";
  };
  outputs =
    inputs:
    inputs.flakeUtils.lib.eachSystem
      [ "x86_64-darwin" "x86_64-linux" ]
      (
        system:
        let
          nixpkgs = import inputs.nixpkgs { inherit system; };
        in
        {
          devShells =
            builtins.listToAttrs
              (
                builtins.map
                  (
                    pythonVersion:
                    {
                      name = pythonVersion;
                      value = nixpkgs.mkShell { packages = [ nixpkgs.${ pythonVersion } ]; };
                    }
                  )
                  [ "python37" "python38" "python39" ]
              );
        }
      );
}

Thanks. That’s what I eventually found myself: It took me a while to realize/remember that each{Default,}System not only maps but also transposes / turns inside-out / puts the system inside the attribute, even though (naively speaking) it looks like the attribute goes inside the system.

awesome! happy to help

Next stumbling block: trying to set one of these python versions as the default. The obvious way seems to be to bind devShell to my choice of default from the devShells, but, once again, the inside-outedness of eachSystem seems to be foiling me.

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flakeUtils.url = "github:numtide/flake-utils";
  };
  outputs =
    inputs:
    inputs.flakeUtils.lib.eachSystem
      [ "x86_64-darwin" "x86_64-linux" ]
      (
        system:
        let
          nixpkgs = import inputs.nixpkgs { inherit system; };
        in
        rec {
          # Two options:
          # devShell = devShells.python39;
          # or
          devShell = inputs.self.devShells.${ system }.python39;
          # ---
          devShells =
            builtins.listToAttrs
              (
                builtins.map
                  (
                    pythonVersion:
                    {
                      name = pythonVersion;
                      value = nixpkgs.mkShell { packages = [ nixpkgs.${ pythonVersion } ]; };
                    }
                  )
                  [ "python37" "python38" "python39" ]
              );
        }
      );
}
2 Likes

Thanks. That solves that problem.

Not sure what I was doing wrong when I tried the self-based solution earlier myself. In my case I have an explicit self parameter in outputs, but that makes a minimal difference. It works now, so I don’t know what lesson I have learned.

Also thought about the rec-based approach, but shied away from it because I recall some style guide advising against rec. Not sure I’m convinced, or rather, I don’t remember what (if any) the reasoning was behind this recommendation. Do you have any wisdom to share on the use of rec?

Continuing the theme, I have a shim in shell.nix which adapts flake.nix for use with legacy nix-shell:

(import
  (
    let
      lock = builtins.fromJSON (builtins.readFile ./flake.lock);
    in
    fetchTarball {
      url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
      sha256 = lock.nodes.flake-compat.locked.narHash;
    }
  )
  {
    src = ./.;
  }).shellNix

This works like a charm without arguments but doesn’t reproduce the parametrization described at the top of the thread. How could this be done?

Unlike the previous two steps where I was close but no cigar, here I’m hopelessly lost. I guess that the biggest problem is the mysterious relation between the shellNix requested by the shim, and the devShells provided by the flake.

1 Like

This is a question about flake-compat, and maybe there is no solution. If the flake-compat implementation does not expose the dev-shells, there is nothing to do

You could try:

  • builtins.getFlake, if your nix version is high enough
  • moving your devShells implementation to some utils.nix file, and then re-use utils.nix both in flakes.nix and in shell.nix

As per the rec or no rec, it depends, I try to use it when it makes the code more readable, and avoiding otherwise. Minimizing accidental complexity is always a good idea

1 Like

You can find some inspirations here: GitHub - loophp/nix-shell: Nix shells for PHP development

It’s the same issue with PHP…and I ended up creating a huge matrix.

I’m abstracting things here and there as much as I can to avoid duplicating code… At a slow pace, I need to learn more of the nix language.