stackage/Stackage2/PackageDescription.hs
2014-12-08 13:54:32 +02:00

148 lines
5.7 KiB
Haskell

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
-- | Manipulate @GenericPackageDescription@ from Cabal into something more
-- useful for us.
module Stackage2.PackageDescription
( SimpleDesc (..)
, toSimpleDesc
, CheckCond (..)
) where
import Control.Monad.State.Strict (execState, get, put)
import Control.Monad.Writer.Strict (MonadWriter, execWriterT,
tell)
import Data.Aeson
import qualified Data.Map as Map
import qualified Data.Set as Set
import Distribution.Compiler (CompilerFlavor)
import Distribution.Package (Dependency (..))
import Distribution.PackageDescription
import Distribution.System (Arch, OS)
import Stackage2.CorePackages
import Stackage2.GithubPings
import Stackage2.PackageIndex
import Stackage2.Prelude
import Data.Aeson
-- | A simplified package description that tracks:
--
-- * Package dependencies
--
-- * Build tool dependencies
--
-- * Provided executables
--
-- It has fully resolved all conditionals
data SimpleDesc = SimpleDesc
{ sdPackages :: Map PackageName VersionRange
, sdTools :: Map ExeName VersionRange
, sdProvidedExes :: Set ExeName
}
deriving (Show, Eq)
instance Monoid SimpleDesc where
mempty = SimpleDesc mempty mempty mempty
mappend (SimpleDesc a b c) (SimpleDesc x y z) = SimpleDesc
(unionWith intersectVersionRanges a x)
(unionWith intersectVersionRanges b y)
(c ++ z)
instance ToJSON SimpleDesc where
toJSON SimpleDesc {..} = object
[ "packages" .= Map.mapKeysWith const unPackageName (map display sdPackages)
, "tools" .= Map.mapKeysWith const unExeName (map display sdTools)
, "provided-exes" .= sdProvidedExes
]
instance FromJSON SimpleDesc where
parseJSON = withObject "SimpleDesc" $ \o -> do
sdPackages <- (o .: "packages") >>=
either (fail . show) return .
mapM simpleParse .
Map.mapKeysWith const mkPackageName
sdTools <- (o .: "tools") >>=
either (fail . show) return .
mapM simpleParse .
Map.mapKeysWith const ExeName
sdProvidedExes <- o .: "provided-exes"
return SimpleDesc {..}
-- | Convert a 'GenericPackageDescription' into a 'SimpleDesc' by following the
-- constraints in the provided 'CheckCond'.
toSimpleDesc :: MonadThrow m
=> CheckCond
-> GenericPackageDescription
-> m SimpleDesc
toSimpleDesc cc gpd = execWriterT $ do
forM_ (condLibrary gpd) $ tellTree cc libBuildInfo
forM_ (condExecutables gpd) $ tellTree cc buildInfo . snd
tell mempty { sdProvidedExes = setFromList
$ map (fromString . fst)
$ condExecutables gpd
}
when (ccIncludeTests cc) $ forM_ (condTestSuites gpd)
$ tellTree cc testBuildInfo . snd
when (ccIncludeBenchmarks cc) $ forM_ (condBenchmarks gpd)
$ tellTree cc benchmarkBuildInfo . snd
-- | Convert a single CondTree to a 'SimpleDesc'.
tellTree :: (MonadWriter SimpleDesc m, MonadThrow m)
=> CheckCond
-> (a -> BuildInfo)
-> CondTree ConfVar [Dependency] a
-> m ()
tellTree cc getBI (CondNode dat deps comps) = do
tell mempty
{ sdPackages = unionsWith intersectVersionRanges $ flip map deps
$ \(Dependency x y) -> singletonMap x $ simplifyVersionRange y
, sdTools = unionsWith intersectVersionRanges $ flip map (buildTools $ getBI dat)
$ \(Dependency name range) -> singletonMap
-- In practice, cabal files refer to the exe name, not the
-- package name.
(ExeName $ unPackageName name)
(simplifyVersionRange range)
}
forM_ comps $ \(cond, ontrue, onfalse) -> do
b <- checkCond cc cond
if b
then tellTree cc getBI ontrue
else maybe (return ()) (tellTree cc getBI) onfalse
-- | Resolve a condition to a boolean based on the provided 'CheckCond'.
checkCond :: MonadThrow m => CheckCond -> Condition ConfVar -> m Bool
checkCond CheckCond {..} cond0 =
go cond0
where
go (Var (OS os)) = return $ os == ccOS
go (Var (Arch arch)) = return $ arch == ccArch
go (Var (Flag flag)) =
case lookup flag ccFlags of
Nothing -> throwM $ FlagNotDefined ccPackageName flag cond0
Just b -> return b
go (Var (Impl flavor range)) = return
$ flavor == ccCompilerFlavor
&& ccCompilerVersion `withinRange` range
go (Lit b) = return b
go (CNot c) = not `liftM` go c
go (CAnd x y) = (&&) `liftM` go x `ap` go y
go (COr x y) = (||) `liftM` go x `ap` go y
data CheckCondException = FlagNotDefined PackageName FlagName (Condition ConfVar)
deriving (Show, Typeable)
instance Exception CheckCondException
data CheckCond = CheckCond
{ ccPackageName :: PackageName -- for debugging only
, ccOS :: OS
, ccArch :: Arch
, ccFlags :: Map FlagName Bool
, ccCompilerFlavor :: CompilerFlavor
, ccCompilerVersion :: Version
, ccIncludeTests :: Bool
, ccIncludeBenchmarks :: Bool
}