cabal-doctest-1.0.12/0000755000000000000000000000000007346545000012471 5ustar0000000000000000cabal-doctest-1.0.12/LICENSE0000644000000000000000000000276207346545000013505 0ustar0000000000000000Copyright (c) 2017, Oleg Grenrus All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Oleg Grenrus nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cabal-doctest-1.0.12/README.md0000644000000000000000000003001007346545000013742 0ustar0000000000000000cabal-doctest ============= [![Hackage](https://img.shields.io/hackage/v/cabal-doctest.svg)](https://hackage.haskell.org/package/cabal-doctest) [![Haskell-CI](https://github.com/ulidtko/cabal-doctest/actions/workflows/haskell-ci.yml/badge.svg?branch=master)](https://github.com/ulidtko/cabal-doctest/actions/workflows/haskell-ci.yml) [![stack test](https://github.com/ulidtko/cabal-doctest/actions/workflows/stack-test.yml/badge.svg)](https://github.com/ulidtko/cabal-doctest/actions/workflows/stack-test.yml) A `Setup.hs` helper for running [doctests][doctest]. [doctest]: https://github.com/sol/doctest#readme Why this exists --------------- **Doctesting** is a nifty technique that stimulates 3 good things to happen: * library documentation gains *runnable code examples* that are also tested; * library test suite gains *documented usage examples* as "tests for free"; * get both of the above for the price of one. That's what the [doctest][] tool does — not this package! — just for clarity. Off the shelf, `doctest` doesn't require any package management mumbo-jumbo: you just run it on a source file with haddocks with doctests. Issues come in when library authors and maintainers wish to integrate doctests into CI pipelines. When doctests start to require dependencies or non-default compiler flags: that's when it gets hairy. There, if you want `stack test` and/or `cabal test` to run doctests too with minimal shenanigans, then read on. Among different available approaches, this package `cabal-doctest` helps with one, which is known as [custom setup][], `build-type: Custom` more precisely. You should stick to the default `build-type: Simple`, unless you know what you're doing. In a nutshell, this custom Setup.hs shim generates a module `Build_doctests` that allows your doctest driver `test-suite` to look like this: ```haskell module Main where import Build_doctests (flags, pkgs, module_sources) import Test.Doctest (doctest) main :: IO () main = doctest (flags ++ pkgs ++ module_sources) ``` More detailed examples below. Regardless of the name, this **also works with Stack**. For old versions of stack, cabal-install, GHC, see [caveats](#notes) below. [custom setup]: https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-build-type Simple example -------------- [simple example]: https://github.com/ulidtko/cabal-doctest/tree/master/simple-example [simple-example.cabal]: https://github.com/ulidtko/cabal-doctest/tree/master/simple-example/simple-example.cabal Follow [simple example][] for the common case of a single-library `.cabal` package with doctests. To recap the example's code: 1. specify `build-type: Custom` in your `.cabal` file; 2. declare dependencies of `Setup.hs`: ``` custom-setup setup-depends: base >= 4 && <5, cabal-doctest >= 1 && <1.1 ``` See [Notes](#notes) below for a caveat with cabal-install < 2.4. 3. Populate `Setup.hs` like so: ```haskell module Main where import Distribution.Extra.Doctest (defaultMainWithDoctests) main :: IO () main = defaultMainWithDoctests "doctests" ``` This `Setup` will generate a `Build_doctests` module during package build. Note that the module name `Build_doctests` is a fixed constant, and it is not related to the `"doctests"` argument. That argument identifies which `test-suite` (among those defined in your .cabal file) will run the doctest. 4. Use the generated module in a testsuite, simply like so: ```haskell 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 traverse_ putStrLn args -- optionally print arguments unsetEnv "GHC_ENVIRONMENT" -- see 'Notes'; you may not need this doctest args where args = flags ++ pkgs ++ module_sources ``` Ultimately, `cabal test` or `stack test` should run the doctests of your package. Example with multiple cabal components -------------------------------------- `cabal-doctest` also supports more exotic use cases where a `.cabal` file contains more components with doctests than just the main library, including: * doctests in executables, * doctests in internal libraries (if using `Cabal-2.0` or later). Unlike the simple example shown above, these examples involve _named_ components. You don't need to change the `Setup.hs` script to support this use case. However, in this scenario `Build_doctests` will generate extra copies of the `flags`, `pkgs`, and `module_sources` values for each additional named component. The simplest approach is to use `x-doctest-components` field in `.cabal`: ``` x-doctest-components: lib lib:internal exe:example ``` In that case, the test driver is generally: ```haskell module Main where import Build_doctests (Component (..), components) import Data.Foldable (for_) import System.Environment (unsetEnv) import Test.DocTest (doctest) main :: IO () main = for_ components $ \(Component name flags pkgs sources) -> do print name putStrLn "----------------------------------------" let args = flags ++ pkgs ++ sources for_ args putStrLn unsetEnv "GHC_ENVIRONMENT" doctest args ``` There is also a more explicit approach: if you have an executable named `foo`, then `Build_doctests` will contain `flags_exe_foo`, `pkgs_exe_foo`, and `module_sources_exe_foo`. If the name has hyphens in it (e.g., `my-exe`), `cabal-doctest` will convert them to underscores (e.g., you'd get `flags_my_exe`, `pkgs_my_exe`, `module_sources_my_exe`). Internal library `bar` values will have a `_lib_bar` suffix. An example testsuite driver for this use case might look like this: ```haskell module Main where import Build_doctests (flags, pkgs, module_sources, flags_exe_my_exe, pkgs_exe_my_exe, module_sources_exe_my_exe) import Data.Foldable (traverse_) import System.Environment (unsetEnv) import Test.DocTest main :: IO () main = do unsetEnv "GHC_ENVRIONMENT" -- doctests for library traverse_ putStrLn libArgs doctest libArgs -- doctests for executable traverse_ putStrLn exeArgs doctest exeArgs where libArgs = flags ++ pkgs ++ module_sources exeArgs = flags_exe_my_exe ++ pkgs_exe_my_exe ++ module_sources_exe_my_exe ``` See the [multiple-components-example][]. [multiple-components-example]: https://github.com/ulidtko/cabal-doctest/tree/master/multiple-components-example Additional configuration ------------------------ The `cabal-doctest` based `Setup.hs` supports a few extensions fields in `pkg.cabal` files to customize the `doctest` runner behavior, without customizing the default `doctest.hs`. ``` test-suite doctests: if impl(ghc >= 8.0) x-doctest-options: -fdiagnostics-color=never x-doctest-source-dirs: test x-doctest-modules: Servant.Utils.LinksSpec ``` * `x-doctest-options` Additional arguments passed into `doctest` command. * `x-doctest-modules` Additional modules to `doctest`. May be useful if you have doctests in tests or executables (i.e not the default library component). * `x-doctest-src-dirs` Additional source directories to look for the modules. Notes ----- * If support for cabal-install < 2.4 is required, you'll have to add `Cabal` to `setup-depends`; see issue [haskell/cabal#4288][]. * Some versions of `Cabal` (for instance, 2.0) can choose to build a package's `doctest` test suite _before_ the library. However, in order for `cabal-doctest` to work correctly, the library _must_ be built first, as `doctest` relies on the presence of generated files that are only created when the library is built. See [#19][]. A hacky workaround for this problem is to depend on the library itself in a `doctests` test suite. See [simple-example.cabal][] for a demonstration. (This assumes that the test suite has the ability to read build artifacts from the library, a separate build component. In practice, this assumption holds, which is why this library works at all.) * `custom-setup` section is supported starting from `cabal-install-1.24`. For older `cabal-install's` you have to install custom setup dependencies manually. * `stack` respects `custom-setup` starting from version 1.3.3. Before that you have to use `explicit-setup-deps` setting in your `stack.yaml`; [stack#2094][]. * With base < 4.7 (GHC < 7.8, pre-2014), `System.Environment.unsetEnv` function will need to be imported from `base-compat` library. It is already in transitive dependencies of `doctest`. Simply declare the dependency upon `base-compat`, and then `import System.Environment.Compat (unsetEnv)` if you need that old GHC. * You can use `x-doctest-options` field in `test-suite doctests` to pass additional flags to the `doctest`. * For `build-type: Configure` packages, you can use `defaultMainAutoconfWithDoctests` function to make custom `Setup.hs` script. * If you use the default `.` in `hs-source-dirs`, then running `doctests` might fail with weird errors (ambiguous module errors). Workaround is to move sources under `src/` or some non-top-level directory. * The `extensions:` field isn't supported. Upgrade your `.cabal` file to use at least `cabal-version: >= 1.10` and use `default-extensions` or `other-extensions`. * If you use QuickCheck properties (`prop>`) in your doctests, the `test-suite doctest` should depend on `QuickCheck` and `template-haskell`. This is a little HACK: These dependencies aren't needed to build the `doctests` test-suite executable. However, as we let `Cabal` resolve dependencies, we can pass the resolved (and installed!) package identifiers to to the `doctest` command. This way, `QuickCheck` and `template-haskell` are available to `doctest`, otherwise you'll get errors like: ``` Variable not in scope: mkName :: [Char] -> template-haskell-2.11.1.0:Language.Haskell.TH.Syntax.Name ``` or ``` Variable not in scope: polyQuickCheck :: Language.Haskell.TH.Syntax.Name -> Language.Haskell.TH.Lib.ExpQ ``` * From version 2, Stack sets the `GHC_ENVIRONMENT` variable, and GHC (as invoked by `doctest`) will pick that up. This is undesirable: `cabal-doctest` passes all the necessary information on the command line already, and can lead to ambiguous module errors as GHC will load the environment in addition to what `cabal-doctest` instructs it to. Hence, `cabal-doctest` tells GHC to ignore package environments altogether on the command line. However, this is only possible since GHC 8.2. If you are using `cabal-doctest` with Stack 2 and GHC 8.0 or earlier and seeing ambiguous module errors or other mysterious failures, try manually unsetting `GHC_ENVIRONMENT` before invoking `doctest`. * If you are on Nix. `doctest` will not pick up your version of GHC if you don't point it towards it, and therefore will result in "cannot satisfy -package-id" errors. You will need to set `NIX_GHC` and `NIX_GHC_LIBDIR` within your environment in order for doctest to pick up your GHC. Put the following in `shell.nix` and run `nix-shell`. ```nix # shell.nix { pkgs ? import {} }: let myHaskell = (pkgs.haskellPackages.ghcWithHoogle (p: with p; [ # Put your dependencies here containers hslogger ])); in pkgs.mkShell { name = "myPackage"; # These environment variables are important. Without these, # doctest doesn't pick up nix's version of ghc, and will fail # claiming it can't find your dependencies shellHook = '' export NIX_GHC=${myHaskell}/bin/ghc export NIX_GHC_LIBDIR=${myHaskell}/lib/ghc-8.10.7 ''; buildInputs = with pkgs; [ myHaskell ]; } ``` [#19]: https://github.com/ulidtko/cabal-doctest/issues/19 [haskell/cabal#4288]: https://github.com/haskell/cabal/issues/4288 [stack#2094]: https://github.com/commercialhaskell/stack/issues/2094 Copyright --------- Copyright 2017 Oleg Grenrus. With contributions from: * Ryan Scott * Andreas Abel * Max Ulidtko Available under the BSD 3-clause license. cabal-doctest-1.0.12/Setup.hs0000644000000000000000000000005607346545000014126 0ustar0000000000000000import Distribution.Simple main = defaultMain cabal-doctest-1.0.12/cabal-doctest.cabal0000644000000000000000000000377707346545000016200 0ustar0000000000000000name: cabal-doctest version: 1.0.12 -- x-revision: 0 synopsis: A Setup.hs helper for running doctests description: As of now (end of 2024), there isn't @cabal doctest@ command. Yet, to properly work, @doctest@ needs plenty of configuration. This library provides the common bits for writing a custom @Setup.hs@. homepage: https://github.com/ulidtko/cabal-doctest license: BSD3 license-file: LICENSE author: Oleg Grenrus maintainer: Max Ulidtko copyright: (c) 2017-2020 Oleg Grenrus, 2020- package maintainers category: Distribution build-type: Simple cabal-version: >=1.10 extra-source-files: changelog.md README.md tested-with: GHC == 9.10.3 GHC == 9.8.4 GHC == 9.6.7 GHC == 9.4.8 GHC == 9.2.8 GHC == 9.0.2 GHC == 8.10.7 GHC == 8.8.4 GHC == 8.6.5 GHC == 8.4.4 GHC == 8.2.2 GHC == 8.0.2 -- 2023-10-14: Dropped CI support for GHC 7.x source-repository head type: git location: https://github.com/ulidtko/cabal-doctest library exposed-modules: Distribution.Extra.Doctest other-modules: other-extensions: build-depends: -- NOTE: contrary to PVP, some upper-bounds are intentionally set to major-major. -- This is to increase signal-to-noise ratio of CI failures. "Too tight bounds" -- is an extremely boring (and practically guaranteed, repeatedly) failure mode. -- OTOH, genuine build failures due to breaking changes in dependencies are: -- 1) unlikely to occur, as this package is so small, moreso regularly; -- 2) best caught in CI pipelines that don't induce alert fatigue. -- In any case, revisions may set tighter bounds afterwards, if exceptional -- circumstances would warrant that. base >=4.9 && <5 , Cabal >=1.24 && <3.16 , directory >=1.3 && <2 , filepath >=1.4 && <2 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall cabal-doctest-1.0.12/changelog.md0000644000000000000000000000443307346545000014746 0ustar0000000000000000# 1.0.12 -- 2025-11-19 * Fix documentation mistake regarding `Build_doctests` module name. [cabal-doctest#90][] * Clarify function signatures through a meaningful alias onto `String`. * Refresh CI & `tested-with` GHC versions. [cabal-doctest#90]: https://github.com/ulidtko/cabal-doctest/issues/90 # 1.0.11 -- 2024-11-22 * Support Cabal 3.14.0.0. [cabal-doctest#85][]. * Motivate the package in README [cabal-doctest#43][]. * Fix `stack test` of examples, add CI integration. [cabal-doctest#43]: https://github.com/ulidtko/cabal-doctest/issues/43 [cabal-doctest#85]: https://github.com/ulidtko/cabal-doctest/issues/85 # 1.0.10 -- 2024-06-26 * Maintainership hand-over. See [cabal-doctest#79][]. * Support GHC 9.4, 9.6, 9.8, 9.10. * Drop support & CI for GHC < 8.0. [cabal-doctest#79]: https://github.com/ulidtko/cabal-doctest/issues/79 # 1.0.9 -- 2021-11-07 * Support `GHC-9.2`, `base-4.16`, and `Cabal-3.6` (thanks Alistair Burrowes). # 1.0.8 -- 2019-10-02 * Pass `-package-env=-` when compiler supports it. * Amend examples to `unsetEnv "GHC_ENVIRONMENT"`. # 1.0.7 -- 2019-08-26 * Make `Distribution.Extra.Doctest` `-Wall`-clean. * Support `GHC-8.8`, `base-4.13`, and `Cabal-3.0`. # 1.0.6 -- 2018-01-28 * Hook `haddock` build too. Fixes issue when `haddock` fails, as `Build_doctests` isn't generated. # 1.0.5 -- 2018-01-26 * Add a hack so `Build_doctests` module is automatically added to to `other-modules` and `autogen-modules` when compiled with Cabal-2.0. Thanks to that, we don't get warnings because of `-Wmissing-home-modules`. # 1.0.4 -- 2017-12-05 * Add support for doctests in executables and (with `Cabal-2.0` or later) internal libraries. Refer to the `README` for more details. # 1.0.3 -- 2017-11-02 * Add an explicit `Prelude` import to `Build_doctests`. # 1.0.2 -- 2017-05-16 * Add `defaultMainAutoconfWithDoctests` and `addDoctestsUserHook`. * Add support for `.hsc` and other preprocessed files ([#8](https://github.com/phadej/cabal-doctest/issues/8)). * Add support for `x-doctest-source-dirs` and `x-doctest-modules`. # 1.0.1 -- 2017-05-05 * Add support for `x-doctest-options` cabal-file field. * Proper support for `GHC-8.2.1` and `Cabal-2.0.0.0`. * Add support to `default-extensions` in library. # 1 -- 2017-01-31 * First version. Released on an unsuspecting world. cabal-doctest-1.0.12/src/Distribution/Extra/0000755000000000000000000000000007346545000017022 5ustar0000000000000000cabal-doctest-1.0.12/src/Distribution/Extra/Doctest.hs0000644000000000000000000005445307346545000020776 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-} #if MIN_VERSION_Cabal(3,14,0) {-# LANGUAGE DataKinds #-} #endif {-# LANGUAGE MultiParamTypeClasses #-} -- | See cabal-doctest README for full-fledged recipes & caveats. -- -- The provided 'generateBuildModule' generates a module named @Build_doctests@. -- -- That module exports just enough compiler flags, so that doctest could be simply -- -- @ -- module Main where -- -- import Build_doctests (flags, pkgs, module_sources) -- import Test.Doctest (doctest) -- -- main :: IO () -- main = doctest args -- where -- args = flags ++ pkgs ++ module_sources -- @ -- -- As this module-generation is done at build-time, 'generateBuildModule' must be -- invoked from @Setup.hs@, which also necessarily means @build-type: Custom@. -- -- @Setup.hs@ can use libraries, but they must be declared as dependencies in the -- @custom-setup@ stanza of the user's cabal file. To use @cabal-doctest@ then: -- -- @ -- custom-setup -- setup-depends: -- base >= 4 && <5, -- cabal-doctest >= 1 && <1.1 -- @ -- -- Finally, simple shortcuts are provided to avoid an explicit dependency on @Cabal@ -- from @setup-depends@: 'defaultMainWithDoctests' and 'defaultMainAutoconfWithDoctests'. -- module Distribution.Extra.Doctest ( TestSuiteName, defaultMainWithDoctests, defaultMainAutoconfWithDoctests, addDoctestsUserHook, doctestsUserHooks, generateBuildModule, ) where import Control.Monad (when) import Data.IORef (modifyIORef, newIORef, readIORef) import Data.List (nub) import Data.Maybe (mapMaybe, maybeToList) import Data.String (fromString) import Distribution.Package (UnitId, Package (..)) import Distribution.PackageDescription (BuildInfo (..), Executable (..), GenericPackageDescription, Library (..), PackageDescription, TestSuite (..)) import Distribution.Simple (UserHooks (..), autoconfUserHooks, defaultMainWithHooks, simpleUserHooks) import Distribution.Simple.Compiler (CompilerFlavor (GHC), CompilerId (..), compilerId) import Distribution.Simple.LocalBuildInfo (ComponentLocalBuildInfo (componentPackageDeps), LocalBuildInfo, compiler, withExeLBI, withLibLBI, withPackageDB, withTestLBI) import Distribution.Simple.Setup (BuildFlags (..), emptyBuildFlags, fromFlag) import Distribution.Simple.Utils (createDirectoryIfMissingVerbose, info) import Distribution.Text (display) import qualified Data.Foldable as F (for_) import qualified Data.Traversable as T (traverse) import qualified System.FilePath (()) #if MIN_VERSION_base(4,11,0) import Data.Functor ((<&>)) #endif #if MIN_VERSION_Cabal(1,25,0) import Distribution.Simple.BuildPaths (autogenComponentModulesDir) #else import Distribution.Simple.BuildPaths (autogenModulesDir) #endif #if MIN_VERSION_Cabal(2,0,0) import Distribution.Types.MungedPackageId (MungedPackageId) import Distribution.Types.UnqualComponentName (unUnqualComponentName) -- For amendGPD import Distribution.PackageDescription (CondTree (..)) import Distribution.Types.GenericPackageDescription (GenericPackageDescription (condTestSuites)) import Distribution.Version (mkVersion) #else import Data.Version (Version (..)) import Distribution.Package (PackageId) #endif #if MIN_VERSION_Cabal(3,0,0) import Distribution.Simple.Utils (findFileEx) #else import Distribution.Simple.Utils (findFile) #endif #if MIN_VERSION_Cabal(3,0,0) import Distribution.Types.LibraryName (libraryNameString) #endif #if MIN_VERSION_Cabal(3,5,0) import Distribution.Utils.Path (getSymbolicPath) #endif #if MIN_VERSION_Cabal(3,14,0) -- https://github.com/haskell/cabal/issues/10559 import Distribution.Simple.Compiler (PackageDB, PackageDBX (GlobalPackageDB, UserPackageDB, SpecificPackageDB)) import Distribution.Simple.LocalBuildInfo (absoluteWorkingDirLBI, interpretSymbolicPathLBI) import Distribution.Simple.Setup (HaddockFlags, haddockCommonFlags) import Distribution.Utils.Path (FileOrDir(..), SymbolicPath, interpretSymbolicPathAbsolute, makeRelativePathEx, makeSymbolicPath) import qualified Distribution.Utils.Path as SymPath (()) #else import Distribution.Simple.Compiler (PackageDB (GlobalPackageDB, UserPackageDB, SpecificPackageDB)) import Distribution.Simple.Setup (HaddockFlags (haddockDistPref, haddockVerbosity)) #endif #if MIN_VERSION_directory(1,2,2) import System.Directory (makeAbsolute) #else import System.Directory (getCurrentDirectory) import System.FilePath (isAbsolute) #endif {- HLINT ignore "Use fewer imports" -} ------------------------------------------------------------------------------- -- Compat ------------------------------------------------------------------------------- #if !MIN_VERSION_base(4,11,0) (<&>) :: Functor f => f a -> (a -> b) -> f b (<&>) = flip fmap infixl 1 <&> #endif class CompatSymPath p q where () :: p -> FilePath -> q infixr 5 instance CompatSymPath FilePath FilePath where () = (System.FilePath.) #if MIN_VERSION_Cabal(3,14,0) instance CompatSymPath (SymbolicPath allowAbs ('Dir loc1)) (SymbolicPath allowAbs ('Dir loc2)) where dir name = dir SymPath. makeRelativePathEx name #endif #if MIN_VERSION_Cabal(3,14,0) unsymbolizePath = getSymbolicPath #else makeSymbolicPath :: FilePath -> FilePath makeSymbolicPath = id unsymbolizePath :: FilePath -> FilePath unsymbolizePath = id #endif #if !MIN_VERSION_directory(1,2,2) makeAbsolute :: FilePath -> IO FilePath makeAbsolute p | isAbsolute p = return p | otherwise = do cwd <- getCurrentDirectory return $ cwd p #endif #if !MIN_VERSION_Cabal(3,0,0) findFileEx :: verbosity -> [FilePath] -> FilePath -> IO FilePath findFileEx _ = findFile #endif #if !MIN_VERSION_Cabal(2,0,0) mkVersion :: [Int] -> Version mkVersion ds = Version ds [] #endif ------------------------------------------------------------------------------- -- Mains ------------------------------------------------------------------------------- -- | This type is a 'String' which identifies a test-suite in your cabal-file; -- the one you'll be running doctest from. type TestSuiteName = String -- | A default @Setup.hs@ main with doctests: -- -- @ -- import Distribution.Extra.Doctest -- (defaultMainWithDoctests) -- -- main :: IO () -- main = defaultMainWithDoctests "doctests" -- @ -- -- The argument @"doctests"@ identifies a test-suite in your cabal-file; the -- one you'll run doctest from. If you have @test-suite my-test@, you should -- invoke @defaultMainWithDoctests "my-test"@ in Setup.hs. -- -- This argument does not change the generated module name imported from the -- test-driver's @Main@ -- that one always remains @import Build_doctests@. defaultMainWithDoctests :: TestSuiteName -> IO () defaultMainWithDoctests = defaultMainWithHooks . doctestsUserHooks -- | Like 'defaultMainWithDoctests', but for packages with @build-type: Configure@. -- -- @since 1.0.2 defaultMainAutoconfWithDoctests :: TestSuiteName -> IO () defaultMainAutoconfWithDoctests n = defaultMainWithHooks (addDoctestsUserHook n autoconfUserHooks) -- | 'simpleUserHooks' with 'generateBuildModule' already wired-in. doctestsUserHooks :: TestSuiteName -> UserHooks doctestsUserHooks testsuiteName = addDoctestsUserHook testsuiteName simpleUserHooks -- | Compose 'generateBuildModule' into Cabal's 'UserHooks' (prepending the action). -- -- This is exported for advanced custom Setup-s. -- -- @since 1.0.2 addDoctestsUserHook :: TestSuiteName -> UserHooks -> UserHooks addDoctestsUserHook testsuiteName uh = uh { buildHook = \pkg lbi hooks flags -> do generateBuildModule testsuiteName flags pkg lbi buildHook uh pkg lbi hooks flags -- We use confHook to add "Build_doctests" to otherModules and autogenModules. -- -- We cannot use HookedBuildInfo as it lets alter only the library and executables. , confHook = \(gpd, hbi) flags -> confHook uh (amendGPD testsuiteName gpd, hbi) flags , haddockHook = \pkg lbi hooks flags -> do generateBuildModule testsuiteName (haddockToBuildFlags flags) pkg lbi haddockHook uh pkg lbi hooks flags } -- | Convert only flags used by 'generateBuildModule'. haddockToBuildFlags :: HaddockFlags -> BuildFlags haddockToBuildFlags f = #if MIN_VERSION_Cabal(3,14,0) emptyBuildFlags { buildCommonFlags = haddockCommonFlags f } #else emptyBuildFlags { buildVerbosity = haddockVerbosity f , buildDistPref = haddockDistPref f } #endif data Name = NameLib (Maybe String) | NameExe String deriving (Eq, Show) nameToString :: Name -> String nameToString n = case n of NameLib x -> maybe "" (("_lib_" ++) . map fixchar) x NameExe x -> "_exe_" ++ map fixchar x where -- Taken from Cabal: -- https://github.com/haskell/cabal/blob/20de0bfea72145ba1c37e3f500cee5258cc18e51/Cabal/Distribution/Simple/Build/Macros.hs#L156-L158 -- -- Needed to fix component names with hyphens in them, as hyphens aren't -- allowed in Haskell identifier names. fixchar :: Char -> Char fixchar '-' = '_' fixchar c = c data Component = Component Name [String] [String] [String] deriving Show -- | Generate a build module for the test suite. -- -- @ -- import Distribution.Simple -- (defaultMainWithHooks, UserHooks(..), simpleUserHooks) -- import Distribution.Extra.Doctest -- (generateBuildModule) -- -- main :: IO () -- main = defaultMainWithHooks simpleUserHooks -- { buildHook = \pkg lbi hooks flags -> do -- generateBuildModule "doctests" flags pkg lbi -- buildHook simpleUserHooks pkg lbi hooks flags -- } -- @ generateBuildModule :: TestSuiteName -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO () generateBuildModule testSuiteName flags pkg lbi = do let verbosity = fromFlag (buildVerbosity flags) let distPref = fromFlag (buildDistPref flags) -- Package DBs & environments let dbStack = withPackageDB lbi ++ [ SpecificPackageDB $ distPref "package.conf.inplace" ] let dbFlags = "-hide-all-packages" : packageDbArgs dbStack let envFlags | ghcCanBeToldToIgnorePkgEnvs = [ "-package-env=-" ] | otherwise = [] withTestLBI pkg lbi $ \suite suitecfg -> when (testName suite == fromString testSuiteName) $ do -- Locate autogen dir, to put our output into. #if MIN_VERSION_Cabal(3,14,0) let testAutogenDir = interpretSymbolicPathLBI lbi $ autogenComponentModulesDir lbi suitecfg #elif MIN_VERSION_Cabal(1,25,0) let testAutogenDir = autogenComponentModulesDir lbi suitecfg #else let testAutogenDir = autogenModulesDir lbi #endif createDirectoryIfMissingVerbose verbosity True testAutogenDir let buildDoctestsFile = testAutogenDir "Build_doctests.hs" -- First, we create the autogen'd module Build_doctests. -- Initially populate Build_doctests with a simple preamble. info verbosity $ "cabal-doctest: writing Build_doctests to " ++ buildDoctestsFile writeFile buildDoctestsFile $ unlines [ "module Build_doctests where" , "" , "import Prelude" , "" , "data Name = NameLib (Maybe String) | NameExe String deriving (Eq, Show)" , "data Component = Component Name [String] [String] [String] deriving (Eq, Show)" , "" ] -- we cannot traverse, only traverse_ -- so we use IORef to collect components componentsRef <- newIORef [] let testBI = testBuildInfo suite -- TODO: `words` is not proper parser (no support for quotes) let additionalFlags = maybe [] words $ lookup "x-doctest-options" $ customFieldsBI testBI let additionalModules = maybe [] words $ lookup "x-doctest-modules" $ customFieldsBI testBI let additionalDirs' = maybe [] words $ lookup "x-doctest-source-dirs" $ customFieldsBI testBI additionalDirs <- mapM (fmap ("-i" ++) . makeAbsolute) additionalDirs' -- Next, for each component (library or executable), we get to Build_doctests -- the sets of flags needed to run doctest on that component. let getBuildDoctests withCompLBI mbCompName compExposedModules compMainIs compBuildInfo = withCompLBI pkg lbi $ \comp compCfg -> do let compBI = compBuildInfo comp -- modules let modules = compExposedModules comp ++ otherModules compBI -- it seems that doctest is happy to take in module names, not actual files! let module_sources = modules -- We need the directory with the component's cabal_macros.h! #if MIN_VERSION_Cabal(3,14,0) let compAutogenDir = interpretSymbolicPathLBI lbi $ autogenComponentModulesDir lbi compCfg #elif MIN_VERSION_Cabal(1,25,0) let compAutogenDir = autogenComponentModulesDir lbi compCfg #else let compAutogenDir = autogenModulesDir lbi #endif -- Lib sources and includes let iArgsSymbolic = makeSymbolicPath compAutogenDir -- autogen dir -- preprocessed files (.hsc -> .hs); "build" is hardcoded in Cabal. : (distPref "build") #if MIN_VERSION_Cabal(3,14,0) : hsSourceDirs compBI #elif MIN_VERSION_Cabal(3,5,0) : (hsSourceDirs compBI <&> getSymbolicPath) #else : hsSourceDirs compBI #endif #if MIN_VERSION_Cabal(3,14,0) pkgWorkdir <- absoluteWorkingDirLBI lbi let iArgsNoPrefix = iArgsSymbolic <&> interpretSymbolicPathAbsolute pkgWorkdir let includeArgs = includeDirs compBI <&> ("-I"++) . interpretSymbolicPathAbsolute pkgWorkdir #else iArgsNoPrefix <- mapM makeAbsolute iArgsSymbolic includeArgs <- mapM (fmap ("-I"++) . makeAbsolute) $ includeDirs compBI #endif -- We clear all includes, so the CWD isn't used. let iArgs' = map ("-i"++) iArgsNoPrefix iArgs = "-i" : iArgs' -- default-extensions let extensionArgs = map (("-X"++) . display) $ defaultExtensions compBI -- CPP includes, i.e. include cabal_macros.h let cppFlags = map ("-optP"++) $ [ "-include", compAutogenDir ++ "/cabal_macros.h" ] ++ cppOptions compBI -- Unlike other modules, the main-is module of an executable is not -- guaranteed to share a module name with its filepath name. That is, -- even though the main-is module is named Main, its filepath might -- actually be Something.hs. To account for this possibility, we simply -- pass the full path to the main-is module instead. mainIsPath <- T.traverse (findFileEx verbosity iArgsSymbolic) (compMainIs comp) let all_sources = map display module_sources ++ additionalModules ++ maybeToList (mainIsPath <&> unsymbolizePath) let component = Component (mbCompName comp) (formatDeps $ testDeps compCfg suitecfg) (concat [ iArgs , additionalDirs , includeArgs , envFlags , dbFlags , cppFlags , extensionArgs , additionalFlags ]) all_sources -- modify IORef, append component modifyIORef componentsRef (\cs -> cs ++ [component]) -- For now, we only check for doctests in libraries and executables. getBuildDoctests withLibLBI mbLibraryName exposedModules (const Nothing) libBuildInfo getBuildDoctests withExeLBI (NameExe . executableName) (const []) (Just . modulePath) buildInfo components <- readIORef componentsRef F.for_ components $ \(Component cmpName cmpPkgs cmpFlags cmpSources) -> do let compSuffix = nameToString cmpName pkgs_comp = "pkgs" ++ compSuffix flags_comp = "flags" ++ compSuffix module_sources_comp = "module_sources" ++ compSuffix -- write autogen'd file appendFile buildDoctestsFile $ unlines [ -- -package-id etc. flags pkgs_comp ++ " :: [String]" , pkgs_comp ++ " = " ++ show cmpPkgs , "" , flags_comp ++ " :: [String]" , flags_comp ++ " = " ++ show cmpFlags , "" , module_sources_comp ++ " :: [String]" , module_sources_comp ++ " = " ++ show cmpSources , "" ] -- write enabled components, i.e. x-doctest-components -- if none enabled, pick library let enabledComponents = maybe [NameLib Nothing] (mapMaybe parseComponentName . words) $ lookup "x-doctest-components" $ customFieldsBI testBI let components' = filter (\(Component n _ _ _) -> n `elem` enabledComponents) components appendFile buildDoctestsFile $ unlines [ "-- " ++ show enabledComponents , "components :: [Component]" , "components = " ++ show components' ] where parseComponentName :: String -> Maybe Name parseComponentName "lib" = Just (NameLib Nothing) parseComponentName ('l' : 'i' : 'b' : ':' : x) = Just (NameLib (Just x)) parseComponentName ('e' : 'x' : 'e' : ':' : x) = Just (NameExe x) parseComponentName _ = Nothing -- we do this check in Setup, as then doctests don't need to depend on Cabal isNewCompiler = case compilerId $ compiler lbi of CompilerId GHC v -> v >= mkVersion [7,6] _ -> False ghcCanBeToldToIgnorePkgEnvs :: Bool ghcCanBeToldToIgnorePkgEnvs = case compilerId $ compiler lbi of CompilerId GHC v -> v >= mkVersion [8,4,4] _ -> False formatDeps = map formatOne formatOne (installedPkgId, pkgId) -- The problem is how different cabal executables handle package databases -- when doctests depend on the library -- -- If the pkgId is current package, we don't output the full package-id -- but only the name -- -- Because of MungedPackageId we compare display version of identifiers -- not the identifiers themfselves. | display (packageId pkg) == display pkgId = "-package=" ++ display pkgId | otherwise = "-package-id=" ++ display installedPkgId -- From Distribution.Simple.Program.GHC packageDbArgs :: [PackageDB] -> [String] packageDbArgs | isNewCompiler = packageDbArgsDb | otherwise = packageDbArgsConf -- GHC <7.6 uses '-package-conf' instead of '-package-db'. packageDbArgsConf :: [PackageDB] -> [String] packageDbArgsConf dbstack = case dbstack of (GlobalPackageDB:UserPackageDB:dbs) -> concatMap specific dbs (GlobalPackageDB:dbs) -> "-no-user-package-conf" : concatMap specific dbs _ -> ierror where specific (SpecificPackageDB db) = [ "-package-conf=" ++ unsymbolizePath db ] specific _ = ierror ierror = error $ "internal error: unexpected package db stack: " ++ show dbstack -- GHC >= 7.6 uses the '-package-db' flag. See -- https://ghc.haskell.org/trac/ghc/ticket/5977. packageDbArgsDb :: [PackageDB] -> [String] -- special cases to make arguments prettier in common scenarios packageDbArgsDb dbstack = case dbstack of (GlobalPackageDB:UserPackageDB:dbs) | all isSpecific dbs -> concatMap single dbs (GlobalPackageDB:dbs) | all isSpecific dbs -> "-no-user-package-db" : concatMap single dbs dbs -> "-clear-package-db" : concatMap single dbs where single (SpecificPackageDB db) = [ "-package-db=" ++ unsymbolizePath db ] single GlobalPackageDB = [ "-global-package-db" ] single UserPackageDB = [ "-user-package-db" ] isSpecific (SpecificPackageDB _) = True isSpecific _ = False mbLibraryName :: Library -> Name #if MIN_VERSION_Cabal(3,0,0) mbLibraryName = NameLib . fmap unUnqualComponentName . libraryNameString . libName #elif MIN_VERSION_Cabal(2,0,0) -- Cabal-2.0 introduced internal libraries, which are named. mbLibraryName = NameLib . fmap unUnqualComponentName . libName #else -- Before that, there was only ever at most one library per -- .cabal file, which has no name. mbLibraryName _ = NameLib Nothing #endif executableName :: Executable -> String #if MIN_VERSION_Cabal(2,0,0) executableName = unUnqualComponentName . exeName #else executableName = exeName #endif -- | In compat settings it's better to omit the type-signature testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo #if MIN_VERSION_Cabal(2,0,0) -> [(UnitId, MungedPackageId)] #else -> [(UnitId, PackageId)] #endif testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys amendGPD :: TestSuiteName -> GenericPackageDescription -> GenericPackageDescription #if !(MIN_VERSION_Cabal(2,0,0)) amendGPD _ gpd = gpd #else amendGPD testSuiteName gpd = gpd { condTestSuites = map f (condTestSuites gpd) } where f (name, condTree) | name == fromString testSuiteName = (name, condTree') | otherwise = (name, condTree) where -- I miss 'lens' testSuite = condTreeData condTree bi = testBuildInfo testSuite om = otherModules bi am = autogenModules bi -- Cons the module to both other-modules and autogen-modules. -- At the moment, cabal-spec-2.0 and cabal-spec-2.2 don't have -- "all autogen-modules are other-modules if they aren't exposed-modules" -- rule. Hopefully cabal-spec-3.0 will have. -- -- Note: we `nub`, because it's unclear if that's ok to have duplicate -- modules in the lists. om' = nub $ mn : om am' = nub $ mn : am mn = fromString "Build_doctests" bi' = bi { otherModules = om', autogenModules = am' } testSuite' = testSuite { testBuildInfo = bi' } condTree' = condTree { condTreeData = testSuite' } #endif