Skip to content

Commit a070082

Browse files
authored
Improve flake code (#1743)
* Add hydraJobs.devShell and flatten hydraJobs Also moves the `rawFlake` code into a `haskellLib.mkFlake` function * Unflatten hydraJobs and add ciJobs It turns out it is nice to use system first in the hierarchy for cicero. Also both hydra and cicero are ok with a deep hierarchy of attributes. * Refactoring * Add mkFlakeCiJobs * Avoid `:` in ciJobs and hydraJobs * Use cabal names again in ciJobs * More updates Turns off code coverage by default. Rename flattenChecks to mkFlakeChecks. Adds flake packages to ciJobs and hydraJobs. * Expand comment
1 parent 0a0885e commit a070082

File tree

3 files changed

+168
-88
lines changed

3 files changed

+168
-88
lines changed

lib/default.nix

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,8 @@ in {
371371
}) (__attrNames flake.devShells));
372372
} // lib.optionalAttrs (flake ? hydraJobs) {
373373
hydraJobs.${lib.removeSuffix ":" prefix} = flake.hydraJobs;
374+
} // lib.optionalAttrs (flake ? ciJobs) {
375+
ciJobs.${lib.removeSuffix ":" prefix} = flake.ciJobs;
374376
};
375377

376378
# Used by `combineFlakes` to combine flakes together
@@ -393,4 +395,149 @@ in {
393395
# one in the list will be used.
394396
combineFlakes = sep: prefixAndFlakes: builtins.foldl' addFlakes {}
395397
(lib.mapAttrsToList (prefix: flake: prefixFlake prefix sep flake) prefixAndFlakes);
398+
399+
# Make the CI jobs for running code coverage.
400+
# `project` is the base project without code coverage enabled.
401+
# `packages` is a selector function that indicates what packages
402+
# we should run code coverage on (pass haskellLib.selectProjectPackages
403+
# to run it on the packages).
404+
# `coverageProjectModule` is applied to `project` and is useful for
405+
# modifying the project settings when running code coverage (just
406+
# pass `{}` if you do not need to modify anything).
407+
# By default the `doCoverage` flag will be set for the packages
408+
# selected by `packages`.
409+
projectCoverageCiJobs = project: packages: coverageProjectModule:
410+
let
411+
packageNames = project: builtins.attrNames (packages project.hsPkgs);
412+
coverageProject = project.appendModule [
413+
coverageProjectModule
414+
{
415+
modules = [{
416+
packages = lib.genAttrs (packageNames project)
417+
(_: { doCoverage = lib.mkDefault true; });
418+
}];
419+
}
420+
];
421+
in
422+
builtins.listToAttrs (lib.concatMap (packageName: [{
423+
name = packageName;
424+
value = coverageProject.hsPkgs.${packageName}.coverageReport;
425+
}]) (packageNames coverageProject));
426+
427+
# Flake package names that are flat and match the cabal component names.
428+
mkFlakePackages = haskellPackages: builtins.listToAttrs (
429+
lib.concatLists (lib.mapAttrsToList (packageName: package:
430+
lib.optional (package.components ? library)
431+
{ name = "${packageName}:lib:${packageName}"; value = package.components.library; }
432+
++ lib.mapAttrsToList (n: v:
433+
{ name = "${packageName}:lib:${n}"; value = v; })
434+
(package.components.sublibs)
435+
++ lib.mapAttrsToList (n: v:
436+
{ name = "${packageName}:exe:${n}"; value = v; })
437+
(package.components.exes)
438+
++ lib.mapAttrsToList (n: v:
439+
{ name = "${packageName}:test:${n}"; value = v; })
440+
(package.components.tests)
441+
++ lib.mapAttrsToList (n: v:
442+
{ name = "${packageName}:bench:${n}"; value = v; })
443+
(package.components.benchmarks)
444+
) haskellPackages));
445+
446+
# Flake package names that are flat and match the cabal component names.
447+
mkFlakeApps = haskellPackages: builtins.listToAttrs (
448+
lib.concatLists (lib.mapAttrsToList (packageName: package:
449+
lib.mapAttrsToList (n: v:
450+
{ name = "${packageName}:exe:${n}"; value = { type = "app"; program = v.exePath; }; })
451+
(package.components.exes)
452+
++ lib.mapAttrsToList (n: v:
453+
{ name = "${packageName}:test:${n}"; value = { type = "app"; program = v.exePath; }; })
454+
(package.components.tests)
455+
++ lib.mapAttrsToList (n: v:
456+
{ name = "${packageName}:benchmark:${n}"; value = { type = "app"; program = v.exePath; }; })
457+
(package.components.benchmarks)
458+
) haskellPackages));
459+
460+
# Flatten the result of collectChecks or collectChecks' for use in flake `checks`
461+
mkFlakeChecks = allChecks: builtins.listToAttrs (
462+
lib.concatLists (lib.mapAttrsToList (packageName: checks:
463+
# Avoid `recurseForDerivations` issues
464+
lib.optionals (lib.isAttrs checks) (
465+
lib.mapAttrsToList (n: v:
466+
{ name = "${packageName}:test:${n}"; value = v; })
467+
(lib.filterAttrs (_: v: lib.isDerivation v) checks))
468+
) allChecks));
469+
470+
removeRecurseForDerivations = x:
471+
let clean = builtins.removeAttrs x ["recurseForDerivations"];
472+
in
473+
if x.recurseForDerivations or false
474+
then builtins.mapAttrs (_: removeRecurseForDerivations) clean
475+
else clean;
476+
477+
mkFlakeCiJobs = project: {
478+
packages
479+
, checks
480+
, coverage
481+
, devShells
482+
, checkedProject
483+
}: {
484+
# Run all the tests and code coverage
485+
checks = removeRecurseForDerivations checks;
486+
inherit
487+
coverage
488+
# Make sure all the packages build
489+
packages
490+
# Build and cache any tools in the `devShells`
491+
devShells;
492+
# Build tools and cache tools needed for the project
493+
inherit (project) roots;
494+
}
495+
# Build the plan-nix and check it if materialized
496+
// lib.optionalAttrs (checkedProject ? plan-nix) {
497+
plan-nix = checkedProject.plan-nix;
498+
}
499+
# Build the stack-nix and check it if materialized
500+
// lib.optionalAttrs (checkedProject ? stack-nix) {
501+
stack-nix = checkedProject.stack-nix;
502+
};
503+
504+
mkFlake = project: {
505+
selectPackages ? haskellLib.selectProjectPackages
506+
, haskellPackages ? selectPackages project.hsPkgs
507+
, packages ? mkFlakePackages haskellPackages
508+
, apps ? mkFlakeApps haskellPackages
509+
, checks ? mkFlakeChecks (collectChecks' haskellPackages)
510+
, coverage ? {}
511+
, devShell ? project.shell
512+
, devShells ? { default = devShell; }
513+
, checkedProject ? project.appendModule { checkMaterialization = true; }
514+
, ciJobs ? mkFlakeCiJobs project { inherit checks coverage packages devShells checkedProject; }
515+
, hydraJobs ? ciJobs
516+
}: {
517+
inherit
518+
# Used by:
519+
# `nix build .#pkg-name:lib:pkg-name`
520+
# `nix build .#pkg-name:lib:sublib-name`
521+
# `nix build .#pkg-name:exe:exe-name`
522+
# `nix build .#pkg-name:test:test-name`
523+
packages
524+
# Used by:
525+
# `nix flake check`
526+
checks
527+
# `nix run .#pkg-name:exe:exe-name`
528+
# `nix run .#pkg-name:test:test-name`
529+
apps
530+
# Used by hydra.
531+
hydraJobs
532+
# Like `hydraJobs` but with `${system}` first so that it the IFDs will not have
533+
# to run for systems we are not testing (placement of `${system}` is done
534+
# by `flake-utils.eachSystem` and it treats `hydraJobs` differently from
535+
# the other flake attributes).
536+
# See https://github.com/numtide/flake-utils/blob/04c1b180862888302ddfb2e3ad9eaa63afc60cf8/default.nix#L131-L134
537+
ciJobs
538+
# Used by:
539+
# `nix develop`
540+
devShells
541+
devShell; # TODO remove devShell once everyone has nix that supports `devShells.default`
542+
};
396543
}

modules/flake.nix

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,21 @@
2626
```
2727
'';
2828
};
29-
coverage = lib.mkOption {
29+
doCoverage = lib.mkOption {
30+
type = lib.types.bool;
31+
default = false;
32+
description = ''
33+
Specifies if the flake `ciJobs` and `hydraJobs` should include code
34+
coverage reports.
35+
'';
36+
};
37+
coverageProjectModule = lib.mkOption {
3038
type = lib.types.unspecified;
3139
default = {};
3240
description = ''
3341
Project module for use when generating coverage reports.
34-
The project packages will have `doCoverage` by default.
42+
The project packages will have `packages.X.doCoverage`
43+
turned on by default.
3544
'';
3645
};
3746
};

overlays/haskell.nix

Lines changed: 10 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -737,101 +737,25 @@ final: prev: {
737737
"Invalid package component name ${componentName}. Expected package:ctype:component (where ctype is one of lib, flib, exe, test, or bench)";
738738
(getPackage (builtins.elemAt m 0)).getComponent "${builtins.elemAt m 1}:${builtins.elemAt m 2}";
739739

740-
rawFlake =
741-
let
742-
packageNames = project: builtins.attrNames (project.args.flake.packages project.hsPkgs);
743-
checkedProject = project.appendModule { checkMaterialization = true; };
744-
in {
745-
# Used by:
746-
# `nix build .#pkg-name:lib:pkg-name`
747-
# `nix build .#pkg-name:lib:sublib-name`
748-
# `nix build .#pkg-name:exe:exe-name`
749-
# `nix build .#pkg-name:test:test-name`
750-
packages = builtins.listToAttrs (
751-
final.lib.concatMap (packageName:
752-
let package = project.hsPkgs.${packageName};
753-
in final.lib.optional (package.components ? library)
754-
{ name = "${packageName}:lib:${packageName}"; value = package.components.library; }
755-
++ final.lib.mapAttrsToList (n: v:
756-
{ name = "${packageName}:lib:${n}"; value = v; })
757-
(package.components.sublibs)
758-
++ final.lib.mapAttrsToList (n: v:
759-
{ name = "${packageName}:exe:${n}"; value = v; })
760-
(package.components.exes)
761-
++ final.lib.mapAttrsToList (n: v:
762-
{ name = "${packageName}:test:${n}"; value = v; })
763-
(package.components.tests)
764-
++ final.lib.mapAttrsToList (n: v:
765-
{ name = "${packageName}:bench:${n}"; value = v; })
766-
(package.components.benchmarks)
767-
) (packageNames project));
768-
# Used by:
769-
# `nix flake check`
770-
checks = builtins.listToAttrs (
771-
final.lib.concatMap (packageName:
772-
let package = project.hsPkgs.${packageName};
773-
in final.lib.mapAttrsToList (n: v:
774-
{ name = "${packageName}:test:${n}"; value = v; })
775-
(final.lib.filterAttrs (_: v: final.lib.isDerivation v) (package.checks))
776-
) (packageNames project));
777-
# `nix run .#pkg-name:exe:exe-name`
778-
# `nix run .#pkg-name:test:test-name`
779-
apps = builtins.listToAttrs (
780-
final.lib.concatMap (packageName:
781-
let package = project.hsPkgs.${packageName};
782-
in final.lib.mapAttrsToList (n: v:
783-
{ name = "${packageName}:exe:${n}"; value = { type = "app"; program = v.exePath; }; })
784-
(package.components.exes)
785-
++ final.lib.mapAttrsToList (n: v:
786-
{ name = "${packageName}:test:${n}"; value = { type = "app"; program = v.exePath; }; })
787-
(package.components.tests)
788-
++ final.lib.mapAttrsToList (n: v:
789-
{ name = "${packageName}:benchmark:${n}"; value = { type = "app"; program = v.exePath; }; })
790-
(package.components.benchmarks)
791-
) (packageNames project));
792-
# Used by hydra:
793-
hydraJobs = {
794-
checks = rawFlake.checks;
795-
} // final.lib.optionalAttrs (checkedProject ? plan-nix) {
796-
# Build the plan-nix and check it if materialized
797-
plan-nix = checkedProject.plan-nix;
798-
} // final.lib.optionalAttrs (checkedProject ? stack-nix) {
799-
# Build the stack-nix and check it if materialized
800-
stack-nix = checkedProject.stack-nix;
801-
} // {
802-
# Build tools and cache tools needed for the project
803-
roots = project.roots;
804-
coverage =
805-
let
806-
coverageProject = project.appendModule [
807-
project.args.flake.coverage
808-
{
809-
modules = [{
810-
packages = final.lib.genAttrs (packageNames project)
811-
(_: { doCoverage = final.lib.mkDefault true; });
812-
}];
813-
}
814-
];
815-
in builtins.listToAttrs (final.lib.concatMap (packageName: [{
816-
name = packageName;
817-
value = coverageProject.hsPkgs.${packageName}.coverageReport;
818-
}]) (packageNames coverageProject));
819-
};
820-
devShells.default = project.shell;
821-
devShell = project.shell;
822-
};
823740
# Helper function that can be used to make a Nix Flake out of a project
824741
# by including a flake.nix. See docs/tutorials/getting-started-flakes.md
825742
# for an example flake.nix file.
826743
# This flake function maps the build outputs to the flake `packages`,
827744
# `checks` and `apps` output attributes.
828745
flake' =
829746
let
830-
combinePrefix = a: b: if a == "default" then b else "${a}:${b}";
747+
combinePrefix = a: b: if a == "default" then b else "${a}-${b}";
748+
mkFlake = project: haskellLib.mkFlake project rec {
749+
selectPackages = project.args.flake.packages;
750+
coverage = final.lib.optionalAttrs project.args.flake.doCoverage
751+
(haskellLib.projectCoverageCiJobs
752+
project selectPackages project.args.flake.coverageProjectModule);
753+
};
831754
forAllCrossCompilers = prefix: project: (
832-
[{ ${prefix} = project.rawFlake; }]
755+
[{ ${prefix} = mkFlake project; }]
833756
++ (map (project: {
834-
${combinePrefix prefix project.pkgs.stdenv.hostPlatform.config} = project.rawFlake;
757+
${combinePrefix prefix project.pkgs.stdenv.hostPlatform.config} =
758+
mkFlake project;
835759
})
836760
(project.args.flake.crossPlatforms project.projectCross)
837761
));

0 commit comments

Comments
 (0)