nix-community/nixGL

Making nixGL works with Home Manager

thanhnguyen2187 opened this issue · 28 comments

Hi.

Thanks for the awesome work!

I am using Home Manager within Pop OS 22.04 and have got stuck trying to make nixGL works. My configuration basically looks like this:

{ config, pkgs, ... }:
let
  # TODO: find a way to make the config more declarative
  #   nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
  #   nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update
  pkgsUnstable = import <nixpkgs-unstable> {};
  nixGL = import <nixgl> {};
in
{
  programs.alacritty = {
    enable = true;
    settings = {
      font = {
        size = 12;
        normal = {
          family = "JetBrains Mono Nerd Font";
          style = "Regular";
        };
      };
    };
    package = pkgs.writeShellScriptBin "alacritty" ''
      #!/bin/sh

      ${nixGL.auto.nixGLNvidia}/bin/nixGLNvidia ${pkgs.alacritty}/bin/alacritty "$@"
    '';
  };
}

Try running home-manager switch gives me this:

building '/nix/store/38na3sdwf8j5gfhh4mm5x337q0ph384f-impure-nvidia-version-file.drv'...
error: 'submoduleWith' at /nix/store/z2w2v2hv8y3pacq2bjjk5sxmsrnlq188-nixpkgs/nixpkgs/lib/types.nix:570:7 called with unexpected argument 'description'

       at /home/thanh/.nix-defexpr/channels/home-manager/nixos/common.nix:14:14:

           13|
           14|   hmModule = types.submoduleWith {
             |              ^
           15|     description = "Home Manager module";
(use '--show-trace' to show detailed location information)

While nix-env -iA nixgl.auto.nixGLDefault, and then nixGL alacritty works as expected.

I found a few issues on nixGL is not a proper package of nixpkgs, and guess that it probably is the issue. I wonder if there is a workaround? Using nix and Home Manager feels so good that I do not want to come back to good old apt...

Thanks!

  # TODO: find a way to make the config more declarative

flakes is what you are searching for.

    package = pkgs.writeShellScriptBin "alacritty" ''
      #!/bin/sh

      ${nixGL.auto.nixGLNvidia}/bin/nixGLNvidia ${pkgs.alacritty}/bin/alacritty "$@"
    '';

I don't think you can do this. I would try to add this to home.package instead and hope it gets in $PATH before alacrity. If that does not work I would try to make the wrapper you created a full package, without writeShellScript*.

Thanks a lot for the comment @SuperSandro2000. Forgot to mention that I did add nixGL.auto.nixGLNvidia to my home.packages, but the error was the same. Do you have any other idea?

For some inspiration, I wrapped my google-chrome with nixGL like this: Gerschtli/nix-config@ab51b30

Maybe there could be a library function exported by this flake to get the needed lines directly without reading the package. @guibou What do you think? If we can agree on an API, I could create a PR :)

https://github.com/dali99/nix-dotfiles/blob/9639108c536fd7f1ab4d5606d5f5ee1b51bd0a4c/profiles/xsession/default.nix#L121 I have this for my non-nixos machines.

This successfully starts my window manager and lets me run gui programs without wrapping those induvidually

If anyone lands here also see my answer in Issue #114. You can wrap packages with nixGL such that they will start without any issues.

This way you don't necessarily need to install another window manager. My use case is to use Home Manager as a replacement for brew.

@thanhnguyen2187
Here is my wrapper example musing both GL and Vulkan drivers

  nixGLVulkanMesaWrap = pkg:
    pkgs.runCommand "${pkg.name}-nixgl-wrapper" { } ''
      mkdir $out
      ln -s ${pkg}/* $out
      rm $out/bin
      mkdir $out/bin
      for bin in ${pkg}/bin/*; do
       wrapped_bin=$out/bin/$(basename $bin)
       echo "${lib.getExe pkgs.nixgl.nixGLIntel} ${
         lib.getExe pkgs.nixgl.nixVulkanIntel
       } $bin \$@" > $wrapped_bin
       chmod +x $wrapped_bin
      done
    '';

here you can see what I have done so you can easly set it up
rengare/dotfiles

Smona commented

I have been following a similar approach, but combining pkgs.symlinkJoin with pkgs.makeWrapper. Unfortunately, this fails for firefox in home manager, since home manager tries to override the package 😢.

error: attribute 'override' missing

       at /nix/store/xadqf1cap0pvxsx2yyyk57wm13fyir6x-source/modules/programs/firefox.nix:502:9:

          501|       else if versionAtLeast config.home.stateVersion "19.09" then
          502|         cfg.package.override (old: { cfg = old.cfg or { } // fcfg; })
             |         ^
          503|       else

Smona commented

Okay! After lots of hacking around I think I've found a way to solve this problem in the general case.

In one of my home-manager modules, I define this option, which i set per-machine to something like ${nixGL.packages.x86_64-linux.nixGLIntel}/bin/nixGLIntel:

{
  options.nixGLPrefix = lib.mkOption {
    type = lib.types.str;
    default = "";
    description = ''
      Will be prepended to commands which require working OpenGL.

      This needs to be set to the right nixGL package on non-NixOS systems.
    '';
  };
}

Then, I use this function to wrap any packages that I want to be run in the nixGL context:

# Call once on import to load global context
{ pkgs, config }:

# Wrap a single package
pkg:

if config.nixGLPrefix == "" then
  pkg
else
# Wrap the package's binaries with nixGL, while preserving the rest of
# the outputs and derivation attributes.
  (pkg.overrideAttrs (old: {
    name = "nixGL-${pkg.name}";
    buildCommand = ''
      set -eo pipefail

      ${
      # Heavily inspired by https://stackoverflow.com/a/68523368/6259505
      pkgs.lib.concatStringsSep "\n" (map (outputName: ''
        echo "Copying output ${outputName}"
        set -x
        cp -rs --no-preserve=mode "${pkg.${outputName}}" "''$${outputName}"
        set +x
      '') (old.outputs or [ "out" ]))}

      rm -rf $out/bin/*
      shopt -s nullglob # Prevent loop from running if no files
      for file in ${pkg.out}/bin/*; do
        echo "#!${pkgs.bash}/bin/bash" > "$out/bin/$(basename $file)"
        echo "exec -a \"\$0\" ${config.nixGLPrefix} $file \"\$@\"" >> "$out/bin/$(basename $file)"
        chmod +x "$out/bin/$(basename $file)"
      done
      shopt -u nullglob # Revert nullglob back to its normal default state
    '';
  }))

And here is an example of how I would use it:

let nixGL = import ./nixGL.nix { inherit pkgs config; };
in {
    home.packages = with pkgs;
      [
        (nixGL keybase-gui)
        (nixGL spotify)
        (nixGL vlc)
        # ...
      ];

        programs.chromium = {
          enable = true;
          package = (nixGL pkgs.chromium);
        };
}

This has worked for me for every GUI app I install with nix on Ubuntu 22. I'm using it for firefox, kitty, and the emacs-community-overlay with doom emacs, all of which are passed into their respective home manager modules and decorated with additional options after they're wrapped.

Basically, it wraps the passed derivation, preserving every attribute except the build command, which symlinks all the derivation output files to $out, and overrides solely the binary outputs to be wrapper scripts which pass the original binary file into the configured nixGL prefix.

I think with a little more work, this function could be modified to directly take a nixGLPrefix option, rather than config, and could be packaged with nixGL for user consumption.

Pros:

  • Since the binary files themselves are replaced, the binaries will be launched with nixGL regardless of how they are launched, without the need for aliases.
  • No dependency on home-manager.
  • Does not trigger any rebuilds, binary cache can still be used for everything and the wrapper derivation is fast to build.

Cons / TODO:

  • This does cause all the buildInputs of the passed packages to be fetched from the binary cache. This is an acceptable tradeoff for me, since disk space & bandwidth are cheap for me. But I'd like to explore overriding all the other build phases so that buildInputs can be zeroed out for the wrapper derivation. This should make the wrapper completely transparent.

Do you mind explaning this:

{
  options.nixGLPrefix = lib.mkOption {
    type = lib.types.str;
    default = "";
    description = ''
      Will be prepended to commands which require working OpenGL.

      This needs to be set to the right nixGL package on non-NixOS systems.
    '';
  };
}

Where this should go ?

I'm using flakes so i can handle multiple users/machines. Does it go into the modules array for each homeManagerConfiguration ?

Edit: I've managed to make it work without this option by using nixGLDefault in the nixGL wrapper as below:

# Call once on import to load global context
{ pkgs, config }:

# Wrap a single package
pkg:

#if config.nixGLPrefix == "" then
#  pkg
#else
# Wrap the package's binaries with nixGL, while preserving the rest of
# the outputs and derivation attributes.
  (pkg.overrideAttrs (old: {
    name = "nixGL-${pkg.name}";
    buildCommand = ''
      set -eo pipefail

      ${
      # Heavily inspired by https://stackoverflow.com/a/68523368/6259505
      pkgs.lib.concatStringsSep "\n" (map (outputName: ''
        echo "Copying output ${outputName}"
        set -x
        cp -rs --no-preserve=mode "${pkg.${outputName}}" "''$${outputName}"
        set +x
      '') (old.outputs or [ "out" ]))}

      rm -rf $out/bin/*
      shopt -s nullglob # Prevent loop from running if no files
      for file in ${pkg.out}/bin/*; do
        echo "#!${pkgs.bash}/bin/bash" > "$out/bin/$(basename $file)"
        echo "exec -a \"\$0\" ${pkgs.nixgl.auto.nixGLDefault}/bin/nixGL $file \"\$@\"" >> "$out/bin/$(basename $file)"
        chmod +x "$out/bin/$(basename $file)"
      done
      shopt -u nullglob # Revert nullglob back to its normal default state
    '';
  }))

Smona commented

@lamarios I would recommend reading the docs about writing home manager modules, or more specifically NixOS modules. You can either add the option to your home.nix file and wrap the rest of your config in a config attribute, or better yet create a new file (module), and import it into your home.nix:

# options.nix
{ lib, ... }:

{
  options.nixGLPrefix = lib.mkOption {
    type = lib.types.str;
    default = "";
    description = ''
      Will be prepended to commands which require working OpenGL.

      This needs to be set to the right nixGL package on non-NixOS systems.
    '';
  };
}
# home.nix
{
  imports = [ ./options.nix ];
  # ...
}

Thanks both, managed to get it working. I have a bit of a hard time with how things come together in nix. I didn't know that just importing the options.nix file would actually make the option available just like that.

So, how is the home-manager support going to look like? For example, aagl-on-nix and nix-gaming both works on a simple import, then the modules and pkgs are available to access with a prefix, and that's honestly a lot easier than learning how flakes works (I still don't really get it -- anyone have an article with good examples?).

Here's a simple version to wrap all of the executables in an underlying package that works quite nicely, inspired by @Smona's work.

let
  nixGL = import <nixgl> { };
  nixGLWrap = pkg:
    let
      bin = "${pkg}/bin";
      executables = builtins.attrNames (builtins.readDir bin);
    in
    pkgs.buildEnv {
      name = "nixGL-${pkg.name}";
      paths = map
        (name: pkgs.writeShellScriptBin name ''
          exec -a "$0" ${nixGL.auto.nixGLDefault}/bin/nixGL ${bin}/${name} "$@"
        '')
        executables;
    };
in
programs.kitty.package = nixGLWrap pkgs.kitty;

A small downside to this approach is that the composed package does not contain all of the contents of the original package, so if you need to refer to other files from the original package, you'll need to use the unwrapped version.

Here's an improvement to the above that uses pkgs.buildEnv + pkgs.hiPrio to construct a new package that behaves like the underlying package.

{ pkgs }:

pkg:

let
  nixGL = import <nixgl> { };
  bins = "${pkg}/bin";
in
pkgs.buildEnv {
  name = "nixGL-${pkg.name}";
  paths =
    [ pkg ] ++
    (map
      (bin: pkgs.hiPrio (
        pkgs.writeShellScriptBin bin ''
          exec -a "$0" "${nixGL.auto.nixGLDefault}/bin/nixGL" "${bins}/${bin}" "$@"
        ''
      ))
      (builtins.attrNames (builtins.readDir bins)));
}

Here's an improvement to the above that uses pkgs.buildEnv + pkgs.hiPrio to construct a new package that behaves like the underlying package.

{ pkgs }:

pkg:

let
  nixGL = import <nixgl> { };
  bins = "${pkg}/bin";
in
pkgs.buildEnv {
  name = "nixGL-${pkg.name}";
  paths =
    [ pkg ] ++
    (map
      (bin: pkgs.hiPrio (
        pkgs.writeShellScriptBin bin ''
          exec -a "$0" "${nixGL.auto.nixGLDefault}/bin/nixGL" "${bins}/${bin}" "$@"
        ''
      ))
      (builtins.attrNames (builtins.readDir bins)));
}

This works great, unfortunately I now understand what was meant by "does not contain all of the contents of the original package". It seems the .desktop files are no longer installed with the package making launchers like wofi not able to pick them up.

cmm commented

unfortunately I now understand what was meant by "does not contain all of the contents of the original package". It seems the .desktop files are no longer installed with the package making launchers like wofi not able to pick them up.

It should work if you add both the original package and the wrapper env to home.packages -- because the wrapped binaries are hiPrio, they'll be the ones linked into the profile bin directory instead of whatever they wrap. Perhaps it's better to make the whole wrapper env hiPrio instead of each individual wrapper, not sure.

I have been using a bunch of wrappers for a while now, and I've been quite happy with how they work. But I haven't had time to clean up this stuff and create a HM module that others could easily use. And I won't have time for months yet. So I'll just leave this stuff here, maybe someone will pick it up. Perhaps combining it with the hiPrio stuff, which I didn't know about.

My goals:

  • create wrappers for both Intel and Nvidia that can be used to start apps from command line
  • have a Nix function that will create a wrapper package
  • the wrapper needs to include also modified .desktop files
  • HM config must be usable from a flake without --impure

To use this, nixGL has to be an input of your HM flake, and you need to copy its default package to your flake outputs. Like so:

outputs.gpuWrappers = self.inputs.nixgl.defaultPackage;

Then, the config file quoted below will add the function for wrapping packages to pkgs.lib.gpuWrapPackage, and include a package with scripts called nixgl and nvidia-offload. The latter simply injects environment variables needed to run things on the discrete GPU on an Optimus laptop. The nixgl script will check if Nvidia-specific environment is present, and run the given command with either the Intel or Nvidia wrapper.

You can use the wrapper like so:

home.packages = with pkgs;  [
  (lib.gpuWrapCheck targetpackage)
];

This "check" wrapper will only wrap the package if you have enabled the genericLinux HM option. I need this because I use the same config on both Nixos and non-Nixos systems.

Another pecularity is that the nixgl script will cache the nixGL build by creating a symlink in ~/.config/nixgl/result. This symlink is removed when HM config is switched, so expect the next run of the wrapper to take a bit longer.

Hopefully, someone will find this useful 😄

{ config, lib, pkgs, nixpkgs, self, ... }:

{
  targets.genericLinux.enable = true;

  nixpkgs.overlays = [
    (final: prev: {
      lib = (prev.lib or {}) // {
        gpuWrapPackage = pkg: pkgs.runCommand "${pkg.name}-nixgl-pkg-wrapper" {} ''
          # Create a new package that wraps the binaries with nixGL
          mkdir $out
          ln -s ${pkg}/* $out
          rm $out/bin
          mkdir $out/bin
          for bin in ${pkg}/bin/*
          do
           wrapped_bin=$out/bin/$(basename $bin)
           echo "#!/bin/sh" > $wrapped_bin
           echo "exec nixgl $bin \"\$@\"" >> $wrapped_bin
           chmod +x $wrapped_bin
          done

          # If .desktop files refer to the old derivation, replace the references
          if [ -d "${pkg}/share/applications" ] && grep "${pkg}" ${pkg}/share/applications/*.desktop > /dev/null
          then
              rm $out/share
              mkdir -p $out/share
              cd $out/share
              ln -s ${pkg}/share/* ./
              rm applications
              mkdir applications
              cd applications
              cp -a ${pkg}/share/applications/* ./
              for dsk in *.desktop
              do
                  sed -i "s|${pkg}|$out|g" "$dsk"
              done
          fi
        '';
      };

      gpuWrapCheck = pkg:
        if config.targets.genericLinux.enable
        then final.lib.gpuWrapPackage pkg  # Comes from non-nixos.nix
        else pkg;

      gpu-wrappers = let
        system = prev.system;
        nixglPkgs = "${self}#gpuWrappers.${system}";
        wrapIntel = type: lib.getExe self.inputs.nixgl.packages.${system}."nix${type}Intel";
        wrapNvidia = type: ''
          nix shell --quiet --impure ${nixglPkgs}.nix${type}Nvidia -c nix${type}Nvidia-$()
        '';
      in pkgs.runCommand "gpu-wrappers" {} ''
        bin=$out/bin
        mkdir -p $bin

        cat > $bin/nixgl-intel <<EOF
        #!/bin/sh
        exec ${wrapIntel "GL"} ${wrapIntel "Vulkan"} "\$@"
        EOF
        chmod +x $bin/nixgl-intel

        cat > $bin/nixgl-nvidia <<EOF
        #!/bin/sh
        glbin=\$(nix eval --quiet --raw --impure "${nixglPkgs}.nixGLNvidia.meta.name")
        vkbin=\$(echo \$glbin | sed s/GL/Vulkan/)
        exec nix shell --quiet --impure ${nixglPkgs}.nixGLNvidia ${nixglPkgs}.nixVulkanNvidia -c \$glbin \$vkbin "\$@"
        EOF
        chmod +x $bin/nixgl-nvidia

        cat > $bin/nvidia-offload <<EOF
        #!/bin/sh
        export __NV_PRIME_RENDER_OFFLOAD=1
        export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
        export __GLX_VENDOR_LIBRARY_NAME=nvidia
        export __VK_LAYER_NV_optimus=NVIDIA_only
        exec "\$@"
        EOF
        chmod +x $bin/nvidia-offload

        cat > $bin/nixgl <<EOF
        #!/bin/sh
        if [ ! -h "${config.xdg.cacheHome}/nixgl/result" ]
        then
            mkdir -p "${config.xdg.cacheHome}/nixgl"
            nix build --quiet --impure \
              --out-link "${config.xdg.cacheHome}/nixgl/result" \
              ${nixglPkgs}.nixGLNvidia \
              ${nixglPkgs}.nixVulkanNvidia
        fi

        if [ "\$__NV_PRIME_RENDER_OFFLOAD" = "1" ]
        then
            nixgl-nvidia "\$@"
        else
            nixgl-intel "\$@"
        fi
        EOF
        chmod +x $bin/nixgl
      '';
    })
  ];

  home.packages = [ pkgs.gpu-wrappers ];

  home.activation = {
    clearNixglCacle = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      [ -v DRY_RUN ] || rm -f ${config.xdg.cacheHome}/nixgl/result*
    '';
  };
}

Hey everyone,

First of all, thanks a lot for providing these very useful wrapper functions and explanations. It has been key to helping me get home-manager working with graphical apps and giving me some deeper understanding as to how nix works in general. If any of you have a minute, kindly check out my new issue #163 which is trying to deal with a particular edge case when installing a nixgl wrapped version of alacritty.

In addition, if anyone has any advice on how to purify the installation and wrapping of packages using Nvidia NixGL then I am all ears.

@RicSanOP, the wrappers I posted achieve purity by not integrating the nixGL Nvidia wrappers into the home-manager configuration, but by calling nix run on the nixGL flake, like you would do manually on command line. This way, the home-manager configuration remains pure.

@exzombie I am trying to run your wrappers. I have pretty much copy pasted your above code into my home.nix file. The input parameters to the home.nix are { config, lib, pkgs, nixpkgs, self, ... }:.

Unfortunately, I am getting this error when trying to run home-manager switch

       error: attribute 'gpuWrapCheck' missing

       at /nix/store/0m54m27cnj2pzpsjhix92hn1k5l4knsv-source/home.nix:149:6:

          148|   home.packages = with pkgs;  [
          149|     (lib.gpuWrapCheck brave)
             |      ^
          150|   ];

My current guess is that I am not setting things up correctly within the flake.nix. Could you please take a quick look at my short hacky flake, or perhaps link me to a full working example that you use. Thank you to you or anyone else who knows what is going on.

{
  description = "Home Manager configuration of ricsan";

  inputs = {
    # Specify the source of Home Manager and Nixpkgs.
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    nixgl.url = "github:nix-community/nixGL";
  };

  outputs = { self, nixpkgs, home-manager, nixgl, ... }:
    let
      pkgs = import nixpkgs {
        system = "x86_64-linux";
	overlays = [ nixgl.overlay ];
      };
    in {
      gpuWrappers = self.inputs.nixgl.defaultPackage;
      homeConfigurations."ricsan" = home-manager.lib.homeManagerConfiguration {
        inherit pkgs ;

        # Specify your home configuration modules here, for example,
        # the path to your home.nix.
        modules = [ ./home.nix ];

        # Optionally use extraSpecialArgs
        # to pass through arguments to home.nix
      };
    };
}

@RicSanOP, there's lib and there's pkgs.lib; gpuWrapCheck is in the latter. You are using with pkgs; when defining home.packages, and I'm not sure what the precedence for resolution is. Is it possible that lib there does not refer to pkgs.lib?

@exzombie I simply copy pasted from what you had above just to get it working. Right now my home.nix word for word copies what you have above. I have these inputs to the file

{ config, lib, pkgs, nixpkgs, self, ... }:

and this is now the current home.packages that I am using

  home.packages = [
    pkgs.gpu-wrappers
    (lib.gpuWrapCheck pkgs.brave)
  ];

the rest is a copy paste of the large wrapper blob you provided in your initial comment. I still get the same error regarding the missing gpuWrapCheck attribute. I have even tried changing the line to (pkgs.lib.gpuWrapCheck pkgs.brave) and (nixpkgs.lib.gpuWrapCheck pkgs.brave) and gotten the same problem. I feel the issue is with the parameters I am sending in from the flake to home-manager (please see the flake.nix posted above.

I am providing my entire home.nix file for your reference. Please share your working example if possible (or at least the important snippets needed to get this working).

{ config, lib, pkgs, nixpkgs, self, ... }:
let
  ...
in {
  targets.genericLinux.enable = true;
  nixpkgs.overlays = [
    (final: prev: {
      lib = (prev.lib or {}) // {
        gpuWrapPackage = pkg: pkgs.runCommand "${pkg.name}-nixgl-pkg-wrapper" {} ''
          # Create a new package that wraps the binaries with nixGL
          mkdir $out
          ln -s ${pkg}/* $out
          rm $out/bin
          mkdir $out/bin
          for bin in ${pkg}/bin/*
          do
           wrapped_bin=$out/bin/$(basename $bin)
           echo "#!/bin/sh" > $wrapped_bin
           echo "exec nixgl $bin \"\$@\"" >> $wrapped_bin
           chmod +x $wrapped_bin
          done

          # If .desktop files refer to the old derivation, replace the references
          if [ -d "${pkg}/share/applications" ] && grep "${pkg}" ${pkg}/share/applications/*.desktop > /dev/null
          then
              rm $out/share
              mkdir -p $out/share
              cd $out/share
              ln -s ${pkg}/share/* ./
              rm applications
              mkdir applications
              cd applications
              cp -a ${pkg}/share/applications/* ./
              for dsk in *.desktop
              do
                  sed -i "s|${pkg}|$out|g" "$dsk"
              done
          fi
        '';
      };

      gpuWrapCheck = pkg:
        if config.targets.genericLinux.enable
        then final.lib.gpuWrapPackage pkg  # Comes from non-nixos.nix
        else pkg;

      gpu-wrappers = let
        system = prev.system;
        nixglPkgs = "${self}#gpuWrappers.${system}";
        wrapIntel = type: lib.getExe self.inputs.nixgl.packages.${system}."nix${type}Intel";
        wrapNvidia = type: ''
          nix shell --quiet --impure ${nixglPkgs}.nix${type}Nvidia -c nix${type}Nvidia-$()
        '';
      in pkgs.runCommand "gpu-wrappers" {} ''
        bin=$out/bin
        mkdir -p $bin

        cat > $bin/nixgl-intel <<EOF
        #!/bin/sh
        exec ${wrapIntel "GL"} ${wrapIntel "Vulkan"} "\$@"
        EOF
        chmod +x $bin/nixgl-intel

        cat > $bin/nixgl-nvidia <<EOF
        #!/bin/sh
        glbin=\$(nix eval --quiet --raw --impure "${nixglPkgs}.nixGLNvidia.meta.name")
        vkbin=\$(echo \$glbin | sed s/GL/Vulkan/)
        exec nix shell --quiet --impure ${nixglPkgs}.nixGLNvidia ${nixglPkgs}.nixVulkanNvidia -c \$glbin \$vkbin "\$@"
        EOF
        chmod +x $bin/nixgl-nvidia

        cat > $bin/nvidia-offload <<EOF
        #!/bin/sh
        export __NV_PRIME_RENDER_OFFLOAD=1
        export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
        export __GLX_VENDOR_LIBRARY_NAME=nvidia
        export __VK_LAYER_NV_optimus=NVIDIA_only
        exec "\$@"
        EOF
        chmod +x $bin/nvidia-offload

        cat > $bin/nixgl <<EOF
        #!/bin/sh
        if [ ! -h "${config.xdg.cacheHome}/nixgl/result" ]
        then
            mkdir -p "${config.xdg.cacheHome}/nixgl"
            nix build --quiet --impure \
              --out-link "${config.xdg.cacheHome}/nixgl/result" \
              ${nixglPkgs}.nixGLNvidia \
              ${nixglPkgs}.nixVulkanNvidia
        fi

        if [ "\$__NV_PRIME_RENDER_OFFLOAD" = "1" ]
        then
            nixgl-nvidia "\$@"
        else
            nixgl-intel "\$@"
        fi
        EOF
        chmod +x $bin/nixgl
      '';
    })
  ];


  home.activation = {
    clearNixglCacle = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      [ -v DRY_RUN ] || rm -f ${config.xdg.cacheHome}/nixgl/result*
    '';
  };

  # Home Manager needs a bit of information about you and the paths it should
  # manage.
  home.username = "ricsan";
  home.homeDirectory = "/home/ricsan";

  # This value determines the Home Manager release that your configuration is
  # compatible with. This helps avoid breakage when a new Home Manager release
  # introduces backwards incompatible changes.
  #
  # You should not change this value, even if you update Home Manager. If you do
  # want to update the value, then make sure to first check the Home Manager
  # release notes.
  home.stateVersion = "23.11"; # Please read the comment before changing.

  # nixpkgs configuration
  nixpkgs.config = {
    allowUnfree = true;
  };

  home.packages = [
    pkgs.gpu-wrappers
    (lib.gpuWrapCheck pkgs.brave)
  ];

  # Let Home Manager install and manage itself.
  programs.home-manager.enable = true;
}

@RicSanOP
That's the thing, I can't paste my whole config because it is huge, handling multiple machines. But I think I have solved your problem. There's actually three issues:

  • You need to pass self to the modules, which has to be done through extraSpecialArgs.
  • When extracting the relevant bits from my config, I put gpuWrapCheck in the wrong place, it should be in the lib part of the overlay. There's even a stray comment there about the definition being in another file 🤦
  • Regardless of the previous point, somehow, the lib part of the overlay is not applied, even when the gpu-wrappers package in the same overlay is added correctly. I don't understand why and I don't see a difference to my config. Perhaps someone more familiar with how overlays work could shed some light on that. But it's not really needed for your setup. The reason why I wanted gpuWrapPackage and gpuWrapCheck in pkgs.lib is because I use it in several places in my own config. But there could be better ways to achieve that.

I made adjustments to your flake and moved the wrapper functions out of pkgs.lib. The result is below. It evaluates correctly for me, but I haven't tried applying the configuration to my system, so I can only hope it runs well for you. Don't forget, after applying the configuration, the "cache" (a result symlink, really) for the nixgl package is removed, and the package is rebuilt the next time you run a wrapped application.

flake.nix:

{
  description = "Home Manager configuration of ricsan";

  inputs = {
    # Specify the source of Home Manager and Nixpkgs.
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    nixgl = {
      url = "github:nix-community/nixGL";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager, nixgl, ... }:
    let
      pkgs = nixpkgs.legacyPackages."x86_64-linux";
      extraSpecialArgs = {
        inherit self;
        inherit nixpkgs;
      };
    in {
      gpuWrappers = nixgl.defaultPackage;
      homeConfigurations."ricsan" = home-manager.lib.homeManagerConfiguration {
        inherit pkgs;
        inherit extraSpecialArgs;

        # Specify your home configuration modules here, for example,
        # the path to your home.nix.
        modules = [ ./home.nix ];
      };
    };
}

home.nix:

{ config, lib, pkgs, nixpkgs, self, ... }:
let
  gpuWrapPackage = pkg: pkgs.runCommand "${pkg.name}-nixgl-pkg-wrapper" {} ''
    # Create a new package that wraps the binaries with nixGL
    mkdir $out
    ln -s ${pkg}/* $out
    rm $out/bin
    mkdir $out/bin
    for bin in ${pkg}/bin/*
    do
      wrapped_bin=$out/bin/$(basename $bin)
      echo "#!/bin/sh" > $wrapped_bin
      echo "exec nixgl $bin \"\$@\"" >> $wrapped_bin
      chmod +x $wrapped_bin
    done

    # If .desktop files refer to the old derivation, replace the references
    if [ -d "${pkg}/share/applications" ] && grep "${pkg}" ${pkg}/share/applications/*.desktop > /dev/null
    then
        rm $out/share
        mkdir -p $out/share
        cd $out/share
        ln -s ${pkg}/share/* ./
        rm applications
        mkdir applications
        cd applications
        cp -a ${pkg}/share/applications/* ./
        for dsk in *.desktop
        do
            sed -i "s|${pkg}|$out|g" "$dsk"
        done
    fi
  '';

  gpuWrapCheck = pkg:
    if config.targets.genericLinux.enable
    then gpuWrapPackage pkg
    else pkg;
in {
  targets.genericLinux.enable = true;
  nixpkgs.overlays = [
    (final: prev: {
      gpu-wrappers = let
        system = prev.system;
        nixglPkgs = "${self}#gpuWrappers.${system}";
        wrapIntel = type: lib.getExe self.inputs.nixgl.packages.${system}."nix${type}Intel";
        wrapNvidia = type: ''
          nix shell --quiet --impure ${nixglPkgs}.nix${type}Nvidia -c nix${type}Nvidia-$()
        '';
      in pkgs.runCommand "gpu-wrappers" {} ''
        bin=$out/bin
        mkdir -p $bin

        cat > $bin/nixgl-intel <<EOF
        #!/bin/sh
        exec ${wrapIntel "GL"} ${wrapIntel "Vulkan"} "\$@"
        EOF
        chmod +x $bin/nixgl-intel

        cat > $bin/nixgl-nvidia <<EOF
        #!/bin/sh
        glbin=\$(nix eval --quiet --raw --impure "${nixglPkgs}.nixGLNvidia.meta.name")
        vkbin=\$(echo \$glbin | sed s/GL/Vulkan/)
        exec nix shell --quiet --impure ${nixglPkgs}.nixGLNvidia ${nixglPkgs}.nixVulkanNvidia -c \$glbin \$vkbin "\$@"
        EOF
        chmod +x $bin/nixgl-nvidia

        cat > $bin/nvidia-offload <<EOF
        #!/bin/sh
        export __NV_PRIME_RENDER_OFFLOAD=1
        export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
        export __GLX_VENDOR_LIBRARY_NAME=nvidia
        export __VK_LAYER_NV_optimus=NVIDIA_only
        exec "\$@"
        EOF
        chmod +x $bin/nvidia-offload

        cat > $bin/nixgl <<EOF
        #!/bin/sh
        if [ ! -h "${config.xdg.cacheHome}/nixgl/result" ]
        then
            mkdir -p "${config.xdg.cacheHome}/nixgl"
            nix build --quiet --impure \
              --out-link "${config.xdg.cacheHome}/nixgl/result" \
              ${nixglPkgs}.nixGLNvidia \
              ${nixglPkgs}.nixVulkanNvidia
        fi

        if [ "\$__NV_PRIME_RENDER_OFFLOAD" = "1" ]
        then
            nixgl-nvidia "\$@"
        else
            nixgl-intel "\$@"
        fi
        EOF
        chmod +x $bin/nixgl
      '';
    })
  ];


  home.activation = {
    clearNixglCache = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
      [ -v DRY_RUN ] || rm -f ${config.xdg.cacheHome}/nixgl/result*
    '';
  };

  # Home Manager needs a bit of information about you and the paths it should
  # manage.
  home.username = "ricsan";
  home.homeDirectory = "/home/ricsan";

  # This value determines the Home Manager release that your configuration is
  # compatible with. This helps avoid breakage when a new Home Manager release
  # introduces backwards incompatible changes.
  #
  # You should not change this value, even if you update Home Manager. If you do
  # want to update the value, then make sure to first check the Home Manager
  # release notes.
  home.stateVersion = "23.11"; # Please read the comment before changing.

  # nixpkgs configuration
  nixpkgs.config = {
    allowUnfree = true;
  };

  home.packages = [
    pkgs.gpu-wrappers
    (gpuWrapCheck pkgs.brave)
  ];

  # Let Home Manager install and manage itself.
  programs.home-manager.enable = true;
}

Hey @exzombie , thanks a lot for the help. The above modifications have surely gotten home-manager to successfully switch. Now the gpu-wrappers.nixgl binary does work with intel graphics settings. However, it simply does not work with nvidia graphics settings. I get an error for missing buildInputs which I believe occurs at the following lines within `gpu-wrappers:

nix build --quiet --impure \
              --out-link "${config.xdg.cacheHome}/nixgl/result" \
              ${nixglPkgs}.nixGLNvidia \
              ${nixglPkgs}.nixVulkanNvidia

and

exec nix shell --quiet --impure ${nixglPkgs}.nixGLNvidia ${nixglPkgs}.nixVulkanNvidia -c \$glbin \$vkbin "\$@"

which only happens when the following line (marked with **) from the gpu-wrappers.nixgl binary is run

if [ "\$__NV_PRIME_RENDER_OFFLOAD" = "1" ]
        then
            **nixgl-nvidia "\$@"**
        else
            nixgl-intel "\$@"
        fi
        EOF

Here is the output when a graphical program successfully runs using the intel option

error:
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:9:12:
            8|
            9|   strict = derivationStrict drvAttrs;
             |            ^
           10|

       … while evaluating derivation 'nixGLNvidia-550.54.14'
         whose name attribute is located at /nix/store/371rdljjbpd9njxnrzns793apg1x1x2k-source/pkgs/stdenv/generic/make-derivation.nix:331:7

       … while evaluating attribute 'text' of derivation 'nixGLNvidia-550.54.14'
         at /nix/store/371rdljjbpd9njxnrzns793apg1x1x2k-source/pkgs/build-support/trivial-builders/default.nix:103:17:
          102|       ({
          103|         inherit text executable checkPhase allowSubstitutes preferLocalBuild;
             |                 ^
          104|         passAsFile = [ "text" ]

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

       error: attribute 'buildInputs' missing
       at /nix/store/aqf7gfx4d7fz9gqnxsgmg1shs0bfvcc9-source/nixGL.nix:84:31:
           83|           useGLVND = true;
           84|           nativeBuildInputs = oldAttrs.buildInputs ++ [zstd];
             |                               ^
           85|         });
Warning, nixVulkanIntel overwriting existing LD_LIBRARY_PATH
Gtk-Message: 05:38:52.334: Failed to load module "canberra-gtk-module"
Gtk-Message: 05:38:52.335: Failed to load module "canberra-gtk-module"
DRM kernel driver 'nvidia-drm' in use. NVK requires nouveau.
Fontconfig error: Cannot load default config file: No such file: (null)
[24624:24624:0329/053852.527379:ERROR:gl_surface_presentation_helper.cc(260)] GetVSyncParametersIfAvailable() failed for 1 times!
[24624:24624:0329/053852.659945:ERROR:gl_surface_presentation_helper.cc(260)] GetVSyncParametersIfAvailable() failed for 2 times!
[24624:24624:0329/053852.953710:ERROR:gl_surface_presentation_helper.cc(260)] GetVSyncParametersIfAvailable() failed for 3 times!

Here is the output when a graphical program unsuccessfully runs using the nvidia option

error:
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:9:12:
            8|
            9|   strict = derivationStrict drvAttrs;
             |            ^
           10|

       … while evaluating derivation 'nixGLNvidia-550.54.14'
         whose name attribute is located at /nix/store/371rdljjbpd9njxnrzns793apg1x1x2k-source/pkgs/stdenv/generic/make-derivation.nix:331:7

       … while evaluating attribute 'text' of derivation 'nixGLNvidia-550.54.14'
         at /nix/store/371rdljjbpd9njxnrzns793apg1x1x2k-source/pkgs/build-support/trivial-builders/default.nix:103:17:
          102|       ({
          103|         inherit text executable checkPhase allowSubstitutes preferLocalBuild;
             |                 ^
          104|         passAsFile = [ "text" ]

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

       error: attribute 'buildInputs' missing
       at /nix/store/aqf7gfx4d7fz9gqnxsgmg1shs0bfvcc9-source/nixGL.nix:84:31:
           83|           useGLVND = true;
           84|           nativeBuildInputs = oldAttrs.buildInputs ++ [zstd];
             |                               ^
           85|         });
nvidia
error:
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:9:12:
            8|
            9|   strict = derivationStrict drvAttrs;
             |            ^
           10|

       … while evaluating derivation 'nixGLNvidia-550.54.14'
         whose name attribute is located at /nix/store/371rdljjbpd9njxnrzns793apg1x1x2k-source/pkgs/stdenv/generic/make-derivation.nix:331:7

       … while evaluating attribute 'text' of derivation 'nixGLNvidia-550.54.14'
         at /nix/store/371rdljjbpd9njxnrzns793apg1x1x2k-source/pkgs/build-support/trivial-builders/default.nix:103:17:
          102|       ({
          103|         inherit text executable checkPhase allowSubstitutes preferLocalBuild;
             |                 ^
          104|         passAsFile = [ "text" ]

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

       error: attribute 'buildInputs' missing
       at /nix/store/aqf7gfx4d7fz9gqnxsgmg1shs0bfvcc9-source/nixGL.nix:84:31:
           83|           useGLVND = true;
           84|           nativeBuildInputs = oldAttrs.buildInputs ++ [zstd];
             |                               ^
           85|         });

Do you have any ideas as to what is going on or how to fix it? I am glad this is close to working. Perhaps after I get this working and properly organized, I will take a look into getting that home-manager module made. Really appreciate your help and patience so far.

It appears something changed in nixGL. Now I'm afraid to update my own flake 😅. I can start brave from your configuration by changing the flake inputs to

  inputs = {
    # Specify the source of Home Manager and Nixpkgs.
    nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
    home-manager = {
      url = "github:nix-community/home-manager/release-23.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    nixgl = {
      url = "github:nix-community/nixGL/489d6b095ab9d289fe11af0219a9ff00fe87c7c5";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

I'm not sure whether it's necessary to revert to older nixpgks, it's there because I tried that first. But what really fixed it was using the specific nixGL commit that I had in my own flake.lock. Curiously, just running

nix build --impure github:nix-community/nixGL#defaultPackage.x86_64-linux.{nixGLNvidia,nixVulkanNvidia}

works well enough. I don't know why it would fail when calling build on the flake cached in nix store 🤔

@rengare Your flake wrapper helper (Mesa at least) are working great for me, thank you!

For cross reference, there is also nix-community/home-manager#3968

May we continue there, and close this issue?