With Nix, how can I specify Haskell dependencies with profiling enabled? - haskell

I was trying like this initially:
nix-shell -p "haskell.packages.ghc821.ghcWithPackages (p: with p; [text hspec lens])" -j4 --run 'ghc Main.hs -prof
Then GHC told me
Main.hs:4:1: error:
Could not find module ‘Control.Lens’
Perhaps you haven't installed the profiling libraries for package ‘lens-4.15.4’?
Use -v to see a list of the files searched for.
Searching around the web I found this: https://github.com/NixOS/nixpkgs/issues/22340
So it seems I won't be able to download from the cache. But that's okay, if at least I can build the profiled variants locally.
Can I do that somehow simply by modifying the nix expression given to -p slightly?
Then at this point in writing this question, I remembered this resource: https://github.com/NixOS/nixpkgs/blob/bd6ba7/pkgs/development/haskell-modules/lib.nix
Where I found enableLibraryProfiling. So I tried:
nix-shell -p "haskell.packages.ghc821.ghcWithPackages (p: with p; [text hspec (haskell.lib.enableLibraryProfiling lens)])" -j4 --run 'ghc Main.hs -prof'
Which got me to a new error:
src/Control/Lens/Internal/Getter.hs:26:1: error:
Could not find module ‘Data.Functor.Contravariant’
Perhaps you haven't installed the profiling libraries for package ‘contravariant-1.4’?
Use -v to see a list of the files searched for.
|
26 | import Data.Functor.Contravariant
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
So if I could map over all packages to enableLibraryProfiling on them, then I guess this could work. But my nix knowledge doesn't quite extend that far at the moment. How could I do that? And is this even the correct path to pursue?

With further snooping around in nix-repl and some helpful pointers from ElvishJerricco on #reflex-frp at FreeNode, I was able to construct this, which seems to work:
$ nix-shell -p "(haskell.packages.ghc821.extend (self: super: {mkDerivation = expr: super.mkDerivation (expr // { enableLibraryProfiling = true; });})).ghcWithPackages (p: with p; [text hspec lens])" -j4 --run 'ghc Main.hs -prof'

I figured out a simple approach that I'm working into a largest blog post on Haskell development using Nix. For now, here's the text of just the profiling section:
Nix makes this fairly easy. First, we add the following to a ~/.config/nixpkgs/config.nix:
{
packageOverrides = super: let self = super.pkgs; in
{
profiledHaskellPackages = self.haskellPackages.override {
overrides = self: super: {
mkDerivation = args: super.mkDerivation (args // {
enableLibraryProfiling = true;
});
};
};
};
}
Now in the project we want to profile, we create a new profiling-shell.nix:
let nixpkgs = import <nixpkgs> {};
orig = nixpkgs.pkgs.profiledHaskellPackages.callPackage ./default.nix {};
in (nixpkgs.pkgs.haskell.lib.doBenchmark orig).env
Almost identical to our normal shell.nix, except for the usage of profiledHaskellPackages, which we just defined globally. Now, an invocation of nix-shell profiling-shell.nix will rebuild every dependency in our project with profiling enabled. The first time this is done it will take quite a long time. Luckily this doesn't corrupt our Nix store - a vanilla nix-shell does seem to present us with our regular dependencies again, without redownloading or rebuilding.
WARNING: A nix-collect-garbage -d will wipe away all the custom-built libs from our Nix Store, and we'd have to build them again if they're needed.
If we're writing a library, the closest executable on hand that we could profile would be our benchmark suite. To do that:
Add -prof and -fprof-auto to our benchmark's GHC options
Regenerate default.nix
Enter our profiling shell: nix-shell profiling-shell.nix
cabal configure --enable-library-profiling --enable-benchmarks
cabal build
dist/build/projname/projname-bench +RTS -p
Look at the produced projname-bench.prof file
Based on the results, we can make code changes, remove the profiling options, regenerate default.nix, and benchmark as usual in our normal Nix Shell.

Related

How can I tell Cabal where to find alex and happy so that Cabal doesn't try to build them?

I'm developing a Haskell program using Cabal in a nix-shell. I would like to have as many dependencies of the build installed by Nix as possible.
The simple.cabal file seems fairly standard (it was initially produced by stack). The full contents are here: https://pastebin.com/3wd8j0pp The only place alex and happy appear in the .cabal file are in the build-tool-depends sections.
I tried to keep the shell.nix file as simple as possible (but I'm also inexperienced in Nix):
{ pkgs ? import <nixpkgs> {}}:
let
ghc = pkgs.haskellPackages.ghcWithPackages (p:[
p.array
p.base
p.bound
p.containers
p.deriving-compat
p.haskeline
p.logict
p.mtl
p.text
p.unification-fd
p.alex
p.happy
p.BNFC
p.cabal-install
]);
alex = pkgs.haskellPackages.alex;
happy = pkgs.haskellPackages.happy;
in
pkgs.mkShell {
buildInputs = [ ghc pkgs.haskellPackages.alex pkgs.haskellPackages.happy pkgs.pkg-config];
buildTools = [ pkgs.haskellPackages.alex pkgs.haskellPackages.happy];
buildToolDepends = [pkgs.haskellPackages.alex pkgs.haskellPackages.happy];
ALEX="${alex}/bin/alex";
HAPPY="${happy}/bin/happy";
}
I saved environment variables to reference the locations of alex and happy.
Finally, I tried to tell cabal where to find alex and happy by specifying them in the extra-prog-path section of a cabal.project file. However, that didn't work, so I tried hard-coding in their location
packages: simple.cabal
extra-prog-path:
/nix/store/PATH-TO-ALEX/bin/alex
/nix/store/PATH-TO-HAPPY/bin/happy
Finally, on building to enter the nix-shell, nix-shell --pure shell.nix and then cabal build
The cabal tool build finds all the ghc library packages installed with ghcWithPackages correctly -- it requires none of them to be built. However, it seems to not know about alex nor happy. The output looks like this.
In order, the following will be built (use -v for more details):
- alex-3.2.6 (exe:alex) (requires build)
- happy-1.20.0 (exe:happy) (requires build)
I can also confirm that the version of alex and happy in the nix store are alex-3.2.6 and happy-1.20.0.
On the other hand, cabal v1-build does pick up alex and happy as installed. cabal v1-build
Resolving dependencies...
Configuring simple-0.1.0.0...
Preprocessing library for simple-0.1.0.0..
Building library for simple-0.1.0.0..
and the build completes successfully only compiling the source files in the local package.
It seems like one should favor v2-build or new-build. How can I get cabal to know where to find and also to use the alex and happy versions installed by Nix, or alternatively how to tell Nix what to do so that v2-build or new-build can find alex and happy?

Haskell - could not find module 'Test.QuickCheck'

I'm getting an error that says the module doesn't exist when I try to runhaskell. It's odd because I try to install it first and says its up to date. Any idea how to fix this?
You could try creating the package environment in the local directory that holds your project, like this:
c:\Users\...\ex1haskell> cabal install --lib --package-env . QuickCheck
This should create a file of the form .ghc.environment.xxx in ex1haskell, which hopefully should be picked up by runhaskell/ghci/ghc invocations.
In ghci sessions, a sign that the environment is being picked up is the following message while starting:
Loaded package environment from ...
When the --package-env location is not given explicitly, a default location is used. According to the docs:
By default, it is writing to the global environment in
~/.ghc/$ARCH-$OS-$GHCVER/environments/default. v2-install provides the
--package-env flag to control which of these environments is modified.
But it seems that runhaskell is having problems to find the environment file in that default location.
Note. When creating a package environment, it's possible to specify multiple packages simultaneously, like this:
cabal install --lib --package-env . QuickCheck random aeson transformers
Also, package environments are just text files, so local environments can be deleted and recreated at will. The actual package binaries reside elsewhere and can potentially be reused by cabal.
A Common Environment
It is hard to debug if/when the actual tooling differs so let's first get a unified setup. I'll use docker to get GHC 8 and Cabal 3.x:
docker run --rm -it haskell bash
Understand that this isn't arbitrary or even preemptive. What you have shown - cabal install --lib ... and runhaskell ... does work for sane tool installations. You might have a bad installation or an old version of a tool that has different behavior.
Running a single file with runhaskell
Now we need a project:
root#8a934c302dba:/# mkdir Ex1
root#8a934c302dba:/# cd Ex1
root#8a934c302dba:/Ex1# cat <<EOF >Main.hs
> import Test.QuickCheck
>
> main :: IO ()
> main = print =<< (generate arbitrary :: IO Int)
> EOF
And test failure:
root#8a934c302dba:/Ex1# runhaskell Main.hs
Main.hs:1:1: error:
Could not find module `Test.QuickCheck'
Use -v (or `:set -v` in ghci) to see a list of the files searched for.
|
1 | import Test.QuickCheck
And install the library:
root#8a934c302dba:/Ex1# cabal update && cabal install --lib QuickCheck
And successful run:
root#8a934c302dba:/Ex1# runhaskell Main.hs
15
So my comment above was wrong - we don't need to explicitly list the package as it is already exposed after installation.

XMonad on Nix - cannot find xmonad-contrib

I am trying to use nix on ubuntu, with XMonad as my window manager.
I have this working well on one host using nixOS, but I have a second device that isn't yet ready for nixOS. nix on top of Ubuntu is mostly working well there, but xmonad cannot find contributory libraries.
The relevant packages are installed:
$ nix-env -q | grep xmonad
xmonad-0.13
xmonad-contrib-0.13
xmonad-extras-0.12.1
But recompiling my xmonad.hs, it cannot find the contrib libs:
$ xmonad --recompile
Error detected while loading xmonad configuration file: /home/martyn/.xmonad/xmonad.hs
xmonad.hs:32:1: error:
Failed to load interface for ‘XMonad.Layout.NoBorders’
Use -v to see a list of the files searched for.
...
Please check the file for errors.
The relevant files are installed:
$ ls /nix/store/*xmonad-contrib*/lib/**/NoBorders*
/nix/store/4xrrwsm6362xkn9jn1b17kd891kv9z3a-xmonad-contrib-0.13/lib/ghc-8.0.2/xmonad-contrib-0.13/XMonad/Actions/NoBorders.dyn_hi
/nix/store/4xrrwsm6362xkn9jn1b17kd891kv9z3a-xmonad-contrib-0.13/lib/ghc-8.0.2/xmonad-contrib-0.13/XMonad/Actions/NoBorders.hi
/nix/store/4xrrwsm6362xkn9jn1b17kd891kv9z3a-xmonad-contrib-0.13/lib/ghc-8.0.2/xmonad-contrib-0.13/XMonad/Layout/NoBorders.dyn_hi
/nix/store/4xrrwsm6362xkn9jn1b17kd891kv9z3a-xmonad-contrib-0.13/lib/ghc-8.0.2/xmonad-contrib-0.13/XMonad/Layout/NoBorders.hi
By adding xmonad-contrib to my nixpkgs config.nix, I have gotten these libs added to the ghc package registry:
$ cat ~/.config/nixpkgs/config.nix
with (import <nixpkgs> {});
{
packageOverrides = pkgs: with pkgs; {
myHaskellEnv = pkgs.haskellPackages.ghcWithPackages (haskellPackages: with haskellPackages; [ xmonad-contrib ]);
};
}
$ nix-env -iA nixpkgs.myHaskellEnv
$ ghc-pkg list | grep xmonad
xmonad-0.13
xmonad-contrib-0.13
$
with this, this ghc(i) works well:
$ /nix/store/7mkxsq7ydqcgnjbs59v1v47wfxpwrav5-ghc-8.0.2-with-packages/bin/ghc ~/.xmonad/xmonad.hs
[1 of 1] Compiling Main ( /home/martyn/.xmonad/xmonad.hs, /home/martyn/.xmonad/xmonad.o ) [flags changed]
Linking /home/martyn/.xmonad/xmonad ...
But even the version of xmonad in that dir cannot find the libs:
$ /nix/store/7mkxsq7ydqcgnjbs59v1v47wfxpwrav5-ghc-8.0.2-with-packages/bin/xmonad --recompile
Error detected while loading xmonad configuration file: /home/martyn/.xmonad/xmonad.hs
xmonad.hs:32:1: error:
Failed to load interface for ‘XMonad.Layout.NoBorders’
Use -v to see a list of the files searched for.
I can work around this by compiling using the ghc as above, and moving the output by hand to ~/.xmonad/xmonad-x86_64-linux, and running that. But this is a wee bit hacky, and surely shouldn't be necessary?
A friend solved this for me offline, I reproduce this here for others with the same issue.
Essentially, we need to use xmonad-with-packages, and list the packages, rather than ghc-with-packages.
To achieve this, we provide our own xmonad, referenced from within ~/.nixpkgs/config.nix:
{
packageOverrides = pkgs_: with pkgs_; {
xmonad = import ./xmonad { nixpkgs = pkgs_; };
};
}
And fill out ~/.nixpkgs/xmonad/default.nix thus:
{ nixpkgs ? import <nixpkgs> {} }:
nixpkgs.xmonad-with-packages.override {
packages = hPkgs: with hPkgs; [ xmonad-contrib ];
}
This installs an xmonad that knows where to find its libraries, and everything's good!

Haskell packages not listed in ghc-pkg when installed through Nix

I have installed GHC through Nix:
$ nix-env -i ghc
Then I have installed the aeson package:
$ nix-env -f "<nixpkgs>" -iA haskellPackages.aeson
And pointed GHC to the Nix package folder:
$ export GHC_PACKAGE_PATH=~/.nix-profile/lib/ghc-8.0.1/package.conf.d/
Which seems to work:
$ ghc-pkg list
/Users/zoul/.nix-profile/lib/ghc-8.0.1/package.conf.d
Cabal-1.24.0.0
array-0.5.1.1
…
But the aeson package is missing from the list of packages above and can’t be loaded. Even though there’s clearly something there:
$ ls /Users/zoul/.nix-profile/lib/ghc-8.0.1/ | grep ^ae
aeson-0.11.2.1
What am I doing wrong?
You cannot install Haskell libraries in Nix that way because the ghc compiler you're using does not search your user's profile for libraries. Consequently, installing a library there has no effect. This topic is explained in great detail in the Nixpkgs user manual. I'm citing the relevant bit from "8.5.2.2. How to install a compiler with libraries":
GHC expects to find all installed libraries inside of its own lib directory. This approach works fine on traditional Unix systems, but it doesn’t work for Nix, because GHC’s store path is immutable once it’s built. We cannot install additional libraries into that location. As a consequence, our copies of GHC don’t know any packages except their own core libraries, like base, containers, Cabal, etc.
We can register additional libraries to GHC, however, using a special build function called ghcWithPackages. That function expects one argument: a function that maps from an attribute set of Haskell packages to a list of packages, which determines the libraries known to that particular version of GHC. For example, the Nix expression ghcWithPackages (pkgs: [pkgs.mtl]) generates a copy of GHC that has the mtl library registered in addition to its normal core packages:
$ nix-shell -p "haskellPackages.ghcWithPackages (pkgs: [pkgs.mtl])"
[nix-shell:~]$ ghc-pkg list mtl
/nix/store/zy79...-ghc-7.10.2/lib/ghc-7.10.2/package.conf.d:
mtl-2.2.1
This function allows users to define their own development environment by means of an override. After adding the following snippet to ~/.nixpkgs/config.nix,
{
packageOverrides = super: let self = super.pkgs; in
{
myHaskellEnv = self.haskell.packages.ghc7102.ghcWithPackages
(haskellPackages: with haskellPackages; [
# libraries
arrows async cgi criterion
# tools
cabal-install haskintex
]);
};
}
it’s possible to install that compiler with nix-env -f "<nixpkgs>" -iA myHaskellEnv.

C compiler selection in cabal package

I decided to add some flags to control the way that C source file is compiled (i.e. something like use-clang, use-intel etc.).
C-Sources: c_lib/tiger.c
Include-Dirs: c_lib
Install-Includes: tiger.h
if flag(debug)
GHC-Options: -debug -Wall -fno-warn-orphans
CPP-Options: -DDEBUG
CC-Options: -DDEBUG -g
else
GHC-Options: -Wall -fno-warn-orphans
Question is: which options in descritpion file need to be modified to change C compiler? I did found only CC-Options.
There is no straightforward way, but it is possible.
Assuming that you are using Distribution.Simple, you basically need to add a user hook to the build stage.
All of the following changes need to appear in Setup.hs:
Change main to use a build hook, something like:
main :: IO ()
main = defaultMainWithHooks simpleUserHooks { buildHook = myBuildHook }
Next you need a build hook. It will likely look something like the following:
myBuildHook pkg_descr local_bld_info user_hooks bld_flags =
do
let lib = fromJust (library pkg_descr)
lib_bi = libBuildInfo lib
custom_bi = customFieldsBI lib_bi
cpp_name = fromJust (lookup "x-cc-name" custom_bi)
c_srcs = cSources lib_bi
cc_opts = ccOptions lib_bi
inc_dirs = includeDirs lib_bi
lib_dirs = extraLibDirs lib_bi
bld_dir = buildDir local_bld_info
-- Compile C/C++ sources
putStrLn "invoking my compile phase"
objs <- mapM (compileCxx cpp_name cc_opts inc_dirs bld_dir) c_srcs
-- Remove C/C++ source code from the hooked build (don't change libs)
let lib_bi' = lib_bi { cSources = [] }
lib' = lib { libBuildInfo = lib_bi' }
pkg_descr' = pkg_descr { library = Just lib' }
-- The following line invokes the standard build behaviour
putStrLn "Invoke default build hook"
bh <- buildHook simpleUserHooks pkg_descr' local_bld_info user_hooks bld_flags
return bh
The code above probably needs unpacking a bit. The let clauses are basically about unpacking the required data fields from the structures passed to the build hook. Notice that you can create custom stanzas in your foo.cabal. I have provided the code to support a stanza something like:
x-cc-name: icc
As a means to specify your compiler. Having extracted all of the source files, you map over them using a function to compile a single file (NB: this is sub-optimal in some cases, e.g. those compilers which can efficiently compile multiple source files to produce a single object output and benefit from large scale optimizations, but we'll leave that aside for now).
Last of all, as we've now compiled the C/C++ code, remove it from the build structures before you pass everything on to the default build hook.
Sorry that this is more of a 'HOWTO' than a canned answer, but it should help you to get going.
I should mention that the code is untested. I have adapted it from some work I have been doing on the wxHaskell build system, so I know the idea works fine. The Cabal API is actually pretty well documented - it suffers mainly from being somewhat unstable around some of these areas.
There really doesn't seem to be any way to specify this in a .cabal file; the only thing we seem to have at the moment that would be even remotely useful here is --with-<prog>=path.
I suggest you try filing a ticket against Cabal on the trac.
4.10.1. Replacing the program for one or more phases
-pgmc cmd
        Use cmd as the C compiler.
This works for ghc --make, but I'm not sure how to get Cabal to apply this to the C file compilation.

Resources