Skip to content

Commit 6092855

Browse files
committed
WIP builder: Add a shell env for development
1 parent 3a6c2f8 commit 6092855

14 files changed

+455
-6
lines changed

builder/comp-builder.nix

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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 || componentId.ctype == "test"
2122
, doCrossCheck ? component.doCrossCheck || false
@@ -145,6 +146,10 @@ let
145146
++ component.configureFlags
146147
);
147148

149+
executableToolDepends = lib.concatMap (c: if c.isHaskell or false
150+
then builtins.attrValues (c.components.exes or {})
151+
else [c]) component.build-tools;
152+
148153
in stdenv.mkDerivation ({
149154
name = fullName;
150155

@@ -154,6 +159,8 @@ in stdenv.mkDerivation ({
154159
inherit (package) identifier;
155160
config = component;
156161
inherit configFiles;
162+
163+
isHaskellLibrary = haskellLib.isLibrary componentId;
157164
};
158165

159166
meta = {
@@ -175,9 +182,7 @@ in stdenv.mkDerivation ({
175182
nativeBuildInputs =
176183
[ghc]
177184
++ lib.optional (component.pkgconfig != []) pkgconfig
178-
++ lib.concatMap (c: if c.isHaskell or false
179-
then builtins.attrValues (c.components.exes or {})
180-
else [c]) component.build-tools;
185+
++ executableToolDepends;
181186

182187
SETUP_HS = setup + /bin/Setup;
183188

@@ -229,6 +234,7 @@ in stdenv.mkDerivation ({
229234
''}
230235
runHook postInstall
231236
'';
237+
232238
}
233239
# patches can (if they like) depend on the version and revision of the package.
234240
// lib.optionalAttrs (patches != []) { patches = map (p: if builtins.isFunction p then p { inherit (package.identifier) version; inherit revision; } else p) patches; }
@@ -242,5 +248,6 @@ in stdenv.mkDerivation ({
242248
// lib.optionalAttrs (postCheck != "") { inherit postCheck; }
243249
// lib.optionalAttrs (preInstall != "") { inherit preInstall; }
244250
// lib.optionalAttrs (postInstall != "") { inherit postInstall; }
251+
// lib.optionalAttrs (shellHook != "") { inherit shellHook; }
245252
// lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc"){ LOCALE_ARCHIVE = "${buildPackages.glibcLocales}/lib/locale/locale-archive"; }
246253
)

builder/default.nix

Lines changed: 29 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,36 @@ let
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

81+
components' = haskellLib.applyComponents buildComp config;
82+
83+
devEnv = withPackage (comp-builder {
84+
name = "${name}-devEnv-shell";
85+
inherit setup cabalFile flags src;
86+
componentId = { ctype = "lib"; cname = "devEnv"; }; # fixme: dummy values
87+
component = {
88+
# fixme: needs to be depends of all components
89+
depends = lib.filter (p: p != components'.library)
90+
(config.components.library.depends
91+
++ (lib.concatMap (p: p.depends) (lib.attrValues config.components.exes))
92+
++ (lib.concatMap (p: p.depends) (lib.attrValues config.components.tests)));
93+
frameworks = [];
94+
libs = [];
95+
doExactConfig = false;
96+
};
97+
package = { identifier = { name = "devEnv-shell"; version = "0.0"; }; };
98+
});
99+
78100
in {
79-
components = haskellLib.applyComponents buildComp config;
101+
components = components';
80102
inherit (package) identifier;
81-
inherit setup cabalFile;
103+
inherit setup cabalFile devEnv;
82104
isHaskell = true;
105+
shell = shellEnv {
106+
inherit shellHook;
107+
ghcEnv = devEnv;
108+
};
83109
}

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
@@ -261,6 +261,10 @@ in {
261261
type = nullOr string;
262262
default = null;
263263
};
264+
shellHook = mkOption {
265+
type = nullOr string;
266+
default = null;
267+
};
264268
doCheck = mkOption {
265269
type = bool;
266270
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/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ let
1616
in {
1717
cabal-simple = callPackage ./cabal-simple { inherit haskell; };
1818
cabal-22 = callPackage ./cabal-22 { inherit haskell; };
19+
with-packages = callPackage ./with-packages { inherit haskell; };
1920
}
2021

2122
## possible test cases

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/Setup.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- | Haddock test stuff
2+
module TestWithPackages (hello) where
3+
4+
-- | Standard hello text.
5+
hello :: String
6+
hello = "Hello, world!"

0 commit comments

Comments
 (0)