diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,10 @@
 # Changelog for `proto-lens-protoc`
 
+## v0.3.1.1
+- Fix management of generated files between Cabal components (#171).
+- Bump the lower bound on `base` to indicate we require `ghc>=8.0`.
+- Use autogen-modules. (#196)
+
 ## v0.3.1.0
 - Bump the dependency on `base` for `ghc-8.4.2`.
 - Bump the dependency to `Cabal-2.2.*`.
diff --git a/proto-lens-protoc.cabal b/proto-lens-protoc.cabal
--- a/proto-lens-protoc.cabal
+++ b/proto-lens-protoc.cabal
@@ -1,11 +1,11 @@
--- This file has been generated from package.yaml by hpack version 0.20.0.
+-- This file has been generated from package.yaml by hpack version 0.28.2.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: b5e426f5c980c968df8a4126b27017a59590920178c0bb0902b138cf963d6282
+-- hash: a69cd6e6704df4e3fa208607e7488ad6ff71860a2b10efeb7d46ab1e5e645c63
 
 name:           proto-lens-protoc
-version:        0.3.1.0
+version:        0.3.1.1
 synopsis:       Protocol buffer compiler for the proto-lens library.
 description:    Turn protocol buffer files (.proto) into Haskell files (.hs) which can be used with the proto-lens package.
                 The library component of this package contains compiler code (namely Data.ProtoLens.Compiler.*) is not guaranteed to have stable APIs.'
@@ -18,8 +18,7 @@
 license:        BSD3
 license-file:   LICENSE
 build-type:     Simple
-cabal-version:  >= 1.21
-
+cabal-version:  >= 1.22
 extra-source-files:
     Changelog.md
 
@@ -34,24 +33,6 @@
   default: False
 
 library
-  hs-source-dirs:
-      src
-  build-depends:
-      Cabal >=1.22 && <2.3
-    , base >=4.8 && <4.12
-    , bytestring ==0.10.*
-    , containers ==0.5.*
-    , data-default-class >=0.0 && <0.2
-    , deepseq ==1.4.*
-    , directory >=1.2 && <1.4
-    , filepath >=1.4 && <1.6
-    , haskell-src-exts >=1.17 && <1.21
-    , lens-family ==1.2.*
-    , lens-labels ==0.2.*
-    , pretty ==1.1.*
-    , process >=1.2 && <1.7
-    , proto-lens ==0.3.*
-    , text ==1.2.*
   exposed-modules:
       Data.ProtoLens.Compiler.Combinators
       Data.ProtoLens.Compiler.Definitions
@@ -77,14 +58,35 @@
       Lens.Labels as Data.ProtoLens.Reexport.Lens.Labels,
       Lens.Labels.Prism as Data.ProtoLens.Reexport.Lens.Labels.Prism,
       Text.Read as Data.ProtoLens.Reexport.Text.Read
+  hs-source-dirs:
+      src
+  build-depends:
+      Cabal >=1.22 && <2.3
+    , base >=4.9 && <4.12
+    , bytestring ==0.10.*
+    , containers ==0.5.*
+    , data-default-class >=0.0 && <0.2
+    , deepseq ==1.4.*
+    , directory >=1.2 && <1.4
+    , filepath >=1.4 && <1.6
+    , haskell-src-exts >=1.17 && <1.21
+    , lens-family ==1.2.*
+    , lens-labels ==0.2.*
+    , pretty ==1.1.*
+    , process >=1.2 && <1.7
+    , proto-lens ==0.3.*
+    , temporary ==1.2.*
+    , text ==1.2.*
   default-language: Haskell2010
 
 executable proto-lens-protoc
   main-is: protoc-gen-haskell.hs
+  other-modules:
+      Paths_proto_lens_protoc
   hs-source-dirs:
       app
   build-depends:
-      base >=4.8 && <4.12
+      base >=4.9 && <4.12
     , bytestring ==0.10.*
     , containers ==0.5.*
     , data-default-class >=0.0 && <0.2
@@ -95,6 +97,4 @@
     , proto-lens ==0.3.*
     , proto-lens-protoc
     , text ==1.2.*
-  other-modules:
-      Paths_proto_lens_protoc
   default-language: Haskell2010
diff --git a/src/Data/ProtoLens/Setup.hs b/src/Data/ProtoLens/Setup.hs
--- a/src/Data/ProtoLens/Setup.hs
+++ b/src/Data/ProtoLens/Setup.hs
@@ -26,12 +26,11 @@
 
 import Control.DeepSeq (force)
 import Control.Monad (filterM, forM_, guard, when)
-#if MIN_VERSION_Cabal(2,0,0)
-import qualified Data.Map as Map
-#endif
 import qualified Data.ByteString as BS
+import qualified Data.Map as Map
 import Data.Maybe (maybeToList)
 import qualified Data.Set as Set
+import qualified Data.Text as T
 import Distribution.ModuleName (ModuleName)
 import qualified Distribution.ModuleName as ModuleName
 import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo
@@ -56,6 +55,7 @@
     ( LocalBuildInfo(..)
     , absoluteInstallDirs
     , ComponentName(..)
+    , ComponentLocalBuildInfo
     , componentPackageDeps
 #if MIN_VERSION_Cabal(2,0,0)
     , allComponentsInBuildOrder
@@ -78,6 +78,7 @@
 import Distribution.Verbosity (Verbosity)
 import System.FilePath
     ( (</>)
+    , (<.>)
     , equalFilePath
     , isRelative
     , makeRelative
@@ -85,14 +86,15 @@
     , takeExtension
     )
 import System.Directory
-    ( createDirectoryIfMissing
+    ( copyFile
+    , createDirectoryIfMissing
     , doesDirectoryExist
     , doesFileExist
     , findExecutable
     , removeDirectoryRecursive
-    , renameFile
     )
 import System.IO (hPutStrLn, stderr)
+import System.IO.Temp (withSystemTempDirectory)
 import System.Process (callProcess)
 
 import qualified Data.ProtoLens.Compiler.Plugin as Plugin
@@ -158,15 +160,11 @@
     getProtos l = do
       -- Replicate Cabal's own logic for parsing file globs.
       files <- concat <$> mapM matchFileGlob (extraSrcFiles $ localPkgDescr l)
-      let activeModules = Set.fromList $ collectActiveModules l
-      pure . filter (\f -> relativeFileToProtoModule f
-                            `Set.member` activeModules)
+      pure
            . filter (\f -> takeExtension f == ".proto")
            . map (makeRelative root)
            . filter (isSubdirectoryOf root)
            $ files
-    relativeFileToProtoModule
-        = ModuleName.fromString . Plugin.moduleNameStr "Proto"
 
 
 -- | Augment the given 'UserHooks' to auto-generate Haskell files from the
@@ -187,11 +185,16 @@
     { buildHook = \p l h f -> generate l >> buildHook hooks p l h f
     , haddockHook = \p l h f -> generate l >> haddockHook hooks p l h f
     , replHook = \p l h f args -> generate l >> replHook hooks p l h f args
+#if !MIN_VERSION_Cabal(2,0,0)
+    -- Older versions of Cabal don't support the autogen-modules field.
+    -- Work around it by manually generating the modules and putting them
+    -- in a place where `cabal sdist` will pick them up.
     , sDistHook = \p maybe_l h f -> case maybe_l of
             Nothing -> error "Can't run protoc; run 'cabal configure' first."
             Just l -> do
                         generate l
                         sDistHook hooks (fudgePackageDesc l p) maybe_l h f
+#endif
     , postCopy = \a flags pkg lbi -> do
                   let verb = fromFlag $ copyVerbosity flags
                   let destDir = datadir (absoluteInstallDirs pkg lbi
@@ -204,52 +207,70 @@
     generate l = getProtos l >>= generateSources root l
 
 -- | Generate Haskell source files for the given input .proto files.
+--
+-- Process all the proto files that are referenced in the exposed-modules
+-- or other-modules of some "active" component, and write them all to a
+-- single temporary directory.  (For example, passing --no-enable-tests
+-- makes all test-suite components inactive.)
+--
+-- Then, for each active component, copy the corresponding module files
+-- over to its specific autogen directory (if Cabal-2.*) or to the global
+-- autogen directory (if Cabal-1.*).  However, don't actually do the copy
+-- if it's the same as what's already there.  This way, we don't needlessly
+-- touch the generated .hs files when nothing changes, and thus don't
+-- needlessly make GHC recompile them (as it considers their modification
+-- times for that).
 generateSources :: FilePath -- ^ The root directory
                 -> LocalBuildInfo
                 -> [FilePath] -- ^ Proto files relative to the root directory.
                 -> IO ()
-generateSources _ _ [] = return ()
-generateSources root l files = do
+generateSources root l files = withSystemTempDirectory "protoc-out" $ \tmpDir -> do
     -- Collect import paths from build-depends of this package.
     importDirs <- filterM doesDirectoryExist
                      [ InstalledPackageInfo.dataDir info </> protoLensImportsPrefix
                      | info <- collectDeps l
                      ]
-    -- Generate .hs files into a temporary directory, then move them over
-    -- to the target (autogen) directory only if they are different from
-    -- what's already there. This way, we don't needlessly touch the generated
-    -- .hs files when nothing changes, and thus don't needlessly make GHC
-    -- recompile them (as it considers their modification times for that).
-    let tmpAutogenModulesDir = autogenModulesDir l ++ "-protoc-tmpOutDir"
-    -- Generate .hs files into temp dir.
-    generateProtosWithImports (root : importDirs) tmpAutogenModulesDir
+    -- Generate .hs files for all active components into a single temporary
+    -- directory.
+    let activeModules = collectActiveModules l
+    let allModules = Set.fromList . concat . map snd $ activeModules
+    let usedInComponent f = ModuleName.fromString (Plugin.moduleNameStr "Proto" f)
+                          `Set.member` allModules
+    generateProtosWithImports (root : importDirs) tmpDir
                               -- Applying 'root </>' does nothing if the path is already
                               -- absolute.
-                              (map (root </>) files)
-    -- Discover generated files.
-    -- `getDirectoryContentsRecursive` is lazy IO, so we `force` through
-    -- the list to make it strict hereinafter.
-    !generatedFiles <- force <$> getDirectoryContentsRecursive tmpAutogenModulesDir
-    -- Move files to autogen dir only if file contents are different.
-    forM_ generatedFiles $ \pathRelativeToTmpDir -> do
-        let sourcePath = tmpAutogenModulesDir </> pathRelativeToTmpDir
-        let targetPath = autogenModulesDir l </> pathRelativeToTmpDir
-        identical <- do
-            targetExists <- doesFileExist targetPath
-            if not targetExists
-                then return False
-                else do
-                    -- This could be done in a streaming fashion,
-                    -- but since the .hs files usually easily fit
-                    -- into RAM, this is OK.
-                    sourceContents <- BS.readFile sourcePath
-                    targetContents <- BS.readFile targetPath
-                    return (sourceContents == targetContents)
-        -- Do the move if necessary.
-        when (not identical) $ do
-            createDirectoryIfMissing True (takeDirectory targetPath)
-            renameFile sourcePath targetPath
+                              $ map (root </>) $ filter usedInComponent files
+    -- Copy each active component's files over to its autogen directory, but
+    -- only if they've changed since last time.
+    forM_ activeModules $ \(compBI, mods) -> forM_ mods $ \m -> do
+          let f = T.unpack (Plugin.outputFilePath $ ModuleName.toFilePath m)
+          let sourcePath = tmpDir </> f
+          sourceExists <- doesFileExist sourcePath
+          when sourceExists $ do
+            let dest = autogenComponentModulesDir l compBI </> f
+            copyIfDifferent sourcePath dest
 
+-- Note: we do a copy rather than a move since a given module may be used in
+-- more than one component.
+copyIfDifferent :: FilePath -> FilePath -> IO ()
+copyIfDifferent sourcePath targetPath = do
+    targetExists <- doesFileExist targetPath
+    identical <- do
+        if not targetExists
+            then return False
+            else do
+                -- This could be done in a streaming fashion,
+                -- but since the .hs files usually easily fit
+                -- into RAM, this is OK.
+                sourceContents <- BS.readFile sourcePath
+                targetContents <- BS.readFile targetPath
+                return (sourceContents == targetContents)
+    -- Do the move if necessary.
+    when (not identical) $ do
+        createDirectoryIfMissing True (takeDirectory targetPath)
+        copyFile sourcePath targetPath
+
+
 -- | Copy each .proto file into the installed "data-dir" path,
 -- so that it can be included by other packages that depend on this one.
 copyProtosToDataDir :: Verbosity
@@ -281,22 +302,30 @@
 fudgePackageDesc :: LocalBuildInfo -> PackageDescription -> PackageDescription
 fudgePackageDesc lbi p = p
     { library =
-        (\lib -> lib { libBuildInfo = fudgeBuildInfo (libBuildInfo lib) })
+        (\lib -> lib { libBuildInfo = fudgeBuildInfo CLibName $ libBuildInfo lib })
             <$> library p
     , executables =
-        (\exe -> exe { buildInfo = fudgeBuildInfo (buildInfo exe) })
+        (\exe -> exe { buildInfo = fudgeBuildInfo (CExeName $ exeName exe)
+                                        $ buildInfo exe })
             <$> executables p
     , testSuites =
-        (\test -> test { testBuildInfo = fudgeBuildInfo (testBuildInfo test) })
+        (\test -> test { testBuildInfo = fudgeBuildInfo (CTestName $ testName test)
+                                            $ testBuildInfo test })
             <$> testSuites p
     , benchmarks =
         (\bench -> bench { benchmarkBuildInfo =
-                              fudgeBuildInfo (benchmarkBuildInfo bench) })
+                              fudgeBuildInfo (CBenchName $ benchmarkName bench)
+                                  $ benchmarkBuildInfo bench })
             <$> benchmarks p
     }
   where
-    fudgeBuildInfo bi =
-        bi { hsSourceDirs = autogenModulesDir lbi : hsSourceDirs bi }
+    comps = allComponents lbi
+    fudgeBuildInfo n bi
+        | Just compLBI <- Map.lookup n comps
+            = bi { hsSourceDirs = autogenComponentModulesDir lbi compLBI
+                                    : hsSourceDirs bi }
+        | otherwise = bi -- Could happen if a component isn't active; try
+                         -- anyway and see whether Cabal complains later on.
 
 -- | Returns whether the @root@ is a parent folder of @f@.
 isSubdirectoryOf :: FilePath -> FilePath -> Bool
@@ -331,6 +360,7 @@
     -> FilePath -- ^ The output directory for the generated Haskell files.
     -> [FilePath] -- ^ The .proto files to process.
     -> IO ()
+generateProtosWithImports _ _ [] = return ()
 generateProtosWithImports imports output files = do
     protoLensProtoc
         <- findExecutableOrDie "proto-lens-protoc"
@@ -366,25 +396,22 @@
 -- | Collect all the module names that we need to build.
 -- For example: only include test-suites if we're building with tests enabled
 -- (e.g., `stack test` vs `stack build`).
-collectActiveModules :: LocalBuildInfo -> [ModuleName]
-collectActiveModules l = let
-    in (activeLib >>= exposedModules)
-        ++ concatMap otherModules
-            (concat
-                [ libBuildInfo <$> activeLib
-                , buildInfo <$> activeExes
-                , testBuildInfo <$> activeTests
-                , benchmarkBuildInfo <$> activeBenchmarks
-                ])
+collectActiveModules
+    :: LocalBuildInfo -> [(ComponentLocalBuildInfo, [ModuleName])]
+collectActiveModules l = map (\(n, l) -> (l, f n)) $ Map.toList $ allComponents l
   where
     p = localPkgDescr l
-    activeLib = guard (active CLibName) >> maybeToList (library p)
-    activeExes = filter (active . CExeName . exeName) $ executables p
-    activeTests = filter (active . CTestName . testName) $ testSuites p
-    activeBenchmarks = filter (active . CBenchName . benchmarkName)
-                          $ benchmarks p
-    comps = Set.fromList $ allComponentNames l
-    active = (`Set.member` comps)
+    f CLibName = maybeToList (library p) >>= \l -> exposedModules l
+                                                    ++ otherModules (libBuildInfo l)
+    f (CExeName n) = otherModules . buildInfo $ exes Map.! n
+    f (CTestName n) = otherModules . testBuildInfo $ tests Map.! n
+    f (CBenchName n) = otherModules . benchmarkBuildInfo $ benchs Map.! n
+#if MIN_VERSION_Cabal(2,0,0)
+    f _ = []  -- TODO: other lib kinds; for now just suppress the warning
+#endif
+    exes = Map.fromList [(exeName e, e) | e <- executables p]
+    tests = Map.fromList [(testName e, e) | e <- testSuites p]
+    benchs = Map.fromList [(benchmarkName e, e) | e <- benchmarks p]
 
 -------------------------------------------------------
 -- Compatibility layer between Cabal-1.* and Cabal-2.*
@@ -405,21 +432,25 @@
 #endif
 
 -- | All the components that will be built by this Cabal command.
-allComponentNames :: LocalBuildInfo -> [ComponentName]
+allComponents :: LocalBuildInfo -> Map.Map ComponentName ComponentLocalBuildInfo
 #if MIN_VERSION_Cabal(2,0,0)
-allComponentNames l = Map.keys $ componentNameMap l
+allComponents l = fmap requireOne $ componentNameMap l
+  where
+    -- TODO: this doesn't support Backpack, which can have more than one
+    -- ComponentLocalBuildInfo associated with a name.
+    requireOne [x] = x
+    requireOne xs = error $ "Data.ProtoLens.Setup.allComponents: expected one "
+                          ++ "component per name, got " ++ show xs
+
 #else
-allComponentNames l = [c | (c, _, _) <- componentsConfigs l]
+allComponents l = Map.fromList [(c, b) | (c, b, _) <- componentsConfigs l]
 #endif
 
--- | Get the package-level "autogen" directory where we're putting the
--- generated .hs files.  (The 'BuildPaths.autogenModulesDir' file was
--- deprecated in Cabal-2.0 in favor of module-specific directories
--- (@autogenComponentModulesDir@), but our setup currently needs the more
--- global location.)
-autogenModulesDir :: LocalBuildInfo -> FilePath
+-- | Get the component-level "autogen" directory where we're putting the
+-- generated .hs files.  (For Cabal-1.0, use the shared 'BuildPaths.autogenModulesDir'.)
+autogenComponentModulesDir :: LocalBuildInfo -> ComponentLocalBuildInfo -> FilePath
 #if MIN_VERSION_Cabal(2,0,0)
-autogenModulesDir = BuildPaths.autogenPackageModulesDir
+autogenComponentModulesDir = BuildPaths.autogenComponentModulesDir
 #else
-autogenModulesDir = BuildPaths.autogenModulesDir
+autogenComponentModulesDir lbi _ = BuildPaths.autogenModulesDir lbi
 #endif
