diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
deleted file mode 100644
--- a/CHANGELOG.markdown
+++ /dev/null
@@ -1,7 +0,0 @@
-# Change log
-
-Autoexporter uses [Semantic Versioning][].
-The change log is available through the [releases on GitHub][].
-
-[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
-[releases on GitHub]: https://github.com/tfausak/autoexporter/releases
diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -1,7 +1,8 @@
-# [Autoexporter][]
+# Autoexporter
 
-[![Version badge][]][version]
-[![Build badge][]][build]
+[![Travis CI](https://travis-ci.org/tfausak/autoexporter.svg?branch=master)](https://travis-ci.org/tfausak/autoexporter)
+[![Hackage](https://img.shields.io/hackage/v/autoexporter)](https://hackage.haskell.org/package/autoexporter)
+[![Stackage](https://www.stackage.org/package/autoexporter/badge/nightly?label=stackage)](https://www.stackage.org/package/autoexporter)
 
 Autoexporter automatically re-exports Haskell modules.
 
@@ -63,9 +64,3 @@
   ```haskell
   {-# OPTIONS_GHC -F -pgmF autoexporter -optF --deep #-}
   ```
-
-[Autoexporter]: https://github.com/tfausak/autoexporter
-[Version badge]: https://www.stackage.org/package/autoexporter/badge/nightly?label=version
-[version]: https://www.stackage.org/nightly/package/autoexporter
-[Build badge]: https://travis-ci.org/tfausak/autoexporter.svg?branch=master
-[build]: https://travis-ci.org/tfausak/autoexporter
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-import qualified Distribution.Simple as Cabal
-
-main :: IO ()
-main = Cabal.defaultMain
diff --git a/autoexporter.cabal b/autoexporter.cabal
--- a/autoexporter.cabal
+++ b/autoexporter.cabal
@@ -1,58 +1,58 @@
-cabal-version: 1.12
+cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.33.0.
---
--- see: https://github.com/sol/hpack
---
--- hash: 984d7105fe5e1e9e1d0a652367e133918b821d074ea7d914f171334af3c402b4
+name: autoexporter
+version: 1.1.17
 
-name:           autoexporter
-version:        1.1.16
-synopsis:       Automatically re-export modules.
-description:    Autoexporter automatically re-exports modules.
-category:       Utility
-homepage:       https://github.com/tfausak/autoexporter#readme
-bug-reports:    https://github.com/tfausak/autoexporter/issues
-maintainer:     Taylor Fausak
-license:        MIT
-license-file:   LICENSE.markdown
-build-type:     Simple
-extra-source-files:
-    CHANGELOG.markdown
-    package.yaml
-    README.markdown
-    stack.yaml
+synopsis: Automatically re-export modules.
+description: Autoexporter automatically re-exports modules.
 
+build-type: Simple
+category: Utility
+extra-source-files: README.markdown
+license-file: LICENSE.markdown
+license: MIT
+maintainer: Taylor Fausak
+
 source-repository head
-  type: git
   location: https://github.com/tfausak/autoexporter
+  type: git
 
+common basics
+  default-language: Haskell2010
+  ghc-options:
+    -Weverything
+    -Wno-all-missed-specialisations
+    -Wno-implicit-prelude
+    -Wno-missing-exported-signatures
+    -Wno-missing-import-lists
+    -Wno-safe
+    -Wno-unsafe
+
+  if impl(ghc >= 8.8)
+    ghc-options:
+      -Wno-missing-deriving-strategies
+
+  if impl(ghc >= 8.10)
+    ghc-options:
+      -Wno-missing-safe-haskell-mode
+      -Wno-prepositive-qualified-module
+
 library
-  exposed-modules:
-      Autoexporter
-  other-modules:
-      Paths_autoexporter
-  hs-source-dirs:
-      library
-  ghc-options: -Weverything -Wno-implicit-prelude -Wno-safe -Wno-unsafe
+  import: basics
+
   build-depends:
-      Cabal >=1.24.0 && <1.25 || >=2.0.1 && <2.5 || >=3.0.0 && <3.3
-    , base >=4.9.0 && <4.15
-    , directory >=1.2.6 && <1.4
-    , filepath >=1.4.1 && <1.5
-  default-language: Haskell2010
+    base >= 4.9.0 && < 4.15
+    , Cabal >= 1.24.0 && < 1.25 || >= 2.0.1 && < 2.5 || >= 3.0.0 && < 3.3
+    , directory >= 1.2.6 && < 1.4
+    , filepath >= 1.4.1 && < 1.5
+  exposed-modules: Autoexporter
+  hs-source-dirs: src/lib
 
 executable autoexporter
-  main-is: Main.hs
-  other-modules:
-      Paths_autoexporter
-  hs-source-dirs:
-      executables
-  ghc-options: -Weverything -Wno-implicit-prelude -Wno-safe -Wno-unsafe
+  import: basics
+
   build-depends:
-      Cabal >=1.24.0 && <1.25 || >=2.0.1 && <2.5 || >=3.0.0 && <3.3
-    , autoexporter
-    , base >=4.9.0 && <4.15
-    , directory >=1.2.6 && <1.4
-    , filepath >=1.4.1 && <1.5
-  default-language: Haskell2010
+    base -any
+    , autoexporter -any
+  hs-source-dirs: src/exe
+  main-is: Main.hs
diff --git a/executables/Main.hs b/executables/Main.hs
deleted file mode 100644
--- a/executables/Main.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-import qualified Autoexporter
-
-main :: IO ()
-main = Autoexporter.defaultMain
diff --git a/library/Autoexporter.hs b/library/Autoexporter.hs
deleted file mode 100644
--- a/library/Autoexporter.hs
+++ /dev/null
@@ -1,122 +0,0 @@
--- | This package isn't really meant to be used as a library. It's typically
--- used as a GHC preprocessor, like so:
---
--- > {-# OPTIONS_GHC -F -pgmF autoexporter #-}
---
--- For more information, please see the README on GitHub:
--- <https://github.com/tfausak/autoexporter#readme>.
-module Autoexporter
-  ( defaultMain
-  , mainWithArgs
-  , autoexport
-  , findFiles
-  , findFilesDeep
-  , makeModuleName
-  , takeWhileEnd
-  , isModuleName
-  , parseModuleName
-  , makeOutput
-  , isHaskellFile
-  , renderModule
-  , unlines'
-  , renderExport
-  , renderImport
-  ) where
-
-import qualified Data.List as List
-import qualified Data.Maybe as Maybe
-import qualified Data.Traversable as Traversable
-import qualified Distribution.ModuleName as ModuleName
-import qualified Distribution.Text as Text
-import qualified System.Directory as Directory
-import qualified System.Environment as Environment
-import qualified System.FilePath as FilePath
-
-data ExportScope = ExportScopeShallow
-                 | ExportScopeDeep
-
-defaultMain :: IO ()
-defaultMain = do
-  args <- Environment.getArgs
-  mainWithArgs args
-
-mainWithArgs :: [String] -> IO ()
-mainWithArgs args =
-  case args of
-    [name, inputFile, outputFile, "--deep"] ->
-      autoexport ExportScopeDeep name inputFile outputFile
-    [name, inputFile, outputFile] ->
-      autoexport ExportScopeShallow name inputFile outputFile
-    _ -> fail ("unexpected arguments: " ++ show args)
-
-autoexport :: ExportScope -> String -> FilePath -> FilePath -> IO ()
-autoexport exportScope name inputFile outputFile = do
-  let moduleName = makeModuleName name
-  let directory = FilePath.dropExtension inputFile
-  files <- findFiles exportScope directory
-  let output = makeOutput moduleName directory files
-  writeFile outputFile output
-
-findFiles :: ExportScope -> FilePath -> IO [FilePath]
-findFiles exportScope dir =
-  case exportScope of
-    ExportScopeShallow ->
-      fmap (filter isHaskellFile) (Directory.listDirectory dir)
-    ExportScopeDeep ->
-      findFilesDeep dir
-
-findFilesDeep :: FilePath -> IO [FilePath]
-findFilesDeep dir = do
-  rootItems <- Directory.listDirectory dir
-  childItems <- Traversable.for rootItems $ \item -> do
-    let path = dir FilePath.</> item
-    dirExists <- Directory.doesDirectoryExist path
-    if dirExists
-      then fmap (fmap (item FilePath.</>)) (findFilesDeep path)
-      else pure []
-  pure $ mconcat (filter isHaskellFile rootItems : childItems)
-
-makeModuleName :: FilePath -> String
-makeModuleName name =
-  let path = FilePath.dropExtension name
-      parts = FilePath.splitDirectories path
-      rest = takeWhileEnd isModuleName parts
-  in List.intercalate "." rest
-
-takeWhileEnd :: (a -> Bool) -> [a] -> [a]
-takeWhileEnd p xs = reverse (takeWhile p (reverse xs))
-
-isModuleName :: String -> Bool
-isModuleName x = Maybe.isJust (parseModuleName x)
-
-parseModuleName :: String -> Maybe ModuleName.ModuleName
-parseModuleName = Text.simpleParse
-
-makeOutput :: String -> FilePath -> [FilePath] -> String
-makeOutput moduleName directory files =
-  let haskellFiles = filter isHaskellFile files
-      paths = map (directory FilePath.</>) haskellFiles
-      modules = List.sort (map makeModuleName paths)
-  in renderModule moduleName modules
-
-isHaskellFile :: FilePath -> Bool
-isHaskellFile x = List.isSuffixOf ".hs" x || List.isSuffixOf ".lhs" x
-
-renderModule :: String -> [String] -> String
-renderModule name modules =
-  unlines'
-    [ "{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-unused-imports #-}"
-    , unwords ["module", name, "("]
-    , unlines' (map renderExport modules)
-    , ") where"
-    , unlines' (map renderImport modules)
-    ]
-
-unlines' :: [String] -> String
-unlines' = List.intercalate "\n"
-
-renderExport :: String -> String
-renderExport x = "  module " ++ x ++ ","
-
-renderImport :: String -> String
-renderImport x = "import " ++ x
diff --git a/package.yaml b/package.yaml
deleted file mode 100644
--- a/package.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: autoexporter
-version: 1.1.16
-
-category: Utility
-description: Autoexporter automatically re-exports modules.
-extra-source-files:
-  - CHANGELOG.markdown
-  - package.yaml
-  - README.markdown
-  - stack.yaml
-github: tfausak/autoexporter
-license-file: LICENSE.markdown
-license: MIT
-maintainer: Taylor Fausak
-synopsis: Automatically re-export modules.
-
-dependencies:
-  base: '>= 4.9.0 && < 4.15'
-  Cabal: '>= 1.24.0 && < 1.25 || >= 2.0.1 && < 2.5 || >= 3.0.0 && < 3.3'
-  directory: '>= 1.2.6 && < 1.4'
-  filepath: '>= 1.4.1 && < 1.5'
-ghc-options:
-  - -Weverything
-  - -Wno-implicit-prelude
-  - -Wno-safe
-  - -Wno-unsafe
-
-library:
-  source-dirs: library
-
-executable:
-    dependencies:
-      autoexporter: -any
-    main: Main.hs
-    source-dirs: executables
diff --git a/src/exe/Main.hs b/src/exe/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/exe/Main.hs
@@ -0,0 +1,4 @@
+import qualified Autoexporter
+
+main :: IO ()
+main = Autoexporter.defaultMain
diff --git a/src/lib/Autoexporter.hs b/src/lib/Autoexporter.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Autoexporter.hs
@@ -0,0 +1,122 @@
+-- | This package isn't really meant to be used as a library. It's typically
+-- used as a GHC preprocessor, like so:
+--
+-- > {-# OPTIONS_GHC -F -pgmF autoexporter #-}
+--
+-- For more information, please see the README on GitHub:
+-- <https://github.com/tfausak/autoexporter#readme>.
+module Autoexporter
+  ( defaultMain
+  , mainWithArgs
+  , autoexport
+  , findFiles
+  , findFilesDeep
+  , makeModuleName
+  , takeWhileEnd
+  , isModuleName
+  , parseModuleName
+  , makeOutput
+  , isHaskellFile
+  , renderModule
+  , unlines'
+  , renderExport
+  , renderImport
+  ) where
+
+import qualified Data.List as List
+import qualified Data.Maybe as Maybe
+import qualified Data.Traversable as Traversable
+import qualified Distribution.ModuleName as ModuleName
+import qualified Distribution.Text as Text
+import qualified System.Directory as Directory
+import qualified System.Environment as Environment
+import qualified System.FilePath as FilePath
+
+data ExportScope = ExportScopeShallow
+                 | ExportScopeDeep
+
+defaultMain :: IO ()
+defaultMain = do
+  args <- Environment.getArgs
+  mainWithArgs args
+
+mainWithArgs :: [String] -> IO ()
+mainWithArgs args =
+  case args of
+    [name, inputFile, outputFile, "--deep"] ->
+      autoexport ExportScopeDeep name inputFile outputFile
+    [name, inputFile, outputFile] ->
+      autoexport ExportScopeShallow name inputFile outputFile
+    _ -> fail ("unexpected arguments: " ++ show args)
+
+autoexport :: ExportScope -> String -> FilePath -> FilePath -> IO ()
+autoexport exportScope name inputFile outputFile = do
+  let moduleName = makeModuleName name
+  let directory = FilePath.dropExtension inputFile
+  files <- findFiles exportScope directory
+  let output = makeOutput moduleName directory files
+  writeFile outputFile output
+
+findFiles :: ExportScope -> FilePath -> IO [FilePath]
+findFiles exportScope dir =
+  case exportScope of
+    ExportScopeShallow ->
+      fmap (filter isHaskellFile) (Directory.listDirectory dir)
+    ExportScopeDeep ->
+      findFilesDeep dir
+
+findFilesDeep :: FilePath -> IO [FilePath]
+findFilesDeep dir = do
+  rootItems <- Directory.listDirectory dir
+  childItems <- Traversable.for rootItems $ \item -> do
+    let path = dir FilePath.</> item
+    dirExists <- Directory.doesDirectoryExist path
+    if dirExists
+      then fmap (fmap (item FilePath.</>)) (findFilesDeep path)
+      else pure []
+  pure $ mconcat (filter isHaskellFile rootItems : childItems)
+
+makeModuleName :: FilePath -> String
+makeModuleName name =
+  let path = FilePath.dropExtension name
+      parts = FilePath.splitDirectories path
+      rest = takeWhileEnd isModuleName parts
+  in List.intercalate "." rest
+
+takeWhileEnd :: (a -> Bool) -> [a] -> [a]
+takeWhileEnd p xs = reverse (takeWhile p (reverse xs))
+
+isModuleName :: String -> Bool
+isModuleName x = Maybe.isJust (parseModuleName x)
+
+parseModuleName :: String -> Maybe ModuleName.ModuleName
+parseModuleName = Text.simpleParse
+
+makeOutput :: String -> FilePath -> [FilePath] -> String
+makeOutput moduleName directory files =
+  let haskellFiles = filter isHaskellFile files
+      paths = map (directory FilePath.</>) haskellFiles
+      modules = List.sort (map makeModuleName paths)
+  in renderModule moduleName modules
+
+isHaskellFile :: FilePath -> Bool
+isHaskellFile x = List.isSuffixOf ".hs" x || List.isSuffixOf ".lhs" x
+
+renderModule :: String -> [String] -> String
+renderModule name modules =
+  unlines'
+    [ "{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-unused-imports #-}"
+    , unwords ["module", name, "("]
+    , unlines' (map renderExport modules)
+    , ") where"
+    , unlines' (map renderImport modules)
+    ]
+
+unlines' :: [String] -> String
+unlines' = List.intercalate "\n"
+
+renderExport :: String -> String
+renderExport x = "  module " ++ x ++ ","
+
+renderImport :: String -> String
+renderImport x = "import " ++ x
diff --git a/stack.yaml b/stack.yaml
deleted file mode 100644
--- a/stack.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-resolver: lts-15.4
