diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,16 @@
+# Changelog for horizon-gen-nix
+
+## v0.2
+
+* Support horizon-spec-0.2
+* horizon-gen-nix now supports two modes with several command line options.
+  * Running in `make-package-set`mode will by default create a file called `inital-packages.nix` suitable for use with the `makePackageSet` function from nixpkgs.
+  * Running in `overlay` mode will by default create a file called `overlay.nix` containing an overlay suitable for use with `haskell.packages.${compiler}.override`.
+
+## v0.1
+
+* Support horizon-spec-0.1
+
+## v0.0.0.1
+
+* Generate nix expressions for haskell packages from a dhall manifest.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright 2022 Homotopic.Tech Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# horizon-gen-nix
+
+horizon-gen-nix is an executable for generating nix expressions
+from [horizon-spec](https://hackage.haskell.org/package/horizon-spec)
+dhall definitions.
+
+```
+Usage: horizon-gen-nix [--input-file INPUT_FILE] [--packages-dir PACKAGES_DIR]
+                       COMMAND
+
+Available options:
+  -h,--help                Show this help text
+  --input-file INPUT_FILE  The name of the input file.
+  --packages-dir PACKAGES_DIR
+                           The directory to put haskell packages.
+
+Available commands:
+  make-package-set         Run in MakePackageSet Mode
+  overlay                  Run in Overlay Mode
+```
+
+## Building
+
+```
+nix build
+```
+
+## Development
+
+```
+nix develop
+```
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import qualified Horizon.Gen.Nix (main)
+
+main :: IO ()
+main = Horizon.Gen.Nix.main
diff --git a/horizon-gen-nix.cabal b/horizon-gen-nix.cabal
new file mode 100644
--- /dev/null
+++ b/horizon-gen-nix.cabal
@@ -0,0 +1,100 @@
+cabal-version:      3.0
+name:               horizon-gen-nix
+version:            0.2
+synopsis:           Generate nix expressions from horizon-spec definitions
+description:        Generate nix expressions from horizon-spec definitions
+category:           Package Management, Nix
+author:             Daniel Firth
+maintainer:         dan.firth@homotopic.tech
+copyright:          2022 Daniel Firth
+license:            MIT
+license-file:       LICENSE
+build-type:         Simple
+extra-source-files:
+  ChangeLog.md
+  README.md
+
+source-repository head
+  type:     git
+  location: https://gitlab.homotopic.tech/horizon/horizon-gen-nix
+
+library
+  exposed-modules:
+    Horizon.Gen.Nix
+    Horizon.Gen.Nix.Cabal2Nix.Options
+    Horizon.Gen.Nix.Options
+    Horizon.Gen.Nix.Pretty
+    Horizon.Gen.Nix.Types.HorizonMode
+    Horizon.Gen.Nix.Types.OverlayFile
+    Horizon.Gen.Nix.Types.PackagesDirectory
+    Horizon.Gen.Nix.Types.PackageSetFile
+    Horizon.Gen.Nix.Writers
+
+  hs-source-dirs:     src
+  default-extensions:
+    DataKinds
+    DerivingStrategies
+    GADTs
+    StandaloneKindSignatures
+    TypeApplications
+
+  ghc-options:
+    -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude
+    -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module
+    -Wno-safe -Wno-unsafe
+
+  build-depends:
+    , base                  >=4.7 && <5
+    , Cabal-syntax
+    , cabal2nix
+    , containers
+    , dhall
+    , directory
+    , distribution-nixpkgs
+    , either
+    , horizon-spec          >=0.2 && <0.3
+    , language-nix
+    , lens
+    , optparse-applicative
+    , path
+    , path-dhall-instance
+    , pretty
+    , silently
+    , text
+
+  default-language:   Haskell2010
+
+executable horizon-gen-nix
+  main-is:            Main.hs
+  hs-source-dirs:     app
+  default-extensions:
+    DataKinds
+    DerivingStrategies
+
+  ghc-options:
+    -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude
+    -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module
+    -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N
+
+  build-depends:
+    , base             >=4.7 && <5
+    , horizon-gen-nix
+
+  default-language:   GHC2021
+
+executable horizon-gen-nix-tests
+  main-is:          Spec.hs
+  hs-source-dirs:   test
+  ghc-options:
+    -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude
+    -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module
+    -Wno-safe -Wno-unsafe
+
+  build-depends:
+    , base             >=4.7 && <5
+    , directory
+    , horizon-gen-nix
+    , path
+    , sydtest
+
+  default-language: Haskell2010
diff --git a/src/Horizon/Gen/Nix.hs b/src/Horizon/Gen/Nix.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE RecordWildCards #-}
+module Horizon.Gen.Nix (
+    main
+  , horizonGenNix
+  , makeMakePackageSet
+  , makeOverlay
+  ) where
+
+import           Dhall                                   (auto, inputFile)
+import           Horizon.Gen.Nix.Options                 (HorizonCommand (MakePackageSetCommand, OverlayCommand),
+                                                          HorizonOptions (MkHorizonOptions),
+                                                          MakePackageSetOptions,
+                                                          OverlayOptions,
+                                                          fromInputFile,
+                                                          horizonGenNixOptsInfo,
+                                                          optHorizonCommand,
+                                                          optInputFile,
+                                                          optOverlayFile,
+                                                          optPackageSetFile,
+                                                          optPackagesDirectory)
+import           Horizon.Gen.Nix.Types.PackagesDirectory (PackagesDirectory)
+import           Horizon.Gen.Nix.Writers                 (writeHaskellPackages,
+                                                          writeMakePackageSet,
+                                                          writeOverlay)
+import           Horizon.Spec                            (Overlay, PackageSet,
+                                                          fromOverlay, packages)
+import           Options.Applicative                     (execParser)
+import           Path                                    (toFilePath)
+import           Path.Dhall                              ()
+
+
+makeMakePackageSet :: MakePackageSetOptions -> PackagesDirectory -> PackageSet -> IO ()
+makeMakePackageSet opts d xs = do
+  writeHaskellPackages d (packages xs)
+  writeMakePackageSet d (optPackageSetFile opts) xs
+
+makeOverlay :: OverlayOptions -> PackagesDirectory -> Overlay -> IO ()
+makeOverlay opts d xs = do
+  writeHaskellPackages d (fromOverlay xs)
+  writeOverlay d (optOverlayFile opts) xs
+
+horizonGenNix :: HorizonOptions -> IO ()
+horizonGenNix MkHorizonOptions{..} =
+  let fp = toFilePath . fromInputFile $ optInputFile
+  in case optHorizonCommand of
+    MakePackageSetCommand opts -> do
+      x <- inputFile @PackageSet auto fp
+      makeMakePackageSet opts optPackagesDirectory x
+    OverlayCommand opts -> do
+      x <- inputFile @Overlay auto fp
+      makeOverlay opts optPackagesDirectory x
+
+main :: IO ()
+main = do
+  x <- execParser horizonGenNixOptsInfo
+  horizonGenNix x
diff --git a/src/Horizon/Gen/Nix/Cabal2Nix/Options.hs b/src/Horizon/Gen/Nix/Cabal2Nix/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Cabal2Nix/Options.hs
@@ -0,0 +1,115 @@
+module Horizon.Gen.Nix.Cabal2Nix.Options
+ ( haskellPackageToCabal2NixOptions
+ , mkDefaultCabal2NixOptions
+ , applyGitSourceToCabal2NixOptions
+ , applyHackageSourceToCabal2NixOptions
+ , applyModifiersToCabal2NixOptions
+  ) where
+
+import           Cabal2nix                                      (Options (Options),
+                                                                 optCompiler,
+                                                                 optDoBenchmark,
+                                                                 optDoCheck,
+                                                                 optEnableExecutableProfiling,
+                                                                 optEnableLibraryProfiling,
+                                                                 optEnableProfiling,
+                                                                 optExtraArgs,
+                                                                 optFetchSubmodules,
+                                                                 optFlags,
+                                                                 optHackageDb,
+                                                                 optHackageSnapshot,
+                                                                 optHaddock,
+                                                                 optHpack,
+                                                                 optHyperlinkSource,
+                                                                 optJailbreak,
+                                                                 optMaintainer,
+                                                                 optNixShellOutput,
+                                                                 optNixpkgsIdentifier,
+                                                                 optRevision,
+                                                                 optSha256,
+                                                                 optSubpath,
+                                                                 optSystem,
+                                                                 optUrl)
+import           Control.Lens                                   ((#))
+import qualified Data.Text                                      as T
+import           Distribution.Compiler                          (buildCompilerId)
+import           Distribution.Nixpkgs.Fetch                     (FetchSubmodules (FetchSubmodules))
+import           Distribution.Nixpkgs.Haskell.PackageSourceSpec (HpackUse (NoHpack))
+import           Distribution.System                            (buildPlatform)
+import           Horizon.Spec                                   (GitSource (MkGitSource),
+                                                                 HackageSource (MkHackageSource),
+                                                                 HaskellPackage (MkHaskellPackage),
+                                                                 HaskellSource (FromGit, FromHackage),
+                                                                 Modifiers,
+                                                                 Name (MkName),
+                                                                 Repo (MkRepo),
+                                                                 Url (MkUrl),
+                                                                 Version (MkVersion),
+                                                                 doCheck,
+                                                                 doJailbreak,
+                                                                 enableProfiling,
+                                                                 fromRevision,
+                                                                 fromSubdir)
+import           Language.Nix.Binding                           (binding)
+import           Language.Nix.Identifier                        (ident)
+import           Language.Nix.Path                              (path)
+import           Path                                           (toFilePath)
+
+mkDefaultCabal2NixOptions :: Options
+mkDefaultCabal2NixOptions = Options
+  { optSha256 = Nothing
+  , optMaintainer = []
+  , optHpack = NoHpack
+  , optDoCheck = False
+  , optJailbreak = False
+  , optDoBenchmark = False
+  , optRevision = Nothing
+  , optHyperlinkSource = False
+  , optEnableLibraryProfiling = False
+  , optEnableExecutableProfiling = False
+  , optEnableProfiling = Nothing
+  , optExtraArgs = []
+  , optHackageDb = Nothing
+  , optNixShellOutput = False
+  , optFlags = []
+  , optCompiler = buildCompilerId
+  , optSystem = buildPlatform
+  , optSubpath = Nothing
+  , optHackageSnapshot = Nothing
+  , optNixpkgsIdentifier = \i -> Just (binding # (i, path # [ident # "pkgs", i]))
+  , optUrl = mempty
+  , optHaddock = False
+  , optFetchSubmodules = FetchSubmodules
+  }
+
+haskellPackageToCabal2NixOptions :: HaskellPackage -> Options
+haskellPackageToCabal2NixOptions (MkHaskellPackage x ms _) =
+  applyModifiersToCabal2NixOptions ms $ (
+    case x of
+      FromHackage z -> applyHackageSourceToCabal2NixOptions z
+      FromGit z     -> applyGitSourceToCabal2NixOptions z
+    ) mkDefaultCabal2NixOptions
+
+applyGitSourceToCabal2NixOptions
+  :: GitSource
+  -> Options
+  -> Options
+applyGitSourceToCabal2NixOptions
+  (MkGitSource (MkRepo (MkUrl u)) r s) opts = opts
+  { optRevision = Just $ (T.unpack . fromRevision) r
+  , optSubpath = fmap (toFilePath . fromSubdir) s
+  , optUrl = T.unpack u
+  }
+
+applyHackageSourceToCabal2NixOptions ::  HackageSource -> Options -> Options
+applyHackageSourceToCabal2NixOptions (MkHackageSource (MkName n) (MkVersion v)) opts = opts
+  { optUrl = "cabal://" <> T.unpack n <> "-" <> T.unpack v }
+
+applyModifiersToCabal2NixOptions :: Modifiers -> Options -> Options
+applyModifiersToCabal2NixOptions ms opts = opts
+  { optDoCheck = doCheck ms
+  , optJailbreak = doJailbreak ms
+  , optEnableProfiling = Just $ enableProfiling ms
+  , optEnableLibraryProfiling = enableProfiling ms
+  , optEnableExecutableProfiling = enableProfiling ms
+  }
diff --git a/src/Horizon/Gen/Nix/Options.hs b/src/Horizon/Gen/Nix/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Options.hs
@@ -0,0 +1,170 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE TemplateHaskell       #-}
+module Horizon.Gen.Nix.Options
+  ( horizonGenNixOpts
+  , horizonGenNixOptsInfo
+  , InputFile (..)
+  , HorizonCommand (..)
+  , HorizonOptions (..)
+  , MakePackageSetOptions (..)
+  , OverlayOptions (..)
+  , defaultMakePackageSetOptions
+  , defaultPackagesDirectory
+  , defaultPackageSetFile
+  , defaultOverlayFile
+  , defaultOverlayOptions
+  ,   ) where
+
+import           Data.Either.Combinators                 (mapLeft)
+import           Data.Kind                               (Type)
+import           Horizon.Gen.Nix.Types.OverlayFile       (OverlayFile (MkOverlayFile))
+import           Horizon.Gen.Nix.Types.PackagesDirectory (PackagesDirectory (MkPackagesDirectory))
+import           Horizon.Gen.Nix.Types.PackageSetFile    (PackageSetFile (MkPackageSetFile))
+import           Options.Applicative                     (Parser, ParserInfo,
+                                                          ReadM, command,
+                                                          eitherReader, help,
+                                                          helper, info, long,
+                                                          metavar, option,
+                                                          progDesc, subparser,
+                                                          value)
+import           Path                                    (File, Path, Rel,
+                                                          mkRelDir, mkRelFile,
+                                                          parseRelDir,
+                                                          parseRelFile)
+import           Path.Dhall                              ()
+
+
+parsePackagesDirectory :: ReadM PackagesDirectory
+parsePackagesDirectory = eitherReader $ mapLeft show . fmap MkPackagesDirectory . parseRelDir
+
+defaultPackagesDirectory :: PackagesDirectory
+defaultPackagesDirectory = MkPackagesDirectory $(mkRelDir "pkgs")
+
+packagesDirectoryOption :: Parser PackagesDirectory
+packagesDirectoryOption =
+  option parsePackagesDirectory
+    (  long "packages-dir"
+    <> metavar "PACKAGES_DIR"
+    <> value defaultPackagesDirectory
+    <> help "The directory to put haskell packages."
+    )
+
+
+parsePackageSetFile :: ReadM PackageSetFile
+parsePackageSetFile = eitherReader $ mapLeft show . fmap MkPackageSetFile . parseRelFile
+
+defaultPackageSetFile :: PackageSetFile
+defaultPackageSetFile = MkPackageSetFile $(mkRelFile "initial-packages.nix")
+
+packageSetFileOption :: Parser PackageSetFile
+packageSetFileOption =
+  option parsePackageSetFile
+    (  long "packageset-file"
+    <> metavar "PACKAGESET_FILE"
+    <> value defaultPackageSetFile
+    <> help "The name of the package set file"
+    )
+
+
+parseOverlayFile :: ReadM OverlayFile
+parseOverlayFile = eitherReader $ mapLeft show . fmap MkOverlayFile . parseRelFile
+
+defaultOverlayFile :: OverlayFile
+defaultOverlayFile = MkOverlayFile $(mkRelFile "overlay.nix")
+
+overlayFileOption :: Parser OverlayFile
+overlayFileOption =
+  option parseOverlayFile
+    (  long "overlay-file"
+    <> metavar "OVERLAY_FILE"
+    <> value defaultOverlayFile
+    <> help "The name of the overlay file."
+    )
+
+type InputFile :: Type
+newtype InputFile where
+  MkInputFile :: { fromInputFile :: Path Rel File }
+              -> InputFile
+  deriving stock (Eq, Show)
+
+parseInputFile :: ReadM InputFile
+parseInputFile = eitherReader $ mapLeft show . fmap MkInputFile . parseRelFile
+
+defaultInputFile :: InputFile
+defaultInputFile = MkInputFile $(mkRelFile "horizon.dhall")
+
+inputFileOption :: Parser InputFile
+inputFileOption =
+  option parseInputFile
+    (  long "input-file"
+    <> metavar "INPUT_FILE"
+    <> value defaultInputFile
+    <> help "The name of the input file."
+    )
+
+
+type MakePackageSetOptions :: Type
+data MakePackageSetOptions where
+  MkMakePackageSetOptions :: { optPackageSetFile :: PackageSetFile }
+                          -> MakePackageSetOptions
+  deriving stock (Eq, Show)
+
+makePackageSetOptions :: Parser MakePackageSetOptions
+makePackageSetOptions =
+      MkMakePackageSetOptions
+  <$> packageSetFileOption
+
+defaultMakePackageSetOptions :: MakePackageSetOptions
+defaultMakePackageSetOptions = MkMakePackageSetOptions defaultPackageSetFile
+
+
+type OverlayOptions :: Type
+data OverlayOptions where
+  MkOverlayOptions :: { optOverlayFile :: OverlayFile}
+                   -> OverlayOptions
+  deriving stock (Eq, Show)
+
+overlayOptions :: Parser OverlayOptions
+overlayOptions =
+      MkOverlayOptions
+  <$> overlayFileOption
+
+defaultOverlayOptions :: OverlayOptions
+defaultOverlayOptions = MkOverlayOptions defaultOverlayFile
+
+
+type HorizonCommand :: Type
+data HorizonCommand where
+  MakePackageSetCommand :: MakePackageSetOptions -> HorizonCommand
+  OverlayCommand :: OverlayOptions -> HorizonCommand
+  deriving stock (Eq, Show)
+
+type HorizonOptions :: Type
+data HorizonOptions where
+  MkHorizonOptions :: { optInputFile :: InputFile
+                      , optPackagesDirectory :: PackagesDirectory
+                      , optHorizonCommand :: HorizonCommand} -> HorizonOptions
+  deriving stock (Eq, Show)
+
+makePackageSetCommand :: Parser HorizonCommand
+makePackageSetCommand = fmap MakePackageSetCommand makePackageSetOptions
+
+overlayCommand :: Parser HorizonCommand
+overlayCommand = fmap OverlayCommand overlayOptions
+
+horizonCommandOpts :: Parser HorizonCommand
+horizonCommandOpts = subparser
+  ( command "make-package-set" (info (helper <*> makePackageSetCommand) ( progDesc "Run in MakePackageSet Mode" ))
+ <> command "overlay" (info (helper <*> overlayCommand) ( progDesc "Run in Overlay Mode" ))
+  )
+
+horizonGenNixOpts :: Parser HorizonOptions
+horizonGenNixOpts =
+      MkHorizonOptions
+  <$> inputFileOption
+  <*> packagesDirectoryOption
+  <*> horizonCommandOpts
+
+horizonGenNixOptsInfo :: ParserInfo HorizonOptions
+horizonGenNixOptsInfo = info (helper <*> horizonGenNixOpts) mempty
diff --git a/src/Horizon/Gen/Nix/Pretty.hs b/src/Horizon/Gen/Nix/Pretty.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Pretty.hs
@@ -0,0 +1,125 @@
+module Horizon.Gen.Nix.Pretty (prettyDerivation) where
+
+import           Distribution.Nixpkgs.Haskell.Derivation      (Derivation,
+                                                               benchmarkDepends,
+                                                               cabalFlags,
+                                                               configureFlags,
+                                                               dependencies,
+                                                               doBenchmark,
+                                                               doCheck,
+                                                               editedCabalFile,
+                                                               enableExecutableProfiling,
+                                                               enableLibraryProfiling,
+                                                               enableSeparateDataOutput,
+                                                               executableDepends,
+                                                               extraAttributes,
+                                                               extraFunctionArgs,
+                                                               hyperlinkSource,
+                                                               isExecutable,
+                                                               isLibrary,
+                                                               jailbreak,
+                                                               libraryDepends,
+                                                               metaSection,
+                                                               phaseOverrides,
+                                                               pkgid, revision,
+                                                               runHaddock,
+                                                               setupDepends,
+                                                               src, subpath,
+                                                               testDepends,
+                                                               testTarget)
+import           Language.Nix                                 (ident, localName)
+import           Language.Nix.PrettyPrinting                  (Doc, attr, char,
+                                                               doubleQuotes,
+                                                               empty, funargs,
+                                                               int, lbrace,
+                                                               listattr, nest,
+                                                               onlyIf, pPrint,
+                                                               rbrace, string,
+                                                               text, vcat, ($$),
+                                                               (<+>))
+import qualified Language.Nix.PrettyPrinting                  as P ((<>))
+
+import           Control.Lens                                 (each, folded,
+                                                               view, (^.))
+import           Data.List                                    (isPrefixOf)
+import qualified Data.Map                                     as Map
+import           Data.Set                                     (Set, toAscList)
+import qualified Data.Set                                     as Set
+import           Data.Set.Lens                                (setOf)
+import           Distribution.Nixpkgs.Fetch                   (derivKind,
+                                                               derivKindFunction,
+                                                               derivUrl)
+import           Distribution.Nixpkgs.Haskell.BuildInfo       (pPrintBuildInfo)
+import           Distribution.Nixpkgs.Haskell.OrphanInstances ()
+import           Distribution.Nixpkgs.Meta                    (Meta, broken,
+                                                               description,
+                                                               homepage,
+                                                               license)
+import           Distribution.Package                         (packageName,
+                                                               packageVersion)
+import           Distribution.PackageDescription              (unFlagAssignment,
+                                                               unFlagName)
+
+bool :: Bool -> Doc
+bool True  = text "true"
+bool False = text "false"
+
+
+boolattr :: String -> Bool -> Doc
+boolattr n v = attr n (bool v)
+
+prettyMeta :: Meta -> Doc
+prettyMeta meta = vcat
+    [ onlyIf (not (null $ meta ^. homepage)) $ attr "homepage" $ string (meta ^. homepage)
+    , onlyIf (not (null $ meta ^. description)) $ attr "description" $ string (meta ^. description)
+    , attr "license" $ pPrint $ meta ^. license
+    , boolattr "broken" (meta ^. broken)
+    ]
+
+prettyDerivation :: Derivation -> Doc
+prettyDerivation drv = funargs (map text ("mkDerivation" : toAscList inputs)) $$ vcat
+    [ text "mkDerivation" <+> lbrace
+    , nest 2 $ vcat
+      [ attr "pname"   $ doubleQuotes $ pPrint (packageName $ drv ^. pkgid)
+      , attr "version" $ doubleQuotes $ pPrint (packageVersion $ drv ^. pkgid)
+      , pPrint $ drv ^. src
+      , onlyIf (drv ^. subpath /= ".") $ attr "postUnpack" postUnpack
+      , onlyIf (drv ^. revision > 0) $ attr "revision" $ doubleQuotes $ int $ drv ^. revision
+      , onlyIf (not (null (drv ^. editedCabalFile)) && (drv ^. revision) > 0) $ attr "editedCabalFile" $ string (drv ^. editedCabalFile)
+      , listattr "configureFlags" empty (map (show . show) renderedFlags)
+      , boolattr "isLibrary" $ drv ^. isLibrary
+      , boolattr "isExecutable" $ drv ^. isExecutable
+      , boolattr "enableSeparateDataOutput" $ drv ^. enableSeparateDataOutput
+      , onlyIf (drv ^. setupDepends /= mempty) $ pPrintBuildInfo "setup" $ drv ^. setupDepends
+      , onlyIf (drv ^. libraryDepends /= mempty) $ pPrintBuildInfo "library" $ drv ^. libraryDepends
+      , onlyIf (drv ^. executableDepends /= mempty) $ pPrintBuildInfo "executable" $ drv ^. executableDepends
+      , onlyIf (drv ^. testDepends /= mempty) $ pPrintBuildInfo "test" $ drv ^. testDepends
+      , onlyIf (drv ^. benchmarkDepends /= mempty) $ pPrintBuildInfo "benchmark" $ drv ^. benchmarkDepends
+      , boolattr "enableLibraryProfiling" $ drv ^. enableLibraryProfiling
+      , boolattr "enableExecutableProfiling" $ drv ^. enableExecutableProfiling
+      , boolattr "doHaddock" $ drv ^. runHaddock
+      , boolattr "jailbreak" $ drv ^. jailbreak
+      , boolattr "doCheck" $ drv ^. doCheck
+      , boolattr "doBenchmark" $ drv ^. doBenchmark
+      , onlyIf (not (null $ drv ^. testTarget)) $ attr "testTarget" $ string $ drv ^. testTarget
+      , boolattr "hyperlinkSource" $ drv ^. hyperlinkSource
+      , onlyIf (not (null $ drv ^. phaseOverrides)) $ vcat ((map text . lines) $ drv ^. phaseOverrides)
+      , prettyMeta $ drv ^. metaSection
+      , vcat [ attr k (text v) | (k,v) <- Map.toList $ drv ^. extraAttributes ]
+      ]
+    , rbrace
+    ]
+    where
+      inputs :: Set String
+      inputs = Set.unions [ Set.map (view (localName . ident)) $ drv ^. extraFunctionArgs
+                          , setOf (dependencies . each . folded . localName . ident) drv
+                          , case derivKind (drv ^. src) of
+                              Nothing -> mempty
+                              Just derivKind' -> Set.fromList [derivKindFunction derivKind' | not isHackagePackage]
+                          ]
+
+      renderedFlags = [ text "-f" P.<> (if enable then empty else char '-') P.<> text (unFlagName f) | (f, enable) <- unFlagAssignment $ drv ^. cabalFlags ]
+                      ++ map text (toAscList (drv ^. configureFlags))
+      isHackagePackage = "mirror://hackage/" `isPrefixOf` derivUrl (drv ^. src)
+
+      postUnpack = string $ "sourceRoot+=/" ++ drv ^. subpath ++ "; echo source root reset to $sourceRoot"
diff --git a/src/Horizon/Gen/Nix/Types/HorizonMode.hs b/src/Horizon/Gen/Nix/Types/HorizonMode.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Types/HorizonMode.hs
@@ -0,0 +1,9 @@
+module Horizon.Gen.Nix.Types.HorizonMode (HorizonMode (MakePackageSetMode, OverlayMode)) where
+
+import           Data.Kind (Type)
+
+type HorizonMode :: Type
+data HorizonMode where
+  MakePackageSetMode :: HorizonMode
+  OverlayMode :: HorizonMode
+  deriving stock (Show, Eq)
diff --git a/src/Horizon/Gen/Nix/Types/OverlayFile.hs b/src/Horizon/Gen/Nix/Types/OverlayFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Types/OverlayFile.hs
@@ -0,0 +1,10 @@
+module Horizon.Gen.Nix.Types.OverlayFile (OverlayFile(MkOverlayFile), fromOverlayFile) where
+
+import           Data.Kind (Type)
+import           Path      (File, Path, Rel)
+
+
+type OverlayFile :: Type
+data OverlayFile where
+  MkOverlayFile :: { fromOverlayFile :: Path Rel File } -> OverlayFile
+  deriving stock (Show, Eq)
diff --git a/src/Horizon/Gen/Nix/Types/PackageSetFile.hs b/src/Horizon/Gen/Nix/Types/PackageSetFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Types/PackageSetFile.hs
@@ -0,0 +1,10 @@
+module Horizon.Gen.Nix.Types.PackageSetFile (PackageSetFile(MkPackageSetFile), fromPackageSetFile) where
+
+import           Data.Kind (Type)
+import           Path      (File, Path, Rel)
+
+
+type PackageSetFile :: Type
+data PackageSetFile where
+  MkPackageSetFile :: { fromPackageSetFile :: Path Rel File } -> PackageSetFile
+  deriving stock (Show, Eq)
diff --git a/src/Horizon/Gen/Nix/Types/PackagesDirectory.hs b/src/Horizon/Gen/Nix/Types/PackagesDirectory.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Types/PackagesDirectory.hs
@@ -0,0 +1,10 @@
+module Horizon.Gen.Nix.Types.PackagesDirectory (PackagesDirectory(MkPackagesDirectory), fromPackagesDirectory) where
+
+import           Data.Kind (Type)
+import           Path      (Dir, Path, Rel)
+
+
+type PackagesDirectory :: Type
+data PackagesDirectory where
+  MkPackagesDirectory :: { fromPackagesDirectory :: Path Rel Dir } -> PackagesDirectory
+  deriving stock (Show, Eq)
diff --git a/src/Horizon/Gen/Nix/Writers.hs b/src/Horizon/Gen/Nix/Writers.hs
new file mode 100644
--- /dev/null
+++ b/src/Horizon/Gen/Nix/Writers.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Horizon.Gen.Nix.Writers (
+   writeOverlay
+ , writeMakePackageSet
+ , writeCabal2Nix
+ , writeDerivation
+ , writeHaskellPackage
+ , writeHaskellPackages
+ ) where
+
+import           Cabal2nix                               (Options, cabal2nix')
+import           Control.Monad                           (forM_, unless, (>=>))
+import           Data.Map                                (keys, toList)
+import qualified Data.Text                               as T
+import           Distribution.Nixpkgs.Haskell.Derivation (Derivation)
+import           Horizon.Gen.Nix.Cabal2Nix.Options       (haskellPackageToCabal2NixOptions)
+import           Horizon.Gen.Nix.Pretty                  (prettyDerivation)
+import           Horizon.Gen.Nix.Types.OverlayFile       (OverlayFile (MkOverlayFile))
+import           Horizon.Gen.Nix.Types.PackagesDirectory (PackagesDirectory (MkPackagesDirectory))
+import           Horizon.Gen.Nix.Types.PackageSetFile    (PackageSetFile (MkPackageSetFile))
+import           Horizon.Spec                            (HaskellPackage,
+                                                          Name (MkName),
+                                                          Overlay (MkOverlay),
+                                                          PackageList (MkPackageList),
+                                                          PackageSet (MkPackageSet),
+                                                          fromName,
+                                                          fromPackageList)
+import           Path                                    (File, Path,
+                                                          parseRelFile,
+                                                          toFilePath, (</>))
+import           Path.Dhall                              ()
+import           System.Directory                        (createDirectoryIfMissing,
+                                                          doesFileExist)
+import           System.IO                               (IOMode (WriteMode),
+                                                          hPutStrLn, stderr,
+                                                          stdout, withFile)
+import           System.IO.Silently                      (hSilence)
+import           Text.PrettyPrint.HughesPJClass          (render)
+
+
+writeOverlay :: PackagesDirectory -> OverlayFile -> Overlay -> IO ()
+writeOverlay (MkPackagesDirectory d) (MkOverlayFile f) (MkOverlay (MkPackageList xs)) = withFile (toFilePath f) WriteMode $ \h -> do
+  hPutStrLn h "{ pkgs, ... }:"
+  hPutStrLn h ""
+  hPutStrLn h "final: prev: with pkgs.haskell.lib; {"
+  forM_ (keys xs) $ \x -> do
+    y <- parseRelFile $ T.unpack $ fromName x <> ".nix"
+    hPutStrLn h ("  " <> T.unpack (fromName x) <> " = final.callPackage (./" <> toFilePath (d </> y) <> ") { };")
+    hPutStrLn h ""
+  hPutStrLn h "}"
+
+writeMakePackageSet :: PackagesDirectory -> PackageSetFile -> PackageSet -> IO ()
+writeMakePackageSet (MkPackagesDirectory d) (MkPackageSetFile f) (MkPackageSet _ (MkPackageList xs)) = withFile (toFilePath f) WriteMode $ \h -> do
+  hPutStrLn h "{ pkgs, lib, callPackage, ... }:"
+  hPutStrLn h ""
+  hPutStrLn h "self: with pkgs.haskell.lib; {"
+  forM_ (keys xs) $ \x -> do
+    y <- parseRelFile $ T.unpack $ fromName x <> ".nix"
+    hPutStrLn h ("  " <> T.unpack (fromName x) <> " = self.callPackage (./" <> toFilePath (d </> y) <> ") { };")
+    hPutStrLn h ""
+  hPutStrLn h "}"
+
+writeCabal2Nix :: Path b File -> Options -> IO ()
+writeCabal2Nix f = hSilence [stdout, stderr] . cabal2nix' >=> either (print . render) (writeDerivation f)
+
+writeDerivation :: Path b File -> Derivation -> IO ()
+writeDerivation f = writeFile (toFilePath f) . show . prettyDerivation
+
+writeHaskellPackage :: PackagesDirectory -> Name -> HaskellPackage -> IO ()
+writeHaskellPackage (MkPackagesDirectory d) (MkName f) y = do
+  let o = haskellPackageToCabal2NixOptions y
+  createDirectoryIfMissing True (toFilePath d)
+  f' <- parseRelFile  $ T.unpack (f <> ".nix")
+  let j = d </> f'
+  q  <- doesFileExist $ toFilePath j
+  unless q $ writeCabal2Nix j o
+
+writeHaskellPackages :: PackagesDirectory -> PackageList -> IO ()
+writeHaskellPackages d = mapM_ (uncurry $ writeHaskellPackage d) . toList . fromPackageList
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell   #-}
+module Main ( main ) where
+
+import           Control.Exception       (bracket_)
+import           Horizon.Gen.Nix         (horizonGenNix)
+import           Horizon.Gen.Nix.Options (HorizonCommand (MakePackageSetCommand, OverlayCommand),
+                                          HorizonOptions (MkHorizonOptions),
+                                          InputFile (MkInputFile),
+                                          defaultMakePackageSetOptions,
+                                          defaultOverlayOptions,
+                                          defaultPackagesDirectory,
+                                          optHorizonCommand, optInputFile,
+                                          optPackagesDirectory)
+import           Path                    (mkRelFile)
+import           System.Directory        (removeFile)
+import           Test.Syd                (Spec, aroundAll_, describe,
+                                          doNotRandomiseExecutionOrder,
+                                          goldenStringFile, it, sequential,
+                                          sydTest)
+
+main :: IO ()
+main = sydTest $ doNotRandomiseExecutionOrder $ sequential $ do
+  samplePackageSet
+  modifiedPackageSet
+  sampleOverlay
+  modifiedOverlay
+
+withSamplePackageSet :: IO a -> IO a
+withSamplePackageSet = bracket_ runSamplePackageSet cleanupPackageSet
+
+
+withModifiedPackageSet :: IO a -> IO a
+withModifiedPackageSet = bracket_ runModifiedPackageSet cleanupPackageSet
+
+
+withSampleOverlay :: IO a -> IO a
+withSampleOverlay = bracket_ runSampleOverlay cleanupOverlay
+
+
+withModifiedOverlay :: IO a -> IO a
+withModifiedOverlay = bracket_ runModifiedOverlay cleanupOverlay
+
+
+runSamplePackageSet :: IO ()
+runSamplePackageSet = do
+    let opts = MkHorizonOptions
+                 { optHorizonCommand = MakePackageSetCommand defaultMakePackageSetOptions
+                 , optPackagesDirectory = defaultPackagesDirectory
+                 , optInputFile = MkInputFile $(mkRelFile "test/data/sample-package-set/input.dhall")
+                 }
+    horizonGenNix opts
+
+
+runModifiedPackageSet :: IO ()
+runModifiedPackageSet = do
+    let opts = MkHorizonOptions
+                 { optHorizonCommand = MakePackageSetCommand defaultMakePackageSetOptions
+                 , optPackagesDirectory = defaultPackagesDirectory
+                 , optInputFile = MkInputFile $(mkRelFile "test/data/modified-package-set/input.dhall")
+                 }
+    horizonGenNix opts
+
+runSampleOverlay :: IO ()
+runSampleOverlay = do
+    let opts = MkHorizonOptions
+                 { optHorizonCommand = OverlayCommand defaultOverlayOptions
+                 , optPackagesDirectory = defaultPackagesDirectory
+                 , optInputFile = MkInputFile $(mkRelFile "test/data/sample-overlay/input.dhall")
+                 }
+    horizonGenNix opts
+
+
+runModifiedOverlay :: IO ()
+runModifiedOverlay = do
+    let opts = MkHorizonOptions
+                 { optHorizonCommand = OverlayCommand defaultOverlayOptions
+                 , optPackagesDirectory = defaultPackagesDirectory
+                 , optInputFile = MkInputFile $(mkRelFile "test/data/modified-overlay/input.dhall")
+                 }
+    horizonGenNix opts
+
+cleanupPackageSet :: IO ()
+cleanupPackageSet = mapM_ removeFile ["initial-packages.nix", "pkgs/lens.nix", "pkgs/Cabal-syntax.nix" ]
+
+
+cleanupOverlay :: IO ()
+cleanupOverlay = mapM_ removeFile ["overlay.nix", "pkgs/lens.nix", "pkgs/Cabal-syntax.nix" ]
+
+
+
+samplePackageSet :: Spec
+samplePackageSet = describe "sample package set" $ sequential $
+  aroundAll_ withSamplePackageSet $ do
+    it "initial-packages.nix" $ goldenStringFile "test/data/sample-package-set/output/initial-packages.nix.golden" (readFile "initial-packages.nix")
+    it "pkgs/lens.nix" $ goldenStringFile "test/data/sample-package-set/output/pkgs/lens.nix.golden" (readFile "pkgs/lens.nix")
+    it "pkgs/Cabal-syntax.nix" $ goldenStringFile "test/data/sample-package-set/output/pkgs/Cabal-syntax.nix.golden" (readFile "pkgs/Cabal-syntax.nix")
+
+
+modifiedPackageSet :: Spec
+modifiedPackageSet = describe "modified package set" $ sequential $
+  aroundAll_ withModifiedPackageSet $ do
+    it "initial-packages.nix" $ goldenStringFile "test/data/modified-package-set/output/initial-packages.nix.golden" (readFile "initial-packages.nix")
+    it "pkgs/lens.nix" $ goldenStringFile "test/data/modified-package-set/output/pkgs/lens.nix.golden" (readFile "pkgs/lens.nix")
+    it "pkgs/Cabal-syntax.nix" $ goldenStringFile "test/data/modified-package-set/output/pkgs/Cabal-syntax.nix.golden" (readFile "pkgs/Cabal-syntax.nix")
+
+
+sampleOverlay :: Spec
+sampleOverlay = describe "sample overlay" $ sequential $
+  aroundAll_ withSampleOverlay $ do
+    it "overlay.nix" $ goldenStringFile "test/data/sample-overlay/output/overlay.nix.golden" (readFile "overlay.nix")
+    it "pkgs/lens.nix" $ goldenStringFile "test/data/sample-overlay/output/pkgs/lens.nix.golden" (readFile "pkgs/lens.nix")
+    it "pkgs/Cabal-syntax.nix" $ goldenStringFile "test/data/sample-overlay/output/pkgs/Cabal-syntax.nix.golden" (readFile "pkgs/Cabal-syntax.nix")
+
+
+modifiedOverlay :: Spec
+modifiedOverlay = describe "modified overlay" $ sequential $
+  aroundAll_ withModifiedOverlay $ do
+    it "overlay.nix" $ goldenStringFile "test/data/modified-overlay/output/overlay.nix.golden" (readFile "overlay.nix")
+    it "pkgs/lens.nix" $ goldenStringFile "test/data/modified-overlay/output/pkgs/lens.nix.golden" (readFile "pkgs/lens.nix")
+    it "pkgs/Cabal-syntax.nix" $ goldenStringFile "test/data/modified-overlay/output/pkgs/Cabal-syntax.nix.golden" (readFile "pkgs/Cabal-syntax.nix")
