on iko/ghcjs-patch-master help sign in
Use nix module system to check project arguments (#1087)
4f449d3Hamish Mackenzie
@@ -1,6 +1,11 @@
1This file contains a summary of changes to Haskell.nix and `nix-tools`
1This file contains a summary of changes to Haskell.nix and `nix-tools`
2that will impact users.
2that will impact users.
3
3
4+## Apr 8, 2021
5+* Project arguments are now validated with the Nix module system.
6+ If unexpected argments are passed to a project function this may now
7+ result in an error.
8+
4## Feb 22, 2021
9## Feb 22, 2021
5* Add `.dwarf` to build any component with DWARF dubug info on linux
10* Add `.dwarf` to build any component with DWARF dubug info on linux
6 (ghc >=8.10.2).
11 (ghc >=8.10.2).
lib/call-cabal-project-to-nix.nix+10−2
@@ -21,8 +21,6 @@ in
21, caller ? "callCabalProjectToNix" # Name of the calling function for better warning messages
21, caller ? "callCabalProjectToNix" # Name of the calling function for better warning messages
22, ghc ? null # Deprecated in favour of `compiler-nix-name`
22, ghc ? null # Deprecated in favour of `compiler-nix-name`
23, ghcOverride ? null # Used when we need to set ghc explicitly during bootstrapping
23, ghcOverride ? null # Used when we need to set ghc explicitly during bootstrapping
24−, nix-tools ? evalPackages.haskell-nix.nix-tools-unchecked.${compiler-nix-name} # When building cabal projects we use the nix-tools
25−, cabal-install ? evalPackages.haskell-nix.cabal-install-unchecked.${compiler-nix-name} # and cabal-install compiled with matching ghc version
26, configureArgs ? "" # Extra arguments to pass to `cabal v2-configure`.
24, configureArgs ? "" # Extra arguments to pass to `cabal v2-configure`.
27 # `--enable-tests --enable-benchmarks` are included by default.
25 # `--enable-tests --enable-benchmarks` are included by default.
28 # If the tests and benchmarks are not needed and they
26 # If the tests and benchmarks are not needed and they
@@ -43,6 +41,16 @@ in
43}@args:
41}@args:
44
42
45let
43let
44+ # These defaults are hear rather than in modules/cabal-project.nix to make them
45+ # lazy enough to avoid infinite recursion issues.
46+ # Using null as the default also improves performance as they are not forced by the
47+ # nix module system for `nix-tools-unchecked` and `cabal-install-unchecked`.
48+ nix-tools = if args.nix-tools or null != null
49+ then args.nix-tools
50+ else evalPackages.haskell-nix.nix-tools-unchecked.${compiler-nix-name};
51+ cabal-install = if args.cabal-install or null != null
52+ then args.cabal-install
53+ else evalPackages.haskell-nix.cabal-install-unchecked.${compiler-nix-name};
46 forName = pkgs.lib.optionalString (name != null) (" for " + name);
54 forName = pkgs.lib.optionalString (name != null) (" for " + name);
47 nameAndSuffix = suffix: if name == null then suffix else name + "-" + suffix;
55 nameAndSuffix = suffix: if name == null then suffix else name + "-" + suffix;
48
56
lib/default.nix+17
@@ -333,4 +333,21 @@ in {
333 }
333 }
334 else src.origSrc or src; # If there is a subDir and origSrc (but no filter) use origSrc
334 else src.origSrc or src; # If there is a subDir and origSrc (but no filter) use origSrc
335 };
335 };
336+
337+ # Run evalModules passing the project function argument (m) as a module along with
338+ # the the a projectType module (../modules/cabal-project.nix or ../modules/stack-project.nix).
339+ # The resulting config is then passed to the project function's implementation.
340+ evalProjectModule = projectType: m: f: f
341+ (lib.evalModules {
342+ modules = [m] ++ [
343+ # Include ../modules/cabal-project.nix or ../modules/stack-project.nix
344+ (import projectType)
345+ # Pass the pkgs to the modules
346+ ({ config, lib, ... }: {
347+ _module.args = {
348+ inherit pkgs;
349+ };
350+ })
351+ ];
352+ }).config;
336}
353}
modules/cabal-project.nixnew file+140
@@ -0,0 +1,140 @@
1+{ lib, config, pkgs, haskellLib, ... }:
2+with lib;
3+with types;
4+let readIfExists = src: fileName:
5+ let origSrcDir = src.origSrcSubDir or src;
6+ in
7+ if builtins.elem ((__readDir origSrcDir)."${fileName}" or "") ["regular" "symlink"]
8+ then __readFile (origSrcDir + "/${fileName}")
9+ else null;
10+in {
11+ options = {
12+ # Used by callCabalProjectToNix
13+ name = mkOption {
14+ type = nullOr str;
15+ default = config.src.name or null;
16+ description = "Optional name for better error messages";
17+ };
18+ src = mkOption {
19+ type = either path package;
20+ };
21+ compiler-nix-name = mkOption {
22+ type = str;
23+ description = "The name of the ghc compiler to use eg. \"ghc884\"";
24+ };
25+ index-state = mkOption {
26+ type = nullOr str;
27+ default = null;
28+ description = "Hackage index-state, eg. \"2019-10-10T00:00:00Z\"";
29+ };
30+ index-sha256 = mkOption {
31+ type = nullOr str;
32+ default = null;
33+ description = "The hash of the truncated hackage index-state";
34+ };
35+ plan-sha256 = mkOption {
36+ type = nullOr str;
37+ default = null;
38+ description = "The hash of the plan-to-nix output (makes the plan-to-nix step a fixed output derivation)";
39+ };
40+ materialized = mkOption {
41+ type = nullOr (either path package);
42+ default = null;
43+ description = "Location of a materialized copy of the nix files";
44+ };
45+ checkMaterialization = mkOption {
46+ type = nullOr bool;
47+ default = null;
48+ description = "If true the nix files will be generated used to check plan-sha256 and material";
49+ };
50+ cabalProjectFileName = mkOption {
51+ type = str;
52+ default = "cabal.project";
53+ };
54+ cabalProject = mkOption {
55+ type = nullOr str;
56+ default = readIfExists config.src config.cabalProjectFileName;
57+ };
58+ cabalProjectLocal = mkOption {
59+ type = nullOr str;
60+ default = readIfExists config.src "${config.cabalProjectFileName}.local";
61+ };
62+ cabalProjectFreeze = mkOption {
63+ type = nullOr str;
64+ default = readIfExists config.src "${config.cabalProjectFileName}.freeze";
65+ };
66+ caller = mkOption {
67+ type = str;
68+ default = "callCabalProjectToNix";
69+ description = "Name of the calling function for better warning messages";
70+ };
71+ ghc = mkOption {
72+ type = nullOr package;
73+ default = null;
74+ description = "Deprecated in favour of `compiler-nix-name`";
75+ };
76+ ghcOverride = mkOption {
77+ type = nullOr package;
78+ default = null;
79+ description = "Used when we need to set ghc explicitly during bootstrapping";
80+ };
81+ # The defaults for `nix-tools` and `cabal-install` are in `call-cabal-project-to-nix.nix`
82+ # to make sure it is not evaluated too strictly (which would lead to infinite recursion).
83+ nix-tools = mkOption {
84+ type = nullOr package;
85+ default = null;
86+ description = "nix-tools to use when converting the `plan.json` to nix";
87+ };
88+ cabal-install = mkOption {
89+ type = nullOr package;
90+ default = null;
91+ description = "cabal-install to use when running `cabal configure`";
92+ };
93+ configureArgs = mkOption {
94+ type = nullOr str;
95+ default = "";
96+ description = ''
97+ Extra arguments to pass to `cabal v2-configure`.
98+ `--enable-tests --enable-benchmarks` are included by default.
99+ If the tests and benchmarks are not needed and they
100+ cause the wrong plan to be chosen, then we can use
101+ `configureArgs = "--disable-tests --disable-benchmarks";`
102+ '';
103+ };
104+ sha256map = mkOption {
105+ type = nullOr unspecified;
106+ default = null;
107+ description = ''
108+ An alternative to adding `--sha256` comments into the
109+ cabal.project file:
110+ sha256map =
111+ { "https://github.com/jgm/pandoc-citeproc"."0.17"
112+ = "0dxx8cp2xndpw3jwiawch2dkrkp15mil7pyx7dvd810pwc22pm2q"; };
113+ '';
114+ };
115+ lookupSha256 = mkOption {
116+ type = nullOr unspecified;
117+ default = if config.sha256map != null
118+ then { location, tag, ...}: config.sha256map.${location}.${tag}
119+ else _: null;
120+ };
121+ extra-hackage-tarballs = mkOption {
122+ type = nullOr (listOf unspecified);
123+ default = [];
124+ };
125+
126+ # Used by mkCabalProjectPkgSet
127+ pkg-def-extras = mkOption {
128+ type = nullOr (listOf unspecified);
129+ default = [];
130+ };
131+ modules = mkOption {
132+ type = nullOr (listOf unspecified);
133+ default = [];
134+ };
135+ extra-hackages = mkOption {
136+ type = nullOr (listOf unspecified);
137+ default = [];
138+ };
139+ };
140+}
modules/stack-project.nixnew file+112
@@ -0,0 +1,112 @@
1+{ lib, config, pkgs, haskellLib, ... }:
2+with lib;
3+with types;
4+{
5+ options = {
6+ # Used by callStackToNix
7+ name = mkOption {
8+ type = nullOr str;
9+ default = config.src.name or null;
10+ description = "Optional name for better error messages";
11+ };
12+ src = mkOption {
13+ type = either path package;
14+ };
15+ stackYaml = mkOption {
16+ type = str;
17+ default = "stack.yaml";
18+ };
19+ ignorePackageYaml = mkOption {
20+ type = bool;
21+ default = false;
22+ };
23+ cache = mkOption {
24+ type = nullOr unspecified;
25+ default = null;
26+ };
27+ stack-sha256 = mkOption {
28+ type = nullOr str;
29+ default = null;
30+ };
31+ resolverSha256 = mkOption {
32+ type = nullOr str;
33+ default = null;
34+ };
35+ materialized = mkOption {
36+ type = nullOr (either path package);
37+ default = null;
38+ description = "Location of a materialized copy of the nix files";
39+ };
40+ checkMaterialization = mkOption {
41+ type = nullOr bool;
42+ default = null;
43+ description = "If true the nix files will be generated used to check plan-sha256 and material";
44+ };
45+ caller = mkOption {
46+ type = str;
47+ default = "callCabalProjectToNix";
48+ description = "Name of the calling function for better warning messages";
49+ };
50+ nix-tools = mkOption {
51+ type = nullOr package;
52+ default = pkgs.evalPackages.haskell-nix.internal-nix-tools;
53+ description = "nix-tools to use when converting the `plan.json` to nix";
54+ };
55+
56+ # Used by mkStackPkgSet
57+ pkg-def-extras = mkOption {
58+ type = nullOr (listOf unspecified);
59+ default = [];
60+ };
61+ modules = mkOption {
62+ type = nullOr (listOf unspecified);
63+ default = [];
64+ };
65+ extra-hackages = mkOption {
66+ type = nullOr (listOf unspecified);
67+ default = [];
68+ };
69+
70+ # Used in stack-cache-generator.nix
71+ sha256map = mkOption {
72+ type = nullOr unspecified;
73+ default = null;
74+ description = ''
75+ An alternative to adding `# nix-sha256:` comments into the
76+ stack.yaml file:
77+ sha256map =
78+ { "https://github.com/jgm/pandoc-citeproc"."0.17"
79+ = "0dxx8cp2xndpw3jwiawch2dkrkp15mil7pyx7dvd810pwc22pm2q"; };
80+ '';
81+ };
82+ lookupSha256 = mkOption {
83+ type = nullOr unspecified;
84+ default = if config.sha256map != null
85+ then { location, tag, ...}: config.sha256map.${location}.${tag}
86+ else _: null;
87+ };
88+ branchMap = mkOption {
89+ type = nullOr unspecified;
90+ default = null;
91+ description = "A way to specify in which branch a git commit can be found";
92+ };
93+ lookupBranch = mkOption {
94+ type = nullOr unspecified;
95+ default = if config.branchMap != null
96+ then { location, tag, ...}: config.branchMap.${location}.${tag} or null
97+ else _: null;
98+ };
99+
100+ # Used by stackProject itself
101+ compiler-nix-name = mkOption {
102+ type = nullOr str;
103+ description = "The name of the ghc compiler to use eg. \"ghc884\"";
104+ default = null;
105+ };
106+ ghc = mkOption {
107+ type = nullOr package;
108+ default = null;
109+ description = "Deprecated in favour of `compiler-nix-name`";
110+ };
111+ };
112+}
overlays/bootstrap.nix+1−8
@@ -632,10 +632,6 @@ in {
632 name = "cabal-install";
632 name = "cabal-install";
633 version = "3.4.0.0";
633 version = "3.4.0.0";
634 index-state = final.haskell-nix.internalHackageIndexState;
634 index-state = final.haskell-nix.internalHackageIndexState;
635− # When building cabal-install (only happens when checking materialization)
636− # disable checking of the tools used to avoid infinite recursion.
637− cabal-install = final.evalPackages.haskell-nix.cabal-install-unchecked.${compiler-nix-name};
638− nix-tools = final.evalPackages.haskell-nix.nix-tools-unchecked.${compiler-nix-name};
639 materialized = ../materialized + "/${compiler-nix-name}/cabal-install";
635 materialized = ../materialized + "/${compiler-nix-name}/cabal-install";
640 } // args)).getComponent "exe:cabal";
636 } // args)).getComponent "exe:cabal";
641 nix-tools-set = { compiler-nix-name, ... }@args:
637 nix-tools-set = { compiler-nix-name, ... }@args:
@@ -648,6 +644,7 @@ in {
648 else args.compiler-nix-name;
644 else args.compiler-nix-name;
649 project =
645 project =
650 final.haskell-nix.cabalProject ({
646 final.haskell-nix.cabalProject ({
647+ caller = "nix-tools-set";
651 name = "nix-tools";
648 name = "nix-tools";
652 src = final.haskell-nix.sources.nix-tools;
649 src = final.haskell-nix.sources.nix-tools;
653 # This is a handy way to use a local git clone of nix-tools when developing
650 # This is a handy way to use a local git clone of nix-tools when developing
@@ -657,10 +654,6 @@ in {
657 allow-newer: Cabal:base, cryptohash-sha512:base, haskeline:base
654 allow-newer: Cabal:base, cryptohash-sha512:base, haskeline:base
658 index-state: ${final.haskell-nix.internalHackageIndexState}
655 index-state: ${final.haskell-nix.internalHackageIndexState}
659 '';
656 '';
660− # When building cabal-install (only happens when checking materialization)
661− # disable checking of the tools used to avoid infinite recursion.
662− cabal-install = final.evalPackages.haskell-nix.cabal-install-unchecked.${compiler-nix-name};
663− nix-tools = final.evalPackages.haskell-nix.nix-tools-unchecked.${compiler-nix-name};
664 materialized = ../materialized + "/${compiler-nix-name}/nix-tools";
657 materialized = ../materialized + "/${compiler-nix-name}/nix-tools";
665 modules = [{
658 modules = [{
666 packages.transformers-compat.components.library.doExactConfig = true;
659 packages.transformers-compat.components.library.doExactConfig = true;
overlays/haskell.nix+17−19
@@ -496,6 +496,7 @@ final: prev: {
496 # separately from the hsPkgs. The advantage is that the you can get the
496 # separately from the hsPkgs. The advantage is that the you can get the
497 # plan-nix without building the project.
497 # plan-nix without building the project.
498 cabalProject' =
498 cabalProject' =
499+ projectModule: haskellLib.evalProjectModule ../modules/cabal-project.nix projectModule (
499 { src, compiler-nix-name, ... }@args':
500 { src, compiler-nix-name, ... }@args':
500 let
501 let
501 args = { caller = "cabalProject'"; } // args';
502 args = { caller = "cabalProject'"; } // args';
@@ -515,10 +516,9 @@ final: prev: {
515 { inherit compiler-nix-name plan-pkgs;
516 { inherit compiler-nix-name plan-pkgs;
516 pkg-def-extras = args.pkg-def-extras or [];
517 pkg-def-extras = args.pkg-def-extras or [];
517 modules = (args.modules or [])
518 modules = (args.modules or [])
518− ++ final.lib.optional (args ? ghcOverride || args ? ghc)
519+ ++ final.lib.optional (args.ghcOverride != null || args.ghc != null)
519− { ghc.package = args.ghcOverride or args.ghc; }
520+ { ghc.package = if args.ghcOverride != null then args.ghcOverride else args.ghc; }
520− ++ final.lib.optional (args ? compiler-nix-name)
521− { compiler.nix-name = final.lib.mkForce args.compiler-nix-name; };
521+ ++ [ { compiler.nix-name = final.lib.mkForce args.compiler-nix-name; } ];
522 extra-hackages = args.extra-hackages or [];
522 extra-hackages = args.extra-hackages or [];
523 };
523 };
524
524
@@ -531,9 +531,9 @@ final: prev: {
531 tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
531 tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
532 roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
532 roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
533 projectFunction = haskell-nix: haskell-nix.cabalProject';
533 projectFunction = haskell-nix: haskell-nix.cabalProject';
534− projectArgs = args';
534+ inherit projectModule;
535 };
535 };
536− in project;
536+ in project);
537
537
538
538
539 # Take `hsPkgs` from the `rawProject` and update all the packages and
539 # Take `hsPkgs` from the `rawProject` and update all the packages and
@@ -583,7 +583,7 @@ final: prev: {
583 projectCoverageReport = haskellLib.projectCoverageReport project (map (pkg: pkg.coverageReport) (final.lib.attrValues (haskellLib.selectProjectPackages hsPkgs)));
583 projectCoverageReport = haskellLib.projectCoverageReport project (map (pkg: pkg.coverageReport) (final.lib.attrValues (haskellLib.selectProjectPackages hsPkgs)));
584
584
585 projectCross = (final.lib.mapAttrs (_: pkgs:
585 projectCross = (final.lib.mapAttrs (_: pkgs:
586− rawProject.projectFunction pkgs.haskell-nix rawProject.projectArgs
586+ rawProject.projectFunction pkgs.haskell-nix rawProject.projectModule
587 ) final.pkgsCross) // { recurseForDerivations = false; };
587 ) final.pkgsCross) // { recurseForDerivations = false; };
588
588
589 # Like `.hsPkgs.${packageName}` but when compined with `getComponent` any
589 # Like `.hsPkgs.${packageName}` but when compined with `getComponent` any
@@ -684,25 +684,23 @@ final: prev: {
684 inherit (rawProject.hsPkgs) makeConfigFiles ghcWithHoogle ghcWithPackages shellFor;
684 inherit (rawProject.hsPkgs) makeConfigFiles ghcWithHoogle ghcWithPackages shellFor;
685 });
685 });
686
686
687− cabalProject =
688− { src, compiler-nix-name, ... }@args':
689− let
690− args = { caller = "hackage-package"; } // args';
691− p = cabalProject' args;
687+ cabalProject = args: let p = cabalProject' args;
692 in p.hsPkgs // p;
688 in p.hsPkgs // p;
693
689
694 stackProject' =
690 stackProject' =
691+ projectModule: haskellLib.evalProjectModule ../modules/stack-project.nix projectModule (
695 { src, ... }@args:
692 { src, ... }@args:
696− let callProjectResults = callStackToNix ({ inherit cache; } // args);
693+ let callProjectResults = callStackToNix (args
694+ // final.lib.optionalAttrs (args.cache == null) { inherit cache; });
697 generatedCache = genStackCache args;
695 generatedCache = genStackCache args;
698− cache = args.cache or generatedCache;
696+ cache = if args.cache != null then args.cache else generatedCache;
699 in let pkg-set = mkStackPkgSet
697 in let pkg-set = mkStackPkgSet
700 { stack-pkgs = importAndFilterProject callProjectResults;
698 { stack-pkgs = importAndFilterProject callProjectResults;
701 pkg-def-extras = (args.pkg-def-extras or []);
699 pkg-def-extras = (args.pkg-def-extras or []);
702 modules = final.lib.singleton (mkCacheModule cache)
700 modules = final.lib.singleton (mkCacheModule cache)
703 ++ (args.modules or [])
701 ++ (args.modules or [])
704− ++ final.lib.optional (args ? ghc) { ghc.package = args.ghc; }
702+ ++ final.lib.optional (args.ghc != null) { ghc.package = args.ghc; }
705− ++ final.lib.optional (args ? compiler-nix-name)
703+ ++ final.lib.optional (args.compiler-nix-name != null)
706 { compiler.nix-name = final.lib.mkForce args.compiler-nix-name; };
704 { compiler.nix-name = final.lib.mkForce args.compiler-nix-name; };
707 };
705 };
708
706
@@ -714,9 +712,9 @@ final: prev: {
714 tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
712 tools = final.buildPackages.haskell-nix.tools pkg-set.config.compiler.nix-name;
715 roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
713 roots = final.haskell-nix.roots pkg-set.config.compiler.nix-name;
716 projectFunction = haskell-nix: haskell-nix.stackProject';
714 projectFunction = haskell-nix: haskell-nix.stackProject';
717− projectArgs = args;
715+ inherit projectModule;
718 };
716 };
719− in project;
717+ in project);
720
718
721 stackProject = args: let p = stackProject' args;
719 stackProject = args: let p = stackProject' args;
722 in p.hsPkgs // p;
720 in p.hsPkgs // p;
@@ -732,7 +730,7 @@ final: prev: {
732 # used (as it will use a default `cabal.project`).
730 # used (as it will use a default `cabal.project`).
733 project' = { src, projectFileName ? null, ... }@args':
731 project' = { src, projectFileName ? null, ... }@args':
734 let
732 let
735− args = { caller = "project'"; } // args';
733+ args = { caller = "project'"; } // final.lib.filterAttrs (n: _: n != "projectFileName") args';
736 dir = __readDir (src.origSrcSubDir or src);
734 dir = __readDir (src.origSrcSubDir or src);
737 exists = fileName: builtins.elem (dir.${fileName} or "") ["regular" "symlink"];
735 exists = fileName: builtins.elem (dir.${fileName} or "") ["regular" "symlink"];
738 stackYamlExists = exists "stack.yaml";
736 stackYamlExists = exists "stack.yaml";