Help Understanding Multi-Bitness Circular Packaging (MangoHud)

I was looking into how MangoHud is packaged and noticed how it has a dependency on its 32-bit (i686) package. I am having a hard time understanding what is happening here.

mangohud itself is declared here for all packages:

  mangohud = callPackage ../tools/graphics/mangohud {
    libXNVCtrl = linuxPackages.nvidia_x11.settings.libXNVCtrl;
    mangohud32 = pkgsi686Linux.mangohud;
  };

This makes sense. I know what callPackage is for and meant to do. Here overrides are being declared for those two attributes, since callPackage won’t be able to find them in the function args intersection for the package expression itself here. Here is also where a dependency on the i686 version of the package is introduced.

This is then used in the final 64-bit package to add some 32-bit support workarounds, such as here:

  # Support 32bit Vulkan applications by linking in 32bit Vulkan layers
  # This is needed for the same reason the 32bit preload workaround is needed.
  postInstall = lib.optionalString lowerBitnessSupport ''
    ln -s ${mangohud32}/share/vulkan/implicit_layer.d/MangoHud.x86.json \
      "$out/share/vulkan/implicit_layer.d"
  '';

Since lowerBitnessSupport sorts itself out to the correct value for the architecture being built for, there shouldn’t be issues here. It will be false for the i686 version and true for the 64-bit version. As such mangohud32 won’t be evaluated for the 32-bit build and won’t create an infinite recursion scenario:
lowerBitnessSupport ? stdenv.hostPlatform.isx86_64, # Support 32 bit on 64bit

Even if you were to callPackage the package expression without passing a mangohud32 value, it would error out since the mangohud32 parameter isn’t optional and callPackage seemingly wouldn’t pass a placeholder value to satisfy this. That is unless there are gaps in my understanding. So something has to be passed for the 32-bit build, but I’m not sure what or how this all comes together.

pkgsi686Linux is evaluated here seemingly and then passed through here in all-packages.nix through the with expression at the top, but I am still not tracking.

If the 64-bit derivation depends on the 32-bit derivation that uses the same derivation expression, which itself depends on mangohud32 itself, how does this all work? Can someone provide insight into “the flow” of evaluation to make this all work and not be infinitely recursive?

Thanks.

actually the mangohud32 attribute is passed, and the value associated to it is a thunk, ie a placeholder for a not-yet-evaluated-part-of-program. As you correctly say, this value would lead to infinite recursion if it were evaluated, but it is not evaluated, so it is fine.

An example of manipulating nix sets with some parts that evaluate, and some other which are infinite recursion:

$  nix repl
Nix 2.28.4
Type :? for help.
nix-repl> let result = { key = { subkey = 1; recursive = result; }; }; in result.key.subkey 
1

nix-repl> let result = { key = { subkey = 1; recursive = result; }; }; in result.key.recursive
{
  key = { ... };
}

nix-repl> :p let result = { key = { subkey = 1; recursive = result; }; }; in result.key.recursive
{
  key = {
    recursive = «repeated»;
    subkey = 1;
  };
}
1 Like

Thanks for replying. This makes sense. I’ve seen this elsewhere, since Nix and the ecosystem makes great use of fixed-point computation to efficiently make use of recursion and “unraveling” in evaluation. That’s what the entirety nixpkgs itself is. I saw something similar to your example in the Nix Pills when I went through them, but I just wanted some confirmation that this was actually what was happening. Thanks.