[Nix Language Question] Linking a list of packages to `home.files`

Dear Nix community,

I can’t get the following expression working to add a list of additionalJDKs packages to my home directory via NixOS-module home-manager:

proglangs.nix:

{ pkgs, ... }:

let
  additionalJDKs = with pkgs; [ temurin-bin-11 temurin-bin-17 ];
in
{
  # ...
  programs.java = { /*...*/ };

  home.sessionPath = [ "$HOME/.jdks" ];
} //
(builtins.listToAttrs (builtins.map (jdk: { name = home.file.".jdks/${jdk.version}".source; value = jdk; }) additionalJDKs))

The problem with this script is, that the home-variable isn’t found (what I’m not really getting - the scope should be the same as the “main” set…)

error:
       … while calling the 'head' builtin

         at /nix/store/80v3x99d9cl7h9fbhqrpajwg4vjyxg6y-source/lib/attrsets.nix:922:11:

          921|         || pred here (elemAt values 1) (head values) then
          922|           head values
             |           ^
          923|         else

       … while evaluating the attribute 'value'

         at /nix/store/80v3x99d9cl7h9fbhqrpajwg4vjyxg6y-source/lib/modules.nix:807:9:

          806|     in warnDeprecation opt //
          807|       { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
             |         ^
          808|         inherit (res.defsFinal') highestPrio;

       (stack trace truncated; use '--show-trace' to show the full trace)

       error: undefined variable 'home'

       at /nix/store/byh8bclwsdlx5awqr35jk2mwpfdzimvc-source/home-manager/devel/proglangs.nix:83:53:

           82| } //
           83| ( builtins.listToAttrs (builtins.map ( jdk: {name = home.file.".jdk/${jdk.version}".source; value = jdk; }) additionalJDKs))
             |                                                     ^
           84|

Link to referenced file

A messy workaround I’m currently using to archive the wanted behavior by neglecting length-dynamic evaluation:

{ pkgs, ... }:

let
  additionalJDKs = with pkgs; [ temurin-bin-11 temurin-bin-17 ];
in
{
  # ...
  programs.java = { /*...*/ };

  home.sessionPath = [ "$HOME/.jdks" ];
  home.file.".jdks/${(builtins.elemAt additionalJDKs 0).version}".source = (builtins.elemAt additionalJDKs 0);
  home.file.".jdks/${(builtins.elemAt additionalJDKs 1).version}".source = (builtins.elemAt additionalJDKs 1);
}

Related to: Fix collision with multiple JDKs - #2 by moaxcp

This is a declaration. You’re setting the value of the home attribute of the attrset that makes up the expression your function returns (the entire file contains a function that takes pkgs - and a bunch of ignored arguments - and returns an attrset).

This is also a declaration, however here you use the value of the home variable in an expression to assign something to an attribute of an attrset. There is no home variable in any scope of your file, the fact that you’re returning an attrset that has a home attr from your function doesn’t magically create a variable called home.

You can take the config argument from your function args to make this work:

{ pkgs, config, ... }:

let
  additionalJDKs = with pkgs; [ temurin-bin-11 temurin-bin-17 ];
in
{
  # ...
  programs.java = { /*...*/ };

  home.sessionPath = [ "$HOME/.jdks" ];
} //
(builtins.listToAttrs (builtins.map (jdk: {
  name = config.home.file.".jdks/${jdk.version}".source;
  value = jdk;
}) additionalJDKs))

The home-manager module system will resolve your modules such that the config arg ends up containing a copy of your entire config (note that you can easily get into infinite recursion this way).

With just nix language features, you would need to declare the home variable in a let binding or something first, and inherit it in your attrset. Pretty sure that’s not possible in this case though. It totally is, I got confused about what you were trying to achieve until I read your second snippet.

That said, this code is still not correct. What you really want is something like (didn’t think too hard about listToAttrs here, may still be wrong, just trying to point out you don’t need home at all):

{ pkgs, ... }:

let
  additionalJDKs = with pkgs; [ temurin-bin-11 temurin-bin-17 ];
in
{
  # ...
  programs.java = { /*...*/ };

  home.sessionPath = [ "$HOME/.jdks" ];
  home.file = (builtins.listToAttrs (builtins.map (jdk: {
    name = ".jdks/${jdk.version}";
    value = {
      source = jdk;
    };
  }) additionalJDKs));
}
1 Like

Thanks a lot for your answer, I finally could solve the issue following your explanation and example :slight_smile: