Skip to content

Commit 3451172

Browse files
authored
Merge pull request #12 from input-output-hk/builder-haddock
Dev environments
2 parents 2491b39 + 5704909 commit 3451172

23 files changed

+731
-42
lines changed

.travis.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,4 @@ before_script:
1919
script:
2020
- nix-env -if https://github.com/cachix/cachix/tarball/master --extra-substituters https://cachix.cachix.org --trusted-public-keys 'cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cachix.cachix.org-1:eWNHQldwUO7G2VkjpnjDbWwy4KQ/HNxht7H4SSoMckM='
2121
- cachix use nix-tools
22-
- nix-build
23-
-I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/${CHANNEL}.tar.gz
24-
test/default.nix
25-
--cores 0
26-
-j2
22+
- NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/${CHANNEL}.tar.gz NIX_BUILD_ARGS="--cores 0 --max-jobs 2" ./test/tests.sh

builder/comp-builder.nix

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ stdenv, buildPackages, ghc, lib, pkgconfig, writeText, runCommand, haskellLib, nonReinstallablePkgs }:
1+
{ stdenv, buildPackages, ghc, lib, pkgconfig, writeText, runCommand, haskellLib, nonReinstallablePkgs, withPackage }:
22

33
{ componentId
44
, component
@@ -16,8 +16,9 @@
1616
, preBuild ? null, postBuild ? null
1717
, preCheck ? null, postCheck ? null
1818
, preInstall ? null, postInstall ? null
19+
, shellHook ? null
1920

20-
, doCheck ? component.doCheck || componentId.ctype == "test"
21+
, doCheck ? component.doCheck || haskellLib.isTest componentId
2122
, doCrossCheck ? component.doCrossCheck || false
2223
, dontPatchELF ? true
2324
, dontStrip ? true
@@ -27,7 +28,9 @@
2728
}:
2829

2930
let
30-
fullName = "${name}-${componentId.ctype}-${componentId.cname}";
31+
fullName = if haskellLib.isAll componentId
32+
then "${name}-all"
33+
else "${name}-${componentId.ctype}-${componentId.cname}";
3134

3235
flagsAndConfig = field: xs: lib.optionalString (xs != []) ''
3336
echo ${lib.concatStringsSep " " (map (x: "--${field}=${x}") xs)} >> $out/configure-flags
@@ -44,18 +47,30 @@ let
4447
in map ({val,...}: val) closure;
4548

4649
exactDep = pdbArg: p: ''
47-
if id=$(${ghc.targetPrefix}ghc-pkg -v0 ${pdbArg} field ${p} id --simple-output); then
50+
if id=$(target-pkg ${pdbArg} field ${p} id --simple-output); then
4851
echo "--dependency=${p}=$id" >> $out/configure-flags
4952
fi
50-
if ver=$(${ghc.targetPrefix}ghc-pkg -v0 ${pdbArg} field ${p} version --simple-output); then
53+
if ver=$(target-pkg ${pdbArg} field ${p} version --simple-output); then
5154
echo "constraint: ${p} == $ver" >> $out/cabal.config
5255
echo "constraint: ${p} installed" >> $out/cabal.config
5356
fi
5457
'';
5558

59+
envDep = pdbArg: p: ''
60+
if id=$(target-pkg ${pdbArg} field ${p} id --simple-output); then
61+
echo "package-id $id" >> $out/ghc-environment
62+
fi
63+
'';
64+
5665
configFiles = runCommand "${fullName}-config" { nativeBuildInputs = [ghc]; } (''
5766
mkdir -p $out
58-
${ghc.targetPrefix}ghc-pkg -v0 init $out/package.conf.d
67+
68+
# Calls ghc-pkg for the target platform
69+
target-pkg() {
70+
${ghc.targetPrefix}ghc-pkg "$@"
71+
}
72+
73+
target-pkg init $out/package.conf.d
5974
6075
${lib.concatStringsSep "\n" (lib.mapAttrsToList flagsAndConfig {
6176
"extra-lib-dirs" = map (p: "${lib.getLib p}/lib") component.libs;
@@ -67,18 +82,26 @@ let
6782
# Note: we need to use --global-package-db with ghc-pkg to prevent it
6883
# from looking into the implicit global package db when registering the package.
6984
${lib.concatMapStringsSep "\n" (p: ''
70-
${ghc.targetPrefix}ghc-pkg -v0 describe ${p} | ${ghc.targetPrefix}ghc-pkg -v0 --force --global-package-db $out/package.conf.d register - || true
85+
target-pkg describe ${p} | target-pkg --force --global-package-db $out/package.conf.d register - || true
7186
'') nonReinstallablePkgs}
7287
7388
${lib.concatMapStringsSep "\n" (p: ''
74-
${ghc.targetPrefix}ghc-pkg -v0 --package-db ${p}/package.conf.d dump | ${ghc.targetPrefix}ghc-pkg -v0 --force --package-db $out/package.conf.d register -
89+
target-pkg --package-db ${p}/package.conf.d dump | target-pkg --force --package-db $out/package.conf.d register -
7590
'') flatDepends}
7691
7792
# Note: we pass `clear` first to ensure that we never consult the implicit global package db.
7893
${flagsAndConfig "package-db" ["clear" "$out/package.conf.d"]}
7994
8095
echo ${lib.concatStringsSep " " (lib.mapAttrsToList (fname: val: "--flags=${lib.optionalString (!val) "-" + fname}") flags)} >> $out/configure-flags
8196
97+
# Provide a GHC environment file
98+
cat > $out/ghc-environment <<EOF
99+
clear-package-db
100+
package-db $out/package.conf.d
101+
EOF
102+
${lib.concatMapStringsSep "\n" (p: envDep "--package-db ${p.components.library}/package.conf.d" p.identifier.name) component.depends}
103+
${lib.concatMapStringsSep "\n" (envDep "") (lib.remove "ghc" nonReinstallablePkgs)}
104+
82105
'' + lib.optionalString component.doExactConfig ''
83106
echo "--exact-configuration" >> $out/configure-flags
84107
echo "allow-newer: ${package.identifier.name}:*" >> $out/cabal.config
@@ -115,14 +138,14 @@ let
115138
sed -i "s,dynamic-library-dirs: .*,dynamic-library-dirs: $dynamicLinksDir," $f
116139
done
117140
'' + ''
118-
${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d recache
141+
target-pkg --package-db $out/package.conf.d recache
119142
'' + ''
120-
${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d check
143+
target-pkg --package-db $out/package.conf.d check
121144
'');
122145

123146
finalConfigureFlags = lib.concatStringsSep " " (
124147
[ "--prefix=$out"
125-
"${componentId.ctype}:${componentId.cname}"
148+
"${haskellLib.componentTarget componentId}"
126149
"$(cat ${configFiles}/configure-flags)"
127150
# GHC
128151
"--with-ghc=${ghc.targetPrefix}ghc"
@@ -145,6 +168,16 @@ let
145168
++ component.configureFlags
146169
);
147170

171+
executableToolDepends = lib.concatMap (c: if c.isHaskell or false
172+
then builtins.attrValues (c.components.exes or {})
173+
else [c]) component.build-tools;
174+
175+
# Unfortunately, we need to wrap ghc commands for cabal builds to
176+
# work in the nix-shell. See ../doc/removing-with-package-wrapper.md.
177+
shellWrappers = withPackage {
178+
inherit package configFiles;
179+
};
180+
148181
in stdenv.mkDerivation ({
149182
name = fullName;
150183

@@ -154,6 +187,7 @@ in stdenv.mkDerivation ({
154187
inherit (package) identifier;
155188
config = component;
156189
inherit configFiles;
190+
env = shellWrappers;
157191
};
158192

159193
meta = {
@@ -167,6 +201,7 @@ in stdenv.mkDerivation ({
167201
};
168202

169203
CABAL_CONFIG = configFiles + /cabal.config;
204+
GHC_ENVIRONMENT = configFiles + /ghc-environment;
170205
LANG = "en_US.UTF-8"; # GHC needs the locale configured during the Haddock phase.
171206
LC_ALL = "en_US.UTF-8";
172207

@@ -179,9 +214,7 @@ in stdenv.mkDerivation ({
179214
nativeBuildInputs =
180215
[ghc]
181216
++ lib.optional (component.pkgconfig != []) pkgconfig
182-
++ lib.concatMap (c: if c.isHaskell or false
183-
then builtins.attrValues (c.components.exes or {})
184-
else [c]) component.build-tools;
217+
++ executableToolDepends;
185218

186219
SETUP_HS = setup + /bin/Setup;
187220

@@ -217,12 +250,12 @@ in stdenv.mkDerivation ({
217250
installPhase = ''
218251
runHook preInstall
219252
$SETUP_HS copy ${lib.concatStringsSep " " component.setupInstallFlags}
220-
${lib.optionalString (haskellLib.isLibrary componentId) ''
253+
${lib.optionalString (haskellLib.isLibrary componentId || haskellLib.isAll componentId) ''
221254
$SETUP_HS register --gen-pkg-config=${name}.conf
222255
${ghc.targetPrefix}ghc-pkg -v0 init $out/package.conf.d
223256
${ghc.targetPrefix}ghc-pkg -v0 --package-db ${configFiles}/package.conf.d -f $out/package.conf.d register ${name}.conf
224257
''}
225-
${lib.optionalString (componentId.ctype == "test") ''
258+
${lib.optionalString (haskellLib.isTest componentId || haskellLib.isAll componentId) ''
226259
mkdir -p $out/${name}
227260
if [ -f "dist/build/${componentId.cname}/${componentId.cname}" ]; then
228261
cp dist/build/${componentId.cname}/${componentId.cname} $out/${name}/
@@ -233,6 +266,11 @@ in stdenv.mkDerivation ({
233266
''}
234267
runHook postInstall
235268
'';
269+
270+
shellHook = ''
271+
export PATH="${shellWrappers}/bin:$PATH"
272+
${toString shellHook}
273+
'';
236274
}
237275
# patches can (if they like) depend on the version and revision of the package.
238276
// lib.optionalAttrs (patches != []) { patches = map (p: if builtins.isFunction p then p { inherit (package.identifier) version; inherit revision; } else p) patches; }

builder/default.nix

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ pkgs, buildPackages, stdenv, lib, haskellLib, ghc, buildGHC, fetchurl, writeText, runCommand, pkgconfig, nonReinstallablePkgs }:
1+
{ pkgs, buildPackages, stdenv, lib, haskellLib, ghc, buildGHC, fetchurl, writeText, runCommand, pkgconfig, nonReinstallablePkgs, withPackage }:
22

33
{ flags
44
, package
@@ -22,6 +22,8 @@
2222
, preInstall
2323
, postInstall
2424

25+
, shellHook
26+
2527
, ...
2628
}@config:
2729

@@ -67,11 +69,12 @@ let
6769
'';
6870
};
6971

70-
comp-builder = haskellLib.weakCallPackage pkgs ./comp-builder.nix { inherit ghc haskellLib nonReinstallablePkgs; };
72+
comp-builder = haskellLib.weakCallPackage pkgs ./comp-builder.nix { inherit ghc haskellLib nonReinstallablePkgs withPackage; };
7173

7274
buildComp = componentId: component: comp-builder {
7375
inherit componentId component package name src flags setup cabalFile patches revision
7476
preUnpack postUnpack preConfigure postConfigure preBuild postBuild preCheck postCheck preInstall postInstall
77+
shellHook
7578
;
7679
};
7780

builder/with-package-wrapper.nix

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# This is a simplified version of the ghcWithPackages wrapper in
2+
# nixpkgs, adapted to work with the package database of a single
3+
# component.
4+
{ lib, stdenv, ghc, runCommand, lndir, makeWrapper
5+
}:
6+
7+
{ package
8+
, configFiles
9+
, postBuild ? ""
10+
}:
11+
12+
let
13+
isGhcjs = ghc.isGhcjs or false;
14+
ghcCommand' = if isGhcjs then "ghcjs" else "ghc";
15+
ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
16+
ghcCommandCaps= lib.toUpper ghcCommand';
17+
libDir = "$out/lib/${ghcCommand}-${ghc.version}";
18+
docDir = "$out/share/doc/ghc/html";
19+
packageCfgDir = "${libDir}/package.conf.d";
20+
21+
in runCommand "${ghc.name}-with-${package.identifier.name}" {
22+
preferLocalBuild = true;
23+
passthru = {
24+
inherit (ghc) version meta;
25+
baseGhc = ghc;
26+
inherit package;
27+
};
28+
} (
29+
''
30+
. ${makeWrapper}/nix-support/setup-hook
31+
32+
# Start with a ghc...
33+
mkdir -p $out/bin
34+
${lndir}/bin/lndir -silent ${ghc} $out
35+
36+
# ...and replace package database with the one from target package config.
37+
rm -rf ${libDir}
38+
mkdir -p ${libDir}
39+
ln -s ${configFiles}/package.conf.d ${packageCfgDir}
40+
41+
# Wrap compiler executables with correct env variables.
42+
# The NIX_ variables are used by the patched Paths_ghc module.
43+
# The GHC_ENVIRONMENT variable forces ghc to use the build
44+
# dependencies of the component.
45+
46+
for prg in ${ghcCommand} ${ghcCommand}i ${ghcCommand}-${ghc.version} ${ghcCommand}i-${ghc.version} runghc runhaskell; do
47+
if [[ -x "${ghc}/bin/$prg" ]]; then
48+
rm -f $out/bin/$prg
49+
makeWrapper ${ghc}/bin/$prg $out/bin/$prg \
50+
--set "NIX_${ghcCommandCaps}" "$out/bin/${ghcCommand}" \
51+
--set "NIX_${ghcCommandCaps}PKG" "$out/bin/${ghcCommand}-pkg" \
52+
--set "NIX_${ghcCommandCaps}_DOCDIR" "${docDir}" \
53+
--set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}" \
54+
--set "${ghcCommandCaps}_ENVIRONMENT" "${configFiles}/ghc-environment"
55+
fi
56+
done
57+
58+
# Point ghc-pkg to the package database of the component using the
59+
# GHC_PACKAGE_PATH variable.
60+
61+
for prg in ${ghcCommand}-pkg ${ghcCommand}-pkg-${ghc.version}; do
62+
if [[ -x "${ghc}/bin/$prg" ]]; then
63+
rm -f $out/bin/$prg
64+
makeWrapper ${ghc}/bin/$prg $out/bin/$prg \
65+
--set "${ghcCommandCaps}_PACKAGE_PATH" "${configFiles}/package.conf.d"
66+
fi
67+
done
68+
69+
# fixme: check if this is needed
70+
# haddock was referring to the base ghc, https://github.com/NixOS/nixpkgs/issues/36976
71+
if [[ -x "${ghc}/bin/haddock" ]]; then
72+
rm -f $out/bin/haddock
73+
makeWrapper ${ghc}/bin/haddock $out/bin/haddock \
74+
--set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}"
75+
fi
76+
''
77+
)

doc/removing-with-package-wrapper.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# `ghcWithPackages` wrapper removal
2+
3+
The current [Nixpkgs Haskell infrastructure][nixpkgs-haskell] and `haskell.nix` both
4+
provide a `ghcWithPackages` derivation which contains shell script
5+
wrappers that wrap `ghc` and `ghc-pkg`.
6+
7+
In the Nixpkgs Haskell infrastructure, the wrapper scripts are used
8+
for building Haskell packages. However, in `haskell.nix`, the wrappers
9+
are only used for development environments.
10+
11+
The wrapper scripts provide a `ghc` command that "knows" about the
12+
package set and has all Haskell package dependencies available to it.
13+
14+
We would like to remove the wrapper scripts, but it's currently not
15+
possible to configure all build tools using environment variables
16+
alone.
17+
18+
## Plain `ghc`
19+
20+
When using `ghc` or `ghci` by itself, the `GHC_ENVIRONMENT` variable
21+
can point to a configuration file containing an exact package
22+
set. This works quite well.
23+
24+
## `ghc-pkg`
25+
26+
The package tool `ghc-pkg` does not recognize `GHC_ENVIRONMENT`, but
27+
does recognize a `GHC_PACKAGE_PATH` pointing to a `package.conf.d`.
28+
29+
This works well. However, the `cabal` command will refuse to start if
30+
`GHC_PACKAGE_PATH` is set.
31+
32+
## `Setup.hs`
33+
34+
When invoking `Setup.hs configure`, the package database is provided
35+
with the `--package-db` argument and exact dependencies in the package
36+
set can be provided as `--dependency` arguments.
37+
38+
The `haskell.nix` component builder uses `Setup.hs` with these
39+
command-line options to build Haskell packages.
40+
41+
## `cabal new-build`
42+
43+
Cabal-install will observe the `CABAL_CONFIG` environment variable,
44+
which points to a cabal config file. This config file can provide a
45+
`package-db` value, but it can't specify exact versions of packages.
46+
47+
Cabal is designed to solve dependencies, not simply take the package
48+
set which is given to it.
49+
50+
Therefore, `cabal` does not use `GHC_ENVIRONMENT`, but instead creates
51+
its own environment file. It will not accept `--dependency` arguments.
52+
53+
As far as I know, the best way to force `cabal` to take a pre-computed
54+
package set is to use a `new-freeze` file. However there is no
55+
environment variable (or config file entry) which can specify a path
56+
to a freeze file.
57+
58+
Specifying a `package-db` path in the cabal config file is not enough
59+
for it to successfully resolve dependencies.
60+
61+
As mentioned before, `cabal` does not work when `GHC_PACKAGE_PATH` is
62+
set. The best way to work around this is to wrap `ghc` and `ghc-pkg`
63+
in shell scripts.
64+
65+
66+
[nixpkgs-haskell]: https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure

0 commit comments

Comments
 (0)