Skip to content

Commit 252e952

Browse files
committed
Merge remote-tracking branch 'shlevy/prebuilt-depends' into hkm/prebuilt-depends
2 parents f605254 + 5ab033e commit 252e952

File tree

7 files changed

+113
-15
lines changed

7 files changed

+113
-15
lines changed

builder/comp-builder.nix

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,65 @@
9090
# LLVM
9191
, useLLVM ? ghc.useLLVM or false
9292
, smallAddressSpace ? false
93+
94+
# Note [prebuilt dependencies]
95+
#
96+
# Typical cabal project planning starts with the libraries that come with
97+
# the compiler and then plans to build every other needed dependency from
98+
# source (fetched through hackage repositories or source-repository
99+
# dependencies). In cases where some library that isn't part of the compiler
100+
# is only available as a pre-built shared object file (such as for some
101+
# closed-source module from a vendor, or in principle a component whose
102+
# compilation is extremely expensive), we need to be able to tell cabal
103+
# about additional prebuilt dependencies to include in its plan and link to
104+
# as needed at build time.
105+
#
106+
# This can be done by passing the needed libraries in prebuilt-depends. During
107+
# cabal planning and builds, these libraries (and their dependencies) will be
108+
# present in the ghc-pkg database that cabal will draw from for its dependency
109+
# resolution, thereby skipping lookup from hackage or building from source.
110+
#
111+
# The entries in the prebuilt dependencies list may have dependencies that are
112+
# part of the compiler-provided package set, or may have overlap with each other.
113+
# GHC can actually handle this use case fine, since types from different packages
114+
# (even of the same name) will not unify, so at worst you will get a compile error,
115+
# but cabal will need to choose one for the packages you are building. The entries
116+
# in the list are given priority over the compiler-provided ones, with the later
117+
# entries having greater priority than the earlier ones.
118+
#
119+
# For your build to succeed, your prebuilt-dependencies must meet the following:
120+
#
121+
# 1. They are built with the same compiler version and RTS way.
122+
# 2. They have all of their dependencies within the package db directory or
123+
# included as propagatedBuildInputs
124+
# 3. They must include the `envDep` and `exactDep` files that make-config.files.nix
125+
# expects for configuring cabal precisely.
126+
#
127+
# The recommended way to meet this requirement is to build the relevant libraries
128+
# with haskell.nix too, since it sets up the dependencies appropriately. An example
129+
# workflow would be:
130+
#
131+
# 1. Build libraries foo and bar with haskell.nix (the same plan)
132+
# 2. Note down the store paths for foo and bar library outputs
133+
# 3. Make a full nix export of those store paths (using e.g. `nix-store --export $(nix-store --query --requisites $barPath $fooPath) > foobar.closure`
134+
# 4. On the consumer machine, import the store paths (e.g. `nix-store --import foobar.closure` and then add gc roots)
135+
# 5. In the consumer haskell.nix build, add the imported store paths to your prebuilt-depends. E.g.:
136+
#
137+
# prebuilt-depends = let foo = {
138+
# # We need to make this look like a call to derivation for stdenv to do the right thing
139+
# name = "foo-lib-foo-0.1.0.0";
140+
# type = "derivation";
141+
# outputs = [ "out" ];
142+
# out = foo;
143+
# all = [ foo ];
144+
# # $fooPath is already in the store due to the import, so we use the storePath
145+
# # builtin to add it as a source dependency to the build. Note that this does
146+
# # not work in pure evaluation mode, you must use --impure with flakes. An
147+
# # alternative would be to bundle up all of the needed libraries into tarballs that
148+
# # are fetched and unpacked as proper fixed-output derivations.
149+
# outPath = builtins.storePath $fooPath;
150+
# }; in [ foo ]; # Could also do the same for bar of course
151+
, prebuilt-depends ? []
93152
}:
94153
# makeOverridable is called here after all the `? DEFAULT` arguments
95154
# will have been applied. This makes sure that `c.override (oldAttrs: {...})`
@@ -160,6 +219,7 @@ let self =
160219
, enableTSanRTS
161220
, useLLVM
162221
, smallAddressSpace
222+
, prebuilt-depends
163223
}@drvArgs:
164224

165225
let
@@ -213,7 +273,7 @@ let
213273
configFiles = makeConfigFiles {
214274
component = componentForSetup;
215275
inherit (package) identifier;
216-
inherit fullName flags needsProfiling enableDWARF;
276+
inherit fullName flags needsProfiling enableDWARF prebuilt-depends;
217277
};
218278

219279
enableFeature = enable: feature:
@@ -852,5 +912,6 @@ in drv; in self) {
852912
enableDWARF
853913
enableTSanRTS
854914
useLLVM
855-
smallAddressSpace;
915+
smallAddressSpace
916+
prebuilt-depends;
856917
}

builder/hspkg-builder.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ let
5858
inherit allComponent componentId component package name src flags setup cabalFile cabal-generator patches
5959
shellHook
6060
;
61+
inherit (config) prebuilt-depends;
6162
};
6263

6364
in rec {

builder/make-config-files.nix

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{ stdenv, lib, haskellLib, ghc, nonReinstallablePkgs, runCommand, writeText, writeScript }@defaults:
22

3-
{ identifier, component, fullName, flags ? {}, needsProfiling ? false, enableDWARF ? false, chooseDrv ? drv: drv, nonReinstallablePkgs ? defaults.nonReinstallablePkgs }:
3+
{ identifier, component, fullName, flags ? {}, needsProfiling ? false, enableDWARF ? false, chooseDrv ? drv: drv, nonReinstallablePkgs ? defaults.nonReinstallablePkgs, prebuilt-depends ? [] }:
44

55
let
66
# Sort and remove duplicates from nonReinstallablePkgs.
@@ -57,7 +57,7 @@ let
5757
((if needsProfiling then (x: map (p: p.profiled or p) x) else x: x)
5858
(map haskellLib.dependToLib component.depends))
5959
)
60-
);
60+
) ++ prebuilt-depends;
6161
script = ''
6262
${target-pkg} init $configFiles/${packageCfgDir}
6363
@@ -149,17 +149,6 @@ let
149149
echo "allow-older: ${identifier.name}:*" >> $configFiles/cabal.config
150150
''}
151151
152-
for p in "''${pkgsHostTarget[@]}"; do
153-
if [ -e $p/envDep ]; then
154-
cat $p/envDep >> $configFiles/ghc-environment
155-
fi
156-
${ lib.optionalString component.doExactConfig ''
157-
if [ -d $p/exactDep ]; then
158-
cat $p/exactDep/configure-flags >> $configFiles/configure-flags
159-
cat $p/exactDep/cabal.config >> $configFiles/cabal.config
160-
fi
161-
''}
162-
done
163152
for p in ${lib.concatStringsSep " " nonReinstallablePkgs'}; do
164153
if [ -e $ghcDeps/envDeps/$p ]; then
165154
cat $ghcDeps/envDeps/$p >> $configFiles/ghc-environment
@@ -173,6 +162,23 @@ let
173162
fi
174163
done
175164
''
165+
# We put the packages in buildInputs *after* the GHC deps on the command line
166+
# to ensure that any prebuilt dependencies (see Note [prebuilt dependencies])
167+
# take priority over the GHC-provided ones. We can't relink the prebuilt
168+
# libraries, so this is the most likely to avoid conflicts.
169+
+ ''
170+
for p in "''${pkgsHostTarget[@]}"; do
171+
if [ -e $p/envDep ]; then
172+
cat $p/envDep >> $configFiles/ghc-environment
173+
fi
174+
${ lib.optionalString component.doExactConfig ''
175+
if [ -d $p/exactDep ]; then
176+
cat $p/exactDep/configure-flags >> $configFiles/configure-flags
177+
cat $p/exactDep/cabal.config >> $configFiles/cabal.config
178+
fi
179+
''}
180+
done
181+
''
176182
# This code originates in the `generic-builder.nix` from nixpkgs. However GHC has been fixed
177183
# to drop unused libraries referenced from libraries; and this patch is usually included in the
178184
# nixpkgs's GHC builds. This doesn't sadly make this stupid hack unnecessary. It resurfaces in

lib/call-cabal-project-to-nix.nix

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
, evalPackages
6363
, supportHpack ? false # Run hpack on package.yaml files with no .cabal file
6464
, ignorePackageYaml ? false # Ignore package.yaml files even if they exist
65+
, prebuilt-depends ? []
6566
, ...
6667
}@args:
6768
let
@@ -371,6 +372,7 @@ let
371372
};
372373

373374
dummy-ghc-pkg-dump = evalPackages.runCommand "dummy-ghc-pkg-dump" {
375+
buildInputs = prebuilt-depends;
374376
nativeBuildInputs = [
375377
evalPackages.haskell-nix.nix-tools-unchecked.exes.cabal2json
376378
evalPackages.jq
@@ -535,6 +537,15 @@ let
535537
LAST_PKG="ghcjs-th"
536538
''
537539
}
540+
for l in "''${pkgsHostTarget[@]}"; do
541+
if [ -d "$l/package.conf.d" ]; then
542+
files=("$l/package.conf.d/"*.conf)
543+
for file in "''${files[@]}"; do
544+
cat "$file" >> $out
545+
echo '---' >> $out
546+
done
547+
fi
548+
done
538549
for pkg in $PKGS; do
539550
varname="$(echo $pkg | tr "-" "_")"
540551
ver="VER_$varname"

modules/cabal-project.nix

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,5 +144,14 @@ in {
144144
type = nullOr (listOf unspecified);
145145
default = [];
146146
};
147+
prebuilt-depends = mkOption {
148+
type = listOf package;
149+
default = [];
150+
description = ''
151+
pre-built (perhaps proprietary) Haskell packages to make available as dependencies
152+
153+
See Note [prebuilt dependencies] for more details
154+
'';
155+
};
147156
};
148157
}

modules/component-driver.nix

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ in
3737
default = [];
3838
description = "pkgs to globally provide to Setup.hs builds";
3939
};
40+
options.prebuilt-depends = lib.mkOption {
41+
type = lib.types.listOf lib.types.package;
42+
default = [];
43+
description = ''
44+
pre-built (perhaps proprietary) Haskell packages to make available as dependencies
45+
46+
See Note [prebuilt dependencies] for more details
47+
'';
48+
};
4049

4150
# Dependencies (with reinstallable-lib:ghc)
4251
#

overlays/haskell.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ final: prev: {
699699
in if ghc.isHaskellNixCompiler or false then ghc.override { ghcEvalPackages = evalPackages; } else ghc;
700700
compiler.nix-name = final.lib.mkForce config.compiler-nix-name;
701701
evalPackages = final.lib.mkDefault evalPackages;
702+
inherit (config) prebuilt-depends;
702703
} ];
703704
extra-hackages = config.extra-hackages or [] ++ callProjectResults.extra-hackages;
704705
};

0 commit comments

Comments
 (0)