diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,6 +1,6 @@
 -- This file is part of Hoppy.
 --
--- Copyright 2015-2021 Bryan Gardiner <bog@khumba.net>
+-- Copyright 2015-2024 Bryan Gardiner <bog@khumba.net>
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
 -- you may not use this file except in compliance with the License.
diff --git a/hoppy-runtime.cabal b/hoppy-runtime.cabal
--- a/hoppy-runtime.cabal
+++ b/hoppy-runtime.cabal
@@ -1,12 +1,12 @@
 name: hoppy-runtime
-version: 0.8.0
+version: 0.9.0
 synopsis: C++ FFI generator - Runtime support
-homepage: http://khumba.net/projects/hoppy
+homepage: https://khumba.net/projects/hoppy
 license: Apache-2.0
 license-file: LICENSE
 author: Bryan Gardiner <bog@khumba.net>
 maintainer: Bryan Gardiner <bog@khumba.net>
-copyright: Copyright 2015-2021 Bryan Gardiner
+copyright: Copyright 2015-2024 Bryan Gardiner
 category: Foreign
 build-type: Simple
 cabal-version: 1.24
@@ -30,12 +30,12 @@
     , MultiParamTypeClasses
     , ScopedTypeVariables
   build-depends:
-      base >=4.7 && <5
-    , Cabal >=1.24 && <3.3
+      base >=4.10 && <5
+    , Cabal >=1.24 && <3.11
     , containers >=0.5 && <0.7
     , directory >=1.2 && <1.4
     , filepath >=1.3 && <1.5
-    , hoppy-generator >=0.8 && <0.9
+    , hoppy-generator >=0.9 && <0.10
   hs-source-dirs: src
   ghc-options: -W -fwarn-incomplete-patterns -fwarn-unused-do-bind
   default-language: Haskell2010
diff --git a/src/Foreign/Hoppy/Runtime.hs b/src/Foreign/Hoppy/Runtime.hs
--- a/src/Foreign/Hoppy/Runtime.hs
+++ b/src/Foreign/Hoppy/Runtime.hs
@@ -1,6 +1,6 @@
 -- This file is part of Hoppy.
 --
--- Copyright 2015-2021 Bryan Gardiner <bog@khumba.net>
+-- Copyright 2015-2024 Bryan Gardiner <bog@khumba.net>
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
 -- you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@
   -- * Containers
   HasContents (..),
   FromContents (..),
+  fromContentsToGc,
   -- * Internal
   CCallback (..),
   freeHaskellFunPtrFunPtr,
@@ -53,6 +54,7 @@
   ) where
 
 import Control.Exception (Exception, bracket, catch, throwIO)
+import Control.Monad ((<=<))
 import Data.Int (Int8, Int16, Int32, Int64)
 import qualified Data.Map as M
 import Data.Map (Map)
@@ -496,13 +498,13 @@
 -- | Containers whose contents can be convered to a list.
 --
 -- For a container @Cont@ holding values with C-side type @Foo@ and Haskell-side
--- type @Bar@, if the container uses 'Foreign.Hoppy.Generator.Std.ConvertPtr'
+-- type @Bar@, if the container uses @Foreign.Hoppy.Generator.Std.ConvertPtr@
 -- then the following instances are recommended:
 --
 -- > instance HasContents ContConst FooConst
 -- > instance HasContents Cont Foo
 --
--- If the container uses 'Foreign.Hoppy.Generator.Std.ConvertValue' then the
+-- If the container uses @Foreign.Hoppy.Generator.Std.ConvertValue@ then the
 -- following instances are recommended:
 --
 -- > instance HasContents ContConst Bar
@@ -528,7 +530,32 @@
 -- resulting collection to a const pointer.
 class FromContents c e | c -> e where
   -- | Creates and returns a new container holding the given elements.
+  --
+  -- The new container is not managed by the garbage collector.  If this is
+  -- desired, use 'fromContentsToGc'.
   fromContents :: [e] -> IO c
+
+-- | Creates and returns a new container holding the given elements, like
+-- 'fromContents', and additionally assigns ownership to the garbage collector.
+--
+-- This function is useful if the object being created is only needed as a
+-- temporary for passing into another function, for example:
+--
+-- > processBlobs $ fromContentsToGc [Blob 1, Blob 2] :: IO BlobContainer
+--
+-- In this example, @processBlobs@ inspects all of the blobs but does not take
+-- ownership of the container, so the Haskell code has to release the container
+-- itself.  This is done via the garbage collector.
+--
+-- In a simple case like this, we can also write it a bit more efficiently like
+-- below, since we don't truly need the garbage collector:
+--
+-- > withScopedPtr (fromContents [Blob 1, Blob 2] :: IO BlobContainer) processBlobs
+--
+-- In this second example, the container is freed immediately when
+-- @processBlobs@ returns.
+fromContentsToGc :: (Deletable c, FromContents c e) => [e] -> IO c
+fromContentsToGc = toGc <=< fromContents
 
 -- | Internal type that represents a pointer to a C++ callback object (callback
 -- impl object, specifically).
diff --git a/src/Foreign/Hoppy/Setup.hs b/src/Foreign/Hoppy/Setup.hs
--- a/src/Foreign/Hoppy/Setup.hs
+++ b/src/Foreign/Hoppy/Setup.hs
@@ -1,6 +1,6 @@
 -- This file is part of Hoppy.
 --
--- Copyright 2015-2021 Bryan Gardiner <bog@khumba.net>
+-- Copyright 2015-2024 Bryan Gardiner <bog@khumba.net>
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
 -- you may not use this file except in compliance with the License.
@@ -37,23 +37,28 @@
 --
 -- main =
 --   cppMain $
---   ProjectConfig
+--   'ProjectConfig'
 --   { generatorExecutableName = \"foobar-generator\"
 --   , cppPackageName = \"foobar-cpp\"
---   , cppSourcesDir = \"cpp\"
---   , hsSourcesDir = \"src\"
+--   , cppPackagedSourcesLocation = Nothing
+--   , cppSourcesDir = 'GenerateInAutogenDir' \"\"
+--   , hsSourcesDir = 'GenerateInAutogenDir' \"\"
 --   }
 -- @
 --
 -- The Haskell gateway uses the same code, except calling 'hsMain' instead of
--- 'cppMain'.  This causes C++ sources to be generated in @foobar-cpp\/cpp@ and
--- (assuming the Haskell gateway is at @foobar\/@) the Haskell sources to be
--- generated in @foobar\/src@.
+-- 'cppMain'.  This causes all (C++, Haskell) generated sources to be placed in
+-- the \"autogen\" directories provided by Cabal, which keeps the source
+-- directory clean.  See the documentation of the fields of 'ProjectConfig' for
+-- more information on how to set up your project's build process.
 --
 -- The gateway packages need to set @build-type: Custom@ in their @.cabal@ files
 -- to use these setup files.
 module Foreign.Hoppy.Setup (
   ProjectConfig (..),
+  GenerateLocation (..),
+  combinedMain,
+  combinedUserHooks,
   cppMain,
   cppUserHooks,
   hsMain,
@@ -62,7 +67,9 @@
 
 import Control.Monad (unless, when)
 import Data.List (isInfixOf)
+import qualified Data.Map as M
 import Distribution.InstalledPackageInfo (libraryDirs)
+import qualified Distribution.ModuleName as ModuleName
 #if MIN_VERSION_Cabal(2,0,0)
 import Distribution.Package (mkPackageName)
 #else
@@ -71,10 +78,12 @@
 import Distribution.PackageDescription (
   HookedBuildInfo,
   PackageDescription,
+  autogenModules,
   emptyBuildInfo,
   extraLibDirs,
   )
 import Distribution.Simple (defaultMainWithHooks, simpleUserHooks)
+import Distribution.Simple.BuildPaths (autogenComponentModulesDir)
 import Distribution.Simple.LocalBuildInfo (
   LocalBuildInfo,
   absoluteInstallDirs,
@@ -87,6 +96,7 @@
 import Distribution.Simple.Program (
   runDbProgram,
   runProgram,
+  simpleProgram,
   )
 import Distribution.Simple.Program.Find (
   ProgramSearchPathEntry (ProgramSearchPathDefault),
@@ -97,7 +107,6 @@
   Program,
   ProgramLocation (FoundOnSystem),
   simpleConfiguredProgram,
-  simpleProgram,
   )
 import Distribution.Simple.Setup (
   CopyDest (CopyTo, NoCopyDest),
@@ -113,6 +122,8 @@
   installVerbosity,
   regInPlace,
   regVerbosity,
+  replVerbosity,
+  testVerbosity,
   )
 import Distribution.Simple.UserHooks (
   UserHooks (
@@ -137,10 +148,16 @@
 import Distribution.Simple.Utils (die)
 #endif
 import Distribution.Simple.Utils (installOrdinaryFile)
+import Distribution.Types.ComponentName (ComponentName (CLibName))
+#if MIN_VERSION_Cabal(3,0,0)
+import Distribution.Types.LibraryName (LibraryName (LMainLibName))
+#endif
+import Distribution.Types.LocalBuildInfo (componentNameCLBIs)
 import Distribution.Verbosity (Verbosity, normal)
+import Foreign.Hoppy.Generator.Language.Haskell (getModuleName)
 import Foreign.Hoppy.Generator.Main (run)
-import Foreign.Hoppy.Generator.Spec (Interface)
-import System.Directory (createDirectoryIfMissing, doesFileExist)
+import Foreign.Hoppy.Generator.Spec (Interface, interfaceModules)
+import System.Directory (createDirectoryIfMissing, doesFileExist, getCurrentDirectory)
 import System.FilePath ((</>), takeDirectory)
 
 -- | Configuration parameters for a project using Hoppy.
@@ -149,16 +166,51 @@
     -- ^ The interface to run the generator with.  This result is returned from
     -- @Foreign.Hoppy.Generator.Spec.interface@ and some may have failed; the
     -- string is a message indicating the problem.
+
   , cppPackageName :: String
     -- ^ The name of the C++ gateway package.
-  , cppSourcesDir :: FilePath
-    -- ^ The directory into which to generate C++ sources, under the C++ gateway
-    -- package root.
-  , hsSourcesDir :: FilePath
-    -- ^ The directory into which to generate Haskell sources, under the Haskell
-    -- gateway package root.
+    --
+    -- When using separate Cabal packages for the C++ and Haskell gateway
+    -- packages, this must be nonempty.  When using a single combined gateway
+    -- package with 'combinedMain', this must be empty.
+
+  , cppPackagedSourcesLocation :: Maybe FilePath
+    -- ^ If the C++ gateway package includes C++ files needed for compliation
+    -- (either sources or headers), then this should point to the base directory
+    -- holding these files, relative to the root of the project.  The project
+    -- root itself may be specified with @Just \"\"@.
+    --
+    -- When present, this is passed to the C++ package's makefile in the
+    -- environment variable @HOPPY_CPP_PKG_DIR@.  A value of @Just \"\"@ is
+    -- passed with @HOPPY_CPP_PKG_DIR@ set to the base directory of the Cabal
+    -- package (equivalent to @Just \".\"@).
+    --
+    -- This is also added automatically as a system include path (i.e. @gcc -I@)
+    -- for the C++ compiler when compiling the test program for enum
+    -- autodetection.
+
+  , cppGeneratedSourcesLocation :: GenerateLocation
+    -- ^ Specifies the directory where C++ sources will be generated.
+    --
+    -- This is passed to the C++ package's makefile in the environment variable
+    -- @HOPPY_CPP_GEN_DIR@.
   }
 
+-- | Where to generate sources for binding packages.
+data GenerateLocation =
+  GenerateInAutogenDir FilePath
+  -- ^ Generate sources in the package's autogen directory provided by Cabal.
+  -- This is preferrable as it keeps the source directory clean.
+  --
+  -- Sources are generated below the given @FilePath@ if nonempty, otherwise
+  -- sources are generated directly in the autogen directory.
+  | GenerateInSourcesDir FilePath
+  -- ^ Generate sources in the package's root source directory, i.e. the
+  -- directory with the @.cabal@ file.
+  --
+  -- Sources are generated below the given @FilePath@ if nonempty, otherwise
+  -- sources are generated directly in the root directory.
+
 -- | The name of the file we'll use to hold the enum evaluation cache.
 enumEvalCacheFileName :: FilePath
 enumEvalCacheFileName = "hoppy-enum-eval-cache"
@@ -176,6 +228,89 @@
     "Error initializing interface: " ++ errorMsg
   Right iface -> return iface
 
+-- | A @main@ implementation to be used in the @Setup.hs@ of a single Hoppy
+-- binding package that combined both the C++ and Haskell gateway code in one
+-- package.
+--
+-- @combinedMain project = 'defaultMainWithHooks' $ 'combinedUserHooks' project@
+combinedMain :: ProjectConfig -> IO ()
+combinedMain project = defaultMainWithHooks $ combinedUserHooks project
+
+-- | Cabal user hooks for a combined gateway package.  When overriding
+-- overriding fields in the result, be sure to call the previous hook.
+--
+-- The following hooks are defined:
+--
+-- - 'postConf': Runs the generator to generate C++ and Haskell sources.
+combinedUserHooks :: ProjectConfig -> UserHooks
+combinedUserHooks project =
+  simpleUserHooks
+  { postConf = \args flags pkgDesc localBuildInfo -> do
+      let verbosity = fromFlagOrDefault normal $ configVerbosity flags
+
+      when (not $ null $ cppPackageName project) $
+        die' verbosity $
+        "combinedMain expects an empty cppPackageName, found \"" ++
+        cppPackageName project ++ "\"."
+
+      iface <- case interfaceResult project of
+        Left errorMsg ->
+          die' verbosity $
+          "Error initializing interface: " ++ errorMsg
+        Right iface -> return iface
+
+      genCpp project verbosity localBuildInfo iface
+      genHs verbosity localBuildInfo iface
+      postConf simpleUserHooks args flags pkgDesc localBuildInfo
+
+  , preBuild = \_ flags -> myHsPreHook project (fromFlagOrDefault normal $ buildVerbosity flags)
+  , preTest = \_ flags -> myHsPreHook project (fromFlagOrDefault normal $ testVerbosity flags)
+  , preCopy = \_ flags -> myHsPreHook project (fromFlagOrDefault normal $ copyVerbosity flags)
+  , preInst = \_ flags -> myHsPreHook project (fromFlagOrDefault normal $ installVerbosity flags)
+  , preReg = \_ flags -> myHsPreHook project (fromFlagOrDefault normal $ regVerbosity flags)
+  , preRepl = \_ flags -> myHsPreHook project (fromFlagOrDefault normal $ replVerbosity flags)
+  }
+
+  where genCpp :: ProjectConfig -> Verbosity -> LocalBuildInfo -> Interface -> IO ()
+        genCpp project verbosity localBuildInfo iface = do
+          (_, cppGenDir) <-
+            getAutogenAndCppGenDir project verbosity localBuildInfo
+          cppPackagedSourcesDir <- case cppPackagedSourcesLocation project of
+            Nothing -> return ""
+            Just subpath -> fmap (</> subpath) getCurrentDirectory
+          createDirectoryIfMissing True cppGenDir
+          createDirectoryIfMissing True $ buildDir localBuildInfo
+          _ <- run [iface]
+                   [ "--enum-eval-cache-mode", "refresh"
+                   , "--enum-eval-cache-path", buildDir localBuildInfo </> enumEvalCacheFileName
+                   , "--gen-cpp", cppGenDir, cppPackagedSourcesDir
+                   ]
+          return ()
+
+        genHs :: Verbosity -> LocalBuildInfo -> Interface -> IO ()
+        genHs verbosity localBuildInfo iface = do
+          (_, hsGenDir) <- getAutogenAndHsGenDir verbosity localBuildInfo
+          createDirectoryIfMissing True hsGenDir
+          _ <- run [iface]
+                   [ "--enum-eval-cache-mode", "must-exist"
+                   , "--enum-eval-cache-path", buildDir localBuildInfo </> enumEvalCacheFileName
+                   , "--gen-hs", hsGenDir
+                   ]
+          return ()
+
+        -- See also hsPreHook.
+        myHsPreHook :: ProjectConfig -> Verbosity -> IO HookedBuildInfo
+        myHsPreHook project verbosity = do
+          iface <- getInterface project verbosity
+          let moduleNames =
+                map (ModuleName.fromString . getModuleName iface) $
+                M.elems (interfaceModules iface)
+
+          -- Injected autogenerated modules here works for most commands, however sdist
+          -- is not hookable, so autogen-modules should still be written out manually in
+          -- your Cabal file!  (See Cabal issue #6180.)
+          return (Just emptyBuildInfo { autogenModules = moduleNames }, [])
+
 -- | A @main@ implementation to be used in the @Setup.hs@ of a C++ gateway
 -- package.
 --
@@ -212,25 +347,25 @@
   , buildHook = \pkgDesc localBuildInfo hooks flags -> do
       buildHook simpleUserHooks pkgDesc localBuildInfo hooks flags
       let verbosity = fromFlagOrDefault normal $ buildVerbosity flags
-      cppBuild verbosity localBuildInfo
+      cppBuild project verbosity localBuildInfo
 
   , copyHook = \pkgDesc localBuildInfo hooks flags -> do
       copyHook simpleUserHooks pkgDesc localBuildInfo hooks flags
       let verbosity = fromFlagOrDefault normal $ copyVerbosity flags
           dest = fromFlagOrDefault NoCopyDest $ copyDest flags
-      cppInstall verbosity pkgDesc localBuildInfo dest
+      cppInstall project verbosity pkgDesc localBuildInfo dest
 
   , instHook = \pkgDesc localBuildInfo hooks flags -> do
       instHook simpleUserHooks pkgDesc localBuildInfo hooks flags
       let verbosity = fromFlagOrDefault normal $ installVerbosity flags
           dest = maybe NoCopyDest CopyTo $
                  flagToMaybe $ installDistPref flags
-      cppInstall verbosity pkgDesc localBuildInfo dest
+      cppInstall project verbosity pkgDesc localBuildInfo dest
 
   , regHook = \pkgDesc localBuildInfo hooks flags -> do
       regHook simpleUserHooks pkgDesc localBuildInfo hooks flags
       let verbosity = fromFlagOrDefault normal $ regVerbosity flags
-      cppRegister verbosity localBuildInfo flags
+      cppRegister project verbosity localBuildInfo flags
 
   , cleanHook = \pkgDesc z hooks flags -> do
       cleanHook simpleUserHooks pkgDesc z hooks flags
@@ -241,17 +376,79 @@
 makeProgram :: Program
 makeProgram = simpleProgram "make"
 
+defaultLibComponentName :: ComponentName
+defaultLibComponentName =
+#if MIN_VERSION_Cabal(3,0,0)
+  CLibName LMainLibName
+#else
+  CLibName
+#endif
+
+-- | Locates the autogen directory for the library component.
+getAutogenDir :: Verbosity -> LocalBuildInfo -> IO FilePath
+getAutogenDir verbosity localBuildInfo = do
+  let libCLBIs = componentNameCLBIs localBuildInfo defaultLibComponentName
+#if MIN_VERSION_Cabal(2,0,0)
+      dieFn = die' verbosity
+#else
+      dieFn = die
+#endif
+
+  case libCLBIs of
+    [libCLBI] -> return $ autogenComponentModulesDir localBuildInfo libCLBI
+    _ ->
+      -- TODO Show interface name, and "C++" or "Haskell"?
+      dieFn $ concat
+      ["Expected one library ComponentLocalBuildInfo, found ",
+       show $ length libCLBIs, "."]
+
+getAutogenAndGenDir :: GenerateLocation
+                    -> Verbosity
+                    -> LocalBuildInfo
+                    -> IO (FilePath, FilePath)
+getAutogenAndGenDir genLoc verbosity localBuildInfo = do
+  autogenDir <- getAutogenDir verbosity localBuildInfo
+  case genLoc of
+    GenerateInAutogenDir subpath ->
+      return (autogenDir, autogenDir </> subpath)
+    GenerateInSourcesDir subpath -> do
+      curDir <- getCurrentDirectory
+      return (autogenDir, curDir </> subpath)
+
+getAutogenAndCppGenDir :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO (FilePath, FilePath)
+getAutogenAndCppGenDir project verbosity localBuildInfo =
+  getAutogenAndGenDir (cppGeneratedSourcesLocation project) verbosity localBuildInfo
+
+getAutogenAndHsGenDir :: Verbosity -> LocalBuildInfo -> IO (FilePath, FilePath)
+getAutogenAndHsGenDir verbosity localBuildInfo =
+  getAutogenAndGenDir (GenerateInAutogenDir "") verbosity localBuildInfo
+
+getCppDirEnvVars :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO [String]
+getCppDirEnvVars project verbosity localBuildInfo = do
+  (autogenDir, cppGenDir) <- getAutogenAndCppGenDir project verbosity localBuildInfo
+  return $
+    [ "HOPPY_AUTOGEN_DIR=" ++ autogenDir
+    , "HOPPY_CPP_GEN_DIR=" ++ cppGenDir
+    ] ++
+    (case cppPackagedSourcesLocation project of
+      Just "" -> ["HOPPY_CPP_PKG_DIR=."]
+      Just subpath -> ["HOPPY_CPP_PKG_DIR=" ++ subpath]
+      Nothing -> [])
+
 cppConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
 cppConfigure project verbosity localBuildInfo = do
   -- Invoke the generator to create C++ code.
-  let sourcesDir = cppSourcesDir project
+  (_, cppGenDir) <- getAutogenAndCppGenDir project verbosity localBuildInfo
+  cppPackagedSourcesDir <- case cppPackagedSourcesLocation project of
+    Nothing -> return ""
+    Just subpath -> fmap (</> subpath) getCurrentDirectory
   iface <- getInterface project verbosity
-  createDirectoryIfMissing True sourcesDir
+  createDirectoryIfMissing True cppGenDir
   createDirectoryIfMissing True $ buildDir localBuildInfo
   _ <- run [iface]
            [ "--enum-eval-cache-mode", "refresh"
            , "--enum-eval-cache-path", buildDir localBuildInfo </> enumEvalCacheFileName
-           , "--gen-cpp", sourcesDir
+           , "--gen-cpp", cppGenDir, cppPackagedSourcesDir
            ]
 
   -- When there is a configure script, then run it.
@@ -267,8 +464,8 @@
                    then Just $ simpleConfiguredProgram "configure" $ FoundOnSystem "./configure"
                    else Nothing
 
-cppBuild :: Verbosity -> LocalBuildInfo -> IO ()
-cppBuild verbosity localBuildInfo = do
+cppBuild :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
+cppBuild project verbosity localBuildInfo = do
   hasMakefile <- doesFileExist "Makefile"
   unless hasMakefile $
 #if MIN_VERSION_Cabal(2,0,0)
@@ -277,11 +474,19 @@
     die
 #endif
     "No Makefile found."
+
+  cppDirEnvVars <- getCppDirEnvVars project verbosity localBuildInfo
+
   let programDb = withPrograms localBuildInfo
-  runDbProgram verbosity makeProgram programDb []
+  runDbProgram verbosity makeProgram programDb cppDirEnvVars
 
-cppInstall :: Verbosity -> PackageDescription -> LocalBuildInfo -> CopyDest -> IO ()
-cppInstall verbosity pkgDesc localBuildInfo dest = do
+cppInstall :: ProjectConfig
+           -> Verbosity
+           -> PackageDescription
+           -> LocalBuildInfo
+           -> CopyDest
+           -> IO ()
+cppInstall project verbosity pkgDesc localBuildInfo dest = do
   hasMakefile <- doesFileExist "Makefile"
   unless hasMakefile $
 #if MIN_VERSION_Cabal(2,0,0)
@@ -293,7 +498,9 @@
   let programDb = withPrograms localBuildInfo
       libDir = libdir $ absoluteInstallDirs pkgDesc localBuildInfo dest
   createDirectoryIfMissing True libDir
-  runDbProgram verbosity makeProgram programDb ["install", "libdir=" ++ libDir]
+  cppDirEnvVars <- getCppDirEnvVars project verbosity localBuildInfo
+  runDbProgram verbosity makeProgram programDb $
+    ["install", "libdir=" ++ libDir] ++ cppDirEnvVars
 
   -- We're doing an old-style install, so copy the enum eval cache file from the
   -- build directory to the library directory where the Haskell side of the
@@ -305,8 +512,8 @@
                         enumEvalCacheFilePath
                         (libDir </> enumEvalCacheFileName)
 
-cppRegister :: Verbosity -> LocalBuildInfo -> RegisterFlags -> IO ()
-cppRegister verbosity localBuildInfo flags = do
+cppRegister :: ProjectConfig -> Verbosity -> LocalBuildInfo -> RegisterFlags -> IO ()
+cppRegister project verbosity localBuildInfo flags = do
   hasMakefile <- doesFileExist "Makefile"
   unless hasMakefile $
 #if MIN_VERSION_Cabal(2,0,0)
@@ -319,12 +526,25 @@
     let programDb = withPrograms localBuildInfo
         libDir = buildDir localBuildInfo
     createDirectoryIfMissing True libDir
-    runDbProgram verbosity makeProgram programDb ["install", "libdir=" ++ libDir]
+    cppDirEnvVars <- getCppDirEnvVars project verbosity localBuildInfo
+    runDbProgram verbosity makeProgram programDb $
+      ["install", "libdir=" ++ libDir] ++ cppDirEnvVars
 
 cppClean :: ProjectConfig -> Verbosity -> IO ()
 cppClean project verbosity = do
   iface <- getInterface project verbosity
-  _ <- run [iface] ["--clean-cpp", cppSourcesDir project]
+  -- We can remove generated sources if we wrote them to the source directory.
+  -- We don't have access to the autogen directory from this hook though.
+  -- Although, Cabal ought to remove the autogen directory already when
+  -- cleaning...
+  case cppGeneratedSourcesLocation project of
+    GenerateInAutogenDir _ -> return ()
+    GenerateInSourcesDir subpath -> do
+      cppPackagedSourcesDir <- case cppPackagedSourcesLocation project of
+        Nothing -> return ""
+        Just subpath -> fmap (</> subpath) getCurrentDirectory
+      _ <- run [iface] ["--clean-cpp", subpath, cppPackagedSourcesDir]
+      return ()
 
   hasMakefile <- doesFileExist "Makefile"
   unless hasMakefile $
@@ -335,7 +555,7 @@
 #endif
     "No Makefile found."
   make <- findSystemProgram verbosity "make"
-  runProgram verbosity make ["clean"]
+  runProgram verbosity make ["clean"]  -- No Hoppy directories passed in here!
 
 findSystemProgram :: Verbosity -> FilePath -> IO ConfiguredProgram
 findSystemProgram verbosity basename = do
@@ -383,17 +603,12 @@
       let verbosity = fromFlagOrDefault normal $ configVerbosity flags
       hsConfigure project verbosity localBuildInfo
 
-  , preBuild = \_ _ -> addLibDir
-  , preTest = \_ _ -> addLibDir
-  , preCopy = \_ _ -> addLibDir  -- Not sure if necessary, but doesn't hurt.
-  , preInst = \_ _ -> addLibDir  -- Not sure if necessary, but doesn't hurt.
-  , preReg = \_ _ -> addLibDir  -- Necessary.
-  , preRepl = \_ _ -> addLibDir -- Necessary.
-
-  , cleanHook = \pkgDesc z hooks flags -> do
-      cleanHook simpleUserHooks pkgDesc z hooks flags
-      let verbosity = fromFlagOrDefault normal $ cleanVerbosity flags
-      hsClean project verbosity
+  , preBuild = \_ flags -> hsPreHook project (fromFlagOrDefault normal $ buildVerbosity flags)
+  , preTest = \_ flags -> hsPreHook project (fromFlagOrDefault normal $ testVerbosity flags)
+  , preCopy = \_ flags -> hsPreHook project (fromFlagOrDefault normal $ copyVerbosity flags)
+  , preInst = \_ flags -> hsPreHook project (fromFlagOrDefault normal $ installVerbosity flags)
+  , preReg = \_ flags -> hsPreHook project (fromFlagOrDefault normal $ regVerbosity flags)
+  , preRepl = \_ flags -> hsPreHook project (fromFlagOrDefault normal $ replVerbosity flags)
   }
 
 hsCppLibDirFile :: FilePath
@@ -444,22 +659,30 @@
           writeFile hsCppLibDirFile libDir
 
         generateSources iface libDir = do
-          let sourcesDir = hsSourcesDir project
-          createDirectoryIfMissing True sourcesDir
+          (_, hsGenDir) <- getAutogenAndHsGenDir verbosity localBuildInfo
+          createDirectoryIfMissing True hsGenDir
           _ <- run [iface]
                    [ "--enum-eval-cache-mode", "must-exist"
                    , "--enum-eval-cache-path", libDir </> enumEvalCacheFileName
-                   , "--gen-hs", sourcesDir
+                   , "--gen-hs", hsGenDir
                    ]
           return ()
 
-addLibDir :: IO HookedBuildInfo
-addLibDir = do
+-- See also myHsPreHook.
+hsPreHook :: ProjectConfig -> Verbosity -> IO HookedBuildInfo
+hsPreHook project verbosity = do
+  iface <- getInterface project verbosity
+  let moduleNames =
+        map (ModuleName.fromString . getModuleName iface) $
+        M.elems (interfaceModules iface)
+
   libDir <- readFile hsCppLibDirFile
-  return (Just emptyBuildInfo {extraLibDirs = [libDir]}, [])
 
-hsClean :: ProjectConfig -> Verbosity -> IO ()
-hsClean project verbosity = do
-  iface <- getInterface project verbosity
-  _ <- run [iface] ["--clean-hs", hsSourcesDir project]
-  return ()
+  -- Injected autogenerated modules here works for most commands, however sdist
+  -- is not hookable, so autogen-modules should still be written out manually in
+  -- your Cabal file!  (See Cabal issue #6180.)
+  return (Just emptyBuildInfo
+          { autogenModules = moduleNames
+          , extraLibDirs = [libDir]
+          }
+         , [])
