Skip to content

Commit 5c25ce9

Browse files
authored
Add getComponent to project and package (#1060)
* Delays cabal configure errors until after evaluation when possible. * Avoids rerunning long `cabal configure` if we already know it will fail. * Provides a way to get a component from a package or project using a `cabal` like reference. Code using the `tool` functions will automatically use `getComponent`. For `(hackage-package {...}).components.library` is also ok. ``` # Consider changing hackage-package use: (pkgs.haskell-nix.hackage-package {...}).components.exes.something (pkgs.haskell-nix.hackage-package {...}).getComponent "exe:something" # For any cabal project: project.hsPkgs.somepackage.components.exes.something project.getComponent "somepackage:exe:something" # or do it in two steps (project.getPackage "somepackage").getComponent "exe:something" ``` The reason for the new function is that we cannot provide the attribute interface without knowing that packages are in the project first. Here is how the `cabal configure` error output is handled: * The `plan-nix` derivation builds even if `cabal configure` fails. * When it fails, it copies `failed-cabal-configure.nix` to the `$out/default.nix` along with a copy of the `cabal configure` output. * When `failed-cabal-configure.nix` is imported and used in any way it writes the `cabal configure` output with `__trace` so it will always be visible. * Instead of a `plan` the imported nix contains a `configurationError` pointing the `cabal configure` output. * The intermediate functions `configurationError` and bubble it up to where it is needed. * `getPackage` returns a mostly empty proxy for a real package when there is a `configurationError` * The `getComponent` function always returns a derivation, but the version in the proxy writes the `cabal configure` output to stdout and calls `exit 1` (so that it will never build).
1 parent cb264f6 commit 5c25ce9

File tree

13 files changed

+175
-53
lines changed

13 files changed

+175
-53
lines changed

ci.nix

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ dimension "Nixpkgs version" nixpkgsVersions (nixpkgsName: nixpkgs-pin:
8080
} // pkgs.lib.optionalAttrs runTests {
8181
inherit (build) tests tools maintainer-scripts maintainer-script-cache;
8282
} // pkgs.lib.optionalAttrs (ifdLevel >= 1) {
83-
iserv-proxy = pkgs.ghc-extra-packages."${compiler-nix-name}".iserv-proxy.components.exes.iserv-proxy;
83+
iserv-proxy = pkgs.ghc-extra-projects."${compiler-nix-name}".getComponent "iserv-proxy:exe:iserv-proxy";
8484
} // pkgs.lib.optionalAttrs (ifdLevel >= 3) {
85-
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).components.exes.hello;
85+
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).getComponent "exe:hello";
8686
});
8787
}
8888
//
@@ -102,10 +102,10 @@ dimension "Nixpkgs version" nixpkgsVersions (nixpkgsName: nixpkgs-pin:
102102
inherit (build) tests;
103103
}) // pkgs.lib.optionalAttrs (ifdLevel >= 2 && crossSystemName != "ghcjs") {
104104
# GHCJS builds its own template haskell runner.
105-
remote-iserv = pkgs.ghc-extra-packages."${compiler-nix-name}".remote-iserv.components.exes.remote-iserv;
106-
iserv-proxy = pkgs.ghc-extra-packages."${compiler-nix-name}".iserv-proxy.components.exes.iserv-proxy;
105+
remote-iserv = pkgs.ghc-extra-projects."${compiler-nix-name}".getComponent "remote-iserv:exe:remote-iserv";
106+
iserv-proxy = pkgs.ghc-extra-projects."${compiler-nix-name}".getComponent "iserv-proxy:exe:iserv-proxy";
107107
} // pkgs.lib.optionalAttrs (ifdLevel >= 3) {
108-
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).components.exes.hello;
108+
hello = (pkgs.haskell-nix.hackage-package { name = "hello"; version = "1.0.0.2"; inherit compiler-nix-name; }).getComponent "exe:hello";
109109
})
110110
))
111111
)

compiler/ghcjs/ghcjs.nix

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@ let
2424
all-ghcjs = pkgs.buildPackages.symlinkJoin {
2525
name = "ghcjs-${ghcjsVersion}-symlinked";
2626
paths = [
27-
ghcjs.components.exes.ghcjs
28-
ghcjs.components.exes.ghcjs-pkg
29-
ghcjs.components.exes.ghcjs-boot
30-
ghcjs.components.exes.ghcjs-dumparchive
27+
(ghcjs.getComponent "exe:ghcjs")
28+
(ghcjs.getComponent "exe:ghcjs-pkg")
29+
(ghcjs.getComponent "exe:ghcjs-boot")
30+
(ghcjs.getComponent "exe:ghcjs-dumparchive")
3131
] ++ (if isGhcjs88
3232
then [
33-
ghcjs.components.exes.haddock
34-
ghcjs.components.exes.private-ghcjs-run
35-
ghcjs.components.exes.private-ghcjs-unlit
36-
ghcjs.components.exes.private-ghcjs-hsc2hs
33+
(ghcjs.getComponent "exe:haddock")
34+
(ghcjs.getComponent "exe:private-ghcjs-run")
35+
(ghcjs.getComponent "exe:private-ghcjs-unlit")
36+
(ghcjs.getComponent "exe:private-ghcjs-hsc2hs")
3737
]
3838
else [
39-
ghcjs.components.exes.haddock-ghcjs
40-
ghcjs.components.exes.hsc2hs-ghcjs
41-
ghcjs.components.exes.ghcjs-run
39+
(ghcjs.getComponent "exe:haddock-ghcjs")
40+
(ghcjs.getComponent "exe:hsc2hs-ghcjs")
41+
(ghcjs.getComponent "exe:ghcjs-run")
4242
]);
4343
};
4444
libexec = "libexec/${builtins.replaceStrings ["darwin" "i686"] ["osx" "i386"] pkgs.stdenv.buildPlatform.system}-${ghc.name}/ghcjs-${ghcVersion}";
@@ -68,9 +68,9 @@ let
6868
lndir ${all-ghcjs}/bin $out/bin
6969
chmod -R +w $out/bin
7070
rm $out/bin/ghcjs-boot
71-
cp ${ghcjs.components.exes.ghcjs-boot}/bin/ghcjs-boot $out/bin
71+
cp ${ghcjs.getComponent "exe:ghcjs-boot"}/bin/ghcjs-boot $out/bin
7272
rm $out/bin/haddock
73-
cp ${ghcjs.components.exes.haddock}/bin/haddock $out/bin
73+
cp ${ghcjs.getComponent "exe:haddock"}/bin/haddock $out/bin
7474
7575
wrapProgram $out/bin/ghcjs --add-flags "-B$out/lib"
7676
wrapProgram $out/bin/haddock --add-flags "-B$out/lib"

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

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ in
4444

4545
let
4646
forName = pkgs.lib.optionalString (name != null) (" for " + name);
47+
nameAndSuffix = suffix: if name == null then suffix else name + "-" + suffix;
4748

4849
ghc' =
4950
if ghcOverride != null
@@ -378,18 +379,40 @@ let
378379
else null;
379380
} // pkgs.lib.optionalAttrs (checkMaterialization != null) {
380381
inherit checkMaterialization;
381-
}) (pkgs.evalPackages.runCommand (if name == null then "plan-to-nix-pkgs" else name + "-plan-to-nix-pkgs") {
382+
}) (pkgs.evalPackages.runCommand (nameAndSuffix "plan-to-nix-pkgs") {
382383
nativeBuildInputs = [ nix-tools dummy-ghc dummy-ghc-pkg cabal-install pkgs.evalPackages.rsync ];
383384
# Needed or stack-to-nix will die on unicode inputs
384385
LOCALE_ARCHIVE = pkgs.lib.optionalString (pkgs.evalPackages.stdenv.buildPlatform.libc == "glibc") "${pkgs.evalPackages.glibcLocales}/lib/locale/locale-archive";
385386
LANG = "en_US.UTF-8";
386387
meta.platforms = pkgs.lib.platforms.all;
387388
preferLocalBuild = false;
388389
outputs = [
389-
"out" # The results of plan-to-nix
390-
"json" # The `plan.json` file generated by cabal and used for `plan-to-nix` input
391-
"freeze" # The `cabal.project.freeze` file created by `cabal v2-freeze`
390+
"out" # The results of plan-to-nix
391+
# These two output will be present if in cabal configure failed.
392+
# They are used to provide passthru.json and passthru.freeze that
393+
# check first for cabal configure failure.
394+
"maybeJson" # The `plan.json` file generated by cabal and used for `plan-to-nix` input
395+
"maybeFreeze" # The `cabal.project.freeze` file created by `cabal v2-freeze`
392396
];
397+
passthru =
398+
let
399+
checkCabalConfigure = ''
400+
if [[ -f ${plan-nix}/cabal-configure.out ]]; then
401+
cat ${plan-nix}/cabal-configure.out
402+
exit 1
403+
fi
404+
'';
405+
in {
406+
# These check for cabal configure failure
407+
json = pkgs.evalPackages.runCommand (nameAndSuffix "plan-json") {} ''
408+
${checkCabalConfigure}
409+
cp ${plan-nix.maybeJson} $out
410+
'';
411+
freeze = pkgs.evalPackages.runCommand (nameAndSuffix "plan-freeze") {} ''
412+
${checkCabalConfigure}
413+
cp ${plan-nix.maybeFreeze} $out
414+
'';
415+
};
393416
} ''
394417
tmp=$(mktemp -d)
395418
cd $tmp
@@ -430,12 +453,14 @@ let
430453
export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
431454
export GIT_SSL_CAINFO=${cacert}/etc/ssl/certs/ca-bundle.crt
432455
456+
mkdir -p $out
457+
433458
# Using `cabal v2-freeze` will configure the project (since
434459
# it is not configured yet), taking the existing `cabal.project.freeze`
435460
# file into account. Then it "writes out a freeze file which
436461
# records all of the versions and flags that are picked" (from cabal docs).
437462
echo "Using index-state ${index-state-found}"
438-
HOME=${
463+
if(HOME=${
439464
# This creates `.cabal` directory that is as it would have
440465
# been at the time `cached-index-state`. We may include
441466
# some packages that will be excluded by `index-state-found`
@@ -460,14 +485,12 @@ let
460485
--enable-benchmarks \
461486
${pkgs.lib.optionalString (ghc.targetPrefix == "js-unknown-ghcjs-")
462487
"--ghcjs --with-ghcjs=js-unknown-ghcjs-ghc --with-ghcjs-pkg=js-unknown-ghcjs-ghc-pkg"} \
463-
${configureArgs}
488+
${configureArgs} 2>&1 | tee -a cabal-configure.out); then
464489
465-
cp cabal.project.freeze $freeze
490+
cp cabal.project.freeze $maybeFreeze
466491
# Not needed any more (we don't want it to wind up in the $out hash)
467492
rm cabal.project.freeze
468493
469-
mkdir -p $out
470-
471494
# ensure we have all our .cabal files (also those generated from package.yaml) files.
472495
# otherwise we'd need to be careful about putting the `cabal-generator = hpack` into
473496
# the nix expression. As we already called `hpack` on all `package.yaml` files we can
@@ -489,7 +512,7 @@ let
489512
(cd $out${subDir'} && plan-to-nix --full --plan-json $tmp${subDir'}/dist-newstyle/cache/plan.json -o .)
490513
491514
# Make the plan.json file available in case we need to debug plan-to-nix
492-
cp $tmp${subDir'}/dist-newstyle/cache/plan.json $json
515+
cp $tmp${subDir'}/dist-newstyle/cache/plan.json $maybeJson
493516
494517
# Remove the non nix files ".project" ".cabal" "package.yaml" files
495518
# as they should not be in the output hash (they may change slightly
@@ -504,6 +527,18 @@ let
504527
505528
# move pkgs.nix to default.nix ensure we can just nix `import` the result.
506529
mv $out${subDir'}/pkgs.nix $out${subDir'}/default.nix
530+
else
531+
# When cabal configure fails copy the output that we captured above and
532+
# use `failed-cabal-configure.nix` to make a suitable derviation with.
533+
cp cabal-configure.out $out
534+
cp ${./failed-cabal-configure.nix} $out/default.nix
535+
536+
# These should only be used indirectly by `passthru.json` and `passthru.freeze`.
537+
# Those derivations will check for `cabal-configure.out` out first to see if
538+
# it is ok to use these files.
539+
echo "Cabal configure failed see $out/cabal-configure.out for details" > $maybeJson
540+
echo "Cabal configure failed see $out/cabal-configure.out for details" > $maybeFreeze
541+
fi
507542
'');
508543
in {
509544
projectNix = plan-nix;

lib/default.nix

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ in {
6262
benchmarks = "bench";
6363
};
6464

65+
# For looking up the components attribute based on the cabal component type
66+
prefixComponent =
67+
lib.listToAttrs (
68+
lib.mapAttrsToList (value: name: { inherit name value; })
69+
componentPrefix);
70+
6571
applyComponents = f: config:
6672
let
6773
comps = config.components;

lib/failed-cabal-configure.nix

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let
2+
configurationError = ./cabal-configure.out;
3+
in
4+
# Trace the error output to make sure the user has a chance to see it
5+
# (even if the choose not to build anything from the project)
6+
__trace ''
7+
ERROR: cabal configure failed with:
8+
${__readFile configurationError}
9+
'' { inherit configurationError; }

overlays/bootstrap.nix

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ in {
581581
cabal-install = final.evalPackages.haskell-nix.cabal-install-unchecked.${compiler-nix-name};
582582
nix-tools = final.evalPackages.haskell-nix.nix-tools-unchecked.${compiler-nix-name};
583583
materialized = ../materialized + "/${compiler-nix-name}/cabal-install";
584-
} // args)).components.exes.cabal;
584+
} // args)).getComponent "exe:cabal";
585585
nix-tools-set = { compiler-nix-name, ... }@args:
586586
let
587587
project =
@@ -617,7 +617,22 @@ in {
617617
];
618618
}];
619619
} // args);
620-
exes = project.nix-tools.components.exes // project.hpack.components.exes;
620+
exes =
621+
let
622+
package = project.getPackage "nix-tools";
623+
in (builtins.map (name: package.getComponent "exe:${name}") [
624+
"cabal-to-nix"
625+
"hashes-to-nix"
626+
"plan-to-nix"
627+
"hackage-to-nix"
628+
"lts-to-nix"
629+
"stack-to-nix"
630+
"truncate-index"
631+
"stack-repos"
632+
"cabal-name"
633+
]) ++ [
634+
(project.getComponent "hpack:exe:hpack")
635+
];
621636
tools = [
622637
final.buildPackages.nix
623638
# Double buildPackages is intentional, see comment in lib/default.nix for details.
@@ -626,7 +641,7 @@ in {
626641
in
627642
final.symlinkJoin {
628643
name = "nix-tools";
629-
paths = builtins.attrValues exes;
644+
paths = exes;
630645
buildInputs = [ final.makeWrapper ];
631646
meta.platforms = final.lib.platforms.all;
632647
# We wrap the -to-nix executables with the executables from `tools` (e.g. nix-prefetch-git)
@@ -774,7 +789,7 @@ in {
774789
version = "1.24.4";
775790
inherit ghcOverride nix-tools cabal-install index-state;
776791
materialized = ../materialized/bootstrap + "/${buildBootstrapper.compilerNixName}/hscolour";
777-
} // args)).components.exes.HsColour;
792+
} // args)).getComponent "exe:HsColour";
778793
hscolour = bootstrap.packages.hscolour-tool {};
779794
hscolour-unchecked = bootstrap.packages.hscolour-tool { checkMaterialization = false; };
780795
};

overlays/haskell.nix

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ final: prev: {
457457
hackage-package =
458458
{ name, compiler-nix-name, ... }@args':
459459
let args = { caller = "hackage-package"; } // args';
460-
in (hackage-project args).hsPkgs.${name};
460+
in (hackage-project args).getPackage name;
461461
hackage-project =
462462
{ name
463463
, compiler-nix-name
@@ -500,11 +500,19 @@ final: prev: {
500500
let
501501
args = { caller = "cabalProject'"; } // args';
502502
callProjectResults = callCabalProjectToNix args;
503-
in let pkg-set = mkCabalProjectPkgSet
504-
{ inherit compiler-nix-name;
505-
plan-pkgs = importAndFilterProject {
506-
inherit (callProjectResults) projectNix sourceRepos src;
503+
plan-pkgs = importAndFilterProject {
504+
inherit (callProjectResults) projectNix sourceRepos src;
505+
};
506+
pkg-set = if plan-pkgs ? configurationError
507+
then {
508+
inherit (plan-pkgs) configurationError;
509+
config = {
510+
compiler.nix-name = compiler-nix-name;
511+
hsPkgs = {};
507512
};
513+
}
514+
else mkCabalProjectPkgSet
515+
{ inherit compiler-nix-name plan-pkgs;
508516
pkg-def-extras = args.pkg-def-extras or [];
509517
modules = (args.modules or [])
510518
++ final.lib.optional (args ? ghcOverride || args ? ghc)
@@ -514,7 +522,7 @@ final: prev: {
514522
extra-hackages = args.extra-hackages or [];
515523
};
516524

517-
project = addProjectAndPackageAttrs rec {
525+
project = addProjectAndPackageAttrs rec {
518526
inherit (pkg-set.config) hsPkgs;
519527
inherit pkg-set;
520528
plan-nix = callProjectResults.projectNix;
@@ -535,7 +543,7 @@ final: prev: {
535543
final.lib.fix (project':
536544
let project = project' // { recurseForDerivations = false; };
537545
in rawProject // rec {
538-
hsPkgs = final.lib.mapAttrs (n: package':
546+
hsPkgs = final.lib.mapAttrs (packageName: package':
539547
if package' == null
540548
then null
541549
else
@@ -548,6 +556,16 @@ final: prev: {
548556
) package'.components;
549557
inherit project;
550558

559+
# Look up a component in the package based on ctype:name
560+
getComponent = componentName:
561+
let m = builtins.match "(lib|flib|exe|test|bench):([^:]*)" componentName;
562+
in
563+
assert final.lib.asserts.assertMsg (m != null)
564+
"Invalid package component name ${componentName}. Expected it to start with one of lib: flib: exe: test: or bench:";
565+
if builtins.elemAt m 0 == "lib" && builtins.elemAt m 1 == packageName
566+
then components.library
567+
else components.${haskellLib.prefixComponent.${builtins.elemAt m 0}}.${builtins.elemAt m 1};
568+
551569
coverageReport = haskellLib.coverageReport (rec {
552570
name = package.identifier.name + "-" + package.identifier.version;
553571
library = if components ? library then components.library else null;
@@ -568,6 +586,48 @@ final: prev: {
568586
rawProject.projectFunction pkgs.haskell-nix rawProject.projectArgs
569587
) final.pkgsCross) // { recurseForDerivations = false; };
570588

589+
# Like `.hsPkgs.${packageName}` but when compined with `getComponent` any
590+
# cabal configure errors are defered until the components derivation builds.
591+
getPackage = packageName:
592+
if rawProject.pkg-set ? configurationError
593+
then
594+
# A minimal proxy for a package when cabal configure failed
595+
let package = {
596+
# Including the project so that things like:
597+
# (p.getPackage "hello").project.tool "hlint" "latest"
598+
# will still work even if "hello" failed to configure.
599+
inherit project;
600+
601+
# Defer configure time errors for the library component
602+
# (p.getPackage "hello").components.library
603+
components.library = package.getComponent "lib:${packageName}";
604+
605+
# This procide a derivation (even though the component may
606+
# not exist at all). The derivation will never build
607+
# and simple outputs the result of cabal configure.
608+
getComponent = componentName:
609+
final.evalPackages.runCommand "cabal-configure-error" {
610+
passthru = {
611+
inherit project package;
612+
};
613+
} ''
614+
cat ${rawProject.pkg-set.configurationError}
615+
echo Unable to find component ${packageName}:${componentName} \
616+
due to the above cabal configuration error
617+
exit 1
618+
'';
619+
};
620+
in package
621+
else project.hsPkgs.${packageName};
622+
623+
# Look a component in the project based on `pkg:ctype:name`
624+
getComponent = componentName:
625+
let m = builtins.match "([^:]*):(lib|flib|exe|test|bench):([^:]*)" componentName;
626+
in
627+
assert final.lib.asserts.assertMsg (m != null)
628+
"Invalid package component name ${componentName}. Expected package:ctype:component (where ctype is one of lib, flib, exe, test, or bench)";
629+
(getPackage (builtins.elemAt m 0)).getComponent "${builtins.elemAt m 1}:${builtins.elemAt m 2}";
630+
571631
# Helper function that can be used to make a Nix Flake out of a project
572632
# by including a flake.nix. See docs/tutorials/getting-started-flakes.md
573633
# for an example flake.nix file.

overlays/tools.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ in { haskell-nix = prev.haskell-nix // {
6666
{ configureArgs = "--disable-benchmarks --disable-tests"; }
6767
// args
6868
// { name = final.haskell-nix.toolPackageName.${name} or name; }))
69-
.components.exes."${final.haskell-nix.packageToolName.${name} or name}";
69+
.getComponent "exe:${final.haskell-nix.packageToolName.${name} or name}";
7070

7171
tool = compiler-nix-name: name: versionOrArgs:
7272
let

scripts/check-compiler-materialization/default.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
let
55
eval = (import ../../. {}).pkgs;
66
linux = (import ../../. { checkMaterialization = true; system = "x86_64-linux"; }).pkgs;
7-
darwin = (import ../../. { checkMaterialization = true; system = "x86_64-darwin"; }).pkgs;
7+
darwin = (import ../../. { checkMaterialization = true; system = "x86_64-darwin"; }).pkgs-unstable;
88
in eval.linkFarm "check-${compiler-nix-name}" [
99
# This set of derivations should be enough to ensure all the materialized files for a
1010
# given GHC version are checked.
1111
{ name = "linux-cabal-install"; path = linux.haskell-nix.cabal-install.${compiler-nix-name}; }
12-
# { name = "darwin-cabal-install"; path = darwin.haskell-nix.cabal-install.${compiler-nix-name}; }
12+
{ name = "darwin-cabal-install"; path = darwin.haskell-nix.cabal-install.${compiler-nix-name}; }
1313
{ name = "linux-nix-tools"; path = linux.haskell-nix.nix-tools.${compiler-nix-name}; }
1414
{ name = "linux"; path = linux.ghc-extra-projects.${compiler-nix-name}.plan-nix; }
1515
# In some cased you may need comment out one or more of these if the GHC version needed cannot be built.

0 commit comments

Comments
 (0)