diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2017 IOHK
+
+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,59 @@
+# stack2nix
+
+[![Build Status](https://travis-ci.org/input-output-hk/stack2nix.svg)](https://travis-ci.org/input-output-hk/stack2nix)
+
+## About
+
+`stack2nix` automates conversion from [Stack](https://docs.haskellstack.org/en/stable/README/) configuration file to [Nix](http://nixos.org/nix/) expressions.
+
+`stack2nix` high-level workflow:
+
+- invoke `stack list-dependencies` to determine complete fixed version list of packages based on resolver
+- apply any additional configuration (local packages, extra dependencies, etc) from `stack.yaml`
+- generate complete list of dependencies to Nix expressions, replacing upstream `hackage-packages.nix`
+
+## Installation
+
+There are two options. The first is to install stack2nix and its dependencies on your machine directly, and the second is to use the supported virtual machine configuration.
+
+If there are difficulties please file an issue. Generally the virtual machine approach should be more reliable.
+
+### Native Environment
+
+1. Install [nix](https://nixos.org/nix/).
+2. Clone this repo.
+3. Run `stack install` to install.
+4. Ensure `cabal2nix` v2.2.1 or higher and `stack2nix` are in your `$PATH`.
+
+### Virtual Machine
+
+1. Install [VirtualBox](https://www.virtualbox.org/wiki/VirtualBox) and [Vagrant](https://www.vagrantup.com/).
+2. Clone this repo.
+3. Run `./scripts/vagrant.sh` and take a coffee break.
+4. If there are no errors, log into the VM: `vagrant ssh`.
+
+## Usage
+
+Nix expressions generated by stack2nix depend on a somewhat recent upstream change to nixpkgs. If `nix-env` or `nix-build` fails with an error mentioning `initialPackages`, try setting the `NIX_PATH` environment variable: `NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs/archive/21a8239452adae3a4717772f4e490575586b2755.tar.gz`.
+
+### Remote Packages
+
+Stack2nix can generate a nix expressions for Haskell packages hosted in git repositories.
+
+```
+    $ stack2nix --revision 242e2a064f6a32b22e1599bbfe72e64d7b6203b8 https://github.com/jgm/pandoc.git > demo.nix
+    $ nix-build -A pandoc demo.nix
+```
+
+### Local Packages
+
+Sometimes it's convenient to build local Haskell packages. Assuming the current directory is a locally maintained fork of Pandoc:
+
+```
+    $ stack2nix . > default.nix
+    $ nix-build -A pandoc
+```
+
+## Testing
+
+Run `./scripts/travis.sh` to build and test.
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/Stack2nix.hs b/src/Stack2nix.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack2nix.hs
@@ -0,0 +1,362 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+module Stack2nix
+  ( Args(..)
+  , Package(..)
+  , RemotePkgConf(..)
+  , StackConfig(..)
+  , parseStackYaml
+  , stack2nix
+  , version
+  ) where
+
+import           Control.Concurrent.Async
+import           Control.Concurrent.MSem
+import           Control.Exception            (SomeException, catch,
+                                               onException)
+import           Control.Monad                (unless)
+import qualified Data.ByteString              as BS
+import           Data.Fix                     (Fix (..))
+import           Data.List                    (foldl', sort, union, (\\))
+import qualified Data.Map.Strict              as Map
+import           Data.Maybe                   (fromMaybe, listToMaybe)
+import           Data.Monoid                  ((<>))
+import           Data.Text                    (Text, pack, unpack)
+import qualified Data.Traversable             as T
+import           Data.Version                 (Version (..), parseVersion,
+                                               showVersion)
+import           Data.Yaml                    (FromJSON (..), (.!=), (.:),
+                                               (.:?))
+import qualified Data.Yaml                    as Y
+import           Distribution.Text            (display)
+import           Nix.Expr                     (Binding (..), NExpr, NExprF (..),
+                                               NKeyName (..), ParamSet (..),
+                                               Params (..))
+import           Nix.Parser                   (Result (..), parseNixFile,
+                                               parseNixString)
+import           Nix.Pretty                   (prettyNix)
+import           Paths_stack2nix              (version)
+import           Stack2nix.External           (cabal2nix)
+import           Stack2nix.External.Util      (runCmd, runCmdFrom)
+import           Stack2nix.External.VCS.Git   (Command (..), ExternalCmd (..),
+                                               InternalCmd (..), git)
+import           System.Directory             (doesFileExist)
+import           System.Environment           (getEnv)
+import           System.FilePath              (dropExtension, isAbsolute,
+                                               normalise, takeDirectory,
+                                               takeFileName, (</>))
+import           System.FilePath.Glob         (glob)
+import           System.IO                    (hPutStrLn, stderr, stdout)
+import           System.IO.Temp               (withSystemTempDirectory)
+import           Text.ParserCombinators.ReadP (readP_to_S)
+
+data Args = Args
+  { argRev     :: Maybe String
+  , argOutFile :: Maybe FilePath
+  , argUri     :: String
+  }
+  deriving (Show)
+
+data StackConfig = StackConfig
+  { resolver  :: Text
+  , packages  :: [Package]
+  , extraDeps :: [Text]
+  }
+  deriving (Show, Eq)
+
+data Package = LocalPkg FilePath
+             | RemotePkg RemotePkgConf
+             deriving (Show, Eq)
+
+data RemotePkgConf = RemotePkgConf
+  { gitUrl   :: Text
+  , commit   :: Text
+  , extraDep :: Bool
+  }
+  deriving (Show, Eq)
+
+instance FromJSON StackConfig where
+  parseJSON (Y.Object v) =
+    StackConfig <$>
+    v .: "resolver" <*>
+    v .: "packages" <*>
+    v .: "extra-deps"
+  parseJSON _ = fail "Expected Object for StackConfig value"
+
+instance FromJSON Package where
+  parseJSON (Y.String v) = return $ LocalPkg $ unpack v
+  parseJSON obj@(Y.Object _) = RemotePkg <$> parseJSON obj
+  parseJSON _ = fail "Expected String or Object for Package value"
+
+instance FromJSON RemotePkgConf where
+  parseJSON (Y.Object v) = do
+    loc <- v .: "location"
+    gitUrl <- loc .: "git"
+    commit <- loc .: "commit"
+    extra <- v .:? "extra-dep" .!= False
+    return $ RemotePkgConf gitUrl commit extra
+  parseJSON _ = fail "Expected Object for RemotePkgConf value"
+
+parseStackYaml :: BS.ByteString -> Maybe StackConfig
+parseStackYaml = Y.decode
+
+checkRuntimeDeps :: IO ()
+checkRuntimeDeps = do
+  checkVer "cabal2nix" "2.2.1"
+  checkVer "git" "2"
+  checkVer "cabal" "1"
+  where
+    checkVer prog minVer = do
+      hPutStrLn stderr $ unwords ["Ensuring", prog, "version is >=", minVer, "..."]
+      result <- runCmd prog ["--version"] `onException` (error $ "Failed to run " ++ prog ++ ". Not found in PATH.")
+      case result of
+        Right out ->
+          let
+            -- heuristic for parsing version from stdout
+            firstLine = head . lines
+            lastWord = head . reverse . words
+            ver = parseVer . lastWord . firstLine $ out
+          in
+          unless (ver >= parseVer minVer) $ error $ unwords ["ERROR:", prog, "version must be", minVer, "or higher. Current version:", maybe "[parse failure]" showVersion ver]
+        Left err  -> error err
+
+    parseVer :: String -> Maybe Version
+    parseVer =
+      fmap fst . listToMaybe . reverse . readP_to_S parseVersion
+
+stack2nix :: Args -> IO ()
+stack2nix args@Args{..} = do
+  checkRuntimeDeps
+  updateCabalPackageIndex
+  isLocalRepo <- doesFileExist $ argUri </> "stack.yaml"
+  if isLocalRepo
+  then handleStackConfig Nothing argUri
+  else withSystemTempDirectory "s2n-" $ \tmpDir ->
+    tryGit tmpDir >> handleStackConfig (Just argUri) tmpDir
+  where
+    updateCabalPackageIndex :: IO ()
+    updateCabalPackageIndex =
+      getEnv "HOME" >>= \home -> runCmdFrom home "cabal" ["update"] >> return ()
+
+    tryGit :: FilePath -> IO ()
+    tryGit tmpDir = do
+      git $ OutsideRepo $ Clone argUri tmpDir
+      case argRev of
+        Just r  -> git $ InsideRepo tmpDir $ Checkout r
+        Nothing -> return mempty
+
+    handleStackConfig :: Maybe String -> FilePath -> IO ()
+    handleStackConfig remoteUri localDir = do
+      let fname = localDir </> "stack.yaml"
+      alreadyExists <- doesFileExist fname
+      unless alreadyExists $ runCmdFrom localDir "stack" ["init", "--system-ghc"]
+                             >> return ()
+      contents <- BS.readFile fname
+      case parseStackYaml contents of
+        Just config -> toNix args remoteUri localDir config
+        Nothing     -> error $ "Failed to parse " <> (localDir </> "stack.yaml")
+
+-- Credit: https://stackoverflow.com/a/18898822/204305
+mapPool :: T.Traversable t => Int -> (a -> IO b) -> t a -> IO (t b)
+mapPool max' f xs = do
+  sem <- new max'
+  mapConcurrently (with sem . f) xs
+
+c2nPoolSize :: Int
+c2nPoolSize = 4
+
+toNix :: Args -> Maybe String -> FilePath -> StackConfig -> IO ()
+toNix Args{..} remoteUri baseDir StackConfig{..} =
+  withSystemTempDirectory "s2n" $ \outDir -> do
+    _ <- mapPool c2nPoolSize (genNixFile outDir) packages
+    overrides <- mapPool c2nPoolSize overrideFor =<< updateDeps outDir
+    _ <- mapPool c2nPoolSize (genNixFile outDir) packages
+    _ <- mapPool c2nPoolSize patchNixFile =<< glob (outDir </> "*.nix")
+    writeFile (outDir </> "initialPackages.nix") $ initialPackages $ sort overrides
+    pullInNixFiles $ outDir </> "initialPackages.nix"
+    nf <- parseNixFile $ outDir </> "initialPackages.nix"
+    case nf of
+      Success expr ->
+        case argOutFile of
+          Just fname -> writeFile fname $ defaultNix expr
+          Nothing    -> hPutStrLn stdout $ defaultNix expr
+      _ -> error "failed to parse intermediary initialPackages.nix file"
+      where
+        updateDeps :: FilePath -> IO [FilePath]
+        updateDeps outDir = do
+          hPutStrLn stderr $ "Updating deps from " ++ baseDir
+          result <- runCmdFrom baseDir "stack" ["list-dependencies", "--system-ghc", "--separator", "-"]
+          case result of
+            Right pkgs -> do
+              let pkgs' = ["hscolour", "jailbreak-cabal", "cabal-doctest", "happy", "stringbuilder"] ++ lines pkgs
+              hPutStrLn stderr "Haskell dependencies:"
+              mapM_ (hPutStrLn stderr) pkgs'
+              _ <- mapPool c2nPoolSize (\d -> catch (handleExtraDep outDir d) ignoreError) $ pack <$> pkgs'
+              return ()
+            Left err -> error $ unlines ["FAILED: stack list-dependencies", err]
+          glob (outDir </> "*.nix")
+
+        -- TODO: Remove this.
+        ignoreError :: SomeException -> IO ()
+        ignoreError _ = return ()
+
+        genNixFile :: FilePath -> Package -> IO ()
+        genNixFile outDir (LocalPkg relPath) =
+          cabal2nix (fromMaybe baseDir remoteUri) (pack <$> argRev) (Just relPath) (Just outDir)
+        genNixFile outDir (RemotePkg RemotePkgConf{..}) =
+          cabal2nix (unpack gitUrl) (Just commit) Nothing (Just outDir)
+
+        patchNixFile :: FilePath -> IO ()
+        patchNixFile fname = do
+          contents <- readFile fname
+          case parseNixString contents of
+            Success expr ->
+              case takeFileName fname of
+                "hspec.nix" -> do
+                  writeFile fname $ show $ prettyNix $ (addParam "stringbuilder" . stripNonEssentialDeps) expr
+                _ -> writeFile fname $ show $ prettyNix $ stripNonEssentialDeps expr
+            _ -> error "failed to parse intermediary nix package file"
+
+        addParam :: String -> NExpr -> NExpr
+        addParam param expr =
+          let contents = show $ prettyNix expr
+              (l:ls) = lines contents
+              (openBrace, params) = splitAt 1 (words l)
+              l' = unwords $ openBrace ++ [param ++ ", "] ++ params
+          in
+          case parseNixString $ unlines (l':ls) of
+            Success expr' -> expr'
+            _             -> expr
+
+        stripNonEssentialDeps :: NExpr -> NExpr
+        stripNonEssentialDeps expr =
+          let sectsToDrop = ["testHaskellDepends", "testToolDepends", "benchmarkHaskellDepends", "benchmarkToolDepends"]
+              sectsToKeep = ["executableHaskellDepends", "executableToolDepends", "libraryHaskellDepends", "librarySystemDepends", "libraryToolDepends", "setupHaskellDepends"]
+              collectDeps sects = foldr union [] $ fmap (dependenciesFromSection expr) sects
+              depsToStrip = collectDeps sectsToDrop \\ collectDeps sectsToKeep
+              expr' = foldl dropDependencySection expr sectsToDrop
+          in
+          dropParams expr' depsToStrip
+
+        handleExtraDep :: FilePath -> Text -> IO ()
+        handleExtraDep outDir dep =
+          cabal2nix ("cabal://" <> unpack dep) Nothing Nothing (Just outDir)
+
+        overrideFor :: FilePath -> IO String
+        overrideFor nixFile = do
+          deps <- externalDeps
+          return $ "    " <> (dropExtension . takeFileName) nixFile <> " = callPackage ./" <> takeFileName nixFile <> " { " <> deps <> " };"
+          where
+            externalDeps :: IO String
+            externalDeps = do
+              deps <- librarySystemDeps nixFile
+              return . unwords $ fmap (\d -> unpack d <> " = pkgs." <> unpack d <> ";") deps
+
+        pullInNixFiles :: FilePath -> IO ()
+        pullInNixFiles nixFile = do
+          nf <- parseNixFile nixFile
+          case nf of
+            Success expr ->
+              case expr of
+                Fix (NAbs paramSet (Fix (NAbs fnParam (Fix (NSet attrs))))) -> do
+                  attrs' <- mapM patchAttr attrs
+                  let expr' = Fix (NAbs paramSet (Fix (NAbs fnParam (Fix (NSet attrs')))))
+                  writeFile nixFile $ (++ "\n") $ show $ prettyNix expr'
+                _ ->
+                  error $ "unhandled nix expression format\n" ++ show expr
+            _ -> error "failed to parse nix file!"
+            where
+              patchAttr :: Binding (Fix NExprF) -> IO (Binding (Fix NExprF))
+              patchAttr attr =
+                case attr of
+                  NamedVar k (Fix (NApp (Fix (NApp (Fix (NSym "callPackage")) pkg)) deps)) -> do
+                    pkg' <- patchPkgRef pkg
+                    return $ NamedVar k (Fix (NApp (Fix (NApp (Fix (NSym "callPackage")) pkg')) deps))
+                  _ -> error $ "unhandled NamedVar"
+
+              patchPkgRef (Fix (NLiteralPath path)) = do
+                let p = if isAbsolute path then path else normalise $ takeDirectory nixFile </> path
+                nf <- parseNixFile p
+                case nf of
+                  Success expr -> return expr
+                  _ -> error $ "failed to parse referenced nix file '" ++ path ++ "'"
+              patchPkgRef x                   = return x
+
+        dependenciesFromSection :: NExpr -> String -> [Text]
+        dependenciesFromSection expr name = do
+          case expr of
+            Fix (NAbs _ (Fix (NApp _ (Fix (NSet namedVars))))) ->
+              case lookupNamedVar namedVars name of
+                Just (Fix (NList deps)) ->
+                  foldl' (\acc x ->
+                            case x of
+                              Fix (NSym n) -> n : acc
+                              _            -> acc) [] deps
+                _ -> []
+            _ -> []
+
+        librarySystemDeps :: FilePath -> IO [Text]
+        librarySystemDeps nixFile = do
+          nf <- parseNixFile nixFile
+          case nf of
+            Success expr -> return $ dependenciesFromSection expr "librarySystemDepends"
+            _            -> return []
+
+        dropDependencySection :: NExpr -> String -> NExpr
+        dropDependencySection expr name =
+          case expr of
+            Fix (NAbs a (Fix (NApp b (Fix (NSet namedVars))))) ->
+              Fix (NAbs a (Fix (NApp b (Fix (NSet $ dropNamedVar namedVars name)))))
+            _ -> expr
+
+        lookupNamedVar :: [Binding a] -> String -> Maybe a
+        lookupNamedVar [] _ = Nothing
+        lookupNamedVar (x:xs) name =
+          case x of
+            NamedVar [StaticKey k] val ->
+              if unpack k == name
+              then Just val
+              else lookupNamedVar xs name
+            _ -> lookupNamedVar xs name
+
+        dropNamedVar :: [Binding a] -> String -> [Binding a]
+        dropNamedVar xs name = filter differentName xs
+          where
+            differentName (NamedVar [StaticKey k] _) = unpack k /= name
+            differentName _                          = True
+
+        dropParams :: NExpr -> [Text] -> NExpr
+        dropParams (Fix (NAbs (ParamSet (FixedParamSet paramMap) x)
+                    (Fix (NApp mkDeriv (Fix (NSet args)))))) names =
+          Fix (NAbs (ParamSet (FixedParamSet $ foldr Map.delete paramMap names) x)
+                    (Fix (NApp mkDeriv (Fix (NSet args)))))
+        dropParams x _ = x
+
+        initialPackages overrides = unlines $
+          [ "{ pkgs, stdenv, callPackage }:"
+          , ""
+          , "self: {"
+          ] ++ overrides ++
+          [ "}"
+          ]
+
+        defaultNix pkgsNixExpr = unlines
+          [ "# Generated using stack2nix " ++ display version ++ "."
+          , "#"
+          , "# Only works with sufficiently recent nixpkgs, e.g. \"NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs/archive/21a8239452adae3a4717772f4e490575586b2755.tar.gz\"."
+          , ""
+          , "{ pkgs ? (import <nixpkgs> {})"
+          , ", compiler ? pkgs.haskell.packages.ghc802"
+          , ", ghc ? pkgs.haskell.compiler.ghc802"
+          , "}:"
+          , ""
+          , "with (import <nixpkgs/pkgs/development/haskell-modules/lib.nix> { inherit pkgs; });"
+          , ""
+          , "let"
+          , "  stackPackages = " ++ show (prettyNix pkgsNixExpr) ++ ";"
+          , "in"
+          , "compiler.override {"
+          , "  initialPackages = stackPackages;"
+          , "}"
+          ]
diff --git a/src/Stack2nix/External.hs b/src/Stack2nix/External.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack2nix/External.hs
@@ -0,0 +1,7 @@
+module Stack2nix.External
+  ( module Stack2nix.External.Cabal2nix
+  , module Stack2nix.External.VCS
+  ) where
+
+import           Stack2nix.External.Cabal2nix
+import           Stack2nix.External.VCS
diff --git a/src/Stack2nix/External/Cabal2nix.hs b/src/Stack2nix/External/Cabal2nix.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack2nix/External/Cabal2nix.hs
@@ -0,0 +1,43 @@
+module Stack2nix.External.Cabal2nix (
+  cabal2nix
+  ) where
+
+import           Data.List               (stripPrefix, takeWhile)
+import           Data.Maybe              (fromMaybe)
+import           Data.Monoid             ((<>))
+import           Data.Text               (Text, unpack)
+import           Stack2nix.External.Util (runCmd)
+import           System.FilePath         ((</>))
+
+-- Requires cabal2nix >= 2.2 in PATH
+cabal2nix :: FilePath -> Maybe Text -> Maybe FilePath -> Maybe FilePath -> IO ()
+cabal2nix uri commit subpath odir = do
+  result <- runCmd exe (args $ fromMaybe "." subpath)
+  case result of
+    Right stdout ->
+      let basename = pname stdout <> ".nix"
+          fname = maybe basename (</> basename) odir
+      in
+      writeFile fname stdout
+    Left stderr  -> error stderr
+  where
+    exe = "cabal2nix"
+
+    args :: FilePath -> [String]
+    args dir = concat
+      [ maybe [] (\c -> ["--revision", unpack c]) commit
+      -- , maybe [] (\d -> ["--subpath", d]) subpath
+      , ["--subpath", dir]
+      , ["--no-check", "--no-haddock"]          -- TODO: only use on repos that need it.
+      , [uri]
+      ]
+
+    pname :: String -> String
+    pname = pname' . lines
+
+    pname' :: [String] -> String
+    pname' [] = error "nix expression generated by cabal2nix is missing the 'pname' attr"
+    pname' (x:xs) =
+      case stripPrefix "  pname = \"" x of
+        Just x' -> takeWhile (/= '"') x'
+        Nothing -> pname' xs
diff --git a/src/Stack2nix/External/Util.hs b/src/Stack2nix/External/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack2nix/External/Util.hs
@@ -0,0 +1,27 @@
+module Stack2nix.External.Util where
+
+import           Data.List        (intercalate)
+import           Data.Monoid      ((<>))
+import           System.Directory (getCurrentDirectory)
+import           System.Exit      (ExitCode (..))
+import           System.Process   (CreateProcess (..))
+import           System.Process   (proc, readCreateProcessWithExitCode)
+
+runCmdFrom :: FilePath -> String -> [String] -> IO (Either String String)
+runCmdFrom dir prog args = do
+  (exitCode, stdout, stderr) <- readCreateProcessWithExitCode (fromDir dir (proc prog args)) ""
+  case exitCode of
+    ExitSuccess -> return $ Right stdout
+    _           -> return $ Left $ "Command failed: " ++ cmd ++ "\n" ++ stderr
+  where
+    fromDir :: FilePath -> CreateProcess -> CreateProcess
+    fromDir d procDesc = procDesc { cwd = Just d }
+
+    cmd = "cd \"" <> dir <> "\" && " <> intercalate " " (prog:args)
+
+runCmd :: String -> [String] -> IO (Either String String)
+runCmd prog args = getCurrentDirectory >>= (\d -> runCmdFrom d prog args)
+
+failHard :: Show a => Either a b -> IO ()
+failHard (Left stderr) = error $ show stderr
+failHard (Right _)     = mempty
diff --git a/src/Stack2nix/External/VCS.hs b/src/Stack2nix/External/VCS.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack2nix/External/VCS.hs
@@ -0,0 +1,5 @@
+module Stack2nix.External.VCS
+  ( module Stack2nix.External.VCS.Git
+  ) where
+
+import           Stack2nix.External.VCS.Git
diff --git a/src/Stack2nix/External/VCS/Git.hs b/src/Stack2nix/External/VCS/Git.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack2nix/External/VCS/Git.hs
@@ -0,0 +1,29 @@
+module Stack2nix.External.VCS.Git
+  ( Command(..), ExternalCmd(..), InternalCmd(..)
+  , git
+  ) where
+
+import           Stack2nix.External.Util (failHard, runCmd, runCmdFrom)
+
+data Command = OutsideRepo ExternalCmd
+             | InsideRepo FilePath InternalCmd
+data ExternalCmd = Clone String FilePath
+data InternalCmd = Checkout CommitRef
+
+type CommitRef = String
+
+exe :: String
+exe = "git"
+
+-- Requires git binary in PATH
+git :: Command -> IO ()
+git (OutsideRepo cmd)    = runExternal cmd
+git (InsideRepo dir cmd) = runInternal dir cmd
+
+runExternal :: ExternalCmd -> IO ()
+runExternal (Clone uri dir) = do
+   runCmd exe ["clone", uri, dir] >>= failHard
+
+runInternal :: FilePath -> InternalCmd -> IO ()
+runInternal repoDir (Checkout ref) = do
+   runCmdFrom repoDir exe ["checkout", ref] >>= failHard
diff --git a/stack2nix.cabal b/stack2nix.cabal
new file mode 100644
--- /dev/null
+++ b/stack2nix.cabal
@@ -0,0 +1,54 @@
+name:                stack2nix
+version:             0.1.1.0
+synopsis:            Convert stack.yaml files into Nix build instructions.
+description:         Convert stack.yaml files into Nix build instructions.
+license:             MIT
+license-file:        LICENSE
+author:              Jacob Mitchell
+maintainer:          jacob.mitchell@iohk.io
+category:            Distribution, Nix
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >= 1.10
+
+source-repository head
+  type:     git
+  location: https://github.com/input-output-hk/stack2nix.git
+
+library
+  hs-source-dirs:      src
+  build-depends:       base >=4.9 && <4.10
+                     , Cabal
+                     , async
+                     , bytestring
+                     , containers
+                     , data-fix
+                     , directory
+                     , filepath
+                     , Glob
+                     , hnix >= 0.3.4
+                     , monad-parallel
+                     , process
+                     , SafeSemaphore
+                     , temporary
+                     , text
+                     , yaml
+  exposed-modules:     Stack2nix
+  other-modules:       Stack2nix.External
+                     , Stack2nix.External.Cabal2nix
+                     , Stack2nix.External.VCS
+                     , Stack2nix.External.VCS.Git
+                     , Stack2nix.External.Util
+                     , Paths_stack2nix
+  ghc-options:         -Wall
+  default-language:    Haskell2010
+
+executable stack2nix
+  main-is:             Main.hs
+  build-depends:       base >=4.9 && <4.10
+                     , Cabal
+                     , stack2nix
+                     , optparse-applicative
+  hs-source-dirs:      stack2nix
+  ghc-options:         -Wall
+  default-language:    Haskell2010
diff --git a/stack2nix/Main.hs b/stack2nix/Main.hs
new file mode 100644
--- /dev/null
+++ b/stack2nix/Main.hs
@@ -0,0 +1,22 @@
+module Main ( main ) where
+
+import           Data.Semigroup      ((<>))
+import           Distribution.Text   (display)
+import           Options.Applicative
+import           Stack2nix
+
+args :: Parser Args
+args = Args
+       <$> optional (strOption $ long "revision" <> help "revision to use when fetching from VCS")
+       <*> optional (strOption $ short 'o' <> help "output file for generated nix expression")
+       <*> strArgument (metavar "URI")
+
+main :: IO ()
+main = stack2nix =<< execParser opts
+  where
+    opts = info
+      (helper
+       <*> infoOption ("stack2nix " ++ display version) (long "version" <> help "Show version number")
+       <*> args) $
+      fullDesc
+      <> progDesc "Generate a nix expression for a Haskell package using stack"
