mirror of
https://github.com/commercialhaskell/stackage.git
synced 2026-01-12 07:18:31 +01:00
298 lines
12 KiB
Haskell
298 lines
12 KiB
Haskell
{-# LANGUAGE NoImplicitPrelude #-}
|
|
{-# LANGUAGE DeriveDataTypeable #-}
|
|
{-# LANGUAGE TupleSections #-}
|
|
{-# 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, anyVersion, simplifyVersionRange)
|
|
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
|
|
import qualified Distribution.System
|
|
import qualified Distribution.Compiler
|
|
|
|
data BuildPlan desc = BuildPlan
|
|
{ bpCore :: Map PackageName Version
|
|
, bpCoreExecutables :: Set ExeName
|
|
, bpGhcVersion :: Version
|
|
, bpOS :: Distribution.System.OS
|
|
, bpArch :: Distribution.System.Arch
|
|
, bpTools :: Vector (PackageName, Version)
|
|
, bpExtra :: Map PackageName (PackageBuild desc)
|
|
}
|
|
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)
|
|
, "core-exes" .= bpCoreExecutables
|
|
, "ghc-version" .= asText (display bpGhcVersion)
|
|
, "os" .= asText (display bpOS)
|
|
, "arch" .= asText (display bpArch)
|
|
, "tools" .= map goTool bpTools
|
|
, "extra" .= Map.mapKeysWith const (unPackageName) bpExtra
|
|
]
|
|
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 -> do
|
|
bpCore <- (o .: "core") >>= goCore
|
|
bpCoreExecutables <- o .: "core-exes"
|
|
bpGhcVersion <- (o .: "ghc-version") >>= either (fail . show) return . simpleParse . asText
|
|
bpOS <- o .: "os" >>= either (fail . show) return . simpleParse . asText
|
|
bpArch <- (o .: "arch") >>= either (fail . show) return . simpleParse . asText
|
|
bpTools <- (o .: "tools") >>= mapM goTool
|
|
bpExtra <- goExtra <$> (o .: "extra")
|
|
return BuildPlan {..}
|
|
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
|
|
|
|
data PackageBuild desc = PackageBuild
|
|
{ pbVersion :: Version
|
|
, pbVersionRange :: VersionRange
|
|
-- ^ This is vital for ensuring old constraints are kept in place when bumping
|
|
, 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)
|
|
|
|
-- | There seems to be a bug in Cabal where serializing and deserializing
|
|
-- version ranges winds up with different representations. So we have a
|
|
-- super-simplifier to deal with that.
|
|
superSimplifyVersionRange :: VersionRange -> VersionRange
|
|
superSimplifyVersionRange vr =
|
|
fromMaybe (assert False vr') $ simpleParse $ asList $ display vr'
|
|
where
|
|
vr' = simplifyVersionRange vr
|
|
|
|
instance ToJSON (PackageBuild desc) where
|
|
toJSON PackageBuild {..} = object $ concat
|
|
[ maybe [] (\m -> ["maintainer" .= m]) pbMaintainer
|
|
,
|
|
[ "version" .= asText (display pbVersion)
|
|
, "version-range" .= asText (display $ superSimplifyVersionRange pbVersionRange)
|
|
, "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 .: "version-range" >>= fmap superSimplifyVersionRange . 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
|
|
|
|
newBuildPlan :: MonadIO m => PackageConstraints -> m (BuildPlan FlatComponent)
|
|
newBuildPlan pc = liftIO $ do
|
|
extraOrig <- getLatestDescriptions (isAllowed pc) (mkPackageBuild pc)
|
|
let toolMap = makeToolMap extraOrig
|
|
extra = populateUsers $ removeUnincluded pc toolMap extraOrig
|
|
toolNames :: [ExeName]
|
|
toolNames = concatMap (Map.keys . seTools . fcExtra . pbDesc) extra
|
|
tools <- topologicalSortTools toolMap $ mapFromList $ do
|
|
exeName <- toolNames
|
|
guard $ exeName `notMember` pcCoreExecutables pc
|
|
packageName <- maybe mempty setToList $ lookup exeName toolMap
|
|
packageBuild <- maybeToList $ lookup packageName extraOrig
|
|
return (packageName, packageBuild)
|
|
-- FIXME topologically sort packages? maybe just leave that to the build phase
|
|
return BuildPlan
|
|
{ bpCore = pcCorePackages pc
|
|
, bpCoreExecutables = pcCoreExecutables pc
|
|
, bpGhcVersion = pcGhcVersion pc
|
|
, bpOS = pcOS pc
|
|
, bpArch = pcArch pc
|
|
, bpTools = tools
|
|
, bpExtra = extra
|
|
}
|
|
|
|
makeToolMap :: Map PackageName (PackageBuild FlatComponent)
|
|
-> Map ExeName (Set PackageName)
|
|
makeToolMap =
|
|
unionsWith (++) . map go . mapToList
|
|
where
|
|
go (packageName, pb) =
|
|
foldMap go' $ seProvidedExes $ fcExtra $ pbDesc pb
|
|
where
|
|
go' exeName = singletonMap exeName (singletonSet packageName)
|
|
|
|
topologicalSortTools :: MonadThrow m
|
|
=> Map ExeName (Set PackageName)
|
|
-> Map PackageName (PackageBuild FlatComponent)
|
|
-> m (Vector (PackageName, Version))
|
|
topologicalSortTools toolMap = topologicalSort
|
|
pbVersion
|
|
(concatMap (fromMaybe mempty . flip lookup toolMap) . Map.keys . seTools . fcExtra . pbDesc)
|
|
|
|
topologicalSort :: (Ord key, Show key, MonadThrow m, Typeable key)
|
|
=> (value -> finalValue)
|
|
-> (value -> Set key) -- ^ deps
|
|
-> Map key value
|
|
-> m (Vector (key, finalValue))
|
|
topologicalSort toFinal toDeps =
|
|
loop id . mapWithKey removeSelfDeps . fmap (toDeps &&& toFinal)
|
|
where
|
|
removeSelfDeps k (deps, final) = (deleteSet k deps, final)
|
|
loop front toProcess | null toProcess = return $ pack $ front []
|
|
loop front toProcess
|
|
| null noDeps = throwM $ NoEmptyDeps (map fst toProcess')
|
|
| otherwise = loop (front . noDeps') (mapFromList hasDeps)
|
|
where
|
|
toProcess' = fmap (first removeUnavailable) toProcess
|
|
allKeys = Map.keysSet toProcess
|
|
removeUnavailable = asSet . setFromList . filter (`member` allKeys) . setToList
|
|
(noDeps, hasDeps) = partition (null . fst . snd) $ mapToList toProcess'
|
|
noDeps' = (map (second snd) noDeps ++)
|
|
|
|
data TopologicalSortException key = NoEmptyDeps (Map key (Set key))
|
|
deriving (Show, Typeable)
|
|
instance (Show key, Typeable key) => Exception (TopologicalSortException key)
|
|
|
|
removeUnincluded :: PackageConstraints
|
|
-> Map ExeName (Set PackageName)
|
|
-> Map PackageName (PackageBuild FlatComponent)
|
|
-> Map PackageName (PackageBuild FlatComponent)
|
|
removeUnincluded pc toolMap orig =
|
|
mapFromList $ filter (\(x, _) -> x `member` included) $ mapToList orig
|
|
where
|
|
coreExes = pcCoreExecutables pc
|
|
|
|
included :: Set PackageName
|
|
included = flip execState mempty $
|
|
mapM_ (add . fst) $ mapToList $ pcPackages pc
|
|
|
|
add name = do
|
|
inc <- get
|
|
when (name `notMember` inc) $ do
|
|
put $ insertSet name inc
|
|
case lookup name orig of
|
|
Nothing -> return ()
|
|
Just pb -> do
|
|
mapM_ (add . fst) $ mapToList $ fcDeps $ pbDesc pb
|
|
forM_ (map fst $ mapToList $ seTools $ fcExtra $ pbDesc pb) $
|
|
\exeName -> when (exeName `notMember` coreExes)
|
|
$ mapM_ add $ fromMaybe mempty $ lookup exeName toolMap
|
|
|
|
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 :: PackageConstraints
|
|
-> PackageName -> Version -> Bool
|
|
isAllowed pc = \name version ->
|
|
case lookup name $ pcCorePackages pc of
|
|
Just _ -> False -- never reinstall a core package
|
|
Nothing ->
|
|
case lookup name $ pcPackages pc of
|
|
Nothing -> True -- no constraints
|
|
Just (range, _) -> withinRange version range
|
|
|
|
mkPackageBuild :: MonadThrow m
|
|
=> PackageConstraints
|
|
-> GenericPackageDescription
|
|
-> m (PackageBuild FlatComponent)
|
|
mkPackageBuild pc gpd = do
|
|
let overrides = pcFlagOverrides pc name
|
|
getFlag MkFlag {..} =
|
|
(flagName, fromMaybe flagDefault $ lookup flagName overrides)
|
|
flags = mapFromList $ map getFlag $ genPackageFlags gpd
|
|
desc <- getFlattenedComponent
|
|
CheckCond
|
|
{ ccPackageName = name
|
|
, ccOS = pcOS pc
|
|
, ccArch = pcArch pc
|
|
, ccCompilerFlavor = Distribution.Compiler.GHC
|
|
, ccCompilerVersion = pcGhcVersion pc
|
|
, ccFlags = flags
|
|
}
|
|
(pcTests pc name /= Don'tBuild)
|
|
(pcBuildBenchmark pc name)
|
|
gpd
|
|
return PackageBuild
|
|
{ pbVersion = version
|
|
, pbVersionRange = superSimplifyVersionRange
|
|
$ maybe anyVersion fst $ lookup name $ pcPackages pc
|
|
, pbMaintainer = lookup name (pcPackages pc) >>= snd
|
|
, pbGithubPings = getGithubPings gpd
|
|
, pbUsers = mempty -- must be filled in later
|
|
, pbFlags = flags
|
|
, pbTestState = pcTests pc name
|
|
, pbHaddockState = pcHaddocks pc name
|
|
, pbTryBuildBenchmark = pcBuildBenchmark pc name
|
|
, pbDesc = desc
|
|
}
|
|
where
|
|
PackageIdentifier name version = package $ packageDescription gpd
|