@@ -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.