arch-hs-0.14: src/Distribution/ArchHs/Core.hs
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
-- | Copyright: (c) 2020-2021 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.com>
-- Stability: experimental
-- Portability: portable
-- The core functions of @arch-hs@.
module Distribution.ArchHs.Core
( getDependencies,
cabalToPkgBuild,
evalConditionTree,
subsumeGHCVersion,
-- * Helper functions
collectLibDeps,
collectExeDeps,
collectTestDeps,
collectSubLibDeps,
collectSetupDeps,
)
where
import qualified Algebra.Graph.Labelled.AdjacencyMap as G
import Data.Bifunctor (second)
import Data.Containers.ListUtils (nubOrd)
import qualified Data.Map as Map
import Data.List (sort)
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import Distribution.ArchHs.ExtraDB (versionInExtra)
import Distribution.ArchHs.Exception
import Distribution.ArchHs.Hackage
( getLatestCabal,
getLatestSHA256,
getPackageFlag,
)
import Distribution.ArchHs.Internal.Prelude
import Distribution.ArchHs.Local (ignoreList)
import Distribution.ArchHs.Name
import Distribution.ArchHs.PkgBuild
( PkgBuild (..),
mapLicense,
showArchLicense,
)
import Distribution.ArchHs.Types
import Distribution.ArchHs.Utils
import Distribution.Compiler (CompilerFlavor (..))
import Distribution.PackageDescription hiding (pkgName)
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.Utils.ShortText (fromShortText)
archEnv :: Version -> 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 ghcVersion _ (Impl GHC range) = Right $ withinRange ghcVersion range
archEnv _ _ (Impl _ _) = Right False
archEnv _ assignment f@(PkgFlag 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 [KnownGHCVersion, FlagAssignmentsEnv, Trace] r) =>
GenericPackageDescription ->
CondTree ConfVar [Dependency] k ->
Sem r BuildInfo
evalConditionTree cabal cond = do
flagAssignments <- ask
let name = getPkgName' cabal
packageFlags = genPackageFlags cabal
flagAssignment = getFlagAssignment name flagAssignments
thisFlag = defaultFlags packageFlags flagAssignment
trace' $ "Evaluating condition tree of " <> show name
trace' $ "Flags: " <> show thisFlag
traceCallStack
ghcVersion <- ask
return $ (^. L.buildInfo) . snd $ simplifyCondTree (archEnv ghcVersion 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 [KnownGHCVersion, 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, Map.Map PackageName [SystemDependency])
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
let kIgnoreVersionLib = fmap fst
kIgnoreVersionComp = fmap (second $ fmap fst)
kIgnoreVersionSetup = kIgnoreVersionLib
(libDeps, libToolsDeps, libSysDeps) <- collectLibDeps kIgnoreVersionLib cabal
(subLibDeps, subLibToolsDeps, subLibSysDeps) <- collectSubLibDeps kIgnoreVersionComp cabal skip
(exeDeps, exeToolsDeps, exeSysDeps) <- collectExeDeps kIgnoreVersionComp cabal skip
(testDeps, testToolsDeps, testSysDeps) <- collectTestDeps kIgnoreVersionComp cabal skip
setupDeps <- collectSetupDeps kIgnoreVersionSetup cabal
-- 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
filteredSetupDeps = ignore setupDeps
filteredSubLibDepsNames = fmap (unqualComponentNameToPackageName . fst) subLibDeps
ignoreSubLibs = filter (`notElem` filteredSubLibDepsNames)
ignoreResolved = filter (`notElem` resolved)
currentLib = G.edges $ zip3 (repeat $ Set.singleton CLib) (repeat name) filteredLibDeps
currentLibToolDeps = G.edges $ zip3 (repeat $ Set.singleton CLibBuildTools) (repeat name) filteredLibToolsDeps
currentSetupDeps = G.edges $ zip3 (repeat $ Set.singleton CSetup) (repeat name) filteredSetupDeps
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
currentSysDeps = nubOrd $ libSysDeps <> subLibSysDeps <> exeSysDeps <> testSysDeps
processNext = mapM (getDependencies skip (Just name)) . ignoreResolved . ignoreSubLibs
(<+>) = G.overlay
nextLib <- processNext filteredLibDeps
nextSetup <- processNext filteredSetupDeps
nextExe <- processNext $ fmap snd filteredExeDeps
-- TODO: maybe unstable
nextTest <- processNext $ fmap snd filteredTestDeps
nextSubLibs <- processNext $ fmap snd filteredSubLibDeps
let temp = [nextLib, nextSetup, nextExe, nextTest, nextSubLibs]
nexts = G.overlays $ temp ^. each ^.. each . _1
subsubs = temp ^. each ^.. each . _2 ^. each
nextSys = temp ^. each ^.. each . _3 ^. each
return
( currentLib
<+> currentLibToolDeps
<+> currentSetupDeps
<+> currentExe
<+> currentExeTools
<+> currentTest
<+> currentTestTools
<+> currentSubLibs
<+> currentSubLibsTools
-- <+> currentBench
-- <+> currentBenchTools
<+> nexts,
Set.fromList filteredSubLibDepsNames <> subsubs,
(if null currentSysDeps then Map.empty else Map.singleton name currentSysDeps) <> nextSys
)
collectLibDeps ::
(Members [KnownGHCVersion, FlagAssignmentsEnv, DependencyRecord, Trace] r, Show a, Monoid a) =>
([(PackageName, VersionRange)] -> a) ->
GenericPackageDescription ->
Sem r (a, a, [SystemDependency])
collectLibDeps k cabal = do
case cabal & condLibrary of
Just lib -> do
let name = getPkgName' cabal
trace' $ "Getting libs dependencies of " <> show name
info <- evalConditionTree cabal lib
let libDeps = unDepV <$> buildDependsIfBuild info
toolDeps = unBuildTools $ buildToolsAndbuildToolDependsIfBuild info
systemDeps = unSystemDependency $ pkgconfigDependsAndExtraLibsIfBuild info
mapM_ (uncurry updateDependencyRecord) libDeps
mapM_ (uncurry updateDependencyRecord) toolDeps
let result = (k libDeps, k toolDeps, systemDeps)
trace' $ "Found: " <> show result
traceCallStack
return result
Nothing -> return mempty -- 'Monoid a' comes from here
collectComponentialDeps ::
(HasCallStack, Semigroup k, L.HasBuildInfo k, Members [KnownGHCVersion, FlagAssignmentsEnv, DependencyRecord, Trace] r, Show a) =>
String ->
(GenericPackageDescription -> [(UnqualComponentName, CondTree ConfVar [Dependency] k)]) ->
([(UnqualComponentName, [(PackageName, VersionRange)])] -> a) ->
GenericPackageDescription ->
[UnqualComponentName] ->
Sem r (a, a, [SystemDependency])
collectComponentialDeps tag f k cabal skip = do
let conds = cabal & f
name = getPkgName' cabal
trace' $ "Getting " <> tag <> " 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 %~ (unBuildTools . buildToolsAndbuildToolDependsIfBuild)
sysDeps = info <&> _2 %~ (unSystemDependency . pkgconfigDependsAndExtraLibsIfBuild)
mapM_ (uncurry updateDependencyRecord) $ deps ^.. each . _2 ^. each
mapM_ (uncurry updateDependencyRecord) $ toolDeps ^.. each . _2 ^. each
let result = (k deps, k toolDeps, mconcat $ fmap snd sysDeps)
trace' $ "Found: " <> show result
traceCallStack
return result
collectExeDeps ::
(HasCallStack, Members [KnownGHCVersion, FlagAssignmentsEnv, DependencyRecord, Trace] r, Show a) =>
([(UnqualComponentName, [(PackageName, VersionRange)])] -> a) ->
GenericPackageDescription ->
[UnqualComponentName] ->
Sem r (a, a, [SystemDependency])
collectExeDeps = collectComponentialDeps "exe" condExecutables
collectTestDeps ::
(HasCallStack, Members [KnownGHCVersion, FlagAssignmentsEnv, DependencyRecord, Trace] r, Show a) =>
([(UnqualComponentName, [(PackageName, VersionRange)])] -> a) ->
GenericPackageDescription ->
[UnqualComponentName] ->
Sem r (a, a, [SystemDependency])
collectTestDeps = collectComponentialDeps "test" condTestSuites
collectSubLibDeps ::
(HasCallStack, Members [KnownGHCVersion, FlagAssignmentsEnv, DependencyRecord, Trace] r, Show a) =>
([(UnqualComponentName, [(PackageName, VersionRange)])] -> a) ->
GenericPackageDescription ->
[UnqualComponentName] ->
Sem r (a, a, [SystemDependency])
collectSubLibDeps = collectComponentialDeps "sublib" condSubLibraries
collectSetupDeps ::
(Member Trace r, Show a, Monoid a) =>
([(PackageName, VersionRange)] -> a) ->
GenericPackageDescription ->
Sem r a
collectSetupDeps k cabal = do
let name = getPkgName' cabal
trace' $ "Getting setup dependencies of " <> show name
case setupBuildInfo $ packageDescription cabal of
Just (SetupBuildInfo deps _) -> do
let result = k $ unDepV <$> deps
trace' $ "Found: " <> show result
return result
_ -> return mempty
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 -> Bool -> [ArchLinuxName] -> Sem r PkgBuild
cabalToPkgBuild pkg uusi sysDeps = do
let name = pkg ^. pkgName
cabal <- packageDescription <$> getLatestCabal name
pkgFlags <- getPackageFlag name
assignment <- getFlagAssignment name <$> ask
let showFlagForCmd (unFlagName -> fName, enabled) = "-f" <> (if enabled then "" else "-") <> fName
_flags = unwords . fmap showFlagForCmd . unFlagAssignment $ defaultFlags pkgFlags assignment
_sha256sums <- (\case Just s -> "'" <> s <> "'"; Nothing -> "'SKIP'") <$> getLatestSHA256 name
let _hkgName = pkg ^. pkgName & unPackageName
_pkgName = unArchLinuxName . toArchLinuxName $ pkg ^. pkgName
_pkgVer = prettyShow $ getPkgVersion cabal
_pkgDesc = fromShortText $ synopsis cabal
getL NONE = ""
getL (License e) = getE e
getE (ELicense (ELicenseId x) _) = showArchLicense . mapLicense $ x
getE (ELicense (ELicenseIdPlus x) _) = showArchLicense . 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
depends =
pkg ^. pkgDeps
^.. each
. filtered
( \x ->
depNotMyself name x
&& depNotInGHCLib x
&& ( depIsKind Lib x
|| depIsKind Exe x
|| depIsKind SubLibs x
)
)
makeDepends =
pkg ^. pkgDeps
^.. each
. filtered
( \x ->
x `notElem` depends
&& depNotMyself name x
&& depNotInGHCLib x
&& ( depIsKind LibBuildTools x
|| depIsKind ExeBuildTools x
|| depIsKind Test x
|| depIsKind TestBuildTools x
|| depIsKind SubLibsBuildTools x
|| depIsKind Setup x
)
)
depsToString k deps = (sort $ deps <&> (wrap . unArchLinuxName . toArchLinuxName . k)) & mconcat
_depends = depsToString _depName depends <> depsToString id sysDeps
_makeDepends = (if uusi then " 'uusi'" else "") <> depsToString _depName makeDepends
_url = getUrl cabal
wrap s = " '" <> s <> "'"
_licenseFile = licenseFile cabal
_enableUusi = uusi
return PkgBuild {..}
-----------------------------------------------------------------------------
-- | Get the ghc version in 'ExtraDB'
subsumeGHCVersion :: Members [ExtraEnv, WithMyErr] r => InterpreterFor KnownGHCVersion r
subsumeGHCVersion m = do
rawVersion <- versionInExtra $ ArchLinuxName "haskell-ghc"
let ghcVersion = fromMaybe (error $ "Impossible: unable to parse ghc version from [extra]: " <> rawVersion) $ simpleParsec rawVersion
runReader ghcVersion m