Skip to content

Commit 7d78d16

Browse files
shlevyhamishmack
andauthored
Support backpack builds (#2467)
* make-install-plan: Include info needed for backpack. * Support backpack builds. Fixes #244 * fixup! Support backpack builds. * Bump head.hackage * Dump head.hackage * Work around GHC JS backend not producing .o files for backpack signatures GHC's JavaScript backend doesn't produce .o files for backpack signature module instantiations, causing ar to fail when creating library archives. Fix this by replacing the ar command in GHC's settings with a wrapper that filters out missing .o files from both command-line arguments and response files. The fix only applies to GHCJS instantiated backpack builds, leaving all other builds unchanged. --------- Co-authored-by: Hamish Mackenzie <Hamish.K.Mackenzie@gmail.com>
1 parent 536ba3c commit 7d78d16

File tree

13 files changed

+165
-13
lines changed

13 files changed

+165
-13
lines changed

builder/comp-builder.nix

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ let self =
220220
, useLLVM
221221
, smallAddressSpace
222222
, prebuilt-depends
223+
, instantiations ? {}
223224
}@drvArgs:
224225

225226
let
@@ -273,14 +274,49 @@ let
273274
configFiles = makeConfigFiles {
274275
component = componentForSetup;
275276
inherit (package) identifier;
276-
inherit fullName flags needsProfiling enableDWARF prebuilt-depends;
277+
inherit fullName flags needsProfiling enableDWARF prebuilt-depends instantiations;
277278
};
278279

279280
enableFeature = enable: feature:
280281
(if enable then "--enable-" else "--disable-") + feature;
281282

282283
disableFeature = disable: enableFeature (!disable);
283284

285+
# When building instantiated backpack packages with GHCJS, GHC's JavaScript
286+
# backend may not produce .o files for backpack signature module instantiations.
287+
# This ar wrapper filters out missing .o files. The real ar path is read from
288+
# GHC's settings file at runtime.
289+
arWrapper = let
290+
settingsFile = "${ghc}/${configFiles.libDir}/settings";
291+
in pkgsBuildBuild.writeShellScript "ar-wrapper" ''
292+
REAL_AR=$(grep '"ar command"' ${settingsFile} | sed 's/.*", *"\([^"]*\)".*/\1/')
293+
if [ -z "$REAL_AR" ]; then
294+
echo "ar-wrapper: Could not find real ar in ${settingsFile}" >&2
295+
exit 1
296+
fi
297+
# Filter out missing .o files from both command-line args and response files
298+
args=()
299+
for arg in "$@"; do
300+
if [[ "$arg" == @* ]]; then
301+
# Response file: filter missing .o entries
302+
rspfile="''${arg#@}"
303+
newrsp="''${rspfile}.filtered"
304+
while IFS= read -r line || [ -n "$line" ]; do
305+
if [[ "$line" == *.o ]] && [[ ! -e "$line" ]]; then
306+
continue
307+
fi
308+
echo "$line"
309+
done < "$rspfile" > "$newrsp"
310+
args+=("@$newrsp")
311+
elif [[ "$arg" == *.o ]] && [[ ! -e "$arg" ]]; then
312+
continue
313+
else
314+
args+=("$arg")
315+
fi
316+
done
317+
exec "$REAL_AR" "''${args[@]}"
318+
'';
319+
284320
finalConfigureFlags = lib.concatStringsSep " " (
285321
[ "--prefix=$out"
286322
] ++
@@ -615,6 +651,17 @@ let
615651
# (this can result in unwanted dependencies on GHC)
616652
+ ''
617653
rm -rf $wrappedGhc/share/doc $wrappedGhc/share/man $wrappedGhc/share/devhelp/books
654+
'' + lib.optionalString (stdenv.hostPlatform.isGhcjs && instantiations != {}) ''
655+
# GHC's JavaScript backend may not produce .o files for backpack
656+
# signature module instantiations, causing ar to fail. Replace the
657+
# ar command in GHC's settings with a wrapper that filters out
658+
# missing .o files.
659+
settingsFile="$wrappedGhc/${configFiles.libDir}/settings"
660+
if [ -L "$settingsFile" ]; then
661+
cp --remove-destination "$(readlink -f "$settingsFile")" "$settingsFile"
662+
fi
663+
sed -i 's|("ar command", "[^"]*")|("ar command", "${arWrapper}")|' "$settingsFile"
664+
'' + ''
618665
PATH=$wrappedGhc/bin:$PATH
619666
620667
runHook preConfigure
@@ -694,7 +741,7 @@ let
694741
cat $SETUP_ERR | tr '\n' ' ' | tr -d '\r' | grep 'No executables and no library found\. Nothing to do\.'
695742
fi
696743
''}
697-
${lib.optionalString (haskellLib.isLibrary componentId) ''
744+
${lib.optionalString (haskellLib.isLibrary componentId) (''
698745
$SETUP_HS register --gen-pkg-config=${name}.conf
699746
${ghc.targetPrefix}ghc-pkg -v0 init $out/package.conf.d
700747
${ghc.targetPrefix}ghc-pkg -v0 --package-db $configFiles/${configFiles.packageCfgDir} -f $out/package.conf.d register ${name}.conf
@@ -741,7 +788,10 @@ let
741788
fi
742789
'')
743790
}
744-
''}
791+
'' + lib.optionalString (instantiations != {}) ''
792+
# An instantiated package will always depend on its indefinite counterpart, and the --dependency= flag added by exactDep/configure-flags is invalid for package IDs with a +
793+
rm -fR $out/exactDep
794+
'')}
745795
${(lib.optionalString (haskellLib.isTest componentId || haskellLib.isBenchmark componentId || (haskellLib.isExe componentId && stdenv.hostPlatform.isGhcjs)) ''
746796
mkdir -p $out/bin
747797
if [ -f ${testExecutable} ]; then

builder/make-config-files.nix

Lines changed: 5 additions & 1 deletion
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, prebuilt-depends ? [] }:
3+
{ identifier, component, fullName, flags ? {}, needsProfiling ? false, enableDWARF ? false, chooseDrv ? drv: drv, nonReinstallablePkgs ? defaults.nonReinstallablePkgs, prebuilt-depends ? [], instantiations ? {} }:
44

55
let
66
# Sort and remove duplicates from nonReinstallablePkgs.
@@ -179,6 +179,10 @@ let
179179
''}
180180
done
181181
''
182+
# Handle backpack instantiations
183+
+ (builtins.concatStringsSep "\n" (lib.mapAttrsToList (modname: val: ''
184+
echo "--instantiate-with=${modname}=$(cut -d ' ' -f 2 ${val.unit}/envDep):${val.module}" >> $configFiles/configure-flags
185+
'') instantiations))
182186
# This code originates in the `generic-builder.nix` from nixpkgs. However GHC has been fixed
183187
# to drop unused libraries referenced from libraries; and this patch is usually included in the
184188
# nixpkgs's GHC builds. This doesn't sadly make this stupid hack unnecessary. It resurfaces in

lib/default.nix

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,10 @@ in {
361361
# `d` in the `nix` error should include the name
362362
# eg. `packages.Cabal.components.library`.
363363
if d ? components
364-
then d.components.library
365-
else d;
364+
then if d ? instantiations
365+
then d.components.library.override { inherit (d) instantiations; }
366+
else d.components.library
367+
else d;
366368

367369
projectOverlays = import ./project-overlays.nix {
368370
inherit lib haskellLib;

lib/load-cabal-plan.nix

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,24 @@ let
2727
map (dname: { name = dname; value = null; }) (lookupPreExisting (p.depends or p.components.lib.depends)));
2828
}) plan-json.install-plan);
2929
# Lookup a dependency in `hsPkgs`
30-
lookupDependency = hsPkgs: d:
31-
pkgs.lib.optional (by-id.${d}.type != "pre-existing") (
32-
if by-id.${d}.component-name or "lib" == "lib"
33-
then hsPkgs.${d} or hsPkgs."${by-id.${d}.pkg-name}-${by-id.${d}.pkg-version}" or hsPkgs.${by-id.${d}.pkg-name}
34-
else hsPkgs.${d}.components.sublibs.${pkgs.lib.removePrefix "lib:" by-id.${d}.component-name});
30+
lookupDependency = let
31+
lookupDependency' = hsPkgs: d: let
32+
instantiated-with = by-id.${d}.instantiated-with or {};
33+
instantiations = pkgs.lib.mapAttrs (_: value: value // {
34+
unit = lookupDependency' hsPkgs value.unit-id;
35+
}) instantiated-with;
36+
lib-comp' = hsPkgs.${d} or hsPkgs."${by-id.${d}.pkg-name}-${by-id.${d}.pkg-version}" or hsPkgs.${by-id.${d}.pkg-name};
37+
lib-comp = if instantiations == {} then lib-comp' else lib-comp' // {
38+
inherit instantiations;
39+
};
40+
sublib-comp' = hsPkgs.${d}.components.sublibs.${pkgs.lib.removePrefix "lib:" by-id.${d}.component-name};
41+
sublib-comp = if instantiations == {} then sublib-comp' else sublib-comp'.override { inherit instantiations; };
42+
comp = if by-id.${d}.component-name or "lib" == "lib"
43+
then lib-comp
44+
else sublib-comp;
45+
in comp;
46+
in hsPkgs: d:
47+
pkgs.lib.optional (by-id.${d}.type != "pre-existing") (lookupDependency' hsPkgs d);
3548
# Lookup an executable dependency in `hsPkgs.pkgsBuildBuild`
3649
lookupExeDependency = hsPkgs: d:
3750
# Try to lookup by ID, but if that fails use the name (currently a different plan is used by pkgsBuildBuild when cross compiling)

nix-tools/nix-tools/make-install-plan/ProjectPlanOutput.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Distribution.Client.Types.PackageLocation (PackageLocation (..))
1919
import Distribution.Client.Types.Repo (RemoteRepo (..), Repo (..))
2020
import Distribution.Client.Types.SourceRepo (SourceRepoMaybe, SourceRepositoryPackage (..))
2121
import Distribution.Client.Version (cabalInstallVersion)
22+
import Distribution.ModuleName hiding (components)
2223

2324
import qualified Distribution.Client.InstallPlan as InstallPlan
2425
import qualified Distribution.Client.Utils.Json as J
@@ -46,6 +47,7 @@ import Distribution.Types.Version (
4647

4748
import Distribution.Client.Compat.Prelude
4849
import Prelude ()
50+
import qualified GHC.IsList as IL
4951

5052
import qualified Data.ByteString.Builder as BB
5153
import qualified Data.Map as Map
@@ -180,6 +182,7 @@ encodePlanAsJson distDirLayout elaboratedInstallPlan elaboratedSharedConfig targ
180182
]
181183
, "style" J..= J.String (style2str (elabLocalToProject elab) (elabBuildStyle elab))
182184
, "pkg-src" J..= packageLocationToJ (elabPkgSourceLocation elab)
185+
, "instantiated-with" J..= instantiationsToJ (elabInstantiatedWith elab)
183186
]
184187
++ [ "pkg-cabal-sha256" J..= J.String (showHashValue hash)
185188
| Just hash <- [fmap hashValue (elabPkgDescriptionOverride elab)]
@@ -213,7 +216,7 @@ encodePlanAsJson distDirLayout elaboratedInstallPlan elaboratedSharedConfig targ
213216
]
214217
in ["components" J..= components]
215218
ElabComponent comp ->
216-
[ "depends" J..= map (jdisplay . confInstId) (map fst $ elabLibDependencies elab)
219+
[ "depends" J..= map jdisplay (compOrderLibDependencies comp)
217220
, "exe-depends" J..= map jdisplay (elabExeDependencies elab)
218221
, "component-name" J..= J.String (comp2str (compSolverName comp))
219222
]
@@ -262,6 +265,16 @@ encodePlanAsJson distDirLayout elaboratedInstallPlan elaboratedSharedConfig targ
262265
, "source-repo" J..= sourceRepoToJ srcRepo
263266
]
264267

268+
instantiationsToJ :: Map ModuleName Module -> J.Value
269+
instantiationsToJ m = J.object (instantiationToJ <$> IL.toList m)
270+
271+
instantiationToJ :: (ModuleName, Module) -> J.Pair
272+
instantiationToJ (nm, (Module duid nm')) = prettyShow nm J..= J.object
273+
[ "unit-id" J..= jdisplay duid
274+
, "module" J..= jdisplay nm'
275+
]
276+
277+
265278
repoToJ :: Repo -> J.Value
266279
repoToJ repo =
267280
case repo of

test/backpack/backpack.cabal

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
cabal-version: 3.4
2+
name: backpack
3+
version: 0.1.0.0
4+
license: MIT
5+
author: Moritz Angermann
6+
maintainer: moritz.angermann@iohk.io
7+
build-type: Simple
8+
9+
library sig
10+
build-depends: base
11+
signatures: Module
12+
default-language: Haskell2010
13+
hs-source-dirs: sig
14+
15+
library impl
16+
build-depends: base
17+
exposed-modules: Mod
18+
default-language: Haskell2010
19+
hs-source-dirs: impl
20+
21+
library consumer
22+
build-depends: base, backpack:sig
23+
exposed-modules: Consumer
24+
signatures: Module
25+
default-language: Haskell2010
26+
hs-source-dirs: consumer
27+
28+
executable backpack
29+
build-depends: base, backpack:consumer, backpack:impl
30+
main-is: Main.hs
31+
mixins: backpack:consumer requires (Module as Mod)
32+
default-language: Haskell2010
33+
hs-source-dirs: exe

test/backpack/consumer/Consumer.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Consumer where
2+
3+
import Module
4+
5+
printMsg :: IO ()
6+
printMsg = putStrLn msg

test/backpack/default.nix

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Test backpack
2+
{ stdenv, lib, haskellLib, cabalProject', testSrc, compiler-nix-name, evalPackages }:
3+
4+
let
5+
project = cabalProject' {
6+
inherit compiler-nix-name evalPackages;
7+
src = testSrc "backpack";
8+
inherit (evalPackages.haskell-nix) nix-tools;
9+
cabalProjectLocal = builtins.readFile ../cabal.project.local
10+
+ lib.optionalString (haskellLib.isCrossHost && stdenv.hostPlatform.isAarch64) ''
11+
constraints: text -simdutf, text source
12+
'';
13+
};
14+
in lib.recurseIntoAttrs {
15+
ifdInputs = { inherit (project) plan-nix; };
16+
build = project.hsPkgs.backpack.components.exes.backpack;
17+
}

test/backpack/exe/Main.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Main where
2+
3+
import Consumer
4+
5+
main :: IO ()
6+
main = printMsg

test/backpack/impl/Mod.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module Mod where
2+
3+
msg :: String
4+
msg = "Hello, world!"

0 commit comments

Comments
 (0)