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 +5/−0
- README.md +82/−5
- cabal-doctest.cabal +2/−2
- src/Distribution/Extra/Doctest.hs +193/−70
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