stackage/Stackage2/BuildPlan.hs
2014-12-04 18:46:36 +02:00

237 lines
8.9 KiB
Haskell

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TypeFamilies #-}
-- | Representation of a concrete build plan, and how to generate a new one
-- based on constraints.
module Stackage2.BuildPlan
( BuildPlan (..)
, PackageBuild (..)
, newBuildPlan
) where
import Distribution.Package (Dependency (..))
import Distribution.PackageDescription
import Distribution.Version (withinRange, intersectVersionRanges)
import Stackage2.CorePackages
import Stackage2.PackageConstraints
import Stackage2.PackageIndex
import Stackage2.Prelude
import Stackage2.GithubPings
import Control.Monad.State.Strict (execState, get, put)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Aeson
import Stackage2.PackageDescription
data BuildPlan desc = BuildPlan
{ bpCore :: Map PackageName Version
, bpTools :: Vector (PackageName, Version)
, bpExtra :: Map PackageName (PackageBuild desc)
, bpGlobalFlags :: Map FlagName Bool
}
deriving (Functor, Foldable, Traversable, Show, Eq)
type instance Element (BuildPlan desc) = desc
instance MonoFunctor (BuildPlan desc)
instance MonoFoldable (BuildPlan desc)
instance MonoTraversable (BuildPlan desc)
instance ToJSON (BuildPlan desc) where
toJSON BuildPlan {..} = object
[ "core" .= asMap (mapFromList $ map toCore $ mapToList bpCore)
, "tools" .= map goTool bpTools
, "extra" .= Map.mapKeysWith const (unPackageName) bpExtra
, "global-flags" .= Map.mapKeysWith const (\(FlagName f) -> f) bpGlobalFlags
]
where
toCore (x, y) = (asText $ display x, asText $ display y)
goTool (name, version) = object
[ "name" .= asText (display name)
, "version" .= asText (display version)
]
instance desc ~ () => FromJSON (BuildPlan desc) where
parseJSON = withObject "BuildPlan" $ \o -> BuildPlan
<$> ((o .: "core") >>= goCore)
<*> ((o .: "tools") >>= mapM goTool)
<*> (goExtra <$> (o .: "extra"))
<*> (goFlags <$> (o .: "global-flags"))
where
goCore =
fmap mapFromList . mapM goCore' . mapToList . asHashMap
where
goCore' (k, v) = do
k' <- either (fail . show) return $ simpleParse $ asText k
v' <- either (fail . show) return $ simpleParse $ asText v
return (k', v')
goTool = withObject "Tool" $ \o -> (,)
<$> ((o .: "name") >>=
either (fail . show) return . simpleParse . asText)
<*> ((o .: "version") >>=
either (fail . show) return . simpleParse . asText)
goExtra = Map.mapKeysWith const PackageName
goFlags = Map.mapKeysWith const FlagName
data PackageBuild desc = PackageBuild
{ pbVersion :: Version
, pbMaintainer :: Maybe Maintainer
, pbGithubPings :: Set Text
, pbUsers :: Set PackageName
, pbFlags :: Map FlagName Bool
, pbTestState :: TestState
, pbHaddockState :: TestState
, pbTryBuildBenchmark :: Bool
, pbDesc :: desc
}
deriving (Functor, Foldable, Traversable, Show, Eq)
type instance Element (PackageBuild desc) = desc
instance MonoFunctor (PackageBuild desc)
instance MonoFoldable (PackageBuild desc)
instance MonoTraversable (PackageBuild desc)
instance ToJSON (PackageBuild desc) where
toJSON PackageBuild {..} = object $ concat
[ maybe [] (\m -> ["maintainer" .= m]) pbMaintainer
,
[ "version" .= asText (display pbVersion)
, "github-pings" .= pbGithubPings
, "users" .= map unPackageName (unpack pbUsers)
, "flags" .= Map.mapKeysWith const (\(FlagName f) -> asText $ pack f) pbFlags
, "test-state" .= pbTestState
, "haddock-state" .= pbHaddockState
, "build-benchmark" .= pbTryBuildBenchmark
]
]
instance desc ~ () => FromJSON (PackageBuild desc) where
parseJSON = withObject "PackageBuild" $ \o -> PackageBuild
<$> (o .: "version" >>= efail . simpleParse . asText)
<*> o .:? "maintainer"
<*> o .:? "github-pings" .!= mempty
<*> (Set.map PackageName <$> (o .:? "users" .!= mempty))
<*> (toFlags <$> (o .:? "flags" .!= mempty))
<*> o .: "test-state"
<*> o .: "haddock-state"
<*> o .: "build-benchmark"
<*> pure ()
where
toFlags = Map.mapKeysWith const (FlagName . unpack . asText)
efail = either (fail . show) return
data TestState = ExpectSuccess
| ExpectFailure
| Don'tBuild -- ^ when the test suite will pull in things we don't want
deriving (Show, Eq, Ord, Bounded, Enum)
testStateToText :: TestState -> Text
testStateToText ExpectSuccess = "expect-success"
testStateToText ExpectFailure = "expect-failure"
testStateToText Don'tBuild = "do-not-build"
instance ToJSON TestState where
toJSON = toJSON . testStateToText
instance FromJSON TestState where
parseJSON = withText "TestState" $ \t ->
case lookup t states of
Nothing -> fail $ "Invalid state: " ++ unpack t
Just v -> return v
where
states = asHashMap $ mapFromList
$ map (\x -> (testStateToText x, x)) [minBound..maxBound]
newBuildPlan :: MonadIO m => m (BuildPlan FlatComponent)
newBuildPlan = liftIO $ do
core <- getCorePackages
extraOrig <- getLatestDescriptions (isAllowed core) mkPackageBuild
let toolNames = concatMap (seTools . fcExtra . pbDesc) extraOrig -- FIXME extraOrig ==> extra
extra = populateUsers $ removeUnincluded (Map.keysSet toolNames) extraOrig
return BuildPlan
{ bpCore = core
, bpTools = topologicalSort
$ filter (\(x, _) -> x `member` toolNames)
$ mapToList extra
, bpExtra = extra
, bpGlobalFlags = defaultGlobalFlags
}
topologicalSort :: [(PackageName, PackageBuild FlatComponent)]
-> Vector (PackageName, Version)
topologicalSort = fromList . fmap (fmap pbVersion) -- FIXME
removeUnincluded :: Set PackageName -- ^ tool names
-> Map PackageName (PackageBuild FlatComponent)
-> Map PackageName (PackageBuild FlatComponent)
removeUnincluded toolNames orig =
mapFromList $ filter (\(x, _) -> x `member` included) $ mapToList orig
where
included :: Set PackageName
included = flip execState mempty $ do
mapM_ (add . fst) $ mapToList $ pcPackages defaultPackageConstraints
mapM_ add toolNames -- FIXME remove this
add name = do
inc <- get
when (name `notMember` inc) $ do
put $ insertSet name inc
case lookup name orig of
Nothing -> return ()
Just pb -> mapM_ (add . fst) $ mapToList $ fcDeps $ pbDesc pb
-- FIXME add tools here
populateUsers :: Map PackageName (PackageBuild FlatComponent)
-> Map PackageName (PackageBuild FlatComponent)
populateUsers orig =
mapWithKey go orig
where
go name pb = pb { pbUsers = foldMap (go2 name) (mapToList orig) }
go2 dep (user, pb)
| dep `member` fcDeps (pbDesc pb) = singletonSet user
| otherwise = mempty
isAllowed :: Map PackageName Version -- ^ core
-> PackageName -> Version -> Bool
isAllowed core = \name version ->
case lookup name core of
Just _ -> False -- never reinstall a core package
Nothing ->
case lookup name $ pcPackages defaultPackageConstraints of
Nothing -> True -- no constraints
Just (range, _) -> withinRange version range
mkPackageBuild :: Monad m
=> GenericPackageDescription
-> m (PackageBuild FlatComponent)
mkPackageBuild gpd =
return PackageBuild
{ pbVersion = version
, pbMaintainer = fmap snd $ lookup name $ pcPackages defaultPackageConstraints
, pbGithubPings = getGithubPings gpd
, pbUsers = mempty -- must be filled in later
, pbFlags = packageFlags name
, pbTestState =
case () of
()
| not $ tryBuildTest name -> Don'tBuild
| name `member` pcExpectedFailures defaultPackageConstraints
-> ExpectFailure
| otherwise -> ExpectSuccess
, pbHaddockState =
case () of
()
| name `member` pcExpectedFailures defaultPackageConstraints
-> ExpectFailure
| otherwise -> ExpectSuccess
, pbTryBuildBenchmark = tryBuildBenchmark name
, pbDesc = getFlattenedComponent
(tryBuildTest name)
(tryBuildBenchmark name)
gpd
}
where
PackageIdentifier name version = package $ packageDescription gpd