on iko/ghcjs-patch-master help sign in
support cabal-doctest (#427)
6c627b4Dennis Gosnell
builder/comp-builder.nix+51−11
@@ -23,6 +23,14 @@ let self =
23, preInstall ? component.preInstall , postInstall ? component.postInstall
23, preInstall ? component.preInstall , postInstall ? component.postInstall
24, preHaddock ? component.preHaddock , postHaddock ? component.postHaddock
24, preHaddock ? component.preHaddock , postHaddock ? component.postHaddock
25, shellHook ? ""
25, shellHook ? ""
26+, configureAllComponents ? component.configureAllComponents ||
27+ # When set, configure all the components in the package
28+ # (not just the one we are building).
29+ # Enable for tests in packages that use cabal-doctest.
30+ ( haskellLib.isTest componentId &&
31+ lib.any (x: x.identifier.name or "" == "cabal-doctest") package.setup-depends
32+ )
33+, allComponent # Used when `configureAllComponents` is set to get a suitable configuration.
26
34
27, build-tools ? component.build-tools
35, build-tools ? component.build-tools
28, pkgconfig ? component.pkgconfig
36, pkgconfig ? component.pkgconfig
@@ -47,7 +55,7 @@ let self =
47, doHoogle ? component.doHoogle # Also build a hoogle index
55, doHoogle ? component.doHoogle # Also build a hoogle index
48, hyperlinkSource ? component.doHyperlinkSource # Link documentation to the source code
56, hyperlinkSource ? component.doHyperlinkSource # Link documentation to the source code
49, quickjump ? component.doQuickjump # Generate an index for interactive documentation navigation
57, quickjump ? component.doQuickjump # Generate an index for interactive documentation navigation
50−, keepSource ? component.keepSource # Build from `source` output in the store then delete `dist`
58+, keepSource ? component.keepSource || configureAllComponents # Build from `source` output in the store then delete `dist`
51, setupHaddockFlags ? component.setupHaddockFlags
59, setupHaddockFlags ? component.setupHaddockFlags
52
60
53# Profiling
61# Profiling
@@ -77,6 +85,11 @@ let self =
77}@drvArgs:
85}@drvArgs:
78
86
79let
87let
88+ componentForSetup =
89+ if configureAllComponents
90+ then allComponent
91+ else component;
92+
80 # Ignore attempts to include DWARF info when it is not possible
93 # Ignore attempts to include DWARF info when it is not possible
81 enableDWARF = drvArgs.enableDWARF or false
94 enableDWARF = drvArgs.enableDWARF or false
82 && stdenv.hostPlatform.isLinux
95 && stdenv.hostPlatform.isLinux
@@ -96,7 +109,7 @@ let
96 # is the sub directory in that root path that contains the package.
109 # is the sub directory in that root path that contains the package.
97 # `cleanSrc.subDir` is used in `prePatch` and `lib/cover.nix`.
110 # `cleanSrc.subDir` is used in `prePatch` and `lib/cover.nix`.
98 cleanSrc = haskellLib.rootAndSubDir (if canCleanSource
111 cleanSrc = haskellLib.rootAndSubDir (if canCleanSource
99− then haskellLib.cleanCabalComponent package component "${componentId.ctype}-${componentId.cname}" src
112+ then haskellLib.cleanCabalComponent package componentForSetup "${componentId.ctype}-${componentId.cname}" src
100 else
113 else
101 # We can clean out the siblings though to at least avoid changes to other packages
114 # We can clean out the siblings though to at least avoid changes to other packages
102 # from triggering a rebuild of this one.
115 # from triggering a rebuild of this one.
@@ -118,8 +131,9 @@ let
118 needsProfiling = enableExecutableProfiling || enableLibraryProfiling;
131 needsProfiling = enableExecutableProfiling || enableLibraryProfiling;
119
132
120 configFiles = makeConfigFiles {
133 configFiles = makeConfigFiles {
134+ component = componentForSetup;
121 inherit (package) identifier;
135 inherit (package) identifier;
122− inherit component fullName flags needsProfiling enableDWARF;
136+ inherit fullName flags needsProfiling enableDWARF;
123 };
137 };
124
138
125 enableFeature = enable: feature:
139 enableFeature = enable: feature:
@@ -129,8 +143,13 @@ let
129
143
130 finalConfigureFlags = lib.concatStringsSep " " (
144 finalConfigureFlags = lib.concatStringsSep " " (
131 [ "--prefix=$out"
145 [ "--prefix=$out"
146+ ] ++ (
147+ # If configureAllComponents is set we should not specify the component
148+ # and Setup will attempt to configure them all.
149+ if configureAllComponents
150+ then ["--enable-tests" "--enable-benchmarks"]
132− "${haskellLib.componentTarget componentId}"
151+ else ["${haskellLib.componentTarget componentId}"]
133− "$(cat ${configFiles}/configure-flags)"
152+ ) ++ [ "$(cat ${configFiles}/configure-flags)"
134 ] ++ commonConfigureFlags);
153 ] ++ commonConfigureFlags);
135
154
136 # From nixpkgs 20.09, the pkg-config exe has a prefix matching the ghc one
155 # From nixpkgs 20.09, the pkg-config exe has a prefix matching the ghc one
@@ -328,7 +347,7 @@ let
328 ++ (lib.optional enableSeparateDataOutput "data")
347 ++ (lib.optional enableSeparateDataOutput "data")
329 ++ (lib.optional keepSource "source");
348 ++ (lib.optional keepSource "source");
330
349
331− configurePhase =
350+ prePatch =
332 # emcc is very slow if it cannot cache stuff in $HOME
351 # emcc is very slow if it cannot cache stuff in $HOME
333 (lib.optionalString (stdenv.hostPlatform.isGhcjs) ''
352 (lib.optionalString (stdenv.hostPlatform.isGhcjs) ''
334 export HOME=$(mktemp -d)
353 export HOME=$(mktemp -d)
@@ -340,7 +359,9 @@ let
340 cp -r . $source
359 cp -r . $source
341 cd $source
360 cd $source
342 chmod -R +w .
361 chmod -R +w .
343− '') + ''
362+ '') + commonAttrs.prePatch;
363+
364+ configurePhase = ''
344 runHook preConfigure
365 runHook preConfigure
345 echo Configure flags:
366 echo Configure flags:
346 printf "%q " ${finalConfigureFlags}
367 printf "%q " ${finalConfigureFlags}
@@ -368,7 +389,11 @@ let
368 target-pkg-and-db = "${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d";
389 target-pkg-and-db = "${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d";
369 in ''
390 in ''
370 runHook preInstall
391 runHook preInstall
371− $SETUP_HS copy ${lib.concatStringsSep " " setupInstallFlags}
392+ $SETUP_HS copy ${lib.concatStringsSep " " (
393+ setupInstallFlags
394+ ++ lib.optional configureAllComponents
395+ (haskellLib.componentTarget componentId)
396+ )}
372 ${lib.optionalString (haskellLib.isLibrary componentId) ''
397 ${lib.optionalString (haskellLib.isLibrary componentId) ''
373 $SETUP_HS register --gen-pkg-config=${name}.conf
398 $SETUP_HS register --gen-pkg-config=${name}.conf
374 ${ghc.targetPrefix}ghc-pkg -v0 init $out/package.conf.d
399 ${ghc.targetPrefix}ghc-pkg -v0 init $out/package.conf.d
@@ -455,9 +480,24 @@ let
455 '')
480 '')
456 }
481 }
457 runHook postInstall
482 runHook postInstall
483+ '' + (
484+ # Keep just the autogen files and package.conf.inplace package
485+ # DB (probably empty unless this is a library component).
486+ # We also have to remove any refernces to $out to avoid
487+ # circular references.
488+ if configureAllComponents
489+ then ''
490+ mv dist dist-tmp-dir
491+ mkdir -p dist/build
492+ mv dist-tmp-dir/build/${componentId.cname}/autogen dist/build/
493+ mv dist-tmp-dir/package.conf.inplace dist/
494+ remove-references-to -t $out dist/build/autogen/*
495+ rm -rf dist-tmp-dir
496+ ''
458− '' + (lib.optionalString keepSource ''
497+ else lib.optionalString keepSource ''
459− rm -rf dist
498+ rm -rf dist
499+ ''
460− '') + (lib.optionalString (haskellLib.isTest componentId) ''
500+ ) + (lib.optionalString (haskellLib.isTest componentId) ''
461 echo The test ${package.identifier.name}.components.tests.${componentId.cname} was built. To run the test build ${package.identifier.name}.checks.${componentId.cname}.
501 echo The test ${package.identifier.name}.components.tests.${componentId.cname} was built. To run the test build ${package.identifier.name}.checks.${componentId.cname}.
462 '');
502 '');
463
503
builder/hspkg-builder.nix+3−3
@@ -107,14 +107,14 @@ let
107 inherit (pkg) preUnpack postUnpack;
107 inherit (pkg) preUnpack postUnpack;
108 };
108 };
109
109
110− buildComp = componentId: component: comp-builder {
110+ buildComp = allComponent: componentId: component: comp-builder {
111− inherit componentId component package name src flags setup cabalFile cabal-generator patches revision
111+ inherit allComponent componentId component package name src flags setup cabalFile cabal-generator patches revision
112 shellHook
112 shellHook
113 ;
113 ;
114 };
114 };
115
115
116in rec {
116in rec {
117− components = haskellLib.applyComponents buildComp pkg;
117+ components = haskellLib.applyComponents (buildComp pkg.allComponent) pkg;
118 checks = pkgs.recurseIntoAttrs (builtins.mapAttrs
118 checks = pkgs.recurseIntoAttrs (builtins.mapAttrs
119 (_: d: haskellLib.check d)
119 (_: d: haskellLib.check d)
120 (lib.filterAttrs (_: d: d.config.doCheck) components.tests));
120 (lib.filterAttrs (_: d: d.config.doCheck) components.tests));
lib/check.nix+1−1
@@ -10,7 +10,7 @@ let
10in stdenv.mkDerivation ({
10in stdenv.mkDerivation ({
11 name = (drv.name + "-check");
11 name = (drv.name + "-check");
12
12
13− # Useing `srcOnly` (rather than getting the `src` via a `drv.passthru`)
13+ # Using `srcOnly` (rather than getting the `src` via a `drv.passthru`)
14 # should correctly apply the patches from `drv` (if any).
14 # should correctly apply the patches from `drv` (if any).
15 src = drv.source or (srcOnly drv);
15 src = drv.source or (srcOnly drv);
16
16
modules/package.nix+119−80
@@ -21,7 +21,86 @@ with types;
21
21
22# Work around issue that can cause _lots_ of files to be copied into the store.
22# Work around issue that can cause _lots_ of files to be copied into the store.
23# See https://github.com/NixOS/nixpkgs/pull/64691
23# See https://github.com/NixOS/nixpkgs/pull/64691
24+let
24−let path = types.path // { check = x: types.path.check (x.origSrc or x); };
25+ path = types.path // { check = x: types.path.check (x.origSrc or x); };
26+
27+ componentType = submodule {
28+ # add the shared componentOptions
29+ options = (packageOptions config) // {
30+ buildable = mkOption {
31+ type = bool;
32+ default = true;
33+ };
34+ depends = mkOption {
35+ type = listOfFilteringNulls unspecified;
36+ default = [];
37+ };
38+ libs = mkOption {
39+ type = listOfFilteringNulls (nullOr package);
40+ default = [];
41+ };
42+ frameworks = mkOption {
43+ type = listOfFilteringNulls package;
44+ default = [];
45+ };
46+ pkgconfig = mkOption {
47+ type = listOf (listOfFilteringNulls package);
48+ default = [];
49+ };
50+ build-tools = mkOption {
51+ type = listOfFilteringNulls unspecified;
52+ default = [];
53+ };
54+ modules = mkOption {
55+ type = listOfFilteringNulls unspecified;
56+ default = [];
57+ };
58+ asmSources = mkOption {
59+ type = listOfFilteringNulls unspecified;
60+ default = [];
61+ };
62+ cmmSources = mkOption {
63+ type = listOfFilteringNulls unspecified;
64+ default = [];
65+ };
66+ cSources = mkOption {
67+ type = listOfFilteringNulls unspecified;
68+ default = [];
69+ };
70+ cxxSources = mkOption {
71+ type = listOfFilteringNulls unspecified;
72+ default = [];
73+ };
74+ jsSources = mkOption {
75+ type = listOfFilteringNulls unspecified;
76+ default = [];
77+ };
78+ hsSourceDirs = mkOption {
79+ type = listOfFilteringNulls unspecified;
80+ default = ["."];
81+ };
82+ includeDirs = mkOption {
83+ type = listOfFilteringNulls unspecified;
84+ default = [];
85+ };
86+ includes = mkOption {
87+ type = listOfFilteringNulls unspecified;
88+ default = [];
89+ };
90+ mainPath = mkOption {
91+ type = listOfFilteringNulls unspecified;
92+ default = [];
93+ };
94+ extraSrcFiles = mkOption {
95+ type = listOfFilteringNulls unspecified;
96+ default = [];
97+ };
98+ platforms = mkOption {
99+ type = nullOr (listOfFilteringNulls unspecified);
100+ default = null;
101+ };
102+ };
103+ };
25
104
26in {
105in {
27 # This is how the Nix expressions generated by *-to-nix receive
106 # This is how the Nix expressions generated by *-to-nix receive
@@ -138,85 +217,7 @@ in {
138 };
217 };
139 };
218 };
140
219
141− components = let
220+ components = {
142− componentType = submodule {
143− # add the shared componentOptions
144− options = (packageOptions config) // {
145− buildable = mkOption {
146− type = bool;
147− default = true;
148− };
149− depends = mkOption {
150− type = listOfFilteringNulls unspecified;
151− default = [];
152− };
153− libs = mkOption {
154− type = listOfFilteringNulls (nullOr package);
155− default = [];
156− };
157− frameworks = mkOption {
158− type = listOfFilteringNulls package;
159− default = [];
160− };
161− pkgconfig = mkOption {
162− type = listOf (listOfFilteringNulls package);
163− default = [];
164− };
165− build-tools = mkOption {
166− type = listOfFilteringNulls unspecified;
167− default = [];
168− };
169− modules = mkOption {
170− type = listOfFilteringNulls unspecified;
171− default = [];
172− };
173− asmSources = mkOption {
174− type = listOfFilteringNulls unspecified;
175− default = [];
176− };
177− cmmSources = mkOption {
178− type = listOfFilteringNulls unspecified;
179− default = [];
180− };
181− cSources = mkOption {
182− type = listOfFilteringNulls unspecified;
183− default = [];
184− };
185− cxxSources = mkOption {
186− type = listOfFilteringNulls unspecified;
187− default = [];
188− };
189− jsSources = mkOption {
190− type = listOfFilteringNulls unspecified;
191− default = [];
192− };
193− hsSourceDirs = mkOption {
194− type = listOfFilteringNulls unspecified;
195− default = ["."];
196− };
197− includeDirs = mkOption {
198− type = listOfFilteringNulls unspecified;
199− default = [];
200− };
201− includes = mkOption {
202− type = listOfFilteringNulls unspecified;
203− default = [];
204− };
205− mainPath = mkOption {
206− type = listOfFilteringNulls unspecified;
207− default = [];
208− };
209− extraSrcFiles = mkOption {
210− type = listOfFilteringNulls unspecified;
211− default = [];
212− };
213− platforms = mkOption {
214− type = nullOr (listOfFilteringNulls unspecified);
215− default = null;
216− };
217− };
218− };
219− in {
220 setup = mkOption {
221 setup = mkOption {
221 type = nullOr componentType;
222 type = nullOr componentType;
222 default = {
223 default = {
@@ -295,5 +296,43 @@ in {
295 type = listOf (either unspecified path);
296 type = listOf (either unspecified path);
296 default = [];
297 default = [];
297 };
298 };
299+ # This used to be `components.all` but it has been added back as `allComponent` to
300+ # to avoid confusion. It is not mapped by `builder/hspkg-builder.nix` to anything
301+ # you can build. Instead it is used internally when `configureAllComponents`
302+ # is set or for tests whe on `cabal-doctest` is in the `setup-depends` of the package.
303+ allComponent = mkOption {
304+ type = componentType;
305+ apply = all: all // {
306+ # TODO: Should this check for the entire component
307+ # definition to match, rather than just the identifier?
308+ depends = builtins.filter (p: p.identifier != config.package.identifier) all.depends;
309+ };
310+ description = "The merged dependencies of all other components";
311+ };
298 };
312 };
313+
314+ # This has one quirk. Manually setting options on the all component
315+ # will be considered a conflict. This is almost always fine; most
316+ # settings should be modified in either the package options, or an
317+ # individual component's options. When this isn't sufficient,
318+ # mkForce is a reasonable workaround.
319+ #
320+ # An alternative solution to mkForce for many of the options where
321+ # this is relevant would be to switch from the bool type to
322+ # something like an anyBool type, which would merge definitions by
323+ # returning true if any is true.
324+ config.allComponent =
325+ let allComps = haskellLib.getAllComponents config;
326+ in lib.mkMerge (
327+ builtins.map (c:
328+ # Exclude attributes that are likely to have conflicting definitions
329+ # (a common use case for `all` is in `shellFor` and it only has an
330+ # install phase).
331+ builtins.removeAttrs c ["preCheck" "postCheck" "keepSource"]
332+ ) allComps
333+ ) // {
334+ # If any one of the components needs us to keep the source
335+ # then keep it for the `all` component
336+ keepSource = lib.foldl' (x: comp: x || comp.keepSource) false allComps;
337+ };
299}
338}
modules/plan.nix+5
@@ -138,6 +138,11 @@ let
138 type = bool;
138 type = bool;
139 default = (def.enableShared or true);
139 default = (def.enableShared or true);
140 };
140 };
141+ configureAllComponents = mkOption {
142+ description = "If set all the components in the package are configured (useful for cabal-doctest).";
143+ type = bool;
144+ default = false;
145+ };
141 shellHook = mkOption {
146 shellHook = mkOption {
142 description = "Hook to run when entering a shell";
147 description = "Hook to run when entering a shell";
143 type = unspecified; # Can be either a string or a function
148 type = unspecified; # Can be either a string or a function
test/cabal-doctests/Setup.hsnew file+6
@@ -0,0 +1,6 @@
1+module Main where
2+
3+import Distribution.Extra.Doctest (defaultMainWithDoctests)
4+
5+main :: IO ()
6+main = defaultMainWithDoctests "doctests"
test/cabal-doctests/cabal-doctests-test.cabalnew file+31
@@ -0,0 +1,31 @@
1+cabal-version: 1.12
2+
3+name: cabal-doctests-test
4+version: 0.1.0.0
5+build-type: Custom
6+
7+custom-setup
8+ setup-depends:
9+ base >= 4 && <5,
10+ Cabal,
11+ cabal-doctest >= 1 && <1.1
12+
13+library
14+ hs-source-dirs: src
15+ exposed-modules: Lib
16+ other-modules: Paths_cabal_doctests_test
17+ build-depends:
18+ base >=4.7 && <5,
19+ aeson
20+ default-language: Haskell2010
21+
22+test-suite doctests
23+ hs-source-dirs: doctests
24+ x-doctest-options: --no-magic
25+ type: exitcode-stdio-1.0
26+ main-is: Doctests.hs
27+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
28+ build-depends:
29+ base >=4.7 && <5
30+ , doctest
31+ default-language: Haskell2010
test/cabal-doctests/default.nixnew file+47
@@ -0,0 +1,47 @@
1+# Test a package set
2+{ stdenv, util, cabalProject', haskellLib, gmp6, zlib, recurseIntoAttrs, runCommand, testSrc, compiler-nix-name }:
3+
4+with stdenv.lib;
5+
6+let
7+ project =
8+ cabalProject' {
9+ inherit compiler-nix-name;
10+ src = testSrc "cabal-doctests";
11+ index-state = "2021-01-11T00:00:00Z";
12+ };
13+
14+ packages = project.hsPkgs;
15+
16+ meta = {
17+ platforms = platforms.all;
18+ # Making this work for cross compilers will be difficult.
19+ disabled = stdenv.buildPlatform != stdenv.hostPlatform;
20+ };
21+
22+in recurseIntoAttrs ({
23+ ifdInputs = {
24+ plan-nix = addMetaAttrs meta project.plan-nix;
25+ };
26+ run = stdenv.mkDerivation {
27+ name = "cabal-doctests-test";
28+
29+ buildCommand = ''
30+ printf "Checking that doctest tests have run ... " >& 2
31+ cat ${packages.cabal-doctests-test.checks.doctests}/test-stdout >& 2
32+
33+ touch $out
34+ '';
35+
36+ meta = {
37+ platforms = platforms.all;
38+ # Making cabal-doctest work for cross compilers will be difficult.
39+ disabled = stdenv.buildPlatform != stdenv.hostPlatform;
40+ };
41+
42+ passthru = {
43+ # Used for debugging with nix repl
44+ inherit project packages;
45+ };
46+ };
47+})
test/cabal-doctests/doctests/Doctests.hsnew file+16
@@ -0,0 +1,16 @@
1+module Main where
2+
3+import Build_doctests (flags, pkgs, module_sources)
4+import Data.Foldable (traverse_)
5+import System.Environment (unsetEnv)
6+import Test.DocTest (doctest)
7+
8+main :: IO ()
9+main = do
10+ putStrLn ""
11+ traverse_ putStrLn args -- optionally print arguments
12+ unsetEnv "GHC_ENVIRONMENT" -- see 'Notes'; you may not need this
13+ doctest args
14+ where
15+ args :: [String]
16+ args = flags ++ pkgs ++ module_sources -- ++ ["-v"]
test/cabal-doctests/src/Lib.hsnew file+18
@@ -0,0 +1,18 @@
1+module Lib
2+ ( someFunc
3+ ) where
4+
5+import Data.Aeson (encode)
6+import Paths_cabal_doctests_test (version)
7+
8+-- |
9+-- >>> 1 + 1
10+-- 2
11+--
12+-- >>> version
13+-- Version {versionBranch = [0,1,0,0], versionTags = []}
14+--
15+-- >>> encode (Just 1 :: Maybe Int)
16+-- "1"
17+someFunc :: IO ()
18+someFunc = putStrLn "someFunc"
test/default.nix+1
@@ -179,6 +179,7 @@ let
179 ghc-options-stack = callTest ./ghc-options/stack.nix {};
179 ghc-options-stack = callTest ./ghc-options/stack.nix {};
180 exe-only = callTest ./exe-only { inherit util compiler-nix-name; };
180 exe-only = callTest ./exe-only { inherit util compiler-nix-name; };
181 stack-source-repo = callTest ./stack-source-repo {};
181 stack-source-repo = callTest ./stack-source-repo {};
182+ cabal-doctests = callTest ./cabal-doctests { inherit util compiler-nix-name; };
182 extra-hackage = callTest ./extra-hackage { inherit compiler-nix-name; };
183 extra-hackage = callTest ./extra-hackage { inherit compiler-nix-name; };
183 hls-cabal = callTest ./haskell-language-server/cabal.nix { inherit compiler-nix-name; };
184 hls-cabal = callTest ./haskell-language-server/cabal.nix { inherit compiler-nix-name; };
184 hls-stack = callTest ./haskell-language-server/stack.nix { inherit compiler-nix-name; };
185 hls-stack = callTest ./haskell-language-server/stack.nix { inherit compiler-nix-name; };