Skip to content

Commit b66b044

Browse files
authored
Allows project to be easily extended. (#1467)
* Allows project to be easily extended. * Add common project overlays
1 parent f1bb3aa commit b66b044

File tree

4 files changed

+136
-6
lines changed

4 files changed

+136
-6
lines changed

docs/reference/library.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ Then feeding its result into [mkCabalProjectPkgSet](#mkcabalprojectpkgset) passi
167167
| `ghcWithPackages` | Function | [`ghcWithPackages`](#ghcwithpackages) |
168168
| `projectCross` | Attrset | Like `pkgs.pkgsCross.<system>` from nixpkgs `p.projectCross.<system>` returns the project results for cross compilation (where system is a member of nixpkgs lib.systems.examples). So `p.projectCross.ghcjs.hsPkgs` is the same as `hsPkgs` but compiled with ghcjs |
169169
| `appendModule` | Function | Re-eval the project with an extra module (or module list). |
170+
| `extend` and `appendOverlays` | Function | Modify a project, or add attributes, through overlays: `p.extend(final: prev: { })`. The overlays are carried-over `projectCross` and `appendModule` invocations. |
170171

171172
## project, cabalProject and stackProject
172173

lib/default.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,8 @@ in {
340340
# Converts from a `compoent.depends` value to a library derivation.
341341
# In the case of sublibs the `depends` value should already be the derivation.
342342
dependToLib = d: d.components.library or d;
343+
344+
projectOverlays = import ./project-overlays.nix {
345+
inherit lib haskellLib;
346+
};
343347
}

lib/project-overlays.nix

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
lib
3+
, haskellLib
4+
}: {
5+
devshell = final: prev: {
6+
devshell = let
7+
cabal-install = final.pkgs.buildPackages.haskell-nix.cabal-install.${final.args.compiler-nix-name};
8+
inherit (final.pkgs.buildPackages) runCommand runtimeShell;
9+
cabalWrapper = runCommand "cabal" { inherit (cabal-install) meta; } ''
10+
mkdir -p $out/bin
11+
cat << EOF > $out/bin/cabal
12+
#!${runtimeShell}
13+
set -euo pipefail
14+
15+
find_up() {
16+
while [[ \$PWD != / ]] ; do
17+
if [[ -e "\$1" ]]; then
18+
echo "\$PWD"
19+
return
20+
fi
21+
cd ..
22+
done
23+
}
24+
25+
toplevel=\$(find_up "cabal.project")
26+
27+
if [[ -n "\$toplevel" ]]; then
28+
cabal_project="\$toplevel/cabal.project"
29+
nix_cabal_project=\$toplevel/.nix-cabal.project
30+
extra_cabal_opts=("--project-file=\$nix_cabal_project")
31+
awk '
32+
# Add comment with explanation of file
33+
BEGIN {
34+
print "-- Generated from '"\$cabal_project"' by the wrapper script"
35+
print "-- ${placeholder "out"}/cabal"
36+
print "-- Add this file to your .gitignore\n"
37+
}
38+
39+
# Matches all section starts
40+
/^[^ ]/ {
41+
# Remember the section name (they can span multiple lines)
42+
section = \$0
43+
}
44+
# Matches every line
45+
// {
46+
# Only print the line if it is not in the section we want to omit
47+
if (section != "source-repository-package")
48+
print \$0
49+
}
50+
' "\$cabal_project" > "\$nix_cabal_project"
51+
else
52+
extra_cabal_opts=()
53+
fi
54+
55+
cabal=${placeholder "out"}/bin/.cabal
56+
>&2 echo "\$cabal \''${extra_cabal_opts[@]} \$@"
57+
exec "\$cabal" "\''${extra_cabal_opts[@]}" "\$@"
58+
EOF
59+
cp -rn ${cabal-install}/* $out/
60+
cp ${cabal-install}/bin/cabal $out/bin/.cabal
61+
chmod +x $out/bin/*
62+
'';
63+
in {
64+
packages = final.shell.nativeBuildInputs;
65+
env = lib.mapAttrsToList lib.nameValuePair {
66+
inherit (final.shell) CABAL_CONFIG NIX_GHC_LIBDIR;
67+
};
68+
commands = [
69+
{
70+
package = cabalWrapper;
71+
category = "development";
72+
}
73+
];
74+
};
75+
};
76+
77+
projectComponents = final: prev: let
78+
packages = haskellLib.selectProjectPackages final.hsPkgs;
79+
# used to materialize `packages-exes.nix` (project packages and exes list) to avoid some evaluations:
80+
packagesExes = lib.genAttrs
81+
(lib.attrNames final.packages)
82+
(name: lib.attrNames final.hsPkgs.${name}.components.exes);
83+
collectExes = packagesExes: lib.listToAttrs (lib.concatLists (lib.mapAttrsToList
84+
(p: map (exe: lib.nameValuePair exe final.hsPkgs.${p}.components.exes.${exe})) packagesExes));
85+
in {
86+
# local project packages:
87+
packages = haskellLib.selectProjectPackages final.hsPkgs;
88+
# set of all exes (as first level entries):
89+
exes = collectExes packagesExes;
90+
# set of all exes (as first level entries), mapped from a materialized set of packages and exes set (generated via `generatePackagesExes`):
91+
exesFrom = packagesExesMat: collectExes (import packagesExesMat);
92+
# `tests` are the test suites which have been built.
93+
tests = haskellLib.collectComponents' "tests" final.packages;
94+
# `benchmarks` (only built, not run).
95+
benchmarks = haskellLib.collectComponents' "benchmarks" final.packages;
96+
# `checks` collect results of executing the tests:
97+
checks = haskellLib.collectChecks' final.packages;
98+
99+
generatePackagesExesMat = let
100+
nixFile = builtins.toFile "packages-exes.nix" (lib.generators.toPretty {} packagesExes);
101+
in final.pkgs.buildPackages.writeShellScript "generate-packages-exes" ''
102+
set -euo pipefail
103+
104+
TARGET="$1"
105+
TARGET_DIR="$(dirname "$TARGET")"
106+
107+
mkdir -p "$TARGET_DIR"
108+
rm -rf "$TARGET"
109+
cp -r ${nixFile} "$TARGET"
110+
chmod -R +w "$TARGET"
111+
'';
112+
};
113+
114+
}

overlays/haskell.nix

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,12 +546,19 @@ final: prev: {
546546
};
547547
in project);
548548

549-
550549
# Take `hsPkgs` from the `rawProject` and update all the packages and
551550
# components so they have a `.project` attribute and as well as
552551
# a `.package` attribute on the components.
553-
addProjectAndPackageAttrs = rawProject:
554-
final.lib.fix (project':
552+
addProjectAndPackageAttrs = let
553+
# helper function similar to nixpkgs 'makeExtensible' but that keep track
554+
# of extension function so that it can be reused to extend another project:
555+
makeExtensible = f: rattrs: final.lib.fix (final.lib.extends f (self: rattrs self // {
556+
__overlay__ = f;
557+
extend = f: makeExtensible (final.lib.composeExtensions self.__overlay__ f) rattrs;
558+
appendOverlays = extraOverlays: self.extend (final.lib.composeManyExtensions ([self.__overlay__] ++ extraOverlays));
559+
}));
560+
in rawProject:
561+
makeExtensible (final: prev: {}) (project':
555562
let project = project' // { recurseForDerivations = false; };
556563
in rawProject // rec {
557564
# It is often handy to be able to get nix pkgs from the project.
@@ -601,17 +608,21 @@ final: prev: {
601608
# `projectCross.<system>` where system is a member of nixpkgs lib.systems.examples.
602609
# See https://nixos.wiki/wiki/Cross_Compiling
603610
projectCross = (final.lib.mapAttrs (_: pkgs:
604-
rawProject.projectFunction pkgs.haskell-nix rawProject.projectModule
611+
(rawProject.projectFunction pkgs.haskell-nix rawProject.projectModule)
612+
# Re-apply overlay from original project:
613+
.extend project.__overlay__
605614
) final.pkgsCross) // { recurseForDerivations = false; };
606615

607616
# re-eval this project with an extra module (or module list).
608-
appendModule = extraProjectModule: rawProject.projectFunction final.haskell-nix
617+
appendModule = extraProjectModule: (rawProject.projectFunction final.haskell-nix
609618
((if builtins.isList rawProject.projectModule
610619
then rawProject.projectModule
611620
else [rawProject.projectModule])
612621
++ (if builtins.isList extraProjectModule
613622
then extraProjectModule
614-
else [extraProjectModule]));
623+
else [extraProjectModule])))
624+
# Re-apply overlay from original project:
625+
.extend project.__overlay__;
615626

616627
# Add support for passing in `crossPlatforms` argument.
617628
# crossPlatforms is an easy way to include the inputs for a basic

0 commit comments

Comments
 (0)