arch-hs-0.0.0.0: src/Distribution/ArchHs/Core.hs
{-# LANGUAGE RecordWildCards #-}
-- | Copyright: (c) 2020 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <1793913507@qq.com>
-- 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 (intercalate, stripPrefix)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Distribution.ArchHs.Hackage
import Distribution.ArchHs.Local
import Distribution.ArchHs.PkgBuild
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)
import Distribution.Types.Version (mkVersion)
import Distribution.Types.VersionRange
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 ::
(Semigroup k, L.HasBuildInfo k, Member FlagAssignmentsEnv 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)
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 ::
Members [HackageEnv, FlagAssignmentsEnv, WithMyErr, DependencyRecord] r =>
-- | Resolved
Set PackageName ->
-- | Skipped
[UnqualComponentName] ->
-- | Whether recursive
Bool ->
-- | Target
PackageName ->
Sem r (G.AdjacencyMap (Set DependencyType) PackageName)
getDependencies resolved skip recursive name = do
cabal <- getLatestCabal name
-- Ignore subLibraries
(libDeps, libToolsDeps) <- collectLibDeps cabal
(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 list = mconcat $ fmap (\(t, pkgs) -> zip (repeat t) pkgs) list
withThisName :: [(DependencyType, PackageName)] -> [(DependencyType, PackageName, PackageName)]
withThisName = fmap (\(t, pkg) -> (t, name, pkg))
ignored = filter (\x -> not $ x `elem` ignoreList || x == name || x `elem` resolved)
filterNot p = filter (not . p)
currentLib = G.edges $ zip3 (repeat $ Set.singleton CLib) (repeat name) $ filterNot (`elem` ignoreList) libDeps
currentLibDeps = G.edges $ zip3 (repeat $ Set.singleton CLibBuildTools) (repeat name) $ filterNot (`elem` ignoreList) libToolsDeps
runnableEdges k l = G.edges $ fmap (\(x, y, z) -> (Set.singleton x, y, z)) . withThisName . filterNot (\(_, x) -> x `elem` ignoreList) . flatten . uname k $ l
currentExe = runnableEdges CExe exeDeps
currentExeTools = runnableEdges CExeBuildTools exeToolsDeps
currentTest = runnableEdges CTest testDeps
currentTestTools = runnableEdges CTestBuildTools testToolsDeps
-- currentBench = runnableEdges Types.Benchmark benchDeps
-- currentBenchTools = runnableEdges BenchmarkBuildTools benchToolsDeps
(<+>) = G.overlay
-- Only solve lib & exe deps recursively.
nextLib <- mapM (getDependencies (Set.insert name resolved) skip recursive) $ ignored libDeps
nextExe <- mapM (getDependencies (Set.insert name resolved) skip recursive) $ ignored . fmap snd . flatten . uname CExe $ exeDeps
return $
currentLib
<+> currentLibDeps
<+> currentExe
<+> currentExeTools
<+> currentTest
<+> currentTestTools
-- <+> currentBench
-- <+> currentBenchTools
<+> if recursive
then (G.overlays nextLib) <+> (G.overlays nextExe)
else G.empty
collectLibDeps :: Members [FlagAssignmentsEnv, DependencyRecord] r => GenericPackageDescription -> Sem r (PkgList, PkgList)
collectLibDeps cabal = do
case cabal & condLibrary of
Just lib -> do
let name = getPkgName' cabal
info <- evalConditionTree cabal lib
let libDeps = fmap unDepV $ buildDependsIfBuild info
toolDeps = fmap unExeV $ buildToolDependsIfBuild info
updateDependencyRecord name libDeps
updateDependencyRecord name toolDeps
return (fmap fst libDeps, fmap fst toolDeps)
Nothing -> return ([], [])
collectRunnableDeps ::
(Semigroup k, L.HasBuildInfo k, Members [FlagAssignmentsEnv, DependencyRecord] r) =>
(GenericPackageDescription -> [(UnqualComponentName, CondTree ConfVar [Dependency] k)]) ->
GenericPackageDescription ->
[UnqualComponentName] ->
Sem r (ComponentPkgList, ComponentPkgList)
collectRunnableDeps f cabal skip = do
let exes = cabal & f
name = getPkgName' cabal
info <- filter (not . (`elem` skip) . fst) . zip (exes <&> fst) <$> mapM (evalConditionTree cabal . snd) exes
let runnableDeps = info <&> ((_2 %~) $ fmap unDepV . buildDependsIfBuild)
toolDeps = info <&> ((_2 %~) $ fmap unExeV . buildToolDependsIfBuild)
k = fmap (\(c, l) -> (c, fmap fst l))
mapM_ (updateDependencyRecord name) $ fmap snd runnableDeps
mapM_ (updateDependencyRecord name) $ fmap snd toolDeps
return (k runnableDeps, k toolDeps)
collectExeDeps :: Members [FlagAssignmentsEnv, DependencyRecord] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
collectExeDeps = collectRunnableDeps condExecutables
collectTestDeps :: Members [FlagAssignmentsEnv, DependencyRecord] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
collectTestDeps = collectRunnableDeps condTestSuites
updateDependencyRecord :: Member DependencyRecord r => PackageName -> [(PackageName, VersionRange)] -> Sem r ()
updateDependencyRecord parent deps = modify' $ Map.insertWith (<>) parent deps
-- collectBenchMarkDeps :: Members [HackageEnv, FlagAssignmentEnv] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (ComponentPkgList, ComponentPkgList)
-- collectBenchMarkDeps = collectRunnableDeps condBenchmarks
-----------------------------------------------------------------------------
-- | Generate 'PkgBuild' for a 'SolvedPackage'.
cabalToPkgBuild :: Members [HackageEnv, FlagAssignmentsEnv, WithMyErr] r => SolvedPackage -> Sem r PkgBuild
cabalToPkgBuild pkg = do
let name = pkg ^. pkgName
cabal <- packageDescription <$> (getLatestCabal 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))
makeDepends =
pkg ^. pkgDeps
^.. each
. filtered
( \x ->
x `notElem` depends
&& notMyself x
&& notInGHCLib x
&& ( selectDepKind LibBuildTools x
|| selectDepKind Test x
|| selectDepKind TestBuildTools x
)
)
depsToString deps = deps <&> (wrap . fixName . unPackageName . _depName) & intercalate " "
_depends = depsToString depends
_makeDepends = depsToString makeDepends
_url = getUrl cabal
wrap s = '\'' : s <> "\'"
notInGHCLib x = (x ^. depName) `notElem` ghcLibList
notMyself x = x ^. depName /= name
selectDepKind k x = k `elem` (x ^. depType & mapped %~ dependencyTypeToKind)
return PkgBuild {..}