packages feed

cabal-doctest 1.0.3 → 1.0.4

raw patch · 4 files changed

+282/−77 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Distribution.Extra.Doctest: instance GHC.Classes.Eq Distribution.Extra.Doctest.Name
+ Distribution.Extra.Doctest: instance GHC.Show.Show Distribution.Extra.Doctest.Component
+ Distribution.Extra.Doctest: instance GHC.Show.Show Distribution.Extra.Doctest.Name

Files

ChangeLog.md view
@@ -1,3 +1,8 @@+# 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`
README.md view
@@ -5,11 +5,13 @@  A `Setup.hs` helper for running `doctests`. -Example Usage--------------+Simple example+-------------- -See [https://github.com/phadej/cabal-doctest/tree/master/example] for an-example package. (Note that the example requires `Cabal-1.24` or later, but+For most use cases—a `.cabal` file with a single library containing+doctests—adapting the simple example located+[here](https://github.com/phadej/cabal-doctest/tree/master/simple-example)+will be sufficient. (Note that this example requires `Cabal-1.24` or later, but you can relax this bound safely, although running doctests won't be supported on versions of `Cabal` older than 1.24.) @@ -57,6 +59,81 @@     args = flags ++ pkgs ++ module_sources ``` +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.++Simplest approach is to use `x-doctest-components` field, for example+```+x-doctest-components: lib lib:internal exe:example+```++In that case, the testdrive is general:++```haskell+module Main where++import Build_doctests (Component (..), components)+import Data.Foldable (for_)+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+    doctest args+```++There's also a more explicit approach: if you have an executable named `foo`,+then separate values named `flags_exe_foo`, `pkgs_exe_foo`, and `module_sources_exe_foo` will+be generated in `Build_doctests`. If the name has hyphens in it+(e.g., `my-exe`), then `cabal-doctest` will convert those hyphens to+underscores (e.g., you'd get `flags_my_exe`, `pkgs_my_exe`, and+`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 Test.DocTest++main :: IO ()+main = do+    -- 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+[this example](https://github.com/phadej/cabal-doctest/tree/master/multiple-components-example)+for more details.+ Additional configuration ------------------------ @@ -91,7 +168,7 @@    A hacky workaround for this problem is to depend on the library itself in a   `doctests` test suite. See-  [the example's .cabal file](https://github.com/phadej/cabal-doctest/blob/master/example/example.cabal)+  [the simple example's .cabal file](https://github.com/phadej/cabal-doctest/blob/master/simple-example/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.)
cabal-doctest.cabal view
@@ -1,5 +1,5 @@ name:                cabal-doctest-version:             1.0.3+version:             1.0.4 synopsis:            A Setup.hs helper for doctests running description:   Currently (beginning of 2017), there isn't @cabal doctest@@@ -27,7 +27,7 @@   GHC==7.8.4,   GHC==7.10.3,   GHC==8.0.2,-  GHC==8.2.1+  GHC==8.2.2  source-repository head   type:     git
src/Distribution/Extra/Doctest.hs view
@@ -48,14 +48,25 @@        (when) import Data.List        (nub)+import Data.Maybe+       (maybeToList, mapMaybe) import Data.String        (fromString)+import qualified Data.Foldable as F+       (for_)+import qualified Data.Traversable as T+       (traverse)+import qualified Distribution.ModuleName as ModuleName+       (fromString)+import Distribution.ModuleName+       (ModuleName) import Distribution.Package        (InstalledPackageId) import Distribution.Package        (Package (..), PackageId, packageVersion) import Distribution.PackageDescription-       (BuildInfo (..), Library (..), PackageDescription (), TestSuite (..))+       (BuildInfo (..), Executable (..), Library (..),+       PackageDescription (), TestSuite (..)) import Distribution.Simple        (UserHooks (..), autoconfUserHooks, defaultMainWithHooks, simpleUserHooks) import Distribution.Simple.BuildPaths@@ -64,16 +75,18 @@        (PackageDB (..), showCompilerId) import Distribution.Simple.LocalBuildInfo        (ComponentLocalBuildInfo (componentPackageDeps), LocalBuildInfo (),-       compiler, withLibLBI, withPackageDB, withTestLBI)+       compiler, withExeLBI, withLibLBI, withPackageDB, withTestLBI) import Distribution.Simple.Setup        (BuildFlags (buildDistPref, buildVerbosity), fromFlag) import Distribution.Simple.Utils-       (createDirectoryIfMissingVerbose, rewriteFile)+       (createDirectoryIfMissingVerbose, findFile, rewriteFile) import Distribution.Text        (display, simpleParse) import System.FilePath-       ((</>))+       ((</>), (<.>), dropExtension) +import Data.IORef (newIORef, modifyIORef, readIORef)+ #if MIN_VERSION_Cabal(1,25,0) import Distribution.Simple.BuildPaths        (autogenComponentModulesDir)@@ -81,6 +94,8 @@ #if MIN_VERSION_Cabal(2,0,0) import Distribution.Types.MungedPackageId        (MungedPackageId)+import Distribution.Types.UnqualComponentName+       (unUnqualComponentName) #endif  #if MIN_VERSION_directory(1,2,2)@@ -139,6 +154,25 @@        buildHook uh pkg lbi hooks flags     } +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. -- -- @@@ -165,89 +199,161 @@   let dbStack = withPackageDB lbi ++ [ SpecificPackageDB $ distPref </> "package.conf.inplace" ]   let dbFlags = "-hide-all-packages" : packageDbArgs dbStack -  withLibLBI pkg lbi $ \lib libcfg -> do-    let libBI = libBuildInfo lib--    -- modules-    let modules = exposedModules lib ++ otherModules libBI-    -- it seems that doctest is happy to take in module names, not actual files!-    let module_sources = modules--    -- We need the directory with library's cabal_macros.h!+  withTestLBI pkg lbi $ \suite suitecfg -> when (testName suite == fromString testSuiteName) $ do #if MIN_VERSION_Cabal(1,25,0)-    let libAutogenDir = autogenComponentModulesDir lbi libcfg+    let testAutogenDir = autogenComponentModulesDir lbi suitecfg #else-    let libAutogenDir = autogenModulesDir lbi+    let testAutogenDir = autogenModulesDir lbi #endif -    -- Lib sources and includes-    iArgs' <- mapM (fmap ("-i"++) . makeAbsolute)-        $ libAutogenDir            -- autogenerated files-        : (distPref ++ "/build")   -- preprocessed files (.hsc -> .hs); "build" is hardcoded in Cabal.-        : hsSourceDirs libBI-    includeArgs <- mapM (fmap ("-I"++) . makeAbsolute) $ includeDirs libBI-    -- We clear all includes, so the CWD isn't used.-    let iArgs = "-i" : iArgs'+    createDirectoryIfMissingVerbose verbosity True testAutogenDir -    -- default-extensions-    let extensionArgs = map (("-X"++) . display) $ defaultExtensions libBI+    let buildDoctestsFile = testAutogenDir </> "Build_doctests.hs" -    -- CPP includes, i.e. include cabal_macros.h-    let cppFlags = map ("-optP"++) $-            [ "-include", libAutogenDir ++ "/cabal_macros.h" ]-            ++ cppOptions libBI+    -- First, we create the autogen'd module Build_doctests.+    -- Initially populate Build_doctests with a simple preamble.+    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)"+      , ""+      ] -    withTestLBI pkg lbi $ \suite suitecfg -> when (testName suite == fromString testSuiteName) $ do-      let testBI = testBuildInfo suite+    -- we cannot traverse, only traverse_+    -- so we use IORef to collect components+    componentsRef <- newIORef [] -      -- TODO: `words` is not proper parser (no support for quotes)-      let additionalFlags = maybe [] words-            $ lookup "x-doctest-options"-            $ customFieldsBI testBI+    let testBI = testBuildInfo suite -      let additionalModules = maybe [] words-            $ lookup "x-doctest-modules"-            $ customFieldsBI testBI+    -- TODO: `words` is not proper parser (no support for quotes)+    let additionalFlags = maybe [] words+          $ lookup "x-doctest-options"+          $ customFieldsBI testBI -      let additionalDirs' = maybe [] words-            $ lookup "x-doctest-source-dirs"-            $ customFieldsBI testBI-      additionalDirs <- mapM (fmap ("-i" ++) . makeAbsolute) additionalDirs'+    let additionalModules = maybe [] words+          $ lookup "x-doctest-modules"+          $ customFieldsBI testBI +    let additionalDirs' = maybe [] words+          $ lookup "x-doctest-source-dirs"+          $ customFieldsBI testBI -      -- get and create autogen dir+    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(1,25,0)-      let testAutogenDir = autogenComponentModulesDir lbi suitecfg+           let compAutogenDir = autogenComponentModulesDir lbi compCfg #else-      let testAutogenDir = autogenModulesDir lbi+           let compAutogenDir = autogenModulesDir lbi #endif-      createDirectoryIfMissingVerbose verbosity True testAutogenDir -      -- write autogen'd file-      rewriteFile (testAutogenDir </> "Build_doctests.hs") $ unlines-        [ "module Build_doctests where"-        , ""-        , "import Prelude"-        , ""-        -- -package-id etc. flags-        , "pkgs :: [String]"-        , "pkgs = " ++ (show $ formatDeps $ testDeps libcfg suitecfg)-        , ""-        , "flags :: [String]"-        , "flags = " ++ show (concat-          [ iArgs-          , additionalDirs-          , includeArgs-          , dbFlags-          , cppFlags-          , extensionArgs-          , additionalFlags-          ])-        , ""-        , "module_sources :: [String]"-        , "module_sources = " ++ show (map display module_sources ++ additionalModules)-        ]+           -- Lib sources and includes+           iArgsNoPrefix+              <- mapM makeAbsolute+               $ compAutogenDir           -- autogenerated files+               : (distPref ++ "/build")   -- preprocessed files (.hsc -> .hs); "build" is hardcoded in Cabal.+               : hsSourceDirs compBI+           includeArgs <- mapM (fmap ("-I"++) . makeAbsolute) $ includeDirs compBI+           -- 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 (findFile iArgsNoPrefix) (compMainIs comp)++           let all_sources = map display module_sources+                             ++ additionalModules+                             ++ maybeToList mainIsPath++           let component = Component+                (mbCompName comp)+                (formatDeps $ testDeps compCfg suitecfg)+                (concat+                  [ iArgs+                  , additionalDirs+                  , includeArgs+                  , 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 name pkgs flags sources) -> do+       let compSuffix          = nameToString name+           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 pkgs+         , ""+         , flags_comp ++ " :: [String]"+         , flags_comp ++ " = " ++ show flags+         , ""+         , module_sources_comp ++ " :: [String]"+         , module_sources_comp ++ " = " ++ show sources+         , ""+         ]++    -- 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     isOldCompiler = maybe False id $ do       a <- simpleParse $ showCompilerId $ compiler lbi@@ -303,6 +409,23 @@        single UserPackageDB          = [ "-user-package-db" ]        isSpecific (SpecificPackageDB _) = True        isSpecific _                     = False++    mbLibraryName :: Library -> Name+#if 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