stack-1.3.0: src/Stack/Types/Resolver.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
module Stack.Types.Resolver
(Resolver
,IsLoaded(..)
,LoadedResolver
,ResolverThat's(..)
,parseResolverText
,resolverDirName
,resolverName
,customResolverHash
,toResolverNotLoaded
,AbstractResolver(..)
,readAbstractResolver
) where
import Control.Applicative
import Control.Monad.Catch (MonadThrow, throwM)
import Data.Aeson.Extended
(ToJSON, toJSON, FromJSON, parseJSON, object,
WithJSONWarnings(..), Value(String, Object), (.=),
noJSONWarnings, (..:), withObjectWarnings)
import Data.Monoid.Extra
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import Data.Text.Read (decimal)
import Options.Applicative (ReadM)
import qualified Options.Applicative as OA
import qualified Options.Applicative.Types as OA
import Prelude
import Stack.Types.BuildPlan (parseSnapName, renderSnapName, SnapName, SnapshotHash,
trimmedSnapshotHash)
import {-# SOURCE #-} Stack.Types.Config (ConfigException(..))
import Stack.Types.Compiler
data IsLoaded = Loaded | NotLoaded
type LoadedResolver = ResolverThat's 'Loaded
type Resolver = ResolverThat's 'NotLoaded
-- TODO: once GHC 8.0 is the lowest version we support, make these into
-- actual haddock comments...
-- | How we resolve which dependencies to install given a set of packages.
data ResolverThat's (l :: IsLoaded) where
-- Use an official snapshot from the Stackage project, either an LTS
-- Haskell or Stackage Nightly.
ResolverSnapshot :: !SnapName -> ResolverThat's l
-- Require a specific compiler version, but otherwise provide no
-- build plan. Intended for use cases where end user wishes to
-- specify all upstream dependencies manually, such as using a
-- dependency solver.
ResolverCompiler :: !CompilerVersion -> ResolverThat's l
-- A custom resolver based on the given name and URL. When a URL is
-- provided, it file is to be completely immutable. Filepaths are
-- always loaded. This constructor is used before the build-plan has
-- been loaded, as we do not yet know the custom snapshot's hash.
ResolverCustom :: !Text -> !Text -> ResolverThat's 'NotLoaded
-- Like 'ResolverCustom', but after loading the build-plan, so we
-- have a hash. This is necessary in order to identify the location
-- files are stored for the resolver.
ResolverCustomLoaded :: !Text -> !Text -> !SnapshotHash -> ResolverThat's 'Loaded
deriving instance Eq (ResolverThat's k)
deriving instance Show (ResolverThat's k)
instance ToJSON (ResolverThat's k) where
toJSON x = case x of
ResolverSnapshot{} -> toJSON $ resolverName x
ResolverCompiler{} -> toJSON $ resolverName x
ResolverCustom n l -> handleCustom n l
ResolverCustomLoaded n l _ -> handleCustom n l
where
handleCustom n l = object
[ "name" .= n
, "location" .= l
]
instance FromJSON (WithJSONWarnings (ResolverThat's 'NotLoaded)) where
-- Strange structuring is to give consistent error messages
parseJSON v@(Object _) = withObjectWarnings "Resolver" (\o -> ResolverCustom
<$> o ..: "name"
<*> o ..: "location") v
parseJSON (String t) = either (fail . show) return (noJSONWarnings <$> parseResolverText t)
parseJSON _ = fail "Invalid Resolver, must be Object or String"
-- | Convert a Resolver into its @Text@ representation, as will be used by
-- directory names
resolverDirName :: LoadedResolver -> Text
resolverDirName (ResolverSnapshot name) = renderSnapName name
resolverDirName (ResolverCompiler v) = compilerVersionText v
resolverDirName (ResolverCustomLoaded name _ hash) = "custom-" <> name <> "-" <> decodeUtf8 (trimmedSnapshotHash hash)
-- | Convert a Resolver into its @Text@ representation for human
-- presentation.
resolverName :: ResolverThat's l -> Text
resolverName (ResolverSnapshot name) = renderSnapName name
resolverName (ResolverCompiler v) = compilerVersionText v
resolverName (ResolverCustom name _) = "custom-" <> name
resolverName (ResolverCustomLoaded name _ _) = "custom-" <> name
customResolverHash :: LoadedResolver-> Maybe SnapshotHash
customResolverHash (ResolverCustomLoaded _ _ hash) = Just hash
customResolverHash _ = Nothing
-- | Try to parse a @Resolver@ from a @Text@. Won't work for complex resolvers (like custom).
parseResolverText :: MonadThrow m => Text -> m Resolver
parseResolverText t
| Right x <- parseSnapName t = return $ ResolverSnapshot x
| Just v <- parseCompilerVersion t = return $ ResolverCompiler v
| otherwise = throwM $ ParseResolverException t
toResolverNotLoaded :: LoadedResolver -> Resolver
toResolverNotLoaded r = case r of
ResolverSnapshot s -> ResolverSnapshot s
ResolverCompiler v -> ResolverCompiler v
ResolverCustomLoaded n l _ -> ResolverCustom n l
-- | Either an actual resolver value, or an abstract description of one (e.g.,
-- latest nightly).
data AbstractResolver
= ARLatestNightly
| ARLatestLTS
| ARLatestLTSMajor !Int
| ARResolver !Resolver
| ARGlobal
deriving Show
readAbstractResolver :: ReadM AbstractResolver
readAbstractResolver = do
s <- OA.readerAsk
case s of
"global" -> return ARGlobal
"nightly" -> return ARLatestNightly
"lts" -> return ARLatestLTS
'l':'t':'s':'-':x | Right (x', "") <- decimal $ T.pack x ->
return $ ARLatestLTSMajor x'
_ ->
case parseResolverText $ T.pack s of
Left e -> OA.readerError $ show e
Right x -> return $ ARResolver x