How to call executables from Haskell on Nix that are not in my path
I want to write Haskell that calls an executable as part of its job; and install this on nixOS host. I dont want the executable in my PATH (and relying on to break the nice nix dependency model).
If it were, say, a Perl script, I would have a simple builder that looked for strings of a specific format and replace them with executable names based on the dependencies declared in the .nix file. But that seems a little more complicated with the bondage-based foundation common to haskell.
Is there a standard idiom for encoding executable paths at build time (including during development as well as during installation) in Haskell code on nix?
For a concrete example, here's a trivial "script":
import System.Process ( readProcess )
main = do
stdout <- readProcess "hostname" [] ""
putStrLn $ "Hostname: " ++ stdout
I would like to compile running this (in principle) without relying on the hostname found in the PATH, but instead of replacing the hostname with the full path / nix / store / -inetutils- / bin / hostname and thus also gaining the benefits of dependency management in nix.
Perhaps it could be controlled by a shell (or similar) script built using a replacement scheme as defined above, which sets the environment that the haskell executable expects; but it will still require some bootstrapping via cabal.mkDerivation and since I am a lover of OptParse-Applicative bash completion, I don't want to slow this down with another script to run every time I hit the tab key. But if necessary, fair.
I looked at cabal.mkDerivation for some pre-build step, but if it's there, I can't see it.
Thank,
source to share
Assuming you are building a Haskell application in Nix, you can fix the config file through your Nix expression. For an example of how to do this, check out this small project .
The bottom line is that you can define a hook postConfigure
as this :
pkgs.haskell.lib.overrideCabal yourProject (old: {
postConfigure = ''
substituteInPlace src/Configuration.hs --replace 'helloPrefix = Nothing' 'helloPrefix = Just "${pkgs.hello}"'
'';
})
source to share
What I do with my xmonad build in nix 1 is referencing the executable paths like @@compton@@/bin/compton
. Then I use a script to create the default.nix file:
#!/usr/bin/env bash
set -eu
packages=($(grep '@@[^@]*@@' src/Main.hs | sed -e 's/.*@@\(.*\)@@.*/\1/' | sort -u))
extra_args=()
for p in "${packages[@]}"; do
extra_args+=(--extra-arguments "$p")
done
cabal2nix . "${extra_args[@]}" \
| head -n-1
echo " patchPhase = ''";
echo " substituteInPlace src/Main.hs \\"
for p in "${packages[@]}"; do
echo " --replace '@@$p@@' '\${$p}' \\"
done
echo " '';"
echo "}"
What it does is grep through src/Main.hs
(can be easily modified to find all haskell files or some specific config module) and highlight all tags surrounded @@
like @@some-package-name@@
. Then it does 2 things with them:
- passes these to cabal2nix as additional arguments for the nix expression it generates.
- the post-process outputs the output of the nix expression from cabal2nix to add a patch phase that replaces the tag
@@some-package-name@@
in the Haskell source file with the actual output path. 2
This generates a nix expression like this:
{ mkDerivation, base, compton, networkmanagerapplet, notify-osd
, powerline, setxkbmap, stdenv, synapse, system-config-printer
, taffybar, udiskie, unix, X11, xmonad, xmonad-contrib
}:
mkDerivation {
pname = "xmonad-custom";
version = "0.0.0.0";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [
base taffybar unix X11 xmonad xmonad-contrib
];
description = "My XMonad build";
license = stdenv.lib.licenses.bsd3;
patchPhase = ''
substituteInPlace src/Main.hs \
--replace '@@compton@@' '${compton}' \
--replace '@@networkmanagerapplet@@' '${networkmanagerapplet}' \
--replace '@@notify-osd@@' '${notify-osd}' \
--replace '@@powerline@@' '${powerline}' \
--replace '@@setxkbmap@@' '${setxkbmap}' \
--replace '@@synapse@@' '${synapse}' \
--replace '@@system-config-printer@@' '${system-config-printer}' \
--replace '@@udiskie@@' '${udiskie}' \
'';
}
As a result, I can just write the Haskell code and the cabal package file; I don't have to worry too much about maintaining the nix package file, but only restart my generate-nix script if dependencies change.
In my Haskell code, I just write the paths to the executables as if it @@the-nix-package-name@@
were the absolute path to the folder this package is installed in, and everything magically works.
The installed xmonad binary ends up having hard-coded references to the absolute paths to the executables I call, which is how nix likes to work (which means it automatically knows about the dependency during garbage collection, for example). And I don't have to worry about keeping things that I was calling in my interactive environment PATH
, or maintaining a shell that PATH
only installs for that executable.
1 I set it up as a cabal project that builds and installs in the nix repository, instead of dynamically recompiling it from~/.xmonad/xmonad.hs
2 Step 2 is a bit meta as I am using a bash script to generate nix code with an embedded bash script in it
source to share
It's not indented to be an answer, but if I post it in the comments section it turns out to be ugly.
Also I'm not sure if this hack is the correct way to make this work.
I notice that if I use nix-shell
I can get the full path to the nix repository
Let's assume the hash is always the same, AFAIK I believe you can use it in a hardcoded assembly recipe.
$ which bash
/run/current-system/sw/bin/bash
[wizzup@ ~]
$ nix-shell -p bash
[nix-shell:~]$ which bash
/nix/store/wb34dgkpmnssjkq7yj4qbjqxpnapq0lw-bash-4.4-p12/bin/bash
Finally, I doubt you need to do this, if you are using buildInput it should be the same path.
source to share