Skip to content

Commit a7855bc

Browse files
committed
WIP builder: Add a shell env for development
1 parent 1b7c423 commit a7855bc

File tree

8 files changed

+240
-6
lines changed

8 files changed

+240
-6
lines changed

builder/comp-builder.nix

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
, preBuild ? null, postBuild ? null
1616
, preCheck ? null, postCheck ? null
1717
, preInstall ? null, postInstall ? null
18+
, shellHook ? null
1819

1920
, doCheck ? component.doCheck || componentId.ctype == "test"
2021
, doCrossCheck ? component.doCrossCheck || false
@@ -152,6 +153,10 @@ let
152153
# the target dir for haddock documentation
153154
docdir = docoutput: docoutput + "/share/doc/" + componentId.cname;
154155

156+
executableToolDepends = lib.concatMap (c: if c.isHaskell or false
157+
then builtins.attrValues (c.components.exes or {})
158+
else [c]) component.build-tools;
159+
155160
in stdenv.mkDerivation ({
156161
name = fullName;
157162

@@ -167,6 +172,8 @@ in stdenv.mkDerivation ({
167172
# `null' if no haddock documentation was built.
168173
# TODO: fetch the self from the fixpoint instead
169174
haddockDir = self: if doHaddock then "${docdir self.doc}/html" else null;
175+
176+
isHaskellLibrary = haskellLib.isLibrary componentId;
170177
};
171178

172179
meta = {
@@ -188,9 +195,7 @@ in stdenv.mkDerivation ({
188195
nativeBuildInputs =
189196
[ghc buildPackages.removeReferencesTo]
190197
++ lib.optional (component.pkgconfig != []) pkgconfig
191-
++ lib.concatMap (c: if c.isHaskell or false
192-
then builtins.attrValues (c.components.exes or {})
193-
else [c]) component.build-tools;
198+
++ executableToolDepends;
194199

195200
SETUP_HS = setup + /bin/Setup;
196201

@@ -268,6 +273,7 @@ in stdenv.mkDerivation ({
268273
''}
269274
runHook postInstall
270275
'';
276+
271277
}
272278
// lib.optionalAttrs (patches != []) { inherit patches; }
273279
// lib.optionalAttrs (preUnpack != "") { inherit preUnpack; }
@@ -280,5 +286,6 @@ in stdenv.mkDerivation ({
280286
// lib.optionalAttrs (postCheck != "") { inherit postCheck; }
281287
// lib.optionalAttrs (preInstall != "") { inherit preInstall; }
282288
// lib.optionalAttrs (postInstall != "") { inherit postInstall; }
289+
// lib.optionalAttrs (shellHook != "") { inherit shellHook; }
283290
// lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc"){ LOCALE_ARCHIVE = "${buildPackages.glibcLocales}/lib/locale/locale-archive"; }
284291
)

builder/default.nix

Lines changed: 13 additions & 3 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, shellEnv, withPackage }:
22

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

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

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

81+
components' = haskellLib.applyComponents buildComp config;
82+
devEnv = withPackage components'.library;
83+
7884
in {
79-
components = haskellLib.applyComponents buildComp config;
85+
components = components';
8086
inherit (package) identifier;
81-
inherit setup cabalFile;
87+
inherit setup cabalFile devEnv;
8288
isHaskell = true;
89+
shell = shellEnv {
90+
inherit shellHook;
91+
ghcEnv = devEnv;
92+
};
8393
}

modules/component-driver.nix

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,22 @@ let
66
ghc = config.ghc.package;
77
buildGHC = buildModules.config.ghc.package;
88
inherit (config) nonReinstallablePkgs;
9+
inherit shellEnv withPackage;
910
};
11+
12+
withPackage = package: import ./with-package-wrapper.nix {
13+
inherit lib package;
14+
inherit (pkgs) stdenv runCommand makeWrapper;
15+
inherit (pkgs.xorg) lndir;
16+
ghc = config.ghc.package;
17+
llvmPackages = null;
18+
};
19+
20+
shellEnv = import ./shell-env.nix {
21+
inherit haskellLib;
22+
inherit (pkgs) lib stdenv;
23+
};
24+
1025
in
1126

1227
{

modules/package.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ in {
277277
type = nullOr string;
278278
default = null;
279279
};
280+
shellHook = mkOption {
281+
type = nullOr string;
282+
default = null;
283+
};
280284
doCheck = mkOption {
281285
type = bool;
282286
default = false;

modules/shell-env.nix

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{ lib, stdenv, haskellLib }:
2+
3+
{ ghcEnv, shellHook }:
4+
let
5+
inherit (ghcEnv) baseGhc package;
6+
ghcCommand' = if baseGhc.isGhcjs or false then "ghcjs" else "ghc";
7+
ghcCommand = "${baseGhc.targetPrefix}${ghcCommand'}";
8+
ghcCommandCaps = lib.toUpper ghcCommand';
9+
in stdenv.mkDerivation {
10+
name = "shell-for-${package.identifier.name}";
11+
12+
CABAL_CONFIG = package.configFiles + "/cabal.config";
13+
14+
"NIX_${ghcCommandCaps}" = "${ghcEnv}/bin/${ghcCommand}";
15+
"NIX_${ghcCommandCaps}PKG" = "${ghcEnv}/bin/${ghcCommand}-pkg";
16+
"NIX_${ghcCommandCaps}_LIBDIR" = if baseGhc.isHaLVM or false
17+
then "${ghcEnv}/lib/HaLVM-${baseGhc.version}"
18+
else "${ghcEnv}/lib/${ghcCommand}-${baseGhc.version}";
19+
# fixme: docs, haddock, hoogle
20+
# NIX_${ghcCommandCaps}_DOCDIR" = package.configFiles;
21+
22+
nativeBuildInputs = [ ghcEnv ];
23+
phases = ["installPhase"];
24+
installPhase = "echo $nativeBuildInputs $buildInputs > $out";
25+
26+
passthru = {
27+
ghc = ghcEnv;
28+
};
29+
}

modules/with-package-wrapper.nix

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Copied from nixpkgs and adapted to work with the package database of
2+
# a single component-builder package.
3+
{ lib, stdenv, ghc, llvmPackages, package, runCommand, lndir, makeWrapper
4+
, withLLVM ? false
5+
, postBuild ? ""
6+
, ghcLibdir ? null # only used by ghcjs, when resolving plugins
7+
}:
8+
9+
assert ghcLibdir != null -> (ghc.isGhcjs or false);
10+
11+
# This wrapper works only with GHC 6.12 or later.
12+
assert lib.versionOlder "6.12" ghc.version || ghc.isGhcjs || ghc.isHaLVM;
13+
14+
# It's probably a good idea to include the library "ghc-paths" in the
15+
# compiler environment, because we have a specially patched version of
16+
# that package in Nix that honors these environment variables
17+
#
18+
# NIX_GHC
19+
# NIX_GHCPKG
20+
# NIX_GHC_DOCDIR
21+
# NIX_GHC_LIBDIR
22+
#
23+
# instead of hard-coding the paths. The wrapper sets these variables
24+
# appropriately to configure ghc-paths to point back to the wrapper
25+
# instead of to the pristine GHC package, which doesn't know any of the
26+
# additional libraries.
27+
#
28+
# A good way to import the environment set by the wrapper below into
29+
# your shell is to add the following snippet to your ~/.bashrc:
30+
#
31+
# if [ -e ~/.nix-profile/bin/ghc ]; then
32+
# eval $(grep export ~/.nix-profile/bin/ghc)
33+
# fi
34+
35+
let
36+
isGhcjs = ghc.isGhcjs or false;
37+
isHaLVM = ghc.isHaLVM or false;
38+
ghc761OrLater = isGhcjs || isHaLVM || lib.versionOlder "7.6.1" ghc.version;
39+
packageDBFlag = if ghc761OrLater then "--global-package-db" else "--global-conf";
40+
ghcCommand' = if isGhcjs then "ghcjs" else "ghc";
41+
ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
42+
ghcCommandCaps= lib.toUpper ghcCommand';
43+
libDir = if isHaLVM then "$out/lib/HaLVM-${ghc.version}"
44+
else "$out/lib/${ghcCommand}-${ghc.version}";
45+
docDir = "$out/share/doc/ghc/html";
46+
packageCfgDir = "${libDir}/package.conf.d";
47+
# CLang is needed on Darwin for -fllvm to work:
48+
# https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/codegens.html#llvm-code-generator-fllvm
49+
llvm = lib.makeBinPath
50+
([ llvmPackages.llvm ]
51+
++ lib.optional stdenv.targetPlatform.isDarwin llvmPackages.clang);
52+
53+
in runCommand "${ghc.name}-with-${package.identifier.name}" {
54+
preferLocalBuild = true;
55+
passthru = {
56+
inherit (ghc) version meta;
57+
baseGhc = ghc;
58+
inherit package;
59+
};
60+
} (
61+
''
62+
. ${makeWrapper}/nix-support/setup-hook
63+
64+
# Start with a ghc...
65+
mkdir -p $out
66+
${lndir}/bin/lndir -silent ${ghc} $out
67+
# ...and replace package database with the one from target package config.
68+
rm -rf ${packageCfgDir}
69+
cp -R --no-preserve=mode ${package.configFiles}/package.conf.d ${packageCfgDir}
70+
71+
# wrap compiler executables with correct env variables
72+
73+
for prg in ${ghcCommand} ${ghcCommand}i ${ghcCommand}-${ghc.version} ${ghcCommand}i-${ghc.version}; do
74+
if [[ -x "${ghc}/bin/$prg" ]]; then
75+
rm -f $out/bin/$prg
76+
makeWrapper ${ghc}/bin/$prg $out/bin/$prg \
77+
--add-flags '"-B$NIX_${ghcCommandCaps}_LIBDIR"' \
78+
--set "NIX_${ghcCommandCaps}" "$out/bin/${ghcCommand}" \
79+
--set "NIX_${ghcCommandCaps}PKG" "$out/bin/${ghcCommand}-pkg" \
80+
--set "NIX_${ghcCommandCaps}_DOCDIR" "${docDir}" \
81+
--set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}" \
82+
${lib.optionalString (ghc.isGhcjs or false)
83+
''--set NODE_PATH "${ghc.socket-io}/lib/node_modules"''
84+
} \
85+
${lib.optionalString withLLVM ''--prefix "PATH" ":" "${llvm}"''}
86+
fi
87+
done
88+
89+
for prg in runghc runhaskell; do
90+
if [[ -x "${ghc}/bin/$prg" ]]; then
91+
rm -f $out/bin/$prg
92+
makeWrapper ${ghc}/bin/$prg $out/bin/$prg \
93+
--add-flags "-f $out/bin/${ghcCommand}" \
94+
--set "NIX_${ghcCommandCaps}" "$out/bin/${ghcCommand}" \
95+
--set "NIX_${ghcCommandCaps}PKG" "$out/bin/${ghcCommand}-pkg" \
96+
--set "NIX_${ghcCommandCaps}_DOCDIR" "${docDir}" \
97+
--set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}"
98+
fi
99+
done
100+
101+
for prg in ${ghcCommand}-pkg ${ghcCommand}-pkg-${ghc.version}; do
102+
if [[ -x "${ghc}/bin/$prg" ]]; then
103+
rm -f $out/bin/$prg
104+
makeWrapper ${ghc}/bin/$prg $out/bin/$prg --add-flags "${packageDBFlag}=${packageCfgDir}"
105+
fi
106+
done
107+
108+
# haddock was referring to the base ghc, https://github.com/NixOS/nixpkgs/issues/36976
109+
if [[ -x "${ghc}/bin/haddock" ]]; then
110+
rm -f $out/bin/haddock
111+
makeWrapper ${ghc}/bin/haddock $out/bin/haddock \
112+
--add-flags '"-B$NIX_${ghcCommandCaps}_LIBDIR"' \
113+
--set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}"
114+
fi
115+
116+
'' + (lib.optionalString (stdenv.targetPlatform.isDarwin && !isGhcjs && !stdenv.targetPlatform.isiOS) ''
117+
# Work around a linker limit in macOS Sierra (see generic-builder.nix):
118+
local packageConfDir="$out/lib/${ghc.name}/package.conf.d";
119+
local dynamicLinksDir="$out/lib/links"
120+
mkdir -p $dynamicLinksDir
121+
# Clean up the old links that may have been (transitively) included by
122+
# symlinkJoin:
123+
rm -f $dynamicLinksDir/*
124+
for d in $(grep dynamic-library-dirs $packageConfDir/*|awk '{print $2}'|sort -u); do
125+
ln -s $d/*.dylib $dynamicLinksDir
126+
done
127+
for f in $packageConfDir/*.conf; do
128+
# Initially, $f is a symlink to a read-only file in one of the inputs
129+
# (as a result of this symlinkJoin derivation).
130+
# Replace it with a copy whose dynamic-library-dirs points to
131+
# $dynamicLinksDir
132+
cp $f $f-tmp
133+
rm $f
134+
sed "s,dynamic-library-dirs: .*,dynamic-library-dirs: $dynamicLinksDir," $f-tmp > $f
135+
rm $f-tmp
136+
done
137+
'') + ''
138+
$out/bin/${ghcCommand}-pkg recache
139+
${# ghcjs will read the ghc_libdir file when resolving plugins.
140+
lib.optionalString (isGhcjs && ghcLibdir != null) ''
141+
mkdir -p "${libDir}"
142+
rm -f "${libDir}/ghc_libdir"
143+
printf '%s' '${ghcLibdir}' > "${libDir}/ghc_libdir"
144+
''}
145+
146+
# fixme: the check doesn't work
147+
$out/bin/${ghcCommand}-pkg check || true
148+
'' + postBuild
149+
)

test/with-packages/Point.hs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{-# LANGUAGE TemplateHaskell #-}
2+
module Main where
3+
4+
import Control.Lens
5+
6+
data Point = Point { _x :: Double, _y :: Double }
7+
makeLenses ''Point
8+
9+
main :: IO ()
10+
main = print (point^.x + point^.y)
11+
where
12+
point = Point { _x = 40.0, _y = 2.0 }

test/with-packages/default.nix

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ in
3232

3333
buildCommand = let
3434
inherit (packages.test-with-packages.components) library;
35+
inherit (packages.test-with-packages) devEnv;
3536
in ''
3637
########################################################################
3738
# test haddock
@@ -52,6 +53,13 @@ in
5253
grep hello "$docDir/TestWithPackages.html" > /dev/null
5354
echo yes
5455
56+
########################################################################
57+
# test with-packages
58+
59+
printf "checking that package env has the dependencies... " >& 2
60+
${devEnv}/bin/runghc ${./Point.hs}
61+
echo
62+
5563
touch $out
5664
'';
5765

0 commit comments

Comments
 (0)