stackage2nix (empty) → 0.3.0
raw patch · 24 files changed
+1492/−0 lines, 24 filesdep +Cabaldep +QuickCheckdep +aesonsetup-changed
Dependencies added: Cabal, QuickCheck, aeson, base, bytestring, cabal2nix, containers, deepseq, distribution-nixpkgs, exceptions, filepath, gitlib, gitlib-libgit2, hopenssl, hspec, inflections, language-nix, lens, network-uri, optparse-applicative, pretty, shakespeare, stackage-curator, stackage2nix, text, unordered-containers, yaml
Files
- CHANGELOG.md +4/−0
- LICENSE +26/−0
- README.md +66/−0
- Setup.hs +2/−0
- src/AllCabalHashes.hs +82/−0
- src/Distribution/Nixpkgs/Haskell/FromStack.hs +93/−0
- src/Distribution/Nixpkgs/Haskell/FromStack/Package.hs +131/−0
- src/Distribution/Nixpkgs/Haskell/Packages/PrettyPrinting.hs +28/−0
- src/Distribution/Nixpkgs/Haskell/Stack.hs +102/−0
- src/Distribution/Nixpkgs/Haskell/Stack/PrettyPrinting.hs +93/−0
- src/Language/Nix/FilePath.hs +49/−0
- src/LtsHaskell.hs +96/−0
- src/Runner.hs +104/−0
- src/Runner/Cli.hs +190/−0
- src/Stack/Config.hs +110/−0
- src/Stack/Config/TH.hs +23/−0
- src/Stack/Config/Yaml.hs +68/−0
- src/Stack/Types.hs +27/−0
- stackage2nix.cabal +94/−0
- stackage2nix/Main.hs +6/−0
- test/Language/Nix/FilePathSpec.hs +21/−0
- test/PrettyPrintingSpec.hs +12/−0
- test/Spec.hs +1/−0
- test/Stack/Config/YamlSpec.hs +64/−0
+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# 0.3.0++- Initial Hackage release+- Add CHANGELOG.md
+ LICENSE view
@@ -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.
+ README.md view
@@ -0,0 +1,66 @@+# stackage2nix++[](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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/AllCabalHashes.hs view
@@ -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
+ src/Distribution/Nixpkgs/Haskell/FromStack.hs view
@@ -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
+ src/Distribution/Nixpkgs/Haskell/FromStack/Package.hs view
@@ -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)
+ src/Distribution/Nixpkgs/Haskell/Packages/PrettyPrinting.hs view
@@ -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
+ src/Distribution/Nixpkgs/Haskell/Stack.hs view
@@ -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)
+ src/Distribution/Nixpkgs/Haskell/Stack/PrettyPrinting.hs view
@@ -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
+ src/Language/Nix/FilePath.hs view
@@ -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
+ src/LtsHaskell.hs view
@@ -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"
+ src/Runner.hs view
@@ -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 }
+ src/Runner/Cli.hs view
@@ -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)
+ src/Stack/Config.hs view
@@ -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)
+ src/Stack/Config/TH.hs view
@@ -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 }
+ src/Stack/Config/Yaml.hs view
@@ -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
+ src/Stack/Types.hs view
@@ -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
+ stackage2nix.cabal view
@@ -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
+ stackage2nix/Main.hs view
@@ -0,0 +1,6 @@+module Main where++import Runner++main :: IO ()+main = run
+ test/Language/Nix/FilePathSpec.hs view
@@ -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 "." "./."
+ test/PrettyPrintingSpec.hs view
@@ -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")
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Stack/Config/YamlSpec.hs view
@@ -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