diff --git a/builder/comp-builder.nix b/builder/comp-builder.nix index bcda31e238..6f6bf30401 100644 --- a/builder/comp-builder.nix +++ b/builder/comp-builder.nix @@ -23,6 +23,14 @@ let self = , preInstall ? component.preInstall , postInstall ? component.postInstall , preHaddock ? component.preHaddock , postHaddock ? component.postHaddock , shellHook ? "" +, configureAllComponents ? component.configureAllComponents || + # When set, configure all the components in the package + # (not just the one we are building). + # Enable for tests in packages that use cabal-doctest. + ( haskellLib.isTest componentId && + lib.any (x: x.identifier.name or "" == "cabal-doctest") package.setup-depends + ) +, allComponent # Used when `configureAllComponents` is set to get a suitable configuration. , build-tools ? component.build-tools , pkgconfig ? component.pkgconfig @@ -47,7 +55,7 @@ let self = , doHoogle ? component.doHoogle # Also build a hoogle index , hyperlinkSource ? component.doHyperlinkSource # Link documentation to the source code , quickjump ? component.doQuickjump # Generate an index for interactive documentation navigation -, keepSource ? component.keepSource # Build from `source` output in the store then delete `dist` +, keepSource ? component.keepSource || configureAllComponents # Build from `source` output in the store then delete `dist` , setupHaddockFlags ? component.setupHaddockFlags # Profiling @@ -77,6 +85,11 @@ let self = }@drvArgs: let + componentForSetup = + if configureAllComponents + then allComponent + else component; + # Ignore attempts to include DWARF info when it is not possible enableDWARF = drvArgs.enableDWARF or false && stdenv.hostPlatform.isLinux @@ -96,7 +109,7 @@ let # is the sub directory in that root path that contains the package. # `cleanSrc.subDir` is used in `prePatch` and `lib/cover.nix`. cleanSrc = haskellLib.rootAndSubDir (if canCleanSource - then haskellLib.cleanCabalComponent package component "${componentId.ctype}-${componentId.cname}" src + then haskellLib.cleanCabalComponent package componentForSetup "${componentId.ctype}-${componentId.cname}" src else # We can clean out the siblings though to at least avoid changes to other packages # from triggering a rebuild of this one. @@ -118,8 +131,9 @@ let needsProfiling = enableExecutableProfiling || enableLibraryProfiling; configFiles = makeConfigFiles { + component = componentForSetup; inherit (package) identifier; - inherit component fullName flags needsProfiling enableDWARF; + inherit fullName flags needsProfiling enableDWARF; }; enableFeature = enable: feature: @@ -129,8 +143,13 @@ let finalConfigureFlags = lib.concatStringsSep " " ( [ "--prefix=$out" - "${haskellLib.componentTarget componentId}" - "$(cat ${configFiles}/configure-flags)" + ] ++ ( + # If configureAllComponents is set we should not specify the component + # and Setup will attempt to configure them all. + if configureAllComponents + then ["--enable-tests" "--enable-benchmarks"] + else ["${haskellLib.componentTarget componentId}"] + ) ++ [ "$(cat ${configFiles}/configure-flags)" ] ++ commonConfigureFlags); # From nixpkgs 20.09, the pkg-config exe has a prefix matching the ghc one @@ -328,7 +347,7 @@ let ++ (lib.optional enableSeparateDataOutput "data") ++ (lib.optional keepSource "source"); - configurePhase = + prePatch = # emcc is very slow if it cannot cache stuff in $HOME (lib.optionalString (stdenv.hostPlatform.isGhcjs) '' export HOME=$(mktemp -d) @@ -340,7 +359,9 @@ let cp -r . $source cd $source chmod -R +w . - '') + '' + '') + commonAttrs.prePatch; + + configurePhase = '' runHook preConfigure echo Configure flags: printf "%q " ${finalConfigureFlags} @@ -368,7 +389,11 @@ let target-pkg-and-db = "${ghc.targetPrefix}ghc-pkg -v0 --package-db $out/package.conf.d"; in '' runHook preInstall - $SETUP_HS copy ${lib.concatStringsSep " " setupInstallFlags} + $SETUP_HS copy ${lib.concatStringsSep " " ( + setupInstallFlags + ++ lib.optional configureAllComponents + (haskellLib.componentTarget componentId) + )} ${lib.optionalString (haskellLib.isLibrary componentId) '' $SETUP_HS register --gen-pkg-config=${name}.conf ${ghc.targetPrefix}ghc-pkg -v0 init $out/package.conf.d @@ -455,9 +480,24 @@ let '') } runHook postInstall - '' + (lib.optionalString keepSource '' - rm -rf dist - '') + (lib.optionalString (haskellLib.isTest componentId) '' + '' + ( + # Keep just the autogen files and package.conf.inplace package + # DB (probably empty unless this is a library component). + # We also have to remove any refernces to $out to avoid + # circular references. + if configureAllComponents + then '' + mv dist dist-tmp-dir + mkdir -p dist/build + mv dist-tmp-dir/build/${componentId.cname}/autogen dist/build/ + mv dist-tmp-dir/package.conf.inplace dist/ + remove-references-to -t $out dist/build/autogen/* + rm -rf dist-tmp-dir + '' + else lib.optionalString keepSource '' + rm -rf dist + '' + ) + (lib.optionalString (haskellLib.isTest componentId) '' echo The test ${package.identifier.name}.components.tests.${componentId.cname} was built. To run the test build ${package.identifier.name}.checks.${componentId.cname}. ''); diff --git a/builder/hspkg-builder.nix b/builder/hspkg-builder.nix index ac9c6c72ce..144008b8d2 100644 --- a/builder/hspkg-builder.nix +++ b/builder/hspkg-builder.nix @@ -107,14 +107,14 @@ let inherit (pkg) preUnpack postUnpack; }; - buildComp = componentId: component: comp-builder { - inherit componentId component package name src flags setup cabalFile cabal-generator patches revision + buildComp = allComponent: componentId: component: comp-builder { + inherit allComponent componentId component package name src flags setup cabalFile cabal-generator patches revision shellHook ; }; in rec { - components = haskellLib.applyComponents buildComp pkg; + components = haskellLib.applyComponents (buildComp pkg.allComponent) pkg; checks = pkgs.recurseIntoAttrs (builtins.mapAttrs (_: d: haskellLib.check d) (lib.filterAttrs (_: d: d.config.doCheck) components.tests)); diff --git a/lib/check.nix b/lib/check.nix index 0bf44822f7..9d3592e022 100644 --- a/lib/check.nix +++ b/lib/check.nix @@ -10,7 +10,7 @@ let in stdenv.mkDerivation ({ name = (drv.name + "-check"); - # Useing `srcOnly` (rather than getting the `src` via a `drv.passthru`) + # Using `srcOnly` (rather than getting the `src` via a `drv.passthru`) # should correctly apply the patches from `drv` (if any). src = drv.source or (srcOnly drv); diff --git a/modules/package.nix b/modules/package.nix index e700996a28..d827a45e11 100644 --- a/modules/package.nix +++ b/modules/package.nix @@ -21,7 +21,86 @@ with types; # Work around issue that can cause _lots_ of files to be copied into the store. # See https://github.com/NixOS/nixpkgs/pull/64691 -let path = types.path // { check = x: types.path.check (x.origSrc or x); }; +let + path = types.path // { check = x: types.path.check (x.origSrc or x); }; + + componentType = submodule { + # add the shared componentOptions + options = (packageOptions config) // { + buildable = mkOption { + type = bool; + default = true; + }; + depends = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + libs = mkOption { + type = listOfFilteringNulls (nullOr package); + default = []; + }; + frameworks = mkOption { + type = listOfFilteringNulls package; + default = []; + }; + pkgconfig = mkOption { + type = listOf (listOfFilteringNulls package); + default = []; + }; + build-tools = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + modules = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + asmSources = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + cmmSources = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + cSources = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + cxxSources = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + jsSources = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + hsSourceDirs = mkOption { + type = listOfFilteringNulls unspecified; + default = ["."]; + }; + includeDirs = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + includes = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + mainPath = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + extraSrcFiles = mkOption { + type = listOfFilteringNulls unspecified; + default = []; + }; + platforms = mkOption { + type = nullOr (listOfFilteringNulls unspecified); + default = null; + }; + }; + }; in { # This is how the Nix expressions generated by *-to-nix receive @@ -138,85 +217,7 @@ in { }; }; - components = let - componentType = submodule { - # add the shared componentOptions - options = (packageOptions config) // { - buildable = mkOption { - type = bool; - default = true; - }; - depends = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - libs = mkOption { - type = listOfFilteringNulls (nullOr package); - default = []; - }; - frameworks = mkOption { - type = listOfFilteringNulls package; - default = []; - }; - pkgconfig = mkOption { - type = listOf (listOfFilteringNulls package); - default = []; - }; - build-tools = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - modules = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - asmSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - cmmSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - cSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - cxxSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - jsSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - hsSourceDirs = mkOption { - type = listOfFilteringNulls unspecified; - default = ["."]; - }; - includeDirs = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - includes = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - mainPath = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - extraSrcFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - platforms = mkOption { - type = nullOr (listOfFilteringNulls unspecified); - default = null; - }; - }; - }; - in { + components = { setup = mkOption { type = nullOr componentType; default = { @@ -295,5 +296,43 @@ in { type = listOf (either unspecified path); default = []; }; + # This used to be `components.all` but it has been added back as `allComponent` to + # to avoid confusion. It is not mapped by `builder/hspkg-builder.nix` to anything + # you can build. Instead it is used internally when `configureAllComponents` + # is set or for tests whe on `cabal-doctest` is in the `setup-depends` of the package. + allComponent = mkOption { + type = componentType; + apply = all: all // { + # TODO: Should this check for the entire component + # definition to match, rather than just the identifier? + depends = builtins.filter (p: p.identifier != config.package.identifier) all.depends; + }; + description = "The merged dependencies of all other components"; + }; }; + + # This has one quirk. Manually setting options on the all component + # will be considered a conflict. This is almost always fine; most + # settings should be modified in either the package options, or an + # individual component's options. When this isn't sufficient, + # mkForce is a reasonable workaround. + # + # An alternative solution to mkForce for many of the options where + # this is relevant would be to switch from the bool type to + # something like an anyBool type, which would merge definitions by + # returning true if any is true. + config.allComponent = + let allComps = haskellLib.getAllComponents config; + in lib.mkMerge ( + builtins.map (c: + # Exclude attributes that are likely to have conflicting definitions + # (a common use case for `all` is in `shellFor` and it only has an + # install phase). + builtins.removeAttrs c ["preCheck" "postCheck" "keepSource"] + ) allComps + ) // { + # If any one of the components needs us to keep the source + # then keep it for the `all` component + keepSource = lib.foldl' (x: comp: x || comp.keepSource) false allComps; + }; } diff --git a/modules/plan.nix b/modules/plan.nix index 34ddcc9e8a..49ddfe59ea 100644 --- a/modules/plan.nix +++ b/modules/plan.nix @@ -138,6 +138,11 @@ let type = bool; default = (def.enableShared or true); }; + configureAllComponents = mkOption { + description = "If set all the components in the package are configured (useful for cabal-doctest)."; + type = bool; + default = false; + }; shellHook = mkOption { description = "Hook to run when entering a shell"; type = unspecified; # Can be either a string or a function diff --git a/test/cabal-doctests/Setup.hs b/test/cabal-doctests/Setup.hs new file mode 100644 index 0000000000..c0bc21b776 --- /dev/null +++ b/test/cabal-doctests/Setup.hs @@ -0,0 +1,6 @@ +module Main where + +import Distribution.Extra.Doctest (defaultMainWithDoctests) + +main :: IO () +main = defaultMainWithDoctests "doctests" diff --git a/test/cabal-doctests/cabal-doctests-test.cabal b/test/cabal-doctests/cabal-doctests-test.cabal new file mode 100644 index 0000000000..36eb9ee6df --- /dev/null +++ b/test/cabal-doctests/cabal-doctests-test.cabal @@ -0,0 +1,31 @@ +cabal-version: 1.12 + +name: cabal-doctests-test +version: 0.1.0.0 +build-type: Custom + +custom-setup + setup-depends: + base >= 4 && <5, + Cabal, + cabal-doctest >= 1 && <1.1 + +library + hs-source-dirs: src + exposed-modules: Lib + other-modules: Paths_cabal_doctests_test + build-depends: + base >=4.7 && <5, + aeson + default-language: Haskell2010 + +test-suite doctests + hs-source-dirs: doctests + x-doctest-options: --no-magic + type: exitcode-stdio-1.0 + main-is: Doctests.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.7 && <5 + , doctest + default-language: Haskell2010 diff --git a/test/cabal-doctests/default.nix b/test/cabal-doctests/default.nix new file mode 100644 index 0000000000..c91c687d53 --- /dev/null +++ b/test/cabal-doctests/default.nix @@ -0,0 +1,47 @@ +# Test a package set +{ stdenv, util, cabalProject', haskellLib, gmp6, zlib, recurseIntoAttrs, runCommand, testSrc, compiler-nix-name }: + +with stdenv.lib; + +let + project = + cabalProject' { + inherit compiler-nix-name; + src = testSrc "cabal-doctests"; + index-state = "2021-01-11T00:00:00Z"; + }; + + packages = project.hsPkgs; + + meta = { + platforms = platforms.all; + # Making this work for cross compilers will be difficult. + disabled = stdenv.buildPlatform != stdenv.hostPlatform; + }; + +in recurseIntoAttrs ({ + ifdInputs = { + plan-nix = addMetaAttrs meta project.plan-nix; + }; + run = stdenv.mkDerivation { + name = "cabal-doctests-test"; + + buildCommand = '' + printf "Checking that doctest tests have run ... " >& 2 + cat ${packages.cabal-doctests-test.checks.doctests}/test-stdout >& 2 + + touch $out + ''; + + meta = { + platforms = platforms.all; + # Making cabal-doctest work for cross compilers will be difficult. + disabled = stdenv.buildPlatform != stdenv.hostPlatform; + }; + + passthru = { + # Used for debugging with nix repl + inherit project packages; + }; + }; +}) diff --git a/test/cabal-doctests/doctests/Doctests.hs b/test/cabal-doctests/doctests/Doctests.hs new file mode 100644 index 0000000000..ff0b6eb857 --- /dev/null +++ b/test/cabal-doctests/doctests/Doctests.hs @@ -0,0 +1,16 @@ +module Main where + +import Build_doctests (flags, pkgs, module_sources) +import Data.Foldable (traverse_) +import System.Environment (unsetEnv) +import Test.DocTest (doctest) + +main :: IO () +main = do + putStrLn "" + traverse_ putStrLn args -- optionally print arguments + unsetEnv "GHC_ENVIRONMENT" -- see 'Notes'; you may not need this + doctest args + where + args :: [String] + args = flags ++ pkgs ++ module_sources -- ++ ["-v"] diff --git a/test/cabal-doctests/src/Lib.hs b/test/cabal-doctests/src/Lib.hs new file mode 100644 index 0000000000..e232cff521 --- /dev/null +++ b/test/cabal-doctests/src/Lib.hs @@ -0,0 +1,18 @@ +module Lib + ( someFunc + ) where + +import Data.Aeson (encode) +import Paths_cabal_doctests_test (version) + +-- | +-- >>> 1 + 1 +-- 2 +-- +-- >>> version +-- Version {versionBranch = [0,1,0,0], versionTags = []} +-- +-- >>> encode (Just 1 :: Maybe Int) +-- "1" +someFunc :: IO () +someFunc = putStrLn "someFunc" diff --git a/test/default.nix b/test/default.nix index 424caa1cf7..5cc629e77f 100644 --- a/test/default.nix +++ b/test/default.nix @@ -179,6 +179,7 @@ let ghc-options-stack = callTest ./ghc-options/stack.nix {}; exe-only = callTest ./exe-only { inherit util compiler-nix-name; }; stack-source-repo = callTest ./stack-source-repo {}; + cabal-doctests = callTest ./cabal-doctests { inherit util compiler-nix-name; }; extra-hackage = callTest ./extra-hackage { inherit compiler-nix-name; }; hls-cabal = callTest ./haskell-language-server/cabal.nix { inherit compiler-nix-name; }; hls-stack = callTest ./haskell-language-server/stack.nix { inherit compiler-nix-name; };