haskell.nix/lib/default.nix
Moritz Angermann 5884ab02be
Overlays (#261)
The Overlays branch

This is a major reorganization in how haskell.nix is used, and marks our 1.0 release.
The branch doesn't build due to numerous issues that we believe to be with the CI
and not this branch. We expect only very minor adjustments prior to calling this the
official 1.0 release.

* Move iohk-nix patches into haskell.nix

This moves the customizations we did in iohk-nix
into haskell.nix via overlays and config.

Add bootPkgs logic

this moves the nuking of bootPkgs where it belongs. This should eventually still be
removed and replaced by a proper solution, that doesn't require the nuking of
bootPkgs.

Allow us to bootstrap binary ghcs and a cabal-install

With this we can do the following:

```
$ nix repl compiler/old-ghc-nix
nix-repl> :b (let pkgs = import <nixpkgs> {}; in with import ./. {}; import ./compiler/bootstrap/cabal-install.nix { inherit (pkgs) fetchurl stdenv zlib; inherit hackage
; ghc = ghc844; src = pkgs.fetchurl { url = "https://github.com/haskell/cabal/archive/Cabal-v3.0.0.0-rc3.tar.gz"; sha256 = "1zl2mgg8307ykq3v8nmafc6zdhhj1cw7w8ffpap16dsm6
5lbnx33"; }; })
```

which wile it may look daunting, will allow us to bootstrap a
cabal-install with a ghc. From that point onwards, we should be able to
build any hackage package via haskell.nix.

Pass through cabal-install version

Better threading of arguments.

Add bootstrap overlay

Allow alex + happy to be built

This still has a wart: we need nix-tools, and for that we use the ghc865 from
nixpkgs. Ideally we'd build nix-tools against a specific ghc, but then
we'd need a build expression for that.

Make ghcjs work

Building something like this:
```
 nix build '(with import ./. { nixpkgs = ../nixpkgs; nixpkgsArgs = { crossSystem = { config = "js-unknown-ghcjs"; }; }; }; (haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; })).components.exes.hello'
```
will now work. Assuming `nixpkgs` has been appropriately patched to support the `js-unknown-ghcjs` triple.

Also: this will need an additional `Cabal` patch, to make `Cabal` understand what it needs to do with: `dist/build/hello/hello: copyFile: does not exist (No such file or directory)`

It needs to learn that `dist/build/hello/hello.jsexe` is what it wants to copy and that that is a directory.  Luckily we do have some code in Cabal already that does this for `.exe` on windows.

Build `js-unknown-ghcjs` packages with haskell.nix

Using the following expression:
```
with import ./. { nixpkgs = ../nixpkgs; nixpkgsArgs = { crossSystem = { config = "js-unknown-ghcjs"; }; }; };
let Cabal = buildPackages.haskell-nix.hackage-package {
    name = "Cabal"; version = "2.4.1.0";
    modules = [
        { packages.Cabal.patches = [ ./Cabal-install-folder.diff ]; }
    ];
}; in
(haskell-nix.hackage-package {
    name = "hello"; version = "1.0.0.2";
    modules = [
    	    ({config, ... }:{ packages.hello.package.setup-depends = [ Cabal ]; })
    ];}).components.exes.hello
```
in a `test.nix` file. And running
```
nix build -f ./test.nix
```
on it, will produce
```
./result
├── bin
│   └── hello.jsexe
│       ├── all.js
│       ├── all.js.externs
│       ├── index.html
│       ├── lib.js
│       ├── manifest.webapp
│       ├── out.frefs.js
│       ├── out.frefs.json
│       ├── out.js
│       ├── out.stats
│       ├── rts.js
│       └── runmain.js
└── share
    └── doc
        └── x86_64-linux-ghc-8.6.5
            └── hello-1.0.0.2
                └── LICENSE

6 directories, 12 files
```
2019-10-21 20:07:58 +08:00

145 lines
5.4 KiB
Nix

{ lib, haskellLib, runCommand, git }:
with haskellLib;
{
# Within the package components, these are the attribute names of
# nested attrsets.
subComponentTypes = [
"sublibs"
"foreignlibs"
"exes"
"tests"
"benchmarks"
];
foldrAttrVals = f: z: attrs:
lib.foldr (g: acc: g acc) z (lib.mapAttrsToList (_name: f) attrs);
foldComponents = tys: f: z: conf:
let
comps = conf.components or {};
# ensure that comps.library exists and is not null.
libComp = acc: if (comps.library or null) != null then f comps.library acc else acc;
subComps = acc:
lib.foldr
(ty: acc': foldrAttrVals f acc' (comps.${ty} or {}))
acc
tys;
in libComp (subComps z);
getAllComponents = foldComponents subComponentTypes (c: acc:
lib.optional c.buildable c ++ acc) [];
componentPrefix = {
# Are all of these right?
sublibs = "lib";
foreignlibs = "foreignlib";
exes = "exe";
tests = "test";
benchmarks = "bench";
};
applyComponents = f: config:
let
comps = config.components;
applyLibrary = cname: f { cname = config.package.identifier.name; ctype = "lib"; };
applySubComp = ctype: cname: f { inherit cname; ctype = componentPrefix.${ctype} or (throw "Missing component mapping for ${ctype}."); };
applyAllComp = f { cname = config.package.identifier.name; ctype = "all"; };
buildableAttrs = lib.filterAttrs (n: comp: comp.buildable or true);
libComp = if comps.library == null || !(comps.library.buildable or true)
then {}
else lib.mapAttrs applyLibrary (removeAttrs comps (subComponentTypes ++ [ "all" ]));
subComps = lib.mapAttrs
(ctype: attrs: lib.mapAttrs (applySubComp ctype) (buildableAttrs attrs))
(builtins.intersectAttrs (lib.genAttrs subComponentTypes (_: null)) comps);
allComp = { all = applyAllComp comps.all; };
in subComps // libComp // allComp;
isLibrary = componentId: componentId.ctype == "lib";
isAll = componentId: componentId.ctype == "all";
isTest = componentId: componentId.ctype == "test";
isBenchmark = componentId: componentId.ctype == "bench";
# Was there a reference to the package source in the `cabal.project` or `stack.yaml` file.
# This is used to make the default `packages` list for `shellFor`.
isLocalPackage = p: p.isLocal or false;
selectLocalPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p) ps;
# Format a componentId as it should appear as a target on the
# command line of the setup script.
componentTarget = componentId:
if componentId.ctype == "all" then ""
else "${componentId.ctype}:${componentId.cname}";
# Remove null or empty values from an attrset.
optionalHooks = lib.filterAttrs (_: hook: hook != null && hook != "");
# Avoid pkgs.callPackage for now. It does a lot of nonsense with OOP
# style programming that we should avoid until we know we want it.
# weakCallPackage: call a function or (importable expression)
# with scope + args.
#
# weakCallPackage scope f args
# will call f (scope // args)
#
# weakCallpackage scope ./path args
# will call the expression at ./path with (scope // args)
#
weakCallPackage = scope: f: args:
let f' = if lib.isFunction f then f else import f;
args' = (builtins.intersectAttrs (builtins.functionArgs f') scope) // args;
in f' args';
# Collect all (transitive) Haskell library dependencies of a
# component.
## flatLibDepends :: Component -> [Package]
flatLibDepends = component:
let
# this is a minor improvement over the "cannot coerce set to string"
# error. It will now say:
#
# > The option `packages.Win32.package.identifier.name' is used but not defined.
#
# which indicates that the package.Win32 is missing and not defined.
getKey = x: if x ? "outPath" then "${x}" else (throw x.identifier.name);
makePairs = map (p: rec { key=getKey val; val=(p.components.library or p); });
closure = builtins.genericClosure {
startSet = makePairs component.depends;
operator = {val,...}: makePairs val.config.depends;
};
in map ({val,...}: val) closure;
# Extracts a selection of components from a Haskell package set.
#
# This can be used to filter out all test suites or benchmarks of
# your project, so that they can be built in Hydra.
#
# For example:
#
# tests = collectComponents "tests" (package: package.identifier.name == "mypackage") hsPkgs;
#
# Will result in moving:
# from: hsPkgs.mypackage.components.tests.unit-tests
# to: tests.mypackage.unit-tests
#
collectComponents = group: packageSel: haskellPackages:
(lib.mapAttrs (_: package: package.components.${group} // { recurseForDerivations = true; })
(lib.filterAttrs (name: package: (package.isHaskell or false) && packageSel package) haskellPackages))
// { recurseForDerivations = true; };
# Replacement for lib.cleanSourceWith that has a subDir argument.
inherit (import ./clean-source-with.nix { inherit lib; }) cleanSourceWith canCleanSource;
# Use cleanSourceWith to filter just the files needed for a particular
# component of a package
cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith; };
# Clean git directory based on `git ls-files --recurse-submodules`
cleanGit = import ./clean-git.nix {
inherit lib runCommand git cleanSourceWith;
};
}