diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -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`
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -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.)
diff --git a/cabal-doctest.cabal b/cabal-doctest.cabal
--- a/cabal-doctest.cabal
+++ b/cabal-doctest.cabal
@@ -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
diff --git a/src/Distribution/Extra/Doctest.hs b/src/Distribution/Extra/Doctest.hs
--- a/src/Distribution/Extra/Doctest.hs
+++ b/src/Distribution/Extra/Doctest.hs
@@ -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
