diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+# 0.3.0
+
+- Initial Hackage release
+- Add CHANGELOG.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# stackage2nix
+
+[![Build Status](https://travis-ci.org/typeable/stackage2nix.svg?branch=master)](https://travis-ci.org/typeable/stackage2nix)
+
+`stackage2nix` converts a Stack file into a Nix Haskell packages set.
+It creates LTS Stackage packages set, and applies appropriate overrides on top of it.
+
+```
+stackage2nix \
+  --lts-haskell "$LTS_HASKELL_REPO" \
+  --all-cabal-hashes "$ALL_CABAL_HASHES_REPO" \
+  .
+```
+
+`stackage2nix` has three required arguments:
+- `--lts-haskell` - path to [fpco/lts-haskell](https://github.com/fpco/lts-haskell)
+- `--all-cabal-hashes` - path to [commercialhaskell/all-cabal-hashes](https://github.com/commercialhaskell/all-cabal-hashes) checked out to `hackage` branch
+- `.` - path to stack.yaml file or directory
+
+Produced Nix derivation split on following files:
+- packages.nix - Base Stackage packages set
+- configuration-packages.nix - Compiler configuration
+- default.nix - Final Haskell packages set with all overrides applied
+
+A particular package from result Haskell packages set can be built with:
+
+```
+nix-build -A <package-name>
+```
+
+## Runtime dependencies
+
+- `nix-env` is required to be on PATH by the
+  [distribution-nixpkgs](https://hackage.haskell.org/package/distribution-nixpkgs)
+  dependency
+- `nix-prefetch-git` is required on PATH if you have git dependencies in
+  `stack.yaml`
+
+## Override result derivation
+
+Complex projects may require some extra customization.
+Snippet `override.nix` below shows a minimal example of how to apply additional
+overrides on top of Haskell packages set produced by `stackage2nix`.
+
+```
+with import <nixpkgs> {};
+with pkgs.haskell.lib;
+let haskellPackages = import ./. {};
+in haskellPackages.override {
+  overrides = self: super: {
+    stackage2nix = disableSharedExecutables super.stackage2nix;
+  };
+}
+```
+
+```
+nix-build -A stackage2nix override.nix
+```
+
+For more complex overrides and detailed information on how to work with Haskell packages in Nix, see Nixpkgs manual [User’s Guide to the Haskell Infrastructure](http://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure)
+
+
+## Examples
+
+For other examples of `stackage2nix` usage, see [4e6/stackage2nix-examples](https://github.com/4e6/stackage2nix-examples) repository.
+It verifies `stackage2nix` by running it on different public projects.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/AllCabalHashes.hs b/src/AllCabalHashes.hs
new file mode 100644
--- /dev/null
+++ b/src/AllCabalHashes.hs
@@ -0,0 +1,82 @@
+module AllCabalHashes where
+
+import Control.Lens hiding ((<.>))
+import Data.Aeson as A
+import Data.ByteString as BS
+import Data.ByteString.Lazy as BSL
+import Data.Map as M
+import Data.Text as T
+import Data.Text.Encoding as T
+import Distribution.Nixpkgs.Hashes as NH
+import Distribution.Package
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parse
+import Distribution.Text ( display )
+import Git
+import Git.Libgit2 as Libgit2
+import OpenSSL.Digest as SSL ( digest, digestByName )
+import System.FilePath
+
+
+type SHA1Hash = T.Text
+type SHA256Hash = String
+
+data Meta = Meta
+  { _mHashes    :: !(Map String String)
+  , _mLocations :: ![String]
+  , _mPkgsize   :: !Int
+  } deriving (Show)
+
+makeLenses ''Meta
+
+instance FromJSON Meta where
+  parseJSON (Object v) = Meta
+    <$> v .: "package-hashes"
+    <*> v .: "package-locations"
+    <*> v .: "package-size"
+  parseJSON o          = fail ("invalid Cabal metadata: " ++ show o)
+
+readPackageByHash :: FilePath -> SHA1Hash -> IO (GenericPackageDescription, SHA256Hash)
+readPackageByHash repoDir sha1Hash = do
+  let repoOpts = defaultRepositoryOptions { repoPath = repoDir }
+  repo <- openLgRepository repoOpts
+  buf <- runLgRepository repo $ do
+    BlobObj (Blob _ contents) <- lookupObject =<< parseOid sha1Hash
+    case contents of
+      BlobString bs -> return bs
+      BlobStringLazy bsl -> return $ BSL.toStrict bsl
+      _ -> fail $ "Git SHA1 " ++ show sha1Hash ++ ": expected single Blob"
+  cabal <- case parseGenericPackageDescription (T.unpack $ T.decodeUtf8 buf) of
+    ParseOk _ a     -> return a
+    ParseFailed err -> fail ("Git SHA1 " ++ show sha1Hash ++ ": " ++ show err)
+  let
+    hash = printSHA256 (digest (digestByName "sha256") buf)
+    pkg  = setCabalFileHash hash cabal
+  return (pkg, hash)
+
+readPackageByName :: FilePath -> PackageIdentifier -> IO (GenericPackageDescription, SHA256Hash)
+readPackageByName repoDir (PackageIdentifier name version) = do
+  let cabalFile = repoDir </> unPackageName name </> display version </> unPackageName name <.> "cabal"
+  buf <- BS.readFile cabalFile
+  cabal <- case parseGenericPackageDescription (T.unpack $ T.decodeUtf8 buf) of
+             ParseOk _ a  -> return a
+             ParseFailed err -> fail (cabalFile ++ ": " ++ show err)
+  let
+    hash = NH.printSHA256 (SSL.digest (SSL.digestByName "sha256") buf)
+    pkg  = setCabalFileHash hash cabal
+  return (pkg, hash)
+
+setCabalFileHash :: SHA256Hash -> GenericPackageDescription -> GenericPackageDescription
+setCabalFileHash hash cabal = cabal
+  { packageDescription = (packageDescription cabal)
+    { customFieldsPD = ("X-Cabal-File-Hash", hash) : customFieldsPD (packageDescription cabal)
+    }
+  }
+
+readPackageMeta :: FilePath -> PackageIdentifier -> IO Meta
+readPackageMeta dirPrefix (PackageIdentifier name version) = do
+  let metaFile = dirPrefix </> unPackageName name </> display version </> unPackageName name <.> "json"
+  buf <- BS.readFile metaFile
+  case eitherDecodeStrict buf of
+    Left msg -> fail (metaFile ++ ": " ++ msg)
+    Right x  -> return $ over (mHashes . ix "SHA256") (printSHA256 . packHex) x
diff --git a/src/Distribution/Nixpkgs/Haskell/FromStack.hs b/src/Distribution/Nixpkgs/Haskell/FromStack.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Haskell/FromStack.hs
@@ -0,0 +1,93 @@
+module Distribution.Nixpkgs.Haskell.FromStack where
+
+import AllCabalHashes
+import Control.Lens
+import Data.Set.Lens
+import Distribution.Compiler (CompilerInfo(..))
+import Distribution.System (Platform(..))
+import Distribution.Package (PackageName, PackageIdentifier(..), Dependency(..))
+import Distribution.PackageDescription
+import Distribution.Nixpkgs.Haskell.FromStack.Package
+import Distribution.Nixpkgs.Haskell.PackageSourceSpec
+import Distribution.Nixpkgs.Haskell.FromCabal
+import Distribution.Nixpkgs.Haskell.Derivation
+import Stackage.BuildPlan
+import Stackage.Types (CabalFileInfo(..),PackageConstraints(..), DepInfo(..), SimpleDesc(..), TestState(..))
+import qualified Distribution.Nixpkgs.Meta as Nix
+
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+
+data PackageSetConfig = PackageSetConfig
+  { haskellResolver :: HaskellResolver
+  , nixpkgsResolver :: NixpkgsResolver
+  , packageLoader   :: Maybe SHA1Hash -> PackageIdentifier -> IO Package
+  , targetPlatform  :: Platform
+  , targetCompiler  :: CompilerInfo }
+
+data PackageConfig = PackageConfig
+  { enableCheck   :: Bool
+  , enableHaddock :: Bool }
+
+removeTests :: GenericPackageDescription -> GenericPackageDescription
+removeTests gd = gd { condTestSuites = [] }
+
+planDependencies :: PackagePlan -> [Dependency]
+planDependencies = map makeDependency . Map.toList . sdPackages . ppDesc
+ where
+  makeDependency (name, depInfo) = Dependency name (diRange depInfo)
+
+buildNodeM :: PackageSetConfig -> PackageConfig -> PackageName -> PackagePlan -> IO Node
+buildNodeM conf pconf name plan = do
+  let
+    cabalHashes = maybe mempty cfiHashes $ ppCabalFileInfo plan
+    mGitSha1 = Map.lookup "GitSHA1" cabalHashes
+  pkg <- packageLoader conf mGitSha1 $ PackageIdentifier name (ppVersion plan)
+  pure . mkNode $ fromPackage conf pconf plan pkg
+
+fromPackage :: PackageSetConfig -> PackageConfig -> PackagePlan -> Package -> Derivation
+fromPackage conf pconf plan pkg =
+  let
+    constraints = ppConstraints plan
+    testsEnabled =
+      enableCheck pconf &&
+      pcTests constraints == ExpectSuccess
+    haddocksEnabled =
+      enableHaddock pconf &&
+      pcHaddocks constraints == ExpectSuccess &&
+      not (Set.null (sdModules (ppDesc plan)))
+    configureTests
+      | pcTests constraints == Don'tBuild = removeTests
+      | otherwise = id
+    flags = Map.toList (pcFlagOverrides constraints)
+    (descr, missingDeps) = finalizeGenericPackageDescription
+      (haskellResolver conf)
+      (targetPlatform conf)
+      (targetCompiler conf)
+      flags
+      (planDependencies plan)
+      (configureTests (pkgCabal pkg))
+    genericDrv = fromPackageDescription
+      (haskellResolver conf)
+      (nixpkgsResolver conf)
+      missingDeps
+      flags
+      descr
+    depName (Dependency name _) = name
+    testDeps = setOf
+      ( to testSuites . folded
+        . to testBuildInfo . to targetBuildDepends
+        . folded . to depName )
+    hasIntersection a b = not . null $ Set.intersection a b
+    brokenEnabled =
+      testsEnabled &&
+      not (null missingDeps) &&
+      setOf (folded . to depName) missingDeps `hasIntersection` testDeps descr
+  in
+    genericDrv
+      & src .~ pkgSource pkg
+      & doCheck .~ testsEnabled
+      & runHaddock .~ haddocksEnabled
+      & metaSection . Nix.broken .~ brokenEnabled
+      -- TODO: remove after Nixos/Nixpkgs #27196 released
+      & enableSeparateDataOutput .~ False
diff --git a/src/Distribution/Nixpkgs/Haskell/FromStack/Package.hs b/src/Distribution/Nixpkgs/Haskell/FromStack/Package.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Haskell/FromStack/Package.hs
@@ -0,0 +1,131 @@
+module Distribution.Nixpkgs.Haskell.FromStack.Package where
+
+import Control.Lens
+import Data.Graph (Graph, Vertex)
+import Data.Foldable as F
+import Data.Maybe
+import Data.Monoid
+import Data.Ord as O
+import Distribution.Text (display, disp)
+import Distribution.Package (unPackageName, packageName)
+import Distribution.Nixpkgs.Haskell.BuildInfo
+import Distribution.Nixpkgs.Haskell.Derivation
+import Language.Nix.PrettyPrinting (onlyIf)
+import Language.Nix as Nix
+import Stackage.Types (SystemInfo(..))
+import Text.PrettyPrint.HughesPJClass hiding ((<>))
+
+
+import qualified Data.Graph as Graph
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+
+data Node = Node
+  { _nodeDerivation   :: Derivation
+  , _nodeTestDepends  :: Set.Set String
+  , _nodeOtherDepends :: Set.Set String }
+
+makeLenses ''Node
+
+mkNode :: Derivation -> Node
+mkNode _nodeDerivation = Node{..}
+  where
+    haskellDependencies s = Set.map (view (localName . ident))
+      . Set.filter isFromHackage
+      $ view (s . (haskell <> tool)) _nodeDerivation
+    _nodeTestDepends = haskellDependencies testDepends
+    _nodeOtherDepends = haskellDependencies (executableDepends <> libraryDepends)
+
+nodeName :: Node -> String
+nodeName = unPackageName . packageName . view (nodeDerivation . pkgid)
+
+nodeDepends :: Node -> Set.Set String
+nodeDepends = _nodeTestDepends <> _nodeOtherDepends
+
+findCycles :: [Node] -> [[Node]]
+findCycles nodes = mapMaybe cyclic $
+  Graph.stronglyConnComp [(node, nodeName node, Set.toList $ nodeDepends node) | node <- nodes]
+ where
+  cyclic (Graph.AcyclicSCC _) = Nothing
+  cyclic (Graph.CyclicSCC c)  = Just c
+
+breakCycle :: [Node] -> [String]
+breakCycle [] = []
+breakCycle c = nodeName breaker : concatMap breakCycle (findCycles remaining)
+ where
+  breaker = F.maximumBy (O.comparing rate) c
+  remaining = map updateNode c
+  updateNode n
+    | nodeName n == nodeName breaker = n & nodeTestDepends .~ mempty
+    | otherwise = n
+  names = Set.fromList $ map nodeName c
+  rate node = - Set.size (view nodeOtherDepends node `Set.intersection` names)
+
+type FromVertex = Vertex -> (Node, String, [String])
+type FromKey = String -> Maybe Vertex
+
+buildNodeGraph :: [Node] -> (Graph, FromVertex, FromKey)
+buildNodeGraph nodes = Graph.graphFromEdges
+  [(node, nodeName node, Set.toList $ nodeDepends node) | node <- nodes]
+
+reachableDependencies :: [Node] -> [Node] -> [Node]
+reachableDependencies keyNodes nodes = view _1 . fromVertex <$> reachableVertices
+  where
+    (graph, fromVertex, fromKey) = buildNodeGraph nodes
+    keys = mapMaybe (fromKey . nodeName) keyNodes
+    reachableVertices = concatMap F.toList $ Graph.dfs graph keys
+
+-- pretty printing
+
+isFromHackage :: Binding -> Bool
+isFromHackage b = case view (reference . Nix.path) b of
+  ["self",_] -> True
+  _          -> False
+
+
+pPrintOutConfig :: SystemInfo -> [Node] -> Doc
+pPrintOutConfig systemInfo nodes = vcat
+  [ "{ pkgs }:"
+  , ""
+  , "with pkgs.haskell.lib; self: super: {"
+  , ""
+  , "  # core packages"
+  , nest 2 $ vcat $
+      Map.toList (siCorePackages systemInfo) <&> \(pkg, _version) ->
+        onlyIf (pkg /= "ghc")
+          $ hsep [doubleQuotes (text (display pkg)), equals, text "null"] <> semi
+  , ""
+  , nest 2 $ vcat $ pPrintBreakCycle <$> findCycles nodes
+  , ""
+  , "}"
+  ]
+
+pPrintBreakCycle :: [Node] -> Doc
+pPrintBreakCycle c = vcat
+  [ text $ "# break cycle: " ++ unwords (map nodeName c)
+  , vcat $ breakCycle c <&> \breaker ->
+      hsep [doubleQuotes (text breaker), equals, text "dontCheck", text "super." <> text breaker] <> semi
+  ]
+
+pPrintOutPackages :: [Derivation] -> Doc
+pPrintOutPackages drvs = vcat
+  [ "{ pkgs, stdenv, callPackage }:"
+  , ""
+  , "self: {"
+  , ""
+  , nest 2 $ vcat (pPrintPackageOverride <$> drvs)
+  , ""
+  , "}"
+  ]
+
+pPrintPackageOverride :: Derivation -> Doc
+pPrintPackageOverride drv =
+  let
+    name = drv ^. pkgid . to packageName
+    overrides = fsep
+      [ disp bind <> semi
+      | bind <- Set.toList $ view (dependencies . each <> extraFunctionArgs) drv
+      , not $ isFromHackage bind ]
+  in
+    hang (hsep [doubleQuotes (text (display name)), equals, text "callPackage"]) 2
+    $ parens (pPrint drv) <+> (braces overrides <> semi)
diff --git a/src/Distribution/Nixpkgs/Haskell/Packages/PrettyPrinting.hs b/src/Distribution/Nixpkgs/Haskell/Packages/PrettyPrinting.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Haskell/Packages/PrettyPrinting.hs
@@ -0,0 +1,28 @@
+module Distribution.Nixpkgs.Haskell.Packages.PrettyPrinting where
+
+import Language.Nix.PrettyPrinting
+
+compilerConfig :: Doc -> Doc
+compilerConfig body =
+  funargsCurried ["self", "super"] <+> body
+
+packageSetConfig :: Doc -> Doc
+packageSetConfig body = vcat
+  [ funargs ["pkgs", "stdenv", "callPackage"]
+  , ""
+  , funargsCurried ["self"] <+> "{"
+  , nest 2 body
+  , "}"]
+
+overrides :: Doc -> Doc
+overrides body = funargsCurried ["self", "super"] <+> body
+
+-- | Function argument
+-- output 'arg:'
+funarg :: Doc -> Doc
+funarg = flip (<>) colon
+
+-- | Function arguments curried
+-- output 'arg1: arg2:'
+funargsCurried :: [Doc] -> Doc
+funargsCurried = hsep . fmap funarg
diff --git a/src/Distribution/Nixpkgs/Haskell/Stack.hs b/src/Distribution/Nixpkgs/Haskell/Stack.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Haskell/Stack.hs
@@ -0,0 +1,102 @@
+module Distribution.Nixpkgs.Haskell.Stack where
+
+import Control.Lens
+import Data.Text as T
+import Distribution.Compiler as Compiler
+import Distribution.Nixpkgs.Fetch
+import Distribution.Nixpkgs.Haskell.Derivation
+import Distribution.Nixpkgs.Haskell.FromCabal as FromCabal
+import Distribution.Nixpkgs.Haskell.PackageSourceSpec as PackageSourceSpec
+import Distribution.PackageDescription as PackageDescription
+import Distribution.System as System
+import Network.URI as URI
+import Stack.Config
+
+
+newtype HackageDb = HackageDb { fromHackageDB :: T.Text }
+  deriving (Eq, Ord, Show)
+
+makePrisms ''HackageDb
+
+unHackageDb :: HackageDb -> String
+unHackageDb = T.unpack . fromHackageDB
+
+data StackPackagesConfig = StackPackagesConfig
+  { _spcHaskellResolver   :: !HaskellResolver
+  , _spcNixpkgsResolver   :: !NixpkgsResolver
+  , _spcTargetPlatform    :: !Platform
+  , _spcTargetCompiler    :: !CompilerInfo
+  , _spcFlagAssignment    :: !FlagAssignment
+  , _spcDoCheckPackages   :: !Bool
+  , _spcDoHaddockPackages :: !Bool
+  , _spcDoCheckStackage   :: !Bool
+  , _spcDoHaddockStackage :: !Bool
+  }
+
+makeLenses ''StackPackagesConfig
+
+getStackPackageFromDb
+  :: Maybe HackageDb
+  -> StackPackage
+  -> IO Package
+getStackPackageFromDb optHackageDb stackPackage =
+  PackageSourceSpec.getPackage
+    (unHackageDb <$> optHackageDb)
+    (stackLocationToSource $ stackPackage ^. spLocation)
+
+stackLocationToSource :: PackageLocation -> Source
+stackLocationToSource = \case
+  HackagePackage p -> Source
+    { sourceUrl      = "cabal://" ++ T.unpack p
+    , sourceRevision = mempty
+    , sourceHash     = UnknownHash
+    , sourceCabalDir = mempty }
+  StackFilePath p  -> Source
+    { sourceUrl      = p
+    , sourceRevision = mempty
+    , sourceHash     = UnknownHash
+    , sourceCabalDir = mempty }
+  StackUri uri     -> Source
+    { sourceUrl      = URI.uriToString id uri mempty
+    , sourceRevision = mempty
+    , sourceHash     = UnknownHash
+    , sourceCabalDir = mempty }
+  StackRepo r      -> Source
+    { sourceUrl      = T.unpack $ r ^. rUri
+    , sourceRevision = T.unpack $ r ^. rCommit
+    , sourceHash     = UnknownHash
+    , sourceCabalDir = mempty }
+
+packageDerivation
+  :: StackPackagesConfig
+  -> Maybe HackageDb
+  -> StackPackage
+  -> IO Derivation
+packageDerivation conf optHackageDb stackPackage = do
+  pkg <- getStackPackageFromDb optHackageDb stackPackage
+  let
+    drv = genericPackageDerivation conf pkg
+      & src .~ pkgSource pkg
+      -- TODO: remove after Nixos/Nixpkgs #27196 released
+      & enableSeparateDataOutput .~ False
+  return $ if stackPackage ^. spExtraDep
+    then drv
+      & doCheck &&~ conf ^. spcDoCheckStackage
+      & runHaddock &&~ conf ^. spcDoHaddockStackage
+    else drv
+      & doCheck &&~ conf ^. spcDoCheckPackages
+      & runHaddock &&~ conf ^. spcDoHaddockPackages
+
+genericPackageDerivation
+  :: StackPackagesConfig
+  -> Package
+  -> Derivation
+genericPackageDerivation conf pkg =
+  FromCabal.fromGenericPackageDescription
+    (conf ^. spcHaskellResolver)
+    (conf ^. spcNixpkgsResolver)
+    (conf ^. spcTargetPlatform)
+    (conf ^. spcTargetCompiler)
+    (conf ^. spcFlagAssignment)
+    []
+    (pkgCabal pkg)
diff --git a/src/Distribution/Nixpkgs/Haskell/Stack/PrettyPrinting.hs b/src/Distribution/Nixpkgs/Haskell/Stack/PrettyPrinting.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Nixpkgs/Haskell/Stack/PrettyPrinting.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE Rank2Types #-}
+
+module Distribution.Nixpkgs.Haskell.Stack.PrettyPrinting where
+
+import           Control.Lens
+import           Data.Foldable as F
+import           Data.List as L
+import           Data.List.NonEmpty as NE
+import           Data.Maybe
+import           Data.String
+import           Distribution.Nixpkgs.Haskell.Derivation
+import           Distribution.Nixpkgs.Haskell.Packages.PrettyPrinting as PP
+import           Distribution.Package
+import           Distribution.Text
+import           Distribution.Version (Version)
+import qualified Language.Nix.FilePath as Nix
+import           Language.Nix.PrettyPrinting as PP
+
+
+data OverrideConfig = OverrideConfig
+  { _ocGhc              :: !Version
+  , _ocStackagePackages :: !FilePath
+  , _ocStackageConfig   :: !FilePath
+  , _ocNixpkgs          :: !FilePath
+  }
+
+makeLenses ''OverrideConfig
+
+systemNixpkgs :: Doc
+systemNixpkgs = "<nixpkgs>"
+
+hasField :: Lens' a (Maybe b) -> a -> Bool
+hasField p = views p isJust
+
+overridePackages :: (Foldable t, Functor t) => t Derivation -> Doc
+overridePackages = PP.packageSetConfig . PP.cat . F.toList . fmap callPackage
+  where
+    drvNameQuoted   = PP.doubleQuotes . disp . pkgName . view pkgid
+    callPackage drv = hang
+      (drvNameQuoted drv <> " = callPackage") 2
+      (PP.parens (PP.pPrint drv) <+> "{};")
+
+importStackagePackages :: FilePath -> Doc
+importStackagePackages path = hsep
+  [ funarg "self",  "import", disp (fromString path :: Nix.FilePath), "{"
+  , "inherit pkgs stdenv;"
+  , "inherit (self) callPackage;"
+  , "}"
+  ]
+
+callStackageConfig :: FilePath -> Doc
+callStackageConfig path = hsep
+  [ "callPackage", disp (fromString path :: Nix.FilePath), "{}"]
+
+overrideHaskellPackages :: OverrideConfig -> NonEmpty Derivation -> Doc
+overrideHaskellPackages oc packages =
+  let
+    nixpkgs = if oc ^. ocNixpkgs . to fromString == systemNixpkgs
+      then systemNixpkgs
+      else (disp . (fromString :: FilePath -> Nix.FilePath)) (oc ^. ocNixpkgs)
+  in vcat
+  [ funargs
+    [ "nixpkgs ? import " <> nixpkgs <> " {}"
+    ]
+  , ""
+  , "with nixpkgs;"
+  , "let"
+  , nest 2 "inherit (stdenv.lib) extends;"
+  , nest 2 $ vcat
+    [ attr "stackagePackages" . importStackagePackages $ oc ^. ocStackagePackages
+    , attr "stackageConfig" . callStackageConfig $ oc ^. ocStackageConfig ]
+  , nest 2 $ vcat
+    [ "stackPackages ="
+    , nest 2 $ overridePackages packages <> semi
+    , ""
+    , "pkgOverrides = self: stackPackages {"
+    , nest 2 "inherit pkgs stdenv;"
+    , nest 2 "inherit (self) callPackage;"
+    , "};"
+    , ""
+    ]
+  , "in callPackage (nixpkgs.path + \"/pkgs/development/haskell-modules\") {"
+  , nest 2 $ vcat
+    [ attr "ghc" ("pkgs.haskell.compiler." <> toNixGhcVersion (oc ^. ocGhc))
+    , attr "compilerConfig" "self: extends pkgOverrides (extends stackageConfig (stackagePackages self))"
+    , attr "haskellLib" "callPackage (nixpkgs.path + \"/pkgs/development/haskell-modules/lib.nix\") {}"
+    ]
+  , "}"
+  ]
+
+toNixGhcVersion :: Version -> Doc
+toNixGhcVersion =
+  (<>) "ghc" . text . L.filter (/= '.') . show . disp
diff --git a/src/Language/Nix/FilePath.hs b/src/Language/Nix/FilePath.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Nix/FilePath.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Language.Nix.FilePath where
+
+import           Control.DeepSeq
+import           Control.Lens
+import           Control.Monad
+import           Data.Maybe
+import           Data.String
+import           Distribution.Compat.ReadP as ReadP
+import           Distribution.Text
+import           GHC.Generics (Generic)
+import           Prelude hiding (FilePath)
+import qualified System.FilePath.Posix as Posix
+import           Test.QuickCheck as QC
+import           Text.PrettyPrint as PP
+
+
+newtype FilePath = FilePath { unFilePath :: String }
+  deriving (Show, Eq, Ord, Generic)
+
+makePrisms ''FilePath
+
+instance NFData FilePath where
+  rnf = rnf . unFilePath
+
+instance Arbitrary FilePath where
+  arbitrary = FilePath <$> QC.suchThat arbitrary Posix.isValid
+  shrink = fmap FilePath . shrink . unFilePath
+
+instance Text FilePath where
+  disp = text . renderFilePath
+  parse = parseFilePath
+
+instance IsString FilePath where
+  fromString s = fromMaybe (error $ "invalid Nix file path:" ++ s) (simpleParse s)
+
+parseFilePath :: ReadP r FilePath
+parseFilePath = do
+  path <- ReadP.munch (const True)
+  if Posix.isValid path
+    then pure (FilePath path)
+    else pfail
+
+renderFilePath :: FilePath -> String
+renderFilePath = Posix.combine "."
+               . Posix.normalise
+               . Posix.dropTrailingPathSeparator
+               . unFilePath
diff --git a/src/LtsHaskell.hs b/src/LtsHaskell.hs
new file mode 100644
--- /dev/null
+++ b/src/LtsHaskell.hs
@@ -0,0 +1,96 @@
+module LtsHaskell where
+
+import AllCabalHashes
+import Control.Lens
+import Control.Monad.Catch
+import Data.Maybe
+import Data.Text as T
+import Distribution.Text as Text (display)
+import Control.Monad
+import Distribution.Compiler (CompilerInfo(..), AbiTag(NoAbiTag), CompilerId(..), CompilerFlavor(GHC))
+import Distribution.System (Platform(..))
+import Distribution.Package (PackageName, PackageIdentifier(..), Dependency(..))
+import Distribution.Nixpkgs.Haskell.PackageSourceSpec as PackageSourceSpec
+import Distribution.Nixpkgs.Haskell.FromStack
+import Distribution.Nixpkgs.Haskell.Stack
+import Distribution.Nixpkgs.Fetch
+import Distribution.Nixpkgs.PackageMap (readNixpkgPackageMap, resolve)
+import Distribution.Version (Version, withinRange)
+import Language.Haskell.Extension (Language(Haskell98, Haskell2010))
+import Language.Nix
+import Stack.Config
+import Stackage.BuildPlan
+import Stackage.Types (SystemInfo(..))
+import System.FilePath as Path
+
+import qualified Data.Yaml as Yaml
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+
+
+loadBuildPlan :: FilePath -> IO BuildPlan
+loadBuildPlan = Yaml.decodeFileEither >=> \case
+  Left err -> fail $ "Failed to parse stackage build plan: " ++ show err
+  Right bp -> return bp
+
+getPackageFromRepo :: FilePath -> Maybe SHA1Hash -> PackageIdentifier -> IO Package
+getPackageFromRepo allCabalHashesPath mSha1Hash pkgId = do
+  (pkgDesc, _) <- case mSha1Hash of
+    Just sha1 ->
+      readPackageByHash allCabalHashesPath sha1
+      `catchAll` const (readPackageByName allCabalHashesPath pkgId)
+    Nothing   -> readPackageByName allCabalHashesPath pkgId
+  meta <- readPackageMeta allCabalHashesPath pkgId
+  let
+    tarballSHA256 = fromMaybe
+      (error (display pkgId ++ ": meta data has no SHA256 hash for the tarball"))
+      (view (mHashes . at "SHA256") meta)
+    source = DerivationSource "url" ("mirror://hackage/" ++ display pkgId ++ ".tar.gz") "" tarballSHA256
+  return $ Package source pkgDesc
+
+getPackageFromDb :: PackageIdentifier -> IO Package
+getPackageFromDb pkgId =
+  getStackPackageFromDb Nothing
+  $ StackPackage (HackagePackage (T.pack $ Text.display pkgId)) True
+
+loadPackage :: FilePath -> Maybe SHA1Hash -> PackageIdentifier -> IO Package
+loadPackage allCabalHashesPath mSha1Hash pkgId =
+  getPackageFromRepo allCabalHashesPath mSha1Hash pkgId
+  `catchIOError` const (getPackageFromDb pkgId)
+
+ghcCompilerInfo :: Version -> CompilerInfo
+ghcCompilerInfo v = CompilerInfo
+  { compilerInfoId = CompilerId GHC v
+  , compilerInfoAbiTag = NoAbiTag
+  , compilerInfoCompat = Just []
+  , compilerInfoLanguages = Just [Haskell98, Haskell2010]
+  , compilerInfoExtensions = Nothing
+  }
+
+buildPlanContainsDependency :: Map.Map PackageName Version -> Dependency -> Bool
+buildPlanContainsDependency packageVersions (Dependency depName versionRange) =
+  maybe False (`withinRange` versionRange) $ Map.lookup depName packageVersions
+
+buildPackageSetConfig
+  :: FilePath
+  -> FilePath
+  -> BuildPlan
+  -> IO PackageSetConfig
+buildPackageSetConfig optAllCabalHashes optNixpkgsRepository buildPlan = do
+  nixpkgs <- readNixpkgPackageMap optNixpkgsRepository Nothing
+  let
+    systemInfo      = bpSystemInfo buildPlan
+    packageVersions = fmap ppVersion (bpPackages buildPlan) `Map.union` siCorePackages systemInfo
+  return PackageSetConfig
+    { packageLoader   = loadPackage optAllCabalHashes
+    , targetPlatform  = Platform (siArch systemInfo) (siOS systemInfo)
+    , targetCompiler  = ghcCompilerInfo (siGhcVersion systemInfo)
+    , nixpkgsResolver = resolve (Map.map (Set.map (over path ("pkgs":))) nixpkgs)
+    , haskellResolver = buildPlanContainsDependency packageVersions }
+
+-- Path to Yaml build definition file in lts-haskell repository
+buildPlanFilePath :: FilePath -> StackResolver -> FilePath
+buildPlanFilePath ltsRepoDir resolver =
+  ltsRepoDir Path.</>
+  (resolver ^. to unStackResolver) Path.<.>
+  ".yaml"
diff --git a/src/Runner.hs b/src/Runner.hs
new file mode 100644
--- /dev/null
+++ b/src/Runner.hs
@@ -0,0 +1,104 @@
+module Runner ( run ) where
+
+import Control.Lens
+import Data.Foldable as F
+import Data.Functor
+import Distribution.Nixpkgs.Haskell.FromStack
+import Distribution.Nixpkgs.Haskell.FromStack.Package
+import Distribution.Nixpkgs.Haskell.Stack
+import Distribution.Nixpkgs.Haskell.Stack.PrettyPrinting as PP
+import Distribution.Version (Version)
+import Distribution.Compiler (AbiTag(..), unknownCompilerInfo)
+import Distribution.Package (mkPackageName, pkgName)
+import Distribution.Text as Text (display)
+import Language.Nix as Nix
+import Options.Applicative
+import Paths_stackage2nix ( version )
+import Runner.Cli
+import Stack.Config
+import Stack.Types
+import Stackage.Types
+import System.IO (withFile, IOMode(..), hPutStrLn, hPrint)
+import Text.PrettyPrint.HughesPJClass (render)
+
+import qualified Data.Set as Set
+import qualified Data.Map as Map
+import qualified LtsHaskell as LH
+
+
+run :: IO ()
+run = do
+  opts <- execParser pinfo
+  stackYaml <- envStackYaml >>= \case
+    Just p  -> putStrLn "Getting project config file from STACK_YAML environment" $> p
+    Nothing -> pure $ opts ^. optStackYaml
+  stackConf <- either fail pure =<< readStackConfig stackYaml
+  let buildPlanFile = LH.buildPlanFilePath (opts ^. optLtsHaskellRepo) (stackConf ^. scResolver)
+  buildPlan <- LH.loadBuildPlan buildPlanFile
+  packageSetConfig <- LH.buildPackageSetConfig
+    (opts ^. optAllCabalHashesRepo)
+    (opts ^. optNixpkgsRepository)
+    buildPlan
+  -- generate haskell packages override
+  let
+    overrideConfig = mkOverrideConfig opts (siGhcVersion $ bpSystemInfo buildPlan)
+    stackPackagesConfig = mkStackPackagesConfig opts
+  packages <- traverse (packageDerivation stackPackagesConfig (opts ^. optHackageDb))
+    $ stackConf ^. scPackages
+  let out = PP.overrideHaskellPackages overrideConfig packages
+  withFile (opts ^. optOutDerivation) WriteMode $ \h -> do
+    hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ stackYaml ^. syFilePath)
+    hPrint h out
+  let
+    reachable = Set.map mkPackageName
+      $ F.foldr1 Set.union
+      $ nodeDepends . mkNode <$> packages
+    s2nLoader mHash pkgId =
+      if pkgName pkgId `Set.member` reachable
+      then packageLoader packageSetConfig Nothing pkgId
+      else packageLoader packageSetConfig mHash pkgId
+    s2nPackageSetConfig = packageSetConfig { packageLoader = s2nLoader }
+    s2nPackageConfig = PackageConfig
+      { enableCheck   = opts ^. optDoCheckStackage
+      , enableHaddock = opts ^. optDoHaddockStackage }
+  allNodes <- traverse (uncurry (buildNodeM s2nPackageSetConfig s2nPackageConfig))
+    $ Map.toList (bpPackages buildPlan)
+
+  -- Find all reachable dependencies in stackage set to stick into
+  -- stackage packages file. This is performed on the full stackage
+  -- set rather than pruning stackage packages beforehand because
+  -- stackage does not concern itself with build tools while cabal2nix
+  -- does: pruning only after generating full set of packages allows
+  -- us to make sure all those extra dependencies are explicitly
+  -- listed as well.
+  let nodes = case opts ^. optOutPackagesClosure of
+        True -> flip reachableDependencies allNodes
+                -- Originally reachable nodes are root nodes
+                $ filter (\n -> mkPackageName (nodeName n) `Set.member` reachable) allNodes
+        False -> allNodes
+
+  withFile (opts ^. optOutStackagePackages) WriteMode $ \h -> do
+    hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ buildPlanFile)
+    hPutStrLn h $ render $ pPrintOutPackages (view nodeDerivation <$> nodes)
+  withFile (opts ^. optOutStackageConfig) WriteMode $ \h -> do
+    hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ buildPlanFile)
+    hPutStrLn h $ render $ pPrintOutConfig (bpSystemInfo buildPlan) nodes
+
+mkOverrideConfig :: Options -> Version -> OverrideConfig
+mkOverrideConfig opts ghcVersion = OverrideConfig
+  { _ocGhc              = ghcVersion
+  , _ocStackagePackages = opts ^. optOutStackagePackages
+  , _ocStackageConfig   = opts ^. optOutStackageConfig
+  , _ocNixpkgs          = opts ^. optNixpkgsRepository }
+
+mkStackPackagesConfig :: Options -> StackPackagesConfig
+mkStackPackagesConfig opts = StackPackagesConfig
+  { _spcHaskellResolver   = const True
+  , _spcNixpkgsResolver   = \i -> Just (Nix.binding # (i, Nix.path # [i]))
+  , _spcTargetPlatform    = opts ^. optPlatform
+  , _spcTargetCompiler    = unknownCompilerInfo (opts ^. optCompilerId) NoAbiTag
+  , _spcFlagAssignment    = []
+  , _spcDoCheckPackages   = opts ^. optDoCheckPackages
+  , _spcDoHaddockPackages = opts ^. optDoHaddockPackages
+  , _spcDoCheckStackage   = opts ^. optDoCheckStackage
+  , _spcDoHaddockStackage = opts ^. optDoHaddockStackage }
diff --git a/src/Runner/Cli.hs b/src/Runner/Cli.hs
new file mode 100644
--- /dev/null
+++ b/src/Runner/Cli.hs
@@ -0,0 +1,190 @@
+module Runner.Cli where
+
+import           Control.Lens
+import           Data.Monoid ( (<>) )
+import           Data.Text as T
+import qualified Distribution.Compat.ReadP as P
+import           Distribution.Compiler as Compiler
+import           Distribution.Nixpkgs.Haskell.Stack
+import           Distribution.System as System
+import qualified Distribution.Text as Text
+import           Options.Applicative as Opts
+import           Paths_stackage2nix ( version )
+import           Stack.Types
+import           System.Environment
+import           System.FilePath
+
+
+data Options = Options
+  { _optAllCabalHashesRepo  :: !FilePath
+  , _optLtsHaskellRepo      :: !FilePath
+  , _optOutStackagePackages :: !FilePath
+  , _optOutStackageConfig   :: !FilePath
+  , _optOutPackagesClosure  :: !Bool
+  , _optOutDerivation       :: !FilePath
+  , _optDoCheckPackages     :: !Bool
+  , _optDoHaddockPackages   :: !Bool
+  , _optDoCheckStackage     :: !Bool
+  , _optDoHaddockStackage   :: !Bool
+  , _optHackageDb           :: !(Maybe HackageDb)
+  , _optNixpkgsRepository   :: !FilePath
+  , _optCompilerId          :: !CompilerId
+  , _optPlatform            :: !Platform
+  , _optStackYamlArg        :: !FilePath
+  } deriving (Show)
+
+makeLenses ''Options
+
+envStackYaml :: IO (Maybe StackYaml)
+envStackYaml = fmap mkStackYaml <$> lookupEnv "STACK_YAML"
+
+optStackYaml :: Getter Options StackYaml
+optStackYaml = optStackYamlArg . to mkStackYaml
+
+mkStackYaml :: FilePath -> StackYaml
+mkStackYaml p = case splitFileName p of
+  (dir, "")   -> StackYaml dir "stack.yaml"
+  (dir, ".")  -> StackYaml dir "stack.yaml"
+  (_  , "..") -> StackYaml p "stack.yaml"
+  (dir, file) -> StackYaml dir file
+
+options :: Parser Options
+options = Options
+  <$> allCabalHashesRepo
+  <*> ltsHaskellRepo
+  <*> outStackagePackages
+  <*> outStackageConfig
+  <*> outPackagesClosure
+  <*> outDerivation
+  <*> doCheckPackages
+  <*> doHaddockPackages
+  <*> doCheckStackage
+  <*> doHaddockStackage
+  <*> optional hackageDb
+  <*> nixpkgsRepository
+  <*> compilerId
+  <*> platform
+  <*> stackYamlArg
+
+pinfo :: ParserInfo Options
+pinfo = info
+  ( helper
+    <*> infoOption ("stackage2nix " ++ Text.display version) (long "version" <> help "Show version")
+    <*> options )
+  ( fullDesc
+  <> header "stackage2nix converts Stack files into build instructions for Nix." )
+
+nixpkgsRepository :: Parser FilePath
+nixpkgsRepository = option str
+  ( long "nixpkgs"
+    <> metavar "NIXPKGS"
+    <> help "path to Nixpkgs repository"
+    <> value "<nixpkgs>"
+    <> showDefaultWith id )
+
+allCabalHashesRepo :: Parser FilePath
+allCabalHashesRepo = option str
+  ( long "all-cabal-hashes"
+    <> metavar "REPO_DIR"
+    <> help "path to commercialhaskell/all-cabal-hashes repository" )
+
+ltsHaskellRepo :: Parser FilePath
+ltsHaskellRepo = option str
+  ( long "lts-haskell"
+    <> metavar "REPO_DIR"
+    <> help "path to fpco/lts-haskell repository" )
+
+outStackagePackages :: Parser FilePath
+outStackagePackages = option str
+  ( long "out-packages"
+    <> metavar "NIX_FILE"
+    <> help "output file of the stackage packages set"
+    <> value "packages.nix"
+    <> showDefaultWith id)
+
+outStackageConfig :: Parser FilePath
+outStackageConfig = option str
+  ( long "out-config"
+    <> metavar "NIX_FILE"
+    <> help "output file of the stackage packages compiler config"
+    <> value "configuration-packages.nix"
+    <> showDefaultWith id)
+
+outPackagesClosure :: Parser Bool
+outPackagesClosure = flag True False
+  ( long "no-packages-closure"
+    <> help "produce full set of stackage packages, not just dependencies" )
+
+outDerivation :: Parser FilePath
+outDerivation = option str
+  ( long "out-derivation"
+    <> metavar "NIX_FILE"
+    <> help "path to output derivation"
+    <> value "default.nix"
+    <> showDefaultWith id )
+
+doCheckPackages :: Parser Bool
+doCheckPackages = flag True False
+  ( long "no-check"
+    <> help "disable tests for project packages")
+
+doHaddockPackages :: Parser Bool
+doHaddockPackages = flag True False
+  ( long "no-haddock"
+    <> help "disable haddock for project packages")
+
+doCheckStackage :: Parser Bool
+doCheckStackage = switch
+  ( long "do-check-stackage"
+    <> help "enable tests for Stackage packages" )
+
+doHaddockStackage :: Parser Bool
+doHaddockStackage = switch
+  ( long "do-haddock-stackage"
+    <> help "enable haddock for Stackage packages" )
+
+stackYamlArg :: Parser FilePath
+stackYamlArg = Opts.argument str
+  ( metavar "STACK_YAML"
+    <> help "path to stack.yaml file or directory" )
+
+-- inherited from cabal2nix
+
+hackageDb :: Parser HackageDb
+hackageDb = HackageDb <$>
+  option text
+    ( long "hackage-db"
+      <> metavar "HACKAGE_DB"
+      <> help "path to the local hackage db in tar format" )
+
+cabalFlag :: Parser CabalFlag
+cabalFlag = CabalFlag <$>
+  option text
+    ( short 'f'
+      <> long "flag"
+      <> help "Cabal flag (may be specified multiple times)" )
+
+compilerId :: Parser CompilerId
+compilerId = option (readP Text.parse)
+  ( long "compiler"
+    <> help "compiler to use when evaluating the Cabal file"
+    <> value buildCompilerId
+    <> showDefaultWith Text.display )
+
+platform :: Parser Platform
+platform = option (readP Text.parse)
+  ( long "system"
+    <> help "target system to use when evaluating the Cabal file"
+    <> value buildPlatform
+    <> showDefaultWith Text.display )
+
+-- utils
+
+text :: ReadM Text
+text = T.pack <$> str
+
+readP :: P.ReadP a a -> ReadM a
+readP p = eitherReader $ \s ->
+  case [ r' | (r',"") <- P.readP_to_S p s ] of
+    (r:_) -> Right r
+    _     -> Left ("invalid value " ++ show s)
diff --git a/src/Stack/Config.hs b/src/Stack/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Config.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE ViewPatterns #-}
+
+module Stack.Config where
+
+import Control.Lens
+import Data.Bifunctor
+import Data.ByteString as BS
+import Data.Coerce
+import Data.Foldable as F
+import Data.List.NonEmpty as NE
+import Data.Maybe
+import Data.Text as T
+import Data.Yaml
+import Network.URI
+import Stack.Config.Yaml as Yaml
+import Stack.Types
+import System.FilePath
+
+
+newtype StackResolver = StackResolver { fromStackResolver :: Text }
+  deriving (Eq, Ord, Show)
+
+makePrisms ''StackResolver
+
+unStackResolver :: StackResolver -> String
+unStackResolver = T.unpack . fromStackResolver
+
+newtype Vcs = Vcs { fromVcs :: Text }
+  deriving (Eq, Ord, Show)
+
+data Repo = Repo
+  { _rVcs    :: !Vcs
+  , _rUri    :: !Text
+  , _rCommit :: !Text
+  } deriving (Eq, Ord, Show)
+
+makeLenses ''Repo
+
+data PackageLocation
+  = HackagePackage Text
+  | StackFilePath FilePath
+  | StackUri URI
+  | StackRepo Repo
+  deriving (Eq, Ord, Show)
+
+makePrisms ''PackageLocation
+
+data StackPackage = StackPackage
+  { _spLocation :: !PackageLocation
+  , _spExtraDep :: !Bool
+  } deriving (Eq, Ord, Show)
+
+makeLenses ''StackPackage
+
+data StackConfig = StackConfig
+  { _scResolver  :: !StackResolver
+  , _scPackages  :: !(NonEmpty StackPackage)
+  } deriving (Eq, Ord, Show)
+
+makeLenses ''StackConfig
+
+fromYamlConfig :: Yaml.Config -> StackConfig
+fromYamlConfig c = StackConfig{..}
+  where
+    _scResolver    = coerce $ c ^. cResolver
+    _scPackages    = F.foldr (NE.<|) neYamlPackages yamlExtraDeps
+    neYamlPackages = fromMaybe (pure defaultPackage) $ NE.nonEmpty yamlPackages
+    yamlPackages   = fromYamlPackage <$> fromMaybe mempty (c ^. cPackages)
+    yamlExtraDeps  = fromYamlExtraDep <$> fromMaybe mempty (c ^. cExtraDeps)
+    defaultPackage = StackPackage (StackFilePath ".") False
+
+fromYamlPackage :: Yaml.Package -> StackPackage
+fromYamlPackage = \case
+  Yaml.Simple p                                  ->
+    StackPackage (parseSimplePath p) False
+  Yaml.LocationSimple (Yaml.Location p extraDep) ->
+    StackPackage (parseSimplePath p) (fromMaybe False extraDep)
+  Yaml.LocationGit (Location git extraDep)       ->
+    StackPackage (StackRepo $ fromYamlGit git) (fromMaybe False extraDep)
+  Yaml.LocationHg (Location hg extraDep)       ->
+    StackPackage (StackRepo $ fromYamlHg hg) (fromMaybe False extraDep)
+  where
+    parseSimplePath (T.unpack -> p) = maybe (StackFilePath p) StackUri $ parseURI p
+
+fromYamlExtraDep :: Text -> StackPackage
+fromYamlExtraDep = flip StackPackage True . HackagePackage
+
+fromYamlGit :: Yaml.Git -> Repo
+fromYamlGit yg = Repo{..}
+  where
+    _rVcs = Vcs "git"
+    _rUri = yg ^. gGit
+    _rCommit = yg ^. gCommit
+
+fromYamlHg :: Yaml.Hg -> Repo
+fromYamlHg yh = Repo{..}
+  where
+    _rVcs = Vcs "hg"
+    _rUri = yh ^. hHg
+    _rCommit = yh ^. hCommit
+
+readStackConfig :: StackYaml -> IO (Either String StackConfig)
+readStackConfig stackYaml = do
+  let
+    relativeToStackYaml = \case
+      StackFilePath p -> StackFilePath $ stackYaml ^. syDirName </> p
+      packageLocation -> packageLocation
+    mkStackConfig = over (scPackages . traversed . spLocation) relativeToStackYaml
+      . fromYamlConfig
+  second mkStackConfig . decodeEither <$> BS.readFile (stackYaml ^. syFilePath)
diff --git a/src/Stack/Config/TH.hs b/src/Stack/Config/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Config/TH.hs
@@ -0,0 +1,23 @@
+module Stack.Config.TH where
+
+import Data.Aeson.TH
+import Data.Char as C
+import Data.List as L
+import Data.Text as T
+import Text.Inflections as TI
+
+toDashedField :: String -> String
+toDashedField = toDashed' . dropPrefix
+
+toDashed' :: String -> String
+toDashed' = either (const $ error "toDashed failed") T.unpack
+  . TI.toDashed
+  . T.pack
+
+dropPrefix :: String -> String
+dropPrefix = L.dropWhile (not . C.isUpper)
+
+jsonOpts :: Options
+jsonOpts = defaultOptions
+  { fieldLabelModifier = toDashedField
+  , omitNothingFields  = True }
diff --git a/src/Stack/Config/Yaml.hs b/src/Stack/Config/Yaml.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Config/Yaml.hs
@@ -0,0 +1,68 @@
+module Stack.Config.Yaml where
+
+import Control.Applicative
+import Control.Lens
+import Data.Aeson
+import Data.Aeson.TH
+import Data.Text as T
+import Stack.Config.TH
+
+data Location a = Location
+  { _lLocation :: !a
+  , _lExtraDep :: !(Maybe Bool)
+  } deriving (Eq, Show)
+
+makeLenses ''Location
+
+deriveJSON jsonOpts ''Location
+
+data Git = Git
+  { _gGit    :: !Text
+  , _gCommit :: !Text
+  } deriving (Eq, Show)
+
+makeLenses ''Git
+
+deriveJSON jsonOpts ''Git
+
+data Hg = Hg
+  { _hHg     :: !Text
+  , _hCommit :: !Text
+  } deriving (Eq, Show)
+
+makeLenses ''Hg
+
+deriveJSON jsonOpts ''Hg
+
+data Package
+  = Simple Text
+  | LocationSimple (Location Text)
+  | LocationGit (Location Git)
+  | LocationHg (Location Hg)
+  deriving (Eq, Show)
+
+makePrisms ''Package
+
+instance FromJSON Package where
+  parseJSON v =
+    (Simple <$> parseJSON v) <|>
+    (LocationSimple <$> parseJSON v) <|>
+    (LocationGit <$> parseJSON v) <|>
+    (LocationHg <$> parseJSON v)
+
+instance ToJSON Package where
+  toJSON = \case
+    Simple t         -> toJSON t
+    LocationSimple t -> toJSON t
+    LocationGit t    -> toJSON t
+    LocationHg t     -> toJSON t
+
+data Config = Config
+  { _cResolver  :: !Text
+  , _cPackages  :: !(Maybe [Package])
+  , _cExtraDeps :: !(Maybe [Text])
+  } deriving (Eq, Show)
+
+makeLenses ''Config
+
+deriveJSON jsonOpts ''Config
diff --git a/src/Stack/Types.hs b/src/Stack/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack/Types.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Stack.Types where
+
+import Control.Lens
+import Data.Text as T
+import System.FilePath
+
+
+data StackYaml = StackYaml
+  { _syDirName  :: !FilePath
+  , _syFileName :: !FilePath }
+
+makeLenses ''StackYaml
+
+syFilePath :: Getter StackYaml FilePath
+syFilePath = to stackYamlPath
+  where
+    stackYamlPath sy = sy ^. syDirName </> sy ^. syFileName
+
+newtype CabalFlag = CabalFlag { fromCabalFlag :: Text }
+  deriving (Ord, Eq, Show)
+
+makePrisms ''CabalFlag
+
+unCabalFlag :: CabalFlag -> String
+unCabalFlag = T.unpack . fromCabalFlag
diff --git a/stackage2nix.cabal b/stackage2nix.cabal
new file mode 100644
--- /dev/null
+++ b/stackage2nix.cabal
@@ -0,0 +1,94 @@
+name:                stackage2nix
+version:             0.3.0
+synopsis:            Convert Stack files into Nix build instructions.
+homepage:            https://github.com/4e6/stackage2nix#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Dmitry Bushev
+                   , Benno Fünfstück
+maintainer:          bushevdv@gmail.com
+category:            Distribution, Nix
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+extra-source-files:  CHANGELOG.md
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Distribution.Nixpkgs.Haskell.FromStack
+                     , Distribution.Nixpkgs.Haskell.FromStack.Package
+                     , Distribution.Nixpkgs.Haskell.Packages.PrettyPrinting
+                     , Distribution.Nixpkgs.Haskell.Stack
+                     , Distribution.Nixpkgs.Haskell.Stack.PrettyPrinting
+                     , Language.Nix.FilePath
+                     , Runner
+                     , Runner.Cli
+                     , Stack.Config
+                     , Stack.Config.TH
+                     , Stack.Config.Yaml
+                     , Stack.Types
+  other-modules:       AllCabalHashes
+                     , LtsHaskell
+                     , Paths_stackage2nix
+  build-depends:       base > 4.7 && < 5
+                     , Cabal > 1.24
+                     , QuickCheck
+                     , aeson
+                     , bytestring
+                     , cabal2nix >= 2.5
+                     , containers
+                     , deepseq
+                     , distribution-nixpkgs >= 1.1
+                     , exceptions > 0.8
+                     , filepath
+                     , gitlib > 3
+                     , gitlib-libgit2 > 3
+                     , hopenssl > 2.2
+                     , inflections
+                     , language-nix
+                     , lens
+                     , network-uri
+                     , optparse-applicative
+                     , pretty
+                     , stackage-curator >= 0.15
+                     , text
+                     , unordered-containers
+                     , yaml
+  default-extensions:  LambdaCase
+                     , OverloadedStrings
+                     , RecordWildCards
+                     , TemplateHaskell
+  default-language:    Haskell2010
+  ghc-options:         -Wall -funbox-strict-fields
+
+executable stackage2nix
+  hs-source-dirs:      stackage2nix
+  main-is:             Main.hs
+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends:       base
+                     , stackage2nix
+  default-language:    Haskell2010
+
+test-suite spec
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:       base
+                     , Cabal
+                     , bytestring
+                     , hspec
+                     , pretty
+                     , shakespeare
+                     , stackage2nix
+                     , text
+                     , yaml
+  default-extensions:  OverloadedStrings
+  other-modules:       Language.Nix.FilePathSpec
+                     , PrettyPrintingSpec
+                     , Stack.Config.YamlSpec
+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/4e6/stackage2nix
diff --git a/stackage2nix/Main.hs b/stackage2nix/Main.hs
new file mode 100644
--- /dev/null
+++ b/stackage2nix/Main.hs
@@ -0,0 +1,6 @@
+module Main where
+
+import Runner
+
+main :: IO ()
+main = run
diff --git a/test/Language/Nix/FilePathSpec.hs b/test/Language/Nix/FilePathSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Language/Nix/FilePathSpec.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Language.Nix.FilePathSpec where
+
+import           Data.String
+import           Distribution.Text
+import qualified Language.Nix.FilePath as Nix
+import           Test.Hspec
+import           Text.PrettyPrint as PP
+
+displayPath :: String -> Doc -> SpecWith ()
+displayPath path result = specify path
+  $ disp (fromString path :: Nix.FilePath) `shouldBe` result
+
+spec :: Spec
+spec = describe "valid FilePath" $ do
+  displayPath "foo" "./foo"
+  displayPath "./foo" "./foo"
+  displayPath "../foo" "./../foo"
+  displayPath "/foo" "/foo"
+  displayPath "." "./."
diff --git a/test/PrettyPrintingSpec.hs b/test/PrettyPrintingSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/PrettyPrintingSpec.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module PrettyPrintingSpec where
+
+import Control.Monad
+import Distribution.Nixpkgs.Haskell.Packages.PrettyPrinting
+import Test.Hspec
+
+-- test stuff
+spec :: Spec
+spec = specify "test" $
+  when False $ print (overrides "body")
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/Stack/Config/YamlSpec.hs b/test/Stack/Config/YamlSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Stack/Config/YamlSpec.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module Stack.Config.YamlSpec where
+
+import Data.ByteString
+import Data.Text.Encoding as T
+import Data.Yaml as Y
+import Stack.Config.Yaml
+import Test.Hspec
+import Text.Shakespeare.Text
+
+configYamlMinimal :: ByteString
+configYamlMinimal = T.encodeUtf8 [st|
+resolver: lts-8.15
+packages:
+- '.'
+|]
+
+configYaml :: ByteString
+configYaml = T.encodeUtf8 [st|
+resolver: lts-3.7
+packages:
+  - .
+  - location: dir1/dir2
+  - location: https://example.com/foo/bar/baz-0.0.2.tar.gz
+    extra-dep: true
+  - location:
+      git: git@github.com:commercialhaskell/stack.git
+      commit: 6a86ee32e5b869a877151f74064572225e1a0398
+# Comment
+extra-deps:
+- acme-missiles-0.3
+|]
+
+configMinimal :: Config
+configMinimal = Config
+  { _cResolver  = "lts-8.15"
+  , _cPackages  = Just [ Simple "." ]
+  , _cExtraDeps = Nothing
+  }
+
+config :: Config
+config = Config
+  { _cResolver = "lts-3.7"
+  , _cPackages = Just
+    [ Simple "."
+    , LocationSimple (Location "dir1/dir2" Nothing)
+    , LocationSimple (Location "https://example.com/foo/bar/baz-0.0.2.tar.gz" (Just True))
+    , LocationGit $ Location
+       (Git
+        "git@github.com:commercialhaskell/stack.git"
+        "6a86ee32e5b869a877151f74064572225e1a0398")
+       Nothing]
+  , _cExtraDeps = Just
+    ["acme-missiles-0.3"]
+  }
+
+spec :: Spec
+spec = describe "Parse" $ do
+  specify "config-minimal" $
+    (Y.decode configYamlMinimal :: Maybe Config) `shouldBe` Just configMinimal
+  specify "config-full" $
+    (Y.decode configYaml :: Maybe Config) `shouldBe` Just config
