stack-3.7.1: src/Stack/Package.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-|
Module : Stack.Package
Description : Dealing with Cabal.
License : BSD-3-Clause
Dealing with Cabal.
-}
module Stack.Package
( readDotBuildinfo
, resolvePackage
, packageFromPackageDescription
, Package (..)
, PackageConfig (..)
, buildLogPath
, PackageException (..)
, resolvePackageDescription
, packageDependencies
, applyForceCustomBuild
, hasBuildableMainLibrary
, mainLibraryHasExposedModules
, packageUnknownTools
, buildableForeignLibs
, buildableSubLibs
, buildableExes
, buildableTestSuites
, buildableBenchmarks
, getPackageOpts
, processPackageDepsEither
, listOfPackageDeps
, setOfPackageDeps
, topSortPackageComponent
) where
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import qualified Data.Text as T
import Distribution.CabalSpecVersion ( cabalSpecToVersionDigits )
import Distribution.Compiler
( CompilerFlavor (..), PerCompilerFlavor (..) )
import Distribution.ModuleName ( ModuleName )
import Distribution.Package ( mkPackageName )
import Distribution.PackageDescription
( Benchmark (..), BuildInfo (..), BuildType (..)
, CondTree (..), Condition (..), ConfVar (..)
, Dependency (..), Executable (..), ForeignLib (..)
, GenericPackageDescription (..), HookedBuildInfo
, Library (..), PackageDescription (..), PackageFlag (..)
, SetupBuildInfo (..), TestSuite (..), allLibraries
, buildType, depPkgName, depVerRange
, unqualComponentNameToPackageName
)
import qualified Distribution.PackageDescription as Executable
( Executable (..) )
import Distribution.Simple.PackageDescription ( readHookedBuildInfo )
import Distribution.System ( OS (..), Arch, Platform (..) )
import Distribution.Text ( display )
import qualified Distribution.Types.CondTree as Cabal
import Distribution.Utils.Path ( getSymbolicPath )
import Distribution.Verbosity ( silent )
import Distribution.Version
( anyVersion, mkVersion, orLaterVersion )
import Path
( (</>), parent, parseAbsDir, parseRelDir, parseRelFile
, stripProperPrefix
)
import Path.Extra ( concatAndCollapseAbsDir, toFilePathNoTrailingSep )
import Stack.Component
( componentDependencyMap, foldOnNameAndBuildInfo
, isComponentBuildable, stackBenchmarkFromCabal
, stackExecutableFromCabal, stackForeignLibraryFromCabal
, stackLibraryFromCabal, stackTestFromCabal
)
import Stack.ComponentFile
( buildDir, componentAutogenDir, componentBuildDir
, componentOutputDir, packageAutogenDir
)
import Stack.Constants ( relFileCabalMacrosH, relDirLogs )
import Stack.Constants.Config ( distDirFromDir )
import Stack.PackageFile ( getPackageFile, stackPackageFileFromCabal )
import Stack.Prelude hiding ( Display (..) )
import Stack.Types.BuildConfig ( HasBuildConfig (..), getWorkDir )
import Stack.Types.CompCollection
( CompCollection, collectionLookup, foldAndMakeCollection
, foldComponentToAnotherCollection, getBuildableSet
, getBuildableSetText
)
import Stack.Types.Compiler ( ActualCompiler (..) )
import Stack.Types.CompilerPaths ( cabalVersionL )
import Stack.Types.Component
( HasBuildInfo, HasComponentInfo, StackUnqualCompName (..) )
import Stack.Types.ComponentUtils ( emptyCompName, toCabalName )
import qualified Stack.Types.Component as Component
import Stack.Types.Config ( Config (..), HasConfig (..) )
import Stack.Types.Dependency
( DepLibrary (..), DepType (..), DepValue (..)
, cabalSetupDepsToStackDep, libraryDepFromVersionRange
)
import Stack.Types.EnvConfig ( HasEnvConfig )
import Stack.Types.Installed
( InstallMap, Installed (..), InstalledMap
, installedToPackageIdOpt
)
import Stack.Types.NamedComponent
( NamedComponent (..), isPotentialDependency
, subLibComponents
)
import Stack.Types.Package
( BioInput(..), BuildInfoOpts (..), Package (..)
, PackageConfig (..), PackageException (..)
, dotCabalCFilePath, packageIdentifier
)
import Stack.Types.PackageFile
( DotCabalPath, PackageComponentFile (..) )
import Stack.Types.SourceMap ( Target(..), PackageType (..) )
import Stack.Types.Version
( VersionRange, intersectVersionRanges, withinRange )
import System.FilePath ( replaceExtension )
import RIO.Seq ((|>))
-- | Read @<package>.buildinfo@ ancillary files produced by some Setup.hs hooks.
-- The file includes Cabal file syntax to be merged into the package description
-- derived from the package's Cabal file.
--
-- NOTE: not to be confused with BuildInfo, an Stack-internal datatype.
readDotBuildinfo :: MonadIO m => Path Abs File -> m HookedBuildInfo
readDotBuildinfo buildinfofp =
liftIO $ readHookedBuildInfo silent (toFilePath buildinfofp)
-- | Resolve a parsed Cabal file into a t'Package', which contains all of the
-- info needed for Stack to build the t'Package' given the current
-- configuration.
resolvePackage :: PackageConfig -> GenericPackageDescription -> Package
resolvePackage packageConfig gpkg =
packageFromPackageDescription
packageConfig
(genPackageFlags gpkg)
(resolvePackageDescription packageConfig gpkg)
packageFromPackageDescription ::
PackageConfig
-> [PackageFlag]
-> PackageDescription
-> Package
packageFromPackageDescription
packageConfig
pkgFlags
pkg
= Package
{ name = name
, version = pkgVersion pkgId
, license = licenseRaw pkg
, ghcOptions = packageConfig.ghcOptions
, cabalConfigOpts = packageConfig.cabalConfigOpts
, flags = packageConfig.flags
, defaultFlags = M.fromList
[(flagName flag, flagDefault flag) | flag <- pkgFlags]
, library = stackLibraryFromCabal <$> library pkg
, subLibraries =
foldAndMakeCollection stackLibraryFromCabal $ subLibraries pkg
, foreignLibraries =
foldAndMakeCollection stackForeignLibraryFromCabal $ foreignLibs pkg
, testSuites =
foldAndMakeCollection stackTestFromCabal $ testSuites pkg
, benchmarks =
foldAndMakeCollection stackBenchmarkFromCabal $ benchmarks pkg
, executables =
foldAndMakeCollection stackExecutableFromCabal $ executables pkg
, buildType = buildType pkg
, setupDeps = fmap cabalSetupDepsToStackDep (setupBuildInfo pkg)
, cabalSpec = specVersion pkg
, file = stackPackageFileFromCabal pkg
, testEnabled = packageConfig.enableTests
, benchmarkEnabled = packageConfig.enableBenchmarks
}
where
-- Gets all of the modules, files, build files, and data files that constitute
-- the package. This is primarily used for dirtiness checking during build, as
-- well as use by "stack ghci"
pkgId = package pkg
name = pkgName pkgId
-- | This is an action used to collect info needed for "stack ghci". This info
-- isn't usually needed, so computation of it is deferred.
getPackageOpts ::
(HasEnvConfig env, MonadReader env m, MonadThrow m, MonadUnliftIO m )
=> Package
-> InstallMap
-> InstalledMap
-> [PackageName]
-> [PackageName]
-> Path Abs File
-> m ( Map NamedComponent (Map ModuleName (Path Abs File))
, Map NamedComponent [DotCabalPath]
, Map NamedComponent BuildInfoOpts
)
getPackageOpts
stackPackage
installMap
installedMap
omitPkgs
addPkgs
cabalFP
= do
PackageComponentFile !componentsModules componentFiles _ _ <-
getPackageFile stackPackage cabalFP
let subLibs =
S.toList $ subLibComponents $ M.keysSet componentsModules
excludedSubLibs =
map (unqualComponentNameToPackageName . toCabalName) subLibs
componentsOpts <- generatePkgDescOpts
installMap
installedMap
(excludedSubLibs ++ omitPkgs)
addPkgs
cabalFP
stackPackage
componentFiles
pure (componentsModules, componentFiles, componentsOpts)
-- | Generate GHC options for the package's components, and a list of options
-- which apply generally to the package, not one specific component.
generatePkgDescOpts ::
(HasEnvConfig env, MonadThrow m, MonadReader env m, MonadIO m)
=> InstallMap
-> InstalledMap
-> [PackageName]
-- ^ Packages to omit from the "-package" / "-package-id" flags
-> [PackageName]
-- ^ Packages to add to the "-package" flags
-> Path Abs File
-> Package
-> Map NamedComponent [DotCabalPath]
-> m (Map NamedComponent BuildInfoOpts)
generatePkgDescOpts
installMap
installedMap
omitPackages
addPackages
cabalFP
pkg
componentPaths
= do
config <- view configL
cabalVersion <- view cabalVersionL
distDir <- distDirFromDir cabalDir
let generate componentName buildInfo = generateBuildInfoOpts BioInput
{ installMap
, installedMap
, cabalDir
, distDir
, omitPackages
, addPackages
, buildInfo
, dotCabalPaths =
fromMaybe [] (M.lookup componentName componentPaths)
, configLibDirs = config.extraLibDirs
, configIncludeDirs = config.extraIncludeDirs
, componentName
, cabalVersion
}
let insertInMap name compVal = M.insert name (generate name compVal)
let translatedInsertInMap constructor name =
insertInMap (constructor name)
let makeBuildInfoOpts selector constructor =
foldOnNameAndBuildInfo
(selector pkg)
(translatedInsertInMap constructor)
let aggregateAllBuildInfoOpts =
makeBuildInfoOpts (.library) (const CLib)
. makeBuildInfoOpts (.subLibraries) CSubLib
. makeBuildInfoOpts (.executables) CExe
. makeBuildInfoOpts (.benchmarks) CBench
. makeBuildInfoOpts (.testSuites) CTest
pure $ aggregateAllBuildInfoOpts mempty
where
cabalDir = parent cabalFP
-- | Generate GHC options for the target. Since Cabal also figures out these
-- options, currently this is only used for invoking GHCI (via stack ghci).
generateBuildInfoOpts :: BioInput -> BuildInfoOpts
generateBuildInfoOpts bi =
BuildInfoOpts
{ opts =
ghcOpts
++ fmap ("-optP" <>) bi.buildInfo.cppOptions
-- NOTE for future changes: Due to this use of nubOrd (and other uses
-- downstream), these generated options must not rely on multiple
-- argument sequences. For example, ["--main-is", "Foo.hs", "--main-
-- is", "Bar.hs"] would potentially break due to the duplicate
-- "--main-is" being removed.
--
-- See https://github.com/commercialhaskell/stack/issues/1255
, oneWordOpts = nubOrd $ concat
[extOpts, srcOpts, includeOpts, libOpts, fworks, cObjectFiles]
, packageFlags = deps
, cabalMacros = componentAutogen </> relFileCabalMacrosH
}
where
cObjectFiles = mapMaybe
( fmap toFilePath
. makeObjectFilePathFromC bi.cabalDir bi.componentName bi.distDir
)
cfiles
cfiles = mapMaybe dotCabalCFilePath bi.dotCabalPaths
installVersion = snd
-- Generates: -package=base -package=base16-bytestring-0.1.1.6 ...
deps =
concat
[ case M.lookup name bi.installedMap of
Just (_, Stack.Types.Installed.Library _ident installedInfo) ->
installedToPackageIdOpt installedInfo
_ -> ["-package=" <> packageNameString name <>
maybe "" -- This empty case applies to e.g. base.
((("-" <>) . versionString) . installVersion)
(M.lookup name bi.installMap)]
| name <- pkgs
]
pkgs =
bi.addPackages ++
[ name
| Dependency name _ _ <- bi.buildInfo.targetBuildDepends
-- TODO: Cabal 3.0 introduced multiple public libraries in a single
-- dependency
, name `notElem` bi.omitPackages
]
PerCompilerFlavor ghcOpts _ = bi.buildInfo.options
extOpts =
map (("-X" ++) . display) bi.buildInfo.allLanguages
<> map (("-X" ++) . display) bi.buildInfo.usedExtensions
srcOpts =
map (("-i" <>) . toFilePathNoTrailingSep)
(concat
[ [ componentBuildDir bi.componentName bi.distDir ]
, [ bi.cabalDir
| null bi.buildInfo.hsSourceDirs
]
, mapMaybe
(toIncludeDir . getSymbolicPath)
bi.buildInfo.hsSourceDirs
, [ componentAutogen ]
, [ packageAutogenDir bi.distDir ]
, [ componentOutputDir bi.componentName bi.distDir ]
]) ++
[ "-stubdir=" ++ toFilePathNoTrailingSep (buildDir bi.distDir) ]
componentAutogen = componentAutogenDir bi.componentName bi.distDir
toIncludeDir "." = Just bi.cabalDir
toIncludeDir relDir = concatAndCollapseAbsDir bi.cabalDir relDir
includeOpts =
map ("-I" <>) (bi.configIncludeDirs <> pkgIncludeOpts)
pkgIncludeOpts =
[ toFilePathNoTrailingSep absDir
| dir <- bi.buildInfo.includeDirs
, absDir <- handleDir dir
]
libOpts =
map ("-l" <>) bi.buildInfo.extraLibs <>
map ("-L" <>) (bi.configLibDirs <> pkgLibDirs)
pkgLibDirs =
[ toFilePathNoTrailingSep absDir
| dir <- bi.buildInfo.extraLibDirs
, absDir <- handleDir dir
]
handleDir dir = case (parseAbsDir dir, parseRelDir dir) of
(Just ab, _ ) -> [ab]
(_ , Just rel) -> [bi.cabalDir </> rel]
(Nothing, Nothing ) -> []
fworks = map ("-framework=" <>) bi.buildInfo.frameworks
-- | Make the .o path from the .c file path for a component. Example:
--
-- @
-- executable FOO
-- c-sources: cbits/text_search.c
-- @
--
-- Produces
--
-- <dist-dir>/build/FOO/FOO-tmp/cbits/text_search.o
--
-- Example:
--
-- λ> makeObjectFilePathFromC
-- $(mkAbsDir "/Users/chris/Repos/hoogle")
-- CLib
-- $(mkAbsDir "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist")
-- $(mkAbsFile "/Users/chris/Repos/hoogle/cbits/text_search.c")
-- Just "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist/build/cbits/text_search.o"
-- λ> makeObjectFilePathFromC
-- $(mkAbsDir "/Users/chris/Repos/hoogle")
-- (CExe "hoogle")
-- $(mkAbsDir "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist")
-- $(mkAbsFile "/Users/chris/Repos/hoogle/cbits/text_search.c")
-- Just "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist/build/hoogle/hoogle-tmp/cbits/text_search.o"
-- λ>
makeObjectFilePathFromC ::
MonadThrow m
=> Path Abs Dir -- ^ The cabal directory.
-> NamedComponent -- ^ The name of the component.
-> Path Abs Dir -- ^ Dist directory.
-> Path Abs File -- ^ The path to the .c file.
-> m (Path Abs File) -- ^ The path to the .o file for the component.
makeObjectFilePathFromC cabalDir namedComponent distDir cFilePath = do
relCFilePath <- stripProperPrefix cabalDir cFilePath
relOFilePath <-
parseRelFile (replaceExtension (toFilePath relCFilePath) "o")
pure (componentOutputDir namedComponent distDir </> relOFilePath)
-- | Get all dependencies of the package (buildable targets only).
packageDependencies ::
PackageDescription
-> Map PackageName VersionRange
packageDependencies pkg =
M.fromListWith intersectVersionRanges $
map (depPkgName &&& depVerRange) $
concatMap targetBuildDepends (allBuildInfo' pkg)
<> maybe [] setupDepends (setupBuildInfo pkg)
-- | Variant of 'Distribution.Types.PackageDescription.allBuildInfo' from Cabal
-- that, like versions before Cabal 2.2 only includes buildable components.
allBuildInfo' :: PackageDescription -> [BuildInfo]
allBuildInfo' pkg_descr = [ bi | lib <- allLibraries pkg_descr
, let bi = libBuildInfo lib
, buildable bi ]
++ [ bi | flib <- foreignLibs pkg_descr
, let bi = foreignLibBuildInfo flib
, buildable bi ]
++ [ bi | exe <- executables pkg_descr
, let bi = buildInfo exe
, buildable bi ]
++ [ bi | tst <- testSuites pkg_descr
, let bi = testBuildInfo tst
, buildable bi ]
++ [ bi | tst <- benchmarks pkg_descr
, let bi = benchmarkBuildInfo tst
, buildable bi ]
-- | Evaluates the conditions of a t'GenericPackageDescription', yielding
-- a resolved t'PackageDescription'.
resolvePackageDescription ::
PackageConfig
-> GenericPackageDescription
-> PackageDescription
resolvePackageDescription
packageConfig
( GenericPackageDescription
desc _ defaultFlags mlib subLibs foreignLibs' exes tests benches
)
= desc
{ library = fmap (resolveConditions rc updateLibDeps) mlib
, subLibraries = map
( \(n, v) ->
(resolveConditions rc updateLibDeps v){libName = LSubLibName n}
)
subLibs
, foreignLibs = map
( \(n, v) ->
(resolveConditions rc updateForeignLibDeps v){foreignLibName = n}
)
foreignLibs'
, executables = map
( \(n, v) -> (resolveConditions rc updateExeDeps v){exeName = n} )
exes
, testSuites = map
( \(n, v) ->
(resolveConditions rc updateTestDeps v){testName = n}
)
tests
, benchmarks = map
( \(n, v) ->
(resolveConditions rc updateBenchmarkDeps v){benchmarkName = n}
)
benches
}
where
flags = M.union packageConfig.flags (flagMap defaultFlags)
rc = mkResolveConditions
packageConfig.compilerVersion
packageConfig.platform
flags
updateLibDeps lib deps = lib
{ libBuildInfo = (libBuildInfo lib) {targetBuildDepends = deps} }
updateForeignLibDeps lib deps = lib
{ foreignLibBuildInfo =
(foreignLibBuildInfo lib) {targetBuildDepends = deps}
}
updateExeDeps exe deps = exe
{ Executable.buildInfo = (buildInfo exe) {targetBuildDepends = deps} }
updateTestDeps test deps = test
{ testBuildInfo = (testBuildInfo test) {targetBuildDepends = deps} }
updateBenchmarkDeps bench deps = bench
{ benchmarkBuildInfo =
(benchmarkBuildInfo bench) {targetBuildDepends = deps}
}
-- | Make a map from a list of flag specifications.
--
-- What is @flagManual@ for?
flagMap :: [PackageFlag] -> Map FlagName Bool
flagMap = M.fromList . map pair
where
pair :: PackageFlag -> (FlagName, Bool)
pair = flagName &&& flagDefault
data ResolveConditions = ResolveConditions
{ flags :: Map FlagName Bool
, compilerVersion :: ActualCompiler
, os :: OS
, arch :: Arch
}
-- | Generic a @ResolveConditions@ using sensible defaults.
mkResolveConditions ::
ActualCompiler -- ^ Compiler version
-> Platform -- ^ installation target platform
-> Map FlagName Bool -- ^ enabled flags
-> ResolveConditions
mkResolveConditions compilerVersion (Platform arch os) flags = ResolveConditions
{ flags
, compilerVersion
, os
, arch
}
-- | Resolve the condition tree for the library.
resolveConditions ::
(Semigroup target, Monoid target, Show target)
=> ResolveConditions
-> (target -> cs -> target)
-> CondTree ConfVar cs target
-> target
resolveConditions rc addDeps (CondNode lib deps cs) = basic <> children
where
basic = addDeps lib deps
children = mconcat (map apply cs)
where
apply (Cabal.CondBranch cond node mcs) =
if condSatisfied cond
then resolveConditions rc addDeps node
else maybe mempty (resolveConditions rc addDeps) mcs
condSatisfied c =
case c of
Var v -> varSatisfied v
Lit b -> b
CNot c' -> not (condSatisfied c')
COr cx cy -> condSatisfied cx || condSatisfied cy
CAnd cx cy -> condSatisfied cx && condSatisfied cy
varSatisfied v =
case v of
OS os -> os == rc.os
Arch arch -> arch == rc.arch
PackageFlag flag -> fromMaybe False $ M.lookup flag rc.flags
-- NOTE: ^^^^^ This should never happen, as all flags which are used
-- must be declared. Defaulting to False.
Impl flavor range ->
case (flavor, rc.compilerVersion) of
(GHC, ACGhc vghc) -> vghc `withinRange` range
_ -> False
-- | Path for the package's build log.
buildLogPath ::
(MonadReader env m, HasBuildConfig env, MonadThrow m)
=> Package
-> Maybe String
-> m (Path Abs File)
buildLogPath package' msuffix = do
env <- ask
let workDir = getWorkDir env
fp <- parseRelFile $ concat $
packageIdentifierString (packageIdentifier package') :
maybe id (\suffix -> ("-" :) . (suffix :)) msuffix [".log"]
pure $ workDir </> relDirLogs </> fp
{- FIXME
-- | Create a 'ProjectPackage' from a directory containing a package.
mkProjectPackage
:: forall env. (HasPantryConfig env, HasLogFunc env, HasProcessContext env)
=> PrintWarnings
-> ResolvedPath Dir
-> RIO env ProjectPackage
mkProjectPackage printWarnings dir = do
(gpd, name, cabalfp) <- loadCabalFilePath (resolvedAbsolute dir)
pure ProjectPackage
{ ppCabalFP = cabalfp
, ppGPD' = gpd printWarnings
, ppResolvedDir = dir
, ppName = name
}
-- | Create a 'DepPackage' from a 'PackageLocation'
mkDepPackage
:: forall env. (HasPantryConfig env, HasLogFunc env, HasProcessContext env)
=> PackageLocation
-> RIO env DepPackage
mkDepPackage pl = do
(name, gpdio) <-
case pl of
PLMutable dir -> do
(gpdio, name, _cabalfp) <- loadCabalFilePath (resolvedAbsolute dir)
pure (name, gpdio NoPrintWarnings)
PLImmutable pli -> do
PackageIdentifier name _ <- getPackageLocationIdent pli
run <- askRunInIO
pure (name, run $ loadCabalFileImmutable pli)
pure DepPackage
{ dpGPD' = gpdio
, dpLocation = pl
, dpName = name
}
-}
-- | Force a package to be treated as a custom build type, see
-- <https://github.com/commercialhaskell/stack/issues/4488>
applyForceCustomBuild ::
Version -- ^ global Cabal version
-> Package
-> Package
applyForceCustomBuild cabalVersion package
| forceCustomBuild =
package
{ buildType = Custom
, setupDeps = Just $ M.fromList
[ ("Cabal", libraryDepFromVersionRange cabalVersionRange)
, ("base", libraryDepFromVersionRange anyVersion)
]
}
| otherwise = package
where
cabalVersionRange =
orLaterVersion $ mkVersion $ cabalSpecToVersionDigits
package.cabalSpec
forceCustomBuild = package.buildType == Simple
&& not (cabalVersion `withinRange` cabalVersionRange)
-- | Check if the package has a main library that is buildable.
hasBuildableMainLibrary :: Package -> Bool
hasBuildableMainLibrary package =
maybe False isComponentBuildable package.library
-- | Check if the main library has any exposed modules.
--
-- This should become irrelevant at some point since there's nothing inherently
-- wrong or different with packages exposing only modules in internal libraries
-- (for instance).
mainLibraryHasExposedModules :: Package -> Bool
mainLibraryHasExposedModules package =
maybe False (not . null . (.exposedModules)) package.library
-- | Aggregate all unknown tools from all components. Mostly meant for
-- build tools specified in the legacy manner (build-tools:) that failed the
-- hard-coded lookup. See 'Stack.Types.Component.unknownTools' for more
-- information.
packageUnknownTools :: Package -> Set Text
packageUnknownTools pkg = lib (bench <> tests <> flib <> sublib <> exe)
where
lib setT = case pkg.library of
Just libV -> addUnknownTools libV setT
Nothing -> setT
bench = gatherUnknownTools pkg.benchmarks
tests = gatherUnknownTools pkg.testSuites
flib = gatherUnknownTools pkg.foreignLibraries
sublib = gatherUnknownTools pkg.subLibraries
exe = gatherUnknownTools pkg.executables
addUnknownTools :: HasBuildInfo x => x -> Set Text -> Set Text
addUnknownTools = (<>) . (.buildInfo.unknownTools)
gatherUnknownTools :: HasBuildInfo x => CompCollection x -> Set Text
gatherUnknownTools = foldr' addUnknownTools mempty
buildableForeignLibs :: Package -> Set StackUnqualCompName
buildableForeignLibs pkg = getBuildableSet pkg.foreignLibraries
buildableSubLibs :: Package -> Set StackUnqualCompName
buildableSubLibs pkg = getBuildableSet pkg.subLibraries
buildableExes :: Package -> Set StackUnqualCompName
buildableExes pkg = getBuildableSet pkg.executables
buildableTestSuites :: Package -> Set StackUnqualCompName
buildableTestSuites pkg = getBuildableSet pkg.testSuites
buildableBenchmarks :: Package -> Set StackUnqualCompName
buildableBenchmarks pkg = getBuildableSet pkg.benchmarks
-- | Apply a generic processing function in a Monad over all of the Package's
-- components.
processPackageComponent ::
forall m a. (Monad m)
=> Package
-> (forall component. HasComponentInfo component => component -> m a -> m a)
-- ^ Processing function with all the component's info.
-> m a
-- ^ Initial value.
-> m a
processPackageComponent pkg componentFn = do
let componentKindProcessor ::
forall component. HasComponentInfo component
=> (Package -> CompCollection component)
-> m a
-> m a
componentKindProcessor target =
foldComponentToAnotherCollection
(target pkg)
componentFn
processMainLib = maybe id componentFn pkg.library
processAllComp =
( if pkg.benchmarkEnabled
then componentKindProcessor (.benchmarks)
else id
)
. ( if pkg.testEnabled
then componentKindProcessor (.testSuites)
else id
)
. componentKindProcessor (.foreignLibraries)
. componentKindProcessor (.executables)
. componentKindProcessor (.subLibraries)
. processMainLib
processAllComp
-- | This is a function to iterate in a monad over all of a package's
-- dependencies, and yield a collection of results (used with list and set).
processPackageMapDeps ::
(Monad m)
=> Package
-> (Map PackageName DepValue -> m a -> m a)
-> m a
-> m a
processPackageMapDeps pkg fn = do
let packageSetupDepsProcessor resAction = case pkg.setupDeps of
Nothing -> resAction
Just v -> fn v resAction
processAllComp = processPackageComponent pkg (fn . componentDependencyMap)
. packageSetupDepsProcessor
processAllComp
-- | This is a function to iterate in a monad over all of a package component's
-- dependencies, and yield a collection of results.
processPackageDeps ::
(Monad m)
=> Package
-> (smallResT -> resT -> resT)
-> (PackageName -> DepValue -> m smallResT)
-> m resT
-> m resT
processPackageDeps pkg combineResults fn = do
let asPackageNameSet accessor =
S.map (mkPackageName . T.unpack) $ getBuildableSetText $ accessor pkg
(!subLibNames, !foreignLibNames) =
( asPackageNameSet (.subLibraries)
, asPackageNameSet (.foreignLibraries)
)
shouldIgnoreDep (packageNameV :: PackageName)
| packageNameV == pkg.name = True
| packageNameV `S.member` subLibNames = True
| packageNameV `S.member` foreignLibNames = True
| otherwise = False
innerIterator packageName depValue resListInMonad
| shouldIgnoreDep packageName = resListInMonad
| otherwise = do
resList <- resListInMonad
newResElement <- fn packageName depValue
pure $ combineResults newResElement resList
processPackageMapDeps pkg (flip (M.foldrWithKey' innerIterator))
-- | Iterate/fold on all the package dependencies, components, setup deps and
-- all.
processPackageDepsToList ::
Monad m
=> Package
-> (PackageName -> DepValue -> m resT)
-> m [resT]
processPackageDepsToList pkg fn = processPackageDeps pkg (:) fn (pure [])
-- | Iterate/fold on all the package dependencies, components, setup deps and
-- all.
processPackageDepsEither ::
(Monad m, Monoid a, Monoid b)
=> Package
-> (PackageName -> DepValue -> m (Either a b))
-> m (Either a b)
processPackageDepsEither pkg fn =
processPackageDeps pkg combineRes fn (pure (Right mempty))
where
combineRes (Left err) (Left errs) = Left (errs <> err)
combineRes _ (Left b) = Left b
combineRes (Left err) _ = Left err
combineRes (Right a) (Right b) = Right $ a <> b
-- | List all package's dependencies in a "free" context through the identity
-- monad.
listOfPackageDeps :: Package -> [PackageName]
listOfPackageDeps pkg =
runIdentity $ processPackageDepsToList pkg (\pn _ -> pure pn)
-- | The set of package's dependencies.
setOfPackageDeps :: Package -> Set PackageName
setOfPackageDeps pkg =
runIdentity $ processPackageDeps pkg S.insert (\pn _ -> pure pn) (pure mempty)
-- | This implements a topological sort on all targeted components for the build
-- and their dependencies. It's only targeting internal dependencies, so it's
-- doing a topological sort on a subset of a package's components.
--
-- Note that in Cabal they use the Data.Graph struct to pursue the same goal.
-- But dong this here would require a large number intermediate data structure.
-- This is needed because we need to get the right GhcPkgId of the relevant
-- internal dependencies of a component before building it as a component.
topSortPackageComponent ::
Package
-> Target
-> Bool
-- ^ Include directTarget or not. False here means we won't include the
-- actual targets in the result, only their deps. Using it with False here
-- only in GHCi
-> Seq NamedComponent
topSortPackageComponent package target includeDirectTarget =
topProcessPackageComponent package target processor mempty
where
processor packageType component
| not includeDirectTarget && packageType == PTProject = id
| otherwise = \v -> v |> component.qualifiedName
-- | Process a package's internal components in the order of their topological
-- sort. The first iteration will effect the component depending on no other
-- component etc, iterating by increasing amount of required dependencies.
-- 'PackageType' with value 'PTProject' here means the component is a direct
-- target and 'PTDependency' means it's a dependency of a direct target.
topProcessPackageComponent ::
forall b.
Package
-> Target
-> ( forall component. (HasComponentInfo component)
=> PackageType
-> component
-> b
-> b
)
-> b
-> b
topProcessPackageComponent package target fn res = do
let initialState = (mempty, res)
processInitialComponents c = case target of
TargetAll{} -> processComponent PTProject c
TargetComps targetSet -> if S.member c.qualifiedName targetSet
then processComponent PTProject c
else id
snd $ processPackageComponent package processInitialComponents initialState
where
processComponent :: HasComponentInfo component
=> PackageType
-- ^ Finally add this component in the seq
-> component
-> (Set NamedComponent, b)
-> (Set NamedComponent, b)
processComponent packageType component currentRes@(_a, !_b) = do
let depMap = componentDependencyMap component
internalDep = M.lookup package.name depMap
qualName = component.qualifiedName
alreadyProcessed = fst currentRes
!appendToResult = fn packageType component
-- This is an optimization, the only components we are likely to process
-- multiple times are the ones we can find in dependencies, otherwise we
-- only fold on a single version of each component by design.
processedDeps = processOneDep internalDep currentRes
if isPotentialDependency qualName
then
if S.member qualName alreadyProcessed
then currentRes
else bimap (S.insert qualName) appendToResult processedDeps
else second appendToResult processedDeps
lookupLibName isMain name = if isMain
then package.library
else collectionLookup name package.subLibraries
processOneDep mDependency res' =
case (.depType) <$> mDependency of
Just (AsLibrary (DepLibrary mainLibDep subLibDeps)) -> do
let processMainLibDep =
case (mainLibDep, lookupLibName True emptyCompName) of
(True, Just mainLib) ->
processComponent PTDependency mainLib
_ -> id
processSingleSubLib name =
case lookupLibName False name of
Just lib -> processComponent PTDependency lib
Nothing -> id
processSubLibDep r = foldr' processSingleSubLib r subLibDeps
processSubLibDep (processMainLibDep res')
_ -> res'