on iko/ghcjs-patch-master help sign in
builder: Add nix-shell development environments
commit20226fa294f67ea58424300d84954570c5fd878e authorRodney Lorrimar <rodney.lorrimar@iohk.io> dateWed 30 Jan 2019, 15:26 (8 years ago) parent97a9034
6 files changed+208−13
builder/comp-builder.nix+47−11 builder/default.nix+5−2 builder/with-package-wrapper.nix+77 doc/removing-with-package-wrapper.md+66 modules/component-driver.nix+9 modules/package.nix+4
builder/comp-builder.nix+47−11
@@ -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,6 +16,7 @@
1616, preBuild ? null, postBuild ? null
1717, preCheck ? null, postCheck ? null
1818, preInstall ? null, postInstall ? null
19+, shellHook ? null
1920
2021, doCheck ? component.doCheck || haskellLib.isTest componentId
2122, doCrossCheck ? component.doCrossCheck || false
@@ -46,18 +47,30 @@ let
4647 in map ({val,...}: val) closure;
4748
4849 exactDep = pdbArg: p: ''
49− 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
5051 echo "--dependency=${p}=$id" >> $out/configure-flags
5152 fi
52− 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
5354 echo "constraint: ${p} == $ver" >> $out/cabal.config
5455 echo "constraint: ${p} installed" >> $out/cabal.config
5556 fi
5657 '';
5758
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+
5865 configFiles = runCommand "${fullName}-config" { nativeBuildInputs = [ghc]; } (''
5966 mkdir -p $out
60− ${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
6174
6275 ${lib.concatStringsSep "\n" (lib.mapAttrsToList flagsAndConfig {
6376 "extra-lib-dirs" = map (p: "${lib.getLib p}/lib") component.libs;
@@ -69,11 +82,11 @@ let
6982 # Note: we need to use --global-package-db with ghc-pkg to prevent it
7083 # from looking into the implicit global package db when registering the package.
7184 ${lib.concatMapStringsSep "\n" (p: ''
72− ${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
7386 '') nonReinstallablePkgs}
7487
7588 ${lib.concatMapStringsSep "\n" (p: ''
76− ${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 -
7790 '') flatDepends}
7891
7992 # Note: we pass `clear` first to ensure that we never consult the implicit global package db.
@@ -81,6 +94,14 @@ let
8194
8295 echo ${lib.concatStringsSep " " (lib.mapAttrsToList (fname: val: "--flags=${lib.optionalString (!val) "-" + fname}") flags)} >> $out/configure-flags
8396
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+
84105 '' + lib.optionalString component.doExactConfig ''
85106 echo "--exact-configuration" >> $out/configure-flags
86107 echo "allow-newer: ${package.identifier.name}:*" >> $out/cabal.config
@@ -117,9 +138,9 @@ let
117138 sed -i "s,dynamic-library-dirs: .*,dynamic-library-dirs: $dynamicLinksDir," $f
118139 done
119140 '' + ''
120− ${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d recache
141+ target-pkg --package-db $out/package.conf.d recache
121142 '' + ''
122− ${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d check
143+ target-pkg --package-db $out/package.conf.d check
123144 '');
124145
125146 finalConfigureFlags = lib.concatStringsSep " " (
@@ -147,6 +168,16 @@ let
147168 ++ component.configureFlags
148169 );
149170
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+
150181in stdenv.mkDerivation ({
151182 name = fullName;
152183
@@ -156,6 +187,7 @@ in stdenv.mkDerivation ({
156187 inherit (package) identifier;
157188 config = component;
158189 inherit configFiles;
190+ env = shellWrappers;
159191 };
160192
161193 meta = {
@@ -169,6 +201,7 @@ in stdenv.mkDerivation ({
169201 };
170202
171203 CABAL_CONFIG = configFiles + /cabal.config;
204+ GHC_ENVIRONMENT = configFiles + /ghc-environment;
172205 LANG = "en_US.UTF-8"; # GHC needs the locale configured during the Haddock phase.
173206 LC_ALL = "en_US.UTF-8";
174207
@@ -181,9 +214,7 @@ in stdenv.mkDerivation ({
181214 nativeBuildInputs =
182215 [ghc]
183216 ++ lib.optional (component.pkgconfig != []) pkgconfig
184− ++ lib.concatMap (c: if c.isHaskell or false
185− then builtins.attrValues (c.components.exes or {})
186− else [c]) component.build-tools;
217+ ++ executableToolDepends;
187218
188219 SETUP_HS = setup + /bin/Setup;
189220
@@ -235,6 +266,11 @@ in stdenv.mkDerivation ({
235266 ''}
236267 runHook postInstall
237268 '';
269+
270+ shellHook = ''
271+ export PATH="${shellWrappers}/bin:$PATH"
272+ ${toString shellHook}
273+ '';
238274}
239275# patches can (if they like) depend on the version and revision of the package.
240276// lib.optionalAttrs (patches != []) { patches = map (p: if builtins.isFunction p then p { inherit (package.identifier) version; inherit revision; } else p) patches; }
builder/default.nix+5−2
@@ -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.nixnew file+77
@@ -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.mdnew file+66
@@ -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
modules/component-driver.nix+9
@@ -6,7 +6,16 @@ let
66 ghc = config.ghc.package;
77 buildGHC = buildModules.config.ghc.package;
88 inherit (config) nonReinstallablePkgs;
9+ inherit withPackage;
910 };
11+
12+ withPackage = import ../builder/with-package-wrapper.nix {
13+ inherit lib;
14+ inherit (pkgs) stdenv runCommand makeWrapper;
15+ inherit (pkgs.xorg) lndir;
16+ ghc = config.ghc.package;
17+ };
18+
1019in
1120
1221{
modules/package.nix+4
@@ -270,6 +270,10 @@ in {
270270 type = nullOr string;
271271 default = null;
272272 };
273+ shellHook = mkOption {
274+ type = nullOr string;
275+ default = null;
276+ };
273277 doCheck = mkOption {
274278 type = bool;
275279 default = false;