arch-hs-0.1.1.0: src/Distribution/ArchHs/Core.hs
{-# LANGUAGE RecordWildCards #-}
-- | Copyright: (c) 2020 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <1793913507@qq.com>
-- Stability: experimental
-- Portability: portable
-- The core functions of @arch-hs@.
module Distribution.ArchHs.Core
( getDependencies,
cabalToPkgBuild,
evalConditionTree,
)
where
import qualified Algebra.Graph.Labelled.AdjacencyMap as G
import Data.List (stripPrefix)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Distribution.ArchHs.Hackage (getLatestCabal, getLatestSHA256)
import Distribution.ArchHs.Local (ghcLibList, ignoreList)
import Distribution.ArchHs.PkgBuild (PkgBuild (..), mapLicense)
import Distribution.ArchHs.Types
import Distribution.ArchHs.Utils
import Distribution.Compiler (CompilerFlavor (..))
import Distribution.PackageDescription
import Distribution.Pretty (prettyShow)
import Distribution.SPDX
import Distribution.System (Arch (X86_64), OS (Linux))
import qualified Distribution.Types.BuildInfo.Lens as L
import Distribution.Types.CondTree (simplifyCondTree)
import Distribution.Types.Dependency (Dependency)
import Distribution.Types.PackageName (PackageName, unPackageName)
import Distribution.Types.UnqualComponentName (UnqualComponentName, unqualComponentNameToPackageName)
import Distribution.Types.Version (mkVersion)
import Distribution.Types.VersionRange (VersionRange, withinRange)
import Distribution.Utils.ShortText (fromShortText)
archEnv :: FlagAssignment -> ConfVar -> Either ConfVar Bool
archEnv _ (OS Linux) = Right True
archEnv _ (OS _) = Right False
archEnv _ (Arch X86_64) = Right True
archEnv _ (Arch _) = Right False
archEnv _ (Impl GHC range) = Right $ withinRange (mkVersion [8, 10, 2]) range
archEnv _ (Impl _ _) = Right False
archEnv assignment f@(Flag f') = go f $ lookupFlagAssignment f' assignment
where
go _ (Just r) = Right r
go x Nothing = Left x
-- | Simplify the condition tree from 'GenericPackageDescription' with given flag assignments and archlinux system assumption.
evalConditionTree ::
(HasCallStack, Semigroup k, L.HasBuildInfo k, Members [FlagAssignmentsEnv, Trace] r) =>
GenericPackageDescription ->
CondTree ConfVar [Dependency] k ->
Sem r BuildInfo
evalConditionTree cabal cond = do
flagAssignments <- ask
let name = getPkgName' cabal
packageFlags = genPackageFlags cabal
defaultFlagAssignments =
foldr (\f acc -> insertFlagAssignment (flagName f) (flagDefault f) acc) (mkFlagAssignment []) packageFlags
flagAssignment = case Map.lookup name flagAssignments of
Just f -> unFlagAssignment f
_ -> []
flagNames = fmap fst flagAssignment
thisFlag =
mkFlagAssignment
. (<> flagAssignment)
. filter (\(fName, _) -> fName `notElem` flagNames)
$ (unFlagAssignment defaultFlagAssignments)
trace' $ "Evaluating condition tree of " <> show name
trace' $ "Flags: " <> show thisFlag
traceCallStack
return $ (^. L.buildInfo) . snd $ simplifyCondTree (archEnv thisFlag) cond
-----------------------------------------------------------------------------
-- | Get dependencies of a package recursively.
-- All version constraints will be discarded,
-- and only packages depended by executables, libraries, and test suits will be collected.
getDependencies ::
(HasCallStack, Members [HackageEnv, FlagAssignmentsEnv, WithMyErr, DependencyRecord, State (Set PackageName), Trace] r) =>
-- | Skipped
[UnqualComponentName] ->
-- | Parent
Maybe PackageName ->
-- | Target
PackageName ->
Sem r ((G.AdjacencyMap (Set DependencyType) PackageName), Set PackageName)
getDependencies skip parent name = do
resolved <- get @(Set PackageName)
modify' $ Set.insert name
trace' $ "Getting all dependencies of (" <> show name <> "), parent: (" <> show parent <> ")"
trace' $ "Already resolved: " <> show resolved
traceCallStack
cabal <- getLatestCabal name
-- Ignore subLibraries
(libDeps, libToolsDeps) <- collectLibDeps cabal
(subLibDeps, subLibToolsDeps) <- collectSubLibDeps cabal skip
(exeDeps, exeToolsDeps) <- collectExeDeps cabal skip
(testDeps, testToolsDeps) <- collectTestDeps cabal skip
-- Ignore benchmarks
-- (benchDeps, benchToolsDeps) <- collectBenchMarkDeps cabal skip
let uname :: (UnqualComponentName -> DependencyType) -> ComponentPkgList -> [(DependencyType, PkgList)]
uname cons list = zip (fmap (cons . fst) list) (fmap snd list)
flatten :: [(DependencyType, PkgList)] -> [(DependencyType, PackageName)]
flatten = mconcat . fmap (\(t, pkgs) -> zip (repeat t) pkgs)
withThisName :: [(DependencyType, PackageName)] -> [(DependencyType, PackageName, PackageName)]
withThisName = fmap (\(t, pkg) -> (t, name, pkg))
ignoreSingle x = x `notElem` ignoreList
ignore = filter ignoreSingle
ignoreFlatten k = filter (\(_, x) -> ignoreSingle x) . flatten . uname k
filteredLibDeps = ignore libDeps
filteredLibToolsDeps = ignore libToolsDeps
filteredExeDeps = ignoreFlatten CExe $ exeDeps
filteredExeToolsDeps = ignoreFlatten CExeBuildTools $ exeToolsDeps
filteredTestDeps = ignoreFlatten CTest $ testDeps
filteredTestToolsDeps = ignoreFlatten CTest $ testToolsDeps
filteredSubLibDeps = ignoreFlatten CSubLibs $ subLibDeps
filteredSubLibToolsDeps = ignoreFlatten CSubLibsBuildTools $ subLibToolsDeps
filteredSubLibDepsNames = fmap unqualComponentNameToPackageName . fmap fst $ subLibDeps
ignoreSubLibs = filter (`notElem` filteredSubLibDepsNames)
ignoreResolved = filter (`notElem` resolved)
currentLib = G.edges $ zip3 (repeat $ Set.singleton CLib) (repeat name) filteredLibDeps
currentLibDeps = G.edges $ zip3 (repeat $ Set.singleton CLibBuildTools) (repeat name) filteredLibToolsDeps
componentialEdges =
G.edges
. fmap (\(x, y, z) -> (Set.singleton x, y, z))
. withThisName
currentSubLibs = componentialEdges filteredSubLibDeps
currentSubLibsTools = componentialEdges filteredSubLibToolsDeps
currentExe = componentialEdges filteredExeDeps
currentExeTools = componentialEdges filteredExeToolsDeps
currentTest = componentialEdges filteredTestDeps
currentTestTools = componentialEdges filteredTestToolsDeps
-- currentBench = componentialEdges Types.Benchmark benchDeps
-- currentBenchTools = componentialEdges BenchmarkBuildTools benchToolsDeps
(<+>) = G.overlay
-- Only solve lib & exe deps recursively.
nextLib <- mapM (getDependencies skip (Just name)) . ignoreResolved . ignoreSubLibs $ filteredLibDeps
nextExe <- mapM (getDependencies skip (Just name)) . ignoreResolved . ignoreSubLibs $ fmap snd filteredExeDeps
nextSubLibs <- mapM (getDependencies skip (Just name)) $ fmap snd filteredSubLibDeps
let temp = [nextLib, nextExe, nextSubLibs]
nexts = G.overlays $ temp ^. each ^.. each . _1
subsubs = temp ^. each ^.. each . _2 ^. each
return $
( currentLib
<+> currentLibDeps
<+> currentExe
<+> currentExeTools
<+> currentTest
<+> currentTestTools
<+> currentSubLibs
<+> currentSubLibsTools
-- <+> currentBench
-- <+> currentBenchTools
<+> nexts,
Set.fromList filteredSubLibDepsNames <> subsubs
)
collectLibDeps :: Members [FlagAssignmentsEnv, DependencyRecord, Trace] r => GenericPackageDescription -> Sem r (PkgList, PkgList)
collectLibDeps cabal = do
case cabal & condLibrary of
Just lib -> do
let name = getPkgName' cabal
trace' $ "Getting componential dependencies of " <> show name
info <- evalConditionTree cabal lib
let libDeps = fmap unDepV $ buildDependsIfBuild info
toolDeps = fmap unExeV $ buildToolDependsIfBuild info
mapM_ (uncurry updateDependencyRecord) libDeps
mapM_ (uncurry updateDependencyRecord) toolDeps
let result = (fmap fst libDeps, fmap fst toolDeps)
trace' $ "Found: " <> show result
traceCallStack
return result
Nothing -> return ([], [])
collectComponentialDeps ::
(HasCallStack, Semigroup k, L.HasBuildInfo k, Members [FlagAssignmentsEnv, DependencyRecord, Trace] r) =>
(GenericPackageDescription -> [(UnqualComponentName, CondTree ConfVar [Dependency] k)]) ->
GenericPackageDescription ->
[UnqualComponentName] ->
Sem r (ComponentPkgList, ComponentPkgList)
collectComponentialDeps f cabal skip = do
let conds = cabal & f
name = getPkgName' cabal
trace' $ "Getting componential dependencies of " <> show name
info <- filter (not . (`elem` skip) . fst) . zip (conds <&> fst) <$> mapM (evalConditionTree cabal . snd) conds
let deps = info <&> ((_2 %~) $ fmap unDepV . buildDependsIfBuild)
toolDeps = info <&> ((_2 %~) $ fmap unExeV . buildToolDependsIfBuild)
k = fmap (\(c, l) -> (c, fmap fst l))
mapM_ (uncurry updateDependencyRecord) $ deps ^.. each . _2 ^. each
mapM_ (uncurry updateDependencyRecord) $ toolDeps ^.. each . _2 ^. each
let result = (k deps, k toolDeps)
trace' $ "Found: " <> show result
traceCallStack
return result
collectExeDeps :: (HasCallStack, Members [FlagAssignmentsEnv, DependencyRecord, Trace] r) => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
collectExeDeps = collectComponentialDeps condExecutables
collectTestDeps :: (HasCallStack, Members [FlagAssignmentsEnv, DependencyRecord, Trace] r) => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
collectTestDeps = collectComponentialDeps condTestSuites
collectSubLibDeps :: (HasCallStack, Members [FlagAssignmentsEnv, DependencyRecord, Trace] r) => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
collectSubLibDeps = collectComponentialDeps condSubLibraries
updateDependencyRecord :: Member DependencyRecord r => PackageName -> VersionRange -> Sem r ()
updateDependencyRecord name range = modify' $ Map.insertWith (<>) name [range]
-- collectBenchMarkDeps :: Members [HackageEnv, FlagAssignmentEnv] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
-- collectBenchMarkDeps = collectComponentialDeps condBenchmarks
-----------------------------------------------------------------------------
-- | Generate 'PkgBuild' for a 'SolvedPackage'.
cabalToPkgBuild :: Members [HackageEnv, FlagAssignmentsEnv, WithMyErr] r => SolvedPackage -> PkgList -> Bool -> Sem r PkgBuild
cabalToPkgBuild pkg ignored uusi = do
let name = pkg ^. pkgName
cabal <- packageDescription <$> (getLatestCabal name)
_sha256sums <- (\s -> "'" <> s <> "'") <$> getLatestSHA256 name
let _hkgName = pkg ^. pkgName & unPackageName
rawName = toLower' _hkgName
_pkgName = maybe rawName id $ stripPrefix "haskell-" rawName
_pkgVer = prettyShow $ getPkgVersion cabal
_pkgDesc = fromShortText $ synopsis cabal
getL (NONE) = ""
getL (License e) = getE e
getE (ELicense (ELicenseId x) _) = show . mapLicense $ x
getE (ELicense (ELicenseIdPlus x) _) = show . mapLicense $ x
getE (ELicense (ELicenseRef x) _) = "custom:" <> licenseRef x
getE (EAnd x y) = getE x <> " " <> getE y
getE (EOr x y) = getE x <> " " <> getE y
_license = getL . license $ cabal
_enableCheck = any id $ pkg ^. pkgDeps & mapped %~ (\dep -> selectDepKind Test dep && dep ^. depName == pkg ^. pkgName)
depends =
pkg ^. pkgDeps
^.. each
. filtered
( \x ->
notMyself x
&& notInGHCLib x
&& ( selectDepKind Lib x
|| selectDepKind Exe x
|| selectDepKind SubLibs x
)
&& notIgnore x
)
makeDepends =
pkg ^. pkgDeps
^.. each
. filtered
( \x ->
x `notElem` depends
&& notMyself x
&& notInGHCLib x
&& ( selectDepKind LibBuildTools x
|| selectDepKind Test x
|| selectDepKind TestBuildTools x
|| selectDepKind SubLibsBuildTools x
)
&& notIgnore x
)
depsToString deps = deps <&> (wrap . fixName . unPackageName . _depName) & mconcat
_depends = depsToString depends
_makeDepends = (if uusi then " 'uusi'" else "") <> depsToString makeDepends
_url = getUrl cabal
wrap s = " '" <> s <> "'"
notInGHCLib x = (x ^. depName) `notElem` ghcLibList
notMyself x = x ^. depName /= name
notIgnore x = x ^. depName `notElem` ignored
selectDepKind k x = k `elem` (x ^. depType & mapped %~ dependencyTypeToKind)
_licenseFile = licenseFiles cabal ^? ix 0
_enableUusi = uusi
return PkgBuild {..}