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,

+3


source to share


3 answers


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}"'
  '';
})

      

+1


source


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

+1


source


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.

0


source







All Articles