nix-community / nixGL

A wrapper tool for nix OpenGL application [maintainer=@guibou]
628 stars 76 forks source link

Perfecting NixGL Wrappers for Home-Manager Use #163

Open RicSanOP opened 3 months ago

RicSanOP commented 3 months ago

Hello Everyone,

I am new to using nix and home-manager and have been enjoying tinkering around with functional package management. I have reached the point where I can more or less read nix code (like the wrappers in #114). I have gone through the process of setting up home-manager on a non-nixOS distro (Pop_OS! in my case) and have rewritten @ntsanov wrapper code from #114 into the following functions

{ pkgs }:

{
  # This nix file contains helper functions for wrapping other
  # graphical nix packages with nixGL calls depending on the
  # graphics library and graphics chip of choice
  nixIntelWrap = glib: pkg:
  let
    gpkg = "nix${glib}Intel";
    bins = "${pkg}/bin";
  in
  pkgs.buildEnv {
    name = "${gpkg}-${pkg.name}";
    paths =
      [ pkg ] ++
      (map
        (bin: pkgs.hiPrio (
      pkgs.writeShellScriptBin bin ''
exec "${pkgs.nixgl.${gpkg}}/bin/${gpkg}" "${bins}/${bin}" "$@"
      ''
    ))
    (builtins.attrNames (builtins.readDir bins))
      );
  };
  nixNvidiaWrap = glib: ndv: pkg:
  let
    gpkg = "nix${glib}Nvidia";
    gcmd = "${gpkg}-${ndv}";
    bins = "${pkg}/bin";
  in
  pkgs.buildEnv {
    name = "${gpkg}-${pkg.name}";
    paths =
      [ pkg ] ++
      (map
        (bin: pkgs.hiPrio (
      pkgs.writeShellScriptBin bin ''
export __NV_PRIME_RENDER_OFFLOAD=1 
export __GLX_VENDOR_LIBRARY_NAME=nvidia
exec "${pkgs.nixgl.auto.${gpkg}}/bin/${gcmd}" "${bins}/${bin}" "$@"
      ''
    ))
    (builtins.attrNames (builtins.readDir bins))
      );
  };
}

Here are some successful usages of these wrapper functions in my home.nix file:

home.packages = [
    # # Adds the 'hello' command to your environment. It prints a friendly
    # # "Hello, world!" when run.
    # pkgs.hello

    # # It is sometimes useful to fine-tune packages, for example, by applying
    # # overrides. You can do that directly here, just don't forget the
    # # parentheses. Maybe you want to install Nerd Fonts with a limited number of
    # # fonts?
    # (pkgs.nerdfonts.override { fonts = [ "FantasqueSansMono" ]; })

    # # You can also create simple shell scripts directly inside your
    # # configuration. For example, this adds a command 'my-hello' to your
    # # environment:
    # (pkgs.writeShellScriptBin "my-hello" ''
    #   echo "Hello, ${config.home.username}!"
    # '')

    # nixGL packages for running OpenGL and Vulkan nix packages
    # using either an Nvidia driver or Intel driver (Nvidia is default)
    pkgs.nixgl.auto.nixGLNvidia
    pkgs.nixgl.auto.nixVulkanNvidia
    pkgs.nixgl.nixGLIntel
    pkgs.nixgl.nixVulkanIntel

    # terminal-shell-editor environment
    #(nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.wezterm)
    #(nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.alacritty)
    pkgs.zsh
    pkgs.neovim

    # setup podman for container workloads
    pkgs.podman
    pkgs.docker-compose
    pkgs.podman-compose

    # productivity and workflow packages
    (nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.vivaldi)
    (nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.obsidian)
  ];

  programs.wezterm = {
    enable = true;
    package = nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.wezterm;
  };

  programs.alacritty = {
    enable = true;
    package = nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.alacritty;
    settings = {
    };
  };

Now as you may have noticed, the nixgl wrap around pkgs.alacritty and pkgs.wezterm have been commented out from home.packages as they are required in their respective programs.__TERMINAL__.package attributes. Providing the nixgl wrapped derivation has worked fine for wezterm, but unfortunately hasn't worked for alacritty due to the following error:

error:
       … while evaluating a branch condition

         at /nix/store/adkf0aw7d4yfxspb1h23zkhxlskidfpi-source/lib/lists.nix:56:9:

           55|       fold' = n:
           56|         if n == len
             |         ^
           57|         then nul

       … while calling the 'length' builtin

         at /nix/store/adkf0aw7d4yfxspb1h23zkhxlskidfpi-source/lib/lists.nix:54:13:

           53|     let
           54|       len = length list;
             |             ^
           55|       fold' = n:

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

       error: attribute 'version' missing

       at /nix/store/ddnh5dcx1z38990c7h3fzzcy1vq7g9la-source/modules/programs/alacritty.nix:7:32:

            6|   cfg = config.programs.alacritty;
            7|   useToml = lib.versionAtLeast cfg.package.version "0.13";
             |                                ^
            8|   tomlFormat = pkgs.formats.toml { };

It is clear that the wrapper function I use somehow loses the version attribute which is set in nixpkgs at https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/terminal-emulators/alacritty/default.nix and referenced in home-manager at https://github.com/nix-community/home-manager/blob/master/modules/programs/alacritty.nix as can be seen in the error line useToml = lib.versionAtLeast cfg.package.version "0.13";. I came to understand this after playing around with the builtins.trace function and seeing that the wrapped output did not have a version parameter.

Even though I understand the issue, I am very much at a loss on how to re-add the version attribute to the nixgl wrapped derivation. Even if a hacky, I would deeply appreciate any help on navigating this issue and getting the nixgl wrapped version of alacritty to work with home-manager. Thank you to any and all in advance.

Smona commented 3 months ago

Hey @RicSanOP, my wrapper handles this by returning the original package modified via overrideAttrs. This way, only the necessary attributes are overridden, and any other attributes required by home manager are preserved.

The problem with your wrapper is that buildEnv does not inherit any of the attributes from the wrapped derivation. I'm not very familiar with buildEnv, but you could try using overrideAttrs on it if it supports overriding, or explore merging in attributes from the original derivation with the attrset merge operator (//).

RicSanOP commented 3 months ago

Hey @Smona , Thank you for your kind and prompt response.

I had taken a look at your wrapper previously. What had initially caused me to hesitate from using it was that the wrapped package is named differently from the original (with the nixGL- prefix). But I guess that was just a matter of not overriding the old name. I will also admit that the setup was a bit more daunting. Does your wrapper also handle .desktop files well? I am sure you see my lack of experience on this matter, so any points of comparison between your and @ntsanov wrapper on the same thread would be appreciated.

The merge operator // was exactly what I needed. I set my programs.alacritty.package to package = pkgs.alacritty // (nixgl.nixNvidiaWrap "GL" nvidiaDriverVersion pkgs.alacritty); which in essence only modifies elements that the wrapped version of the package changes and allows the home-manager module to run smoothly. I imagine this technique would work well for other packages out there with their own home-manager modules. Thank you for the tip @Smona

Smona commented 3 months ago

thanks for that feedback!

the nixGL- name prefix only really affects the nix store path in this case, and I thought it could be helpful if inspecting the store to see the wrapper packages differentiated from the original ones, rather than just having the same name and a different hash. the output binaries still have the original names.

.desktop files appear to work perfectly with my wrapper, because all files from the original package are symlinked into the wrapper package so they exist where they need to once installed. The setup may be a little more complicated, but you can skip defining the nixgl prefix setting if you only need to use one GPU variant. I designed it that way so that the config module which includes nixgl-wrapped packages can be shared between multiple system definitions, with the nixGL variant passed in via the per-system top-level config. This also prevents unnecessary nixGL variants from being installed on each system like your example code does. you can see a full example of integrating the wrapper this way in my config repo.

As far as buildEnv vs overriding the package, IIRC the main downside with buildEnv was that supporting files (like .desktop or files in /share) were not linked into the user profile if the returned value from the wrapper wasn't a derivation, so app installations would be a bit broken.

ShalokShalom commented 2 months ago

For cross reference, there is also https://github.com/nix-community/home-manager/issues/3968

May we continue there, and close this issue?