Flake-output apps + environment

Flakes’ app outputs have two attributes

  1. type = "app"; AFAICT, the value is always "app". I guess it’s something to do with scope for future expansion. Or maybe it’s related to the (AFAICT undocumented) restriction that all flake outputs must be attribute sets.
  2. program = <nix store path>;

I’m struggling to provide my app with the environment it needs to run successfully. Hacking around this problem with solutions such like this

apps.my-app-with-environment = let
  my-app-env-setting-script = pkgs.writeShellScriptBin "my-app-with-environment" ''
    export PATH=${pkgs.lib.makeBinPath [ self.packages.my-package-without-env ]}:$PATH
    export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath [ pkgs.dep1 pkgs.dep2 ] }:$LD_LIBRARY_PATH
    export EXTRA_REQUIRED_ENV_VAR=<some meaningful value>
    exec something-in-my-package "$@"
  '';
in { type = "app"; program = "${my-app-env-setting-script}/bin/my-app-with-environment"; };

seems to be working, but

  • Is there really no higher level way of endowing an app with an environment? To a first order approximation, my apps need the same environment as provided by the flake’s devShell. Is there some way to reuse it for this purpose?
  • In my devShell, setting of things like EXTRA_REQUIRED_ENV_VAR in my example above, is done automatically by the dependecies’ setupHooks. Is there a way to take advantage of these hooks in my-app-env-setting-script?
1 Like

I think the idiomatic solution is to write a package that uses makeWrapper to begin with, at which point this complexity is abstracted in the package definition and not the flake.

This would also factor out the env setting so both the devShell and the app can share it.

Any particular reason you set these variables, on that note? Those seem like things mkDerivation should be sorting out.

2 Likes

Probably ignorance, given that you say:

Does mkDerivation have some means of affecting the environment, beyond using some other utility such as makeWrapper.

I’m clearly missing something here, and this gap in understanding is preventing me from being able to formulate a good question.

No, but specifically PATH and LD_LIBRARY_PATH shouldn’t be necessary if you compiled the application in question with nix, due to the whole thing where nix digs for store paths in the binary once built.

Is this a non-nix binary you’re repacking? Is it doing some things where it tries to search for binaries in PATH that should really be patched to static paths at build time? It all depends a bit on the application, but usually if you have a nix-built binary these specific env modifications shouldn’t be necessary.

Sometimes they are necessary, of course, which is why makeWrapper exists, I’m just asking because this smells like a complex scenario where knowing context is important to judge what the best way actually is.

The project is all about helping users write applications in the Geant4 framework.

We are providing a user-project template which contains a flake, whose outputs include a package/app which is an application written by the user: the user provides the main function, which is linked against Geant4 (as well as our own) libraries. I think that PATH and LD_LIBRARY_PATH are all set by Nix, automatically. This part seems fine.

However, any Geant4 application needs to know where various datasets are located, and the Geant4 libraries rely on a bunch of G4*DATA environment variables to find these.

In this context, my earlier

expands to

export G4NEUTRONHPDATA="${g4-data.G4NDL}/share/Geant4-11.0.4/data/G4NDL4.6"
export G4LEDATA="${g4-data.G4EMLOW}/share/Geant4-11.0.4/data/G4EMLOW8.0"
export G4LEVELGAMMADATA="${g4-data.G4PhotonEvaporation}/share/Geant4-11.0.4/data/G4PhotonEvaporation5.7"
export G4RADIOACTIVEDATA="${g4-data.G4RadioactiveDecay}/share/Geant4-11.0.4/data/G4RadioactiveDecay5.6"
export G4PARTICLEXSDATA="${g4-data.G4PARTICLEXS}/share/Geant4-11.0.4/data/G4PARTICLEXS4.0"
export G4PIIDATA="${g4-data.G4PII}/share/Geant4-11.0.4/data/G4PII1.3"
export G4REALSURFACEDATA="${g4-data.G4RealSurface}/share/Geant4-11.0.4/data/G4RealSurface2.2"
export G4SAIDXSDATA="${g4-data.G4SAIDDATA}/share/Geant4-11.0.4/data/G4SAIDDATA2.0"
export G4ABLADATA="${g4-data.G4ABLA}/share/Geant4-11.0.4/data/G4ABLA3.1"
export G4INCLDATA="${g4-data.G4INCL}/share/Geant4-11.0.4/data/G4INCL1.0"
export G4ENSDFSTATEDATA="${g4-data.G4ENSDFSTATE}/share/Geant4-11.0.4/data/G4ENSDFSTATE2.3"

where g4-data is essentially a local variable bound to pkgs.geant4.data.

I hope and trust that there be a much better way to do this.

I should have also said that all these G4*DATA envvars are automatically set in devShells which include pkgs.geant4.data in their packages.

Presumably this is done by datasets-hook.sh and we’d dearly love to have it do its magic in our flake output package, but we haven’t figured out how.

It uses a setuphook: https://github.com/NixOS/nixpkgs/blob/8fb28edf7eca2362863aca3e35a1e4f8b5c0ee11/pkgs/development/libraries/physics/geant4/datasets.nix#L25

Iirc, those are explicitly not supported by nix shell, as that was considered too nixpkgs-specific to be supported. It is supported by nix-shell and nix develop via mkShell.

There’s probably a way to execute the shell from mkShell and pass the binary to it directly, but I don’t know how.

Personally I think there’s a need for a library of helpers around outputs.apps that solve little UX problems like this one.

Yes, we know that hook exists (I pointed it out in my previous message), and it seems to be working in nix develop/direnv (the envvars are set correctly, without us having to do anything explicitly). But we have miserably failed to figure out how to do anything better in the app, than setting them by hand in the wrapper script.

1 Like