mirror of
https://github.com/simplex-chat/haskell.nix.git
synced 2026-06-03 09:17:32 +00:00
* Support revisions Cabal files are now obtained from the index tarball (as cabal does) directly. This allows us to always pick the cabal file revision that cabal would pick, without having to understand or reference hackage. * Update nix-tools materialised plan for new tooling * Update materialized files * Eval on x86_64-linux * Turn off full materialization check * Replace pkgs.writeText with passAsFile Nix derivations are already able to pass a attribute to the builder as a file. This means we don't need to use pkgs.writeText to turn the cabalFile attribute into a file, saving one derivation. * Bump closure size limit to 540 --------- Co-authored-by: Hamish Mackenzie <Hamish.K.Mackenzie@gmail.com>
238 lines
8.3 KiB
Haskell
238 lines
8.3 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
|
module Stack2nix.Stack
|
|
( Resolver
|
|
, Name
|
|
, Compiler
|
|
, Sha256
|
|
, CabalRev
|
|
, URL
|
|
, Rev
|
|
, Stack(..)
|
|
, Dependency(..)
|
|
, Location(..)
|
|
, StackSnapshot(..)
|
|
, PackageFlags
|
|
, GhcOptions
|
|
, parsePackageIdentifier
|
|
) where
|
|
|
|
import Data.Char (isDigit)
|
|
import Data.List (isSuffixOf)
|
|
import qualified Data.Text as T
|
|
import Data.Aeson
|
|
import Control.Applicative ((<|>))
|
|
import Data.Monoid (mempty)
|
|
|
|
import Distribution.Types.PackageId
|
|
import Distribution.Pretty
|
|
import Distribution.Parsec
|
|
import Distribution.Compat.CharParsing
|
|
import Distribution.Parsec.FieldLineStream
|
|
import Distribution.Types.Version (nullVersion)
|
|
|
|
import qualified Data.HashMap.Strict as HM
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- The stack.yaml file
|
|
--------------------------------------------------------------------------------
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- packages
|
|
--
|
|
-- * (1) Paths
|
|
-- - ./site1
|
|
-- - ./site2
|
|
-- * (2) Git Locations
|
|
-- - location:
|
|
-- git: https://github.com/yesodweb/yesod
|
|
-- commit: 7038ae6317cb3fe4853597633ba7a40804ca9a46
|
|
-- extra-dep: true
|
|
-- subdirs:
|
|
-- - yesod-core
|
|
-- - yesod-bin
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- extra-deps
|
|
--
|
|
-- * (1) Package index (optional sha of cabal files contents; or revision number)
|
|
-- - acme-missiles-0.3
|
|
-- - acme-missiles-0.3@sha256:2ba66a092a32593880a87fb00f3213762d7bca65a687d45965778deb8694c5d1
|
|
-- - acme-missiles-0.3@rev:0
|
|
--
|
|
-- * (2) Local File Path (foo-1.2.3 would be parsed as a package index)
|
|
-- - vendor/somelib
|
|
-- - ./foo-1.2.3
|
|
--
|
|
-- * (3) Git and Mercurial repos (optional subdirs; or github)
|
|
-- - git: git@github.com:commercialhaskell/stack.git
|
|
-- commit: 6a86ee32e5b869a877151f74064572225e1a0398
|
|
-- - git: git@github.com:snoyberg/http-client.git
|
|
-- commit: "a5f4f3"
|
|
-- - hg: https://example.com/hg/repo
|
|
-- commit: da39a3ee5e6b4b0d3255bfef95601890afd80709
|
|
-- - git: git@github.com:yesodweb/wai
|
|
-- commit: 2f8a8e1b771829f4a8a77c0111352ce45a14c30f
|
|
-- subdirs:
|
|
-- - auto-update
|
|
-- - wai
|
|
-- - github: snoyberg/http-client
|
|
-- commit: a5f4f30f01366738f913968163d856366d7e0342
|
|
--
|
|
-- * (4) Archives (HTTP(S) or local filepath)
|
|
-- - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
|
-- - archive: http://github.com/yesodweb/wai/archive/2f8a8e1b771829f4a8a77c0111352ce45a14c30f.zip
|
|
-- subdirs:
|
|
-- - wai
|
|
-- - warp
|
|
-- - archive: ../acme-missiles-0.3.tar.gz
|
|
-- sha256: e563d8b524017a06b32768c4db8eff1f822f3fb22a90320b7e414402647b735b
|
|
|
|
-- NOTE: We will only parse a suitable subset of the stack.yaml file.
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Some generic types
|
|
type Resolver = String
|
|
type Name = String
|
|
type Compiler = String
|
|
type Sha256 = String
|
|
newtype CabalRev = CabalRev Int -- cabal revision 0,1,2,...
|
|
deriving (Show)
|
|
type URL = String -- Git/Hg/... URL
|
|
type Rev = String -- Git revision
|
|
|
|
instance Pretty CabalRev where
|
|
pretty (CabalRev rev) = "r" <> pretty rev
|
|
|
|
instance Parsec CabalRev where
|
|
parsec = char 'r' *> (CabalRev <$> integral)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Data Types
|
|
-- Dependencies are the merged set of packages and extra-deps.
|
|
-- As we do not distinguish them in the same way stack does, we
|
|
-- can get away with this.
|
|
data Dependency
|
|
= PkgIndex PackageIdentifier (Maybe (Either Sha256 CabalRev)) -- ^ overridden package in the stackage index
|
|
| LocalPath String -- ^ Some local package (potentially overriding a package in the index as well)
|
|
| DVCS Location (Maybe Sha256) [FilePath] -- ^ One or more packages fetched from git or similar.
|
|
-- TODO: Support archives.
|
|
-- | Archive ...
|
|
deriving (Show)
|
|
|
|
-- flags are { pkg -> { flag -> bool } }
|
|
type PackageFlags = HM.HashMap T.Text (HM.HashMap T.Text Bool)
|
|
|
|
type GhcOptions = HM.HashMap T.Text T.Text
|
|
|
|
data Stack
|
|
= Stack Resolver (Maybe Compiler) [Dependency] PackageFlags GhcOptions
|
|
deriving (Show)
|
|
|
|
-- stack supports custom snapshots
|
|
-- https://docs.haskellstack.org/en/stable/custom_snapshot/
|
|
data StackSnapshot
|
|
= Snapshot
|
|
Resolver -- lts-XX.YY/nightly-...
|
|
(Maybe Compiler) -- possible compiler override for the snapshot
|
|
Name -- name
|
|
[Dependency] -- packages
|
|
PackageFlags -- flags
|
|
-- [PackageName] -- drop-packages
|
|
-- [PackageName -> Bool] -- hidden
|
|
GhcOptions -- ghc-options
|
|
deriving (Show)
|
|
|
|
data Location
|
|
= Git URL Rev
|
|
| HG URL Rev
|
|
deriving (Show)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Parsers for package indices
|
|
sha256Suffix :: ParsecParser Sha256
|
|
sha256Suffix = string "@sha256:" *> some (satisfy (`elem` (['0'..'9']++['a'..'z']++['A'..'Z'])))
|
|
-- Stack supports optional cabal file size after revision's SHA value,
|
|
-- we parse it but it doesn't get used
|
|
<* optional (char ',' <* some (satisfy isDigit))
|
|
|
|
revSuffix :: ParsecParser CabalRev
|
|
revSuffix = string "@rev:" *> (CabalRev . read <$> some (satisfy (`elem` ['0'..'9'])))
|
|
|
|
suffix :: ParsecParser (Maybe (Either Sha256 CabalRev))
|
|
suffix = option Nothing (Just <$> (try (Left <$> sha256Suffix) <|> (Right <$> revSuffix)))
|
|
|
|
pkgIndex :: ParsecParser Dependency
|
|
pkgIndex = PkgIndex <$> parsec <*> suffix <* eof
|
|
|
|
parsePackageIdentifier :: String -> Maybe (PackageIdentifier, Maybe (Either Sha256 CabalRev))
|
|
parsePackageIdentifier input =
|
|
case runParsecParser pkgIndex "<parsePackageIdentifier>" (fieldLineStreamFromString input) of
|
|
Right (PkgIndex d rev) -> Just (d, rev)
|
|
_ -> Nothing
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- JSON/YAML destructors
|
|
|
|
instance FromJSON Location where
|
|
parseJSON = withObject "Location" $ \l ->
|
|
parseGitHub l <|> parseGit l
|
|
where
|
|
parseGit l = Git
|
|
<$> l .: "git"
|
|
<*> l .: "commit"
|
|
parseGitHub l = Git
|
|
<$> do gitHubUrl <$> l .: "github"
|
|
<*> l .: "commit"
|
|
gitHubUrl ownerRepo =
|
|
"https://github.com/" <> ownerRepo <> ".git"
|
|
|
|
instance FromJSON Stack where
|
|
parseJSON = withObject "Stack" $ \s -> Stack
|
|
<$> s .: "resolver"
|
|
<*> s .:? "compiler" .!= Nothing
|
|
<*> ((<>) <$> s .:? "packages" .!= [LocalPath "."]
|
|
<*> s .:? "extra-deps" .!= [])
|
|
<*> s .:? "flags" .!= mempty
|
|
<*> s .:? "ghc-options" .!= mempty
|
|
|
|
instance FromJSON StackSnapshot where
|
|
parseJSON = withObject "Snapshot" $ \s -> Snapshot
|
|
<$> s .: "resolver"
|
|
<*> s .:? "compiler" .!= Nothing
|
|
<*> s .: "name"
|
|
<*> s .:? "packages" .!= []
|
|
<*> s .:? "flags" .!= mempty
|
|
<*> s .:? "ghc-options" .!= mempty
|
|
|
|
instance FromJSON Dependency where
|
|
-- Note: we will parse foo-X.Y.Z as a package.
|
|
-- if we want it to be a localPath, it needs
|
|
-- to be ./foo-X.Y.Z
|
|
parseJSON p = parsePkgIndex p <|> parseLocalPath p <|> parseDVCS p
|
|
where parsePkgIndex = withText "Package Index" $ \pi ->
|
|
case parsePackageIdentifier (T.unpack pi) of
|
|
-- Cabal will happily parse "foo" as a packageIdentifier,
|
|
-- we however are only interested in those that have a version
|
|
-- as well. Any valid version is larger than @nullVersion@, as
|
|
-- such we can use that as a filter.
|
|
(Just (pkgIdent, s)) | pkgVersion pkgIdent > nullVersion -> return $ PkgIndex pkgIdent s
|
|
_ -> fail $ "invalid package index: " ++ show pi
|
|
parseLocalPath = withText "Local Path" $
|
|
return . LocalPath . dropTrailingSlash . T.unpack
|
|
parseSubdirs o = do
|
|
subdir' <- o .:? "subdir"
|
|
let subdir = fmap (\d -> [d]) subdir'
|
|
subdirs <- o .:? "subdirs"
|
|
return (subdir <|> subdirs)
|
|
parseDVCS = withObject "DVCS" $ \o -> DVCS
|
|
<$> (o .: "location" <|> parseJSON p)
|
|
<*> o .:? "nix-sha256" .!= Nothing
|
|
<*> parseSubdirs o .!= ["."]
|
|
|
|
-- drop trailing slashes. Nix doesn't like them much;
|
|
-- stack doesn't seem to care.
|
|
dropTrailingSlash p | "/" `isSuffixOf` p = take (length p - 1) p
|
|
dropTrailingSlash p = p
|