diff --git a/.gitignore b/.gitignore
index 01dcdc009e..95dec1e4bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,9 @@ stan.html
# macOS-related
.DS_Store
+# VS Code workspace settings
+.vscode
+
# VS Code Counter (Visual Studio Code Extension)-related
.VSCodeCounter
diff --git a/.hlint.yaml b/.hlint.yaml
index 02f1a89d42..2a571b0be4 100644
--- a/.hlint.yaml
+++ b/.hlint.yaml
@@ -57,13 +57,21 @@
# the GHC2021 set. Other extensions can be added, if need be.
- name:
- NoImplicitPrelude
+ - ConstraintKinds
- DataKinds
- DefaultSignatures
+ - DeriveDataTypeable
+ - DeriveGeneric
- DerivingStrategies
+ - DisambiguateRecordFields
+ - DuplicateRecordFields
+ - FlexibleContexts
- GADTs
+ - GeneralizedNewtypeDeriving
- LambdaCase
- MultiWayIf
- OverloadedLists
+ - OverloadedRecordDot
- OverloadedStrings
- QuasiQuotes
- RecordWildCards
diff --git a/.stan.toml b/.stan.toml
index 4511240028..99909c5b33 100644
--- a/.stan.toml
+++ b/.stan.toml
@@ -52,7 +52,7 @@
# Anti-pattern: Data.ByteString.Char8.pack
[[ignore]]
- id = "OBS-STAN-0203-fki0nd-1111:21"
+ id = "OBS-STAN-0203-fki0nd-1119:21"
# ✦ Description: Usage of 'pack' function that doesn't handle Unicode characters
# ✦ Category: #AntiPattern
# ✦ File: src\Stack\Build\Execute.hs
@@ -63,7 +63,7 @@
# Anti-pattern: Data.ByteString.Char8.pack
[[ignore]]
- id = "OBS-STAN-0203-fki0nd-2656:3"
+ id = "OBS-STAN-0203-fki0nd-2661:3"
# ✦ Description: Usage of 'pack' function that doesn't handle Unicode characters
# ✦ Category: #AntiPattern
# ✦ File: src\Stack\Build\Execute.hs
diff --git a/src/Stack/Build.hs b/src/Stack/Build.hs
index 674d65e4bd..dc7338f50f 100644
--- a/src/Stack/Build.hs
+++ b/src/Stack/Build.hs
@@ -30,7 +30,7 @@ import Stack.Build.Installed ( getInstalled, toInstallMap )
import Stack.Build.Source ( localDependencies, projectLocalPackages )
import Stack.Build.Target ( NeedTargets (..) )
import Stack.FileWatch ( fileWatch, fileWatchPoll )
-import Stack.Package ( resolvePackage )
+import Stack.Package ( buildableExes, resolvePackage )
import Stack.Prelude hiding ( loadPackage )
import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig )
import Stack.Setup ( withNewLocalBuildTargets )
@@ -297,7 +297,7 @@ warnIfExecutablesWithSameNameCouldBeOverwritten locals plan = do
collect
[ (exe, packageName pkg)
| pkg <- map lpPackage locals
- , exe <- Set.toList (packageExes pkg)
+ , exe <- Set.toList (buildableExes pkg)
]
collect :: Ord k => [(k, v)] -> Map k (NonEmpty v)
collect = Map.mapMaybe nonEmpty . Map.fromDistinctAscList . groupSort
diff --git a/src/Stack/Build/ConstructPlan.hs b/src/Stack/Build/ConstructPlan.hs
index 7abd5c50c3..7d8281e2aa 100644
--- a/src/Stack/Build/ConstructPlan.hs
+++ b/src/Stack/Build/ConstructPlan.hs
@@ -28,7 +28,10 @@ import Stack.Build.Cache ( tryGetFlagCache )
import Stack.Build.Haddock ( shouldHaddockDeps )
import Stack.Build.Source ( loadLocalPackage )
import Stack.Constants ( compilerOptionsCabalFlag )
-import Stack.Package ( applyForceCustomBuild )
+import Stack.Package
+ ( applyForceCustomBuild, buildableExes
+ , hasBuildableMainLibrary, packageUnknownTools
+ )
import Stack.Prelude hiding ( loadPackage )
import Stack.SourceMap ( getPLIVersion, mkProjectPackage )
import Stack.Types.Build
@@ -45,6 +48,7 @@ import Stack.Types.BuildConfig
( BuildConfig (..), HasBuildConfig (..), stackYamlL )
import Stack.Types.BuildOpts
( BuildOpts (..), BuildOptsCLI (..), BuildSubset (..) )
+import Stack.Types.CompCollection ( collectionMember )
import Stack.Types.Compiler ( WhichCompiler (..) )
import Stack.Types.CompilerPaths
( CompilerPaths (..), HasCompiler (..) )
@@ -67,8 +71,8 @@ import Stack.Types.NamedComponent ( exeComponents, renderComponent )
import Stack.Types.Package
( ExeName (..), InstallLocation (..), Installed (..)
, InstalledMap, LocalPackage (..), Package (..)
- , PackageLibraries (..), PackageSource (..), installedVersion
- , packageIdentifier, psVersion, runMemoizedWith
+ , PackageSource (..), installedVersion, packageIdentifier
+ , psVersion, runMemoizedWith
)
import Stack.Types.ParentMap ( ParentMap )
import Stack.Types.Platform ( HasPlatform (..) )
@@ -799,7 +803,7 @@ tellExecutablesPackage loc p = do
tell mempty
{ wInstall = Map.fromList $
- map (, loc) $ Set.toList $ filterComps myComps $ packageExes p
+ map (, loc) $ Set.toList $ filterComps myComps $ buildableExes p
}
where
filterComps myComps x
@@ -1162,10 +1166,7 @@ addPackageDeps package = do
-- make sure we consider sub-libraries as libraries too
packageHasLibrary :: Package -> Bool
packageHasLibrary p =
- not (Set.null (packageSubLibraries p)) ||
- case packageLibraries p of
- HasLibraries _ -> True
- NoLibraries -> False
+ hasBuildableMainLibrary p || not (null (packageSubLibraries p))
checkDirtiness ::
PackageSource
@@ -1334,8 +1335,8 @@ checkAndWarnForUnknownTools p = do
-- Check whether the tool is on the PATH or a package executable before
-- warning about it.
warnings <-
- fmap catMaybes $ forM unknownTools $ \name@(ExeName toolName) ->
- runMaybeT $ notOnPath toolName *> notPackageExe toolName *> warn name
+ fmap catMaybes $ forM unknownTools $ \toolName ->
+ runMaybeT $ notOnPath toolName *> notPackageExe toolName *> warn toolName
tell mempty { wWarnings = (map toolWarningText warnings ++) }
pure ()
where
@@ -1349,8 +1350,9 @@ checkAndWarnForUnknownTools p = do
skipIf $ isRight eFound
-- From Cabal 1.12, build-tools can specify another executable in the same
-- package.
- notPackageExe toolName = MaybeT $ skipIf $ toolName `Set.member` packageExes p
- warn name = MaybeT . pure . Just $ ToolWarning name (packageName p)
+ notPackageExe toolName =
+ MaybeT $ skipIf $ collectionMember toolName (packageExecutables p)
+ warn name = MaybeT . pure . Just $ ToolWarning (ExeName name) (packageName p)
skipIf p' = pure $ if p' then Nothing else Just ()
-- | Warn about tools in the snapshot definition. States the tool name
diff --git a/src/Stack/Build/Execute.hs b/src/Stack/Build/Execute.hs
index 167a39e1fa..c9099db537 100644
--- a/src/Stack/Build/Execute.hs
+++ b/src/Stack/Build/Execute.hs
@@ -64,6 +64,7 @@ import Distribution.Types.UnqualComponentName
( mkUnqualComponentName )
import Distribution.Verbosity ( showForCabal )
import Distribution.Version ( mkVersion )
+import GHC.Records ( getField )
import Path
( PathException, (>), addExtension, filename
, isProperPrefixOf, parent, parseRelDir, parseRelFile
@@ -124,7 +125,10 @@ import Stack.Coverage
, generateHpcUnifiedReport, updateTixFile
)
import Stack.GhcPkg ( ghcPkg, unregisterGhcPkgIds )
-import Stack.Package ( buildLogPath )
+import Stack.Package
+ ( buildLogPath, buildableExes, buildableSubLibs
+ , hasBuildableMainLibrary, mainLibraryHasExposedModules
+ )
import Stack.PackageDump ( conduitDumpPackage, ghcPkgDescribe )
import Stack.Prelude
import Stack.Types.ApplyGhcOptions ( ApplyGhcOptions (..) )
@@ -144,6 +148,10 @@ import Stack.Types.BuildOpts
, CabalVerbosity (..), HaddockOpts (..)
, ProgressBarFormat (..), TestOpts (..)
)
+import Stack.Types.CompCollection
+ ( collectionKeyValueList, collectionLookup
+ , getBuildableListText
+ )
import Stack.Types.Compiler
( ActualCompiler (..), WhichCompiler (..)
, compilerVersionString, getGhcVersion, whichCompilerL
@@ -152,6 +160,7 @@ import Stack.Types.CompilerPaths
( CompilerPaths (..), GhcPkgExe (..), HasCompiler (..)
, cabalVersionL, cpWhich, getCompilerPath, getGhcPkgExe
)
+import qualified Stack.Types.Component as Component
import Stack.Types.Config
( Config (..), HasConfig (..), buildOptsL, stackRootL )
import Stack.Types.ConfigureOpts
@@ -175,9 +184,8 @@ import Stack.Types.NamedComponent
)
import Stack.Types.Package
( InstallLocation (..), Installed (..), InstalledMap
- , LocalPackage (..), Package (..), PackageLibraries (..)
- , installedPackageIdentifier, packageIdentifier
- , runMemoizedWith
+ , LocalPackage (..), Package (..), installedPackageIdentifier
+ , packageIdentifier, runMemoizedWith
)
import Stack.Types.PackageFile ( PackageWarning (..) )
import Stack.Types.Platform ( HasPlatform (..) )
@@ -1713,7 +1721,7 @@ singleBuild
&& not isFinalBuild
-- Works around haddock failing on bytestring-builder since it has no
-- modules when bytestring is new enough.
- && packageHasExposedModules package
+ && mainLibraryHasExposedModules package
-- Special help for the curator tool to avoid haddocks that are known
-- to fail
&& maybe True (Set.notMember pname . curatorSkipHaddock) mcurator
@@ -1748,11 +1756,8 @@ singleBuild
(hasLib, hasSubLib, hasExe) = case taskType of
TTLocalMutable lp ->
let package = lpPackage lp
- hasLibrary =
- case packageLibraries package of
- NoLibraries -> False
- HasLibraries _ -> True
- hasSubLibraries = not . Set.null $ packageSubLibraries package
+ hasLibrary = hasBuildableMainLibrary package
+ hasSubLibraries = not . null $ packageSubLibraries package
hasExecutables =
not . Set.null $ exesToBuild executableBuildStatuses lp
in (hasLibrary, hasSubLibraries, hasExecutables)
@@ -1797,9 +1802,9 @@ singleBuild
-- However, we must unregister any such library in the new snapshot, in case
-- it was built with different flags.
let
- subLibNames = Set.toList $ case taskType of
- TTLocalMutable lp -> packageSubLibraries $ lpPackage lp
- TTRemotePackage _ p _ -> packageSubLibraries p
+ subLibNames = Set.toList $ buildableSubLibs $ case taskType of
+ TTLocalMutable lp -> lpPackage lp
+ TTRemotePackage _ p _ -> p
toMungedPackageId :: Text -> MungedPackageId
toMungedPackageId subLib =
let subLibName = LSubLibName $ mkUnqualComponentName $ T.unpack subLib
@@ -2038,13 +2043,9 @@ singleBuild
cabal0 keep KeepTHLoading $ "haddock" : args
- let hasLibrary =
- case packageLibraries package of
- NoLibraries -> False
- HasLibraries _ -> True
- packageHasComponentSet f = not $ Set.null $ f package
- hasSubLibraries = packageHasComponentSet packageSubLibraries
- hasExecutables = packageHasComponentSet packageExes
+ let hasLibrary = hasBuildableMainLibrary package
+ hasSubLibraries = not $ null $ packageSubLibraries package
+ hasExecutables = not $ null $ packageExecutables package
shouldCopy =
not isFinalBuild
&& (hasLibrary || hasSubLibraries || hasExecutables)
@@ -2093,10 +2094,10 @@ singleBuild
let ident = PackageIdentifier (packageName package) (packageVersion package)
-- only pure the sub-libraries to cache them if we also cache the main
-- library (that is, if it exists)
- (mpkgid, subLibsPkgIds) <- case packageLibraries package of
- HasLibraries _ -> do
+ (mpkgid, subLibsPkgIds) <- if hasBuildableMainLibrary package
+ then do
subLibsPkgIds <- fmap catMaybes $
- forM (Set.toList $ packageSubLibraries package) $ \subLib -> do
+ forM (getBuildableListText $ packageSubLibraries package) $ \subLib -> do
let subLibName = MungedPackageName
(packageName package)
(LSubLibName $ mkUnqualComponentName $ T.unpack subLib)
@@ -2112,10 +2113,10 @@ singleBuild
case mpkgid of
Nothing -> throwM $ Couldn'tFindPkgId $ packageName package
Just pkgid -> pure (Library ident pkgid Nothing, subLibsPkgIds)
- NoLibraries -> do
+ else do
markExeInstalled (taskLocation task) pkgId -- TODO unify somehow
-- with writeFlagCache?
- pure (Executable ident, []) -- don't pure sub-libraries in this case
+ pure (Executable ident, []) -- don't pure sublibs in this case
case taskType of
TTRemotePackage Immutable _ loc ->
@@ -2126,7 +2127,7 @@ singleBuild
(configCacheHaddock cache)
mpkgid
subLibsPkgIds
- (packageExes package)
+ (buildableExes package)
_ -> pure ()
case taskType of
@@ -2174,7 +2175,7 @@ getExecutableBuildStatuses package pkgDir = do
platform <- view platformL
fmap
Map.fromList
- (mapM (checkExeStatus platform distDir) (Set.toList (packageExes package)))
+ (mapM (checkExeStatus platform distDir) (Set.toList (buildableExes package)))
-- | Check whether the given executable is defined in the given dist directory.
checkExeStatus ::
@@ -2276,7 +2277,9 @@ singleTest topts testsToRun ac ee task installedMap = do
let suitesToRun
= [ testSuitePair
- | testSuitePair <- Map.toList $ packageTests package
+ | testSuitePair <-
+ (fmap . fmap) (getField @"interface") <$>
+ collectionKeyValueList $ packageTestSuites package
, let testName = fst testSuitePair
, testName `elem` testsToRun
]
@@ -2485,9 +2488,11 @@ singleTest topts testsToRun ac ee task installedMap = do
when needHpc $ do
let testsToRun' = map f testsToRun
f tName =
- case Map.lookup tName (packageTests package) of
- Just C.TestSuiteLibV09{} -> tName <> "Stub"
- _ -> tName
+ case getField @"interface" <$> mComponent of
+ Just C.TestSuiteLibV09{} -> tName <> "Stub"
+ _ -> tName
+ where
+ mComponent = collectionLookup tName (packageTestSuites package)
generateHpcReport pkgDir package testsToRun'
bs <- liftIO $
@@ -2696,17 +2701,17 @@ primaryComponentOptions ::
primaryComponentOptions executableBuildStatuses lp =
-- TODO: get this information from target parsing instead, which will allow
-- users to turn off library building if desired
- ( case packageLibraries package of
- NoLibraries -> []
- HasLibraries names -> map
- T.unpack
- ( T.append "lib:" (T.pack (packageNameString (packageName package)))
- : map (T.append "flib:") (Set.toList names)
- )
+ ( if hasBuildableMainLibrary package
+ then map T.unpack
+ $ T.append "lib:" (T.pack (packageNameString (packageName package)))
+ : map
+ (T.append "flib:")
+ (getBuildableListText (packageForeignLibraries package))
+ else []
)
++ map
(T.unpack . T.append "lib:")
- (Set.toList $ packageSubLibraries package)
+ (getBuildableListText $ packageSubLibraries package)
++ map
(T.unpack . T.append "exe:")
(Set.toList $ exesToBuild executableBuildStatuses lp)
@@ -2729,7 +2734,7 @@ exesToBuild :: Map Text ExecutableBuildStatus -> LocalPackage -> Set Text
exesToBuild executableBuildStatuses lp =
if cabalIsSatisfied executableBuildStatuses && lpWanted lp
then exeComponents (lpComponents lp)
- else packageExes (lpPackage lp)
+ else buildableExes (lpPackage lp)
-- | Do the current executables satisfy Cabal's bugged out requirements?
cabalIsSatisfied :: Map k ExecutableBuildStatus -> Bool
diff --git a/src/Stack/Build/Source.hs b/src/Stack/Build/Source.hs
index 4dd1e45646..0a33756fb8 100644
--- a/src/Stack/Build/Source.hs
+++ b/src/Stack/Build/Source.hs
@@ -24,7 +24,11 @@ import qualified Distribution.PackageDescription as C
import qualified Pantry.SHA256 as SHA256
import Stack.Build.Cache ( tryGetBuildCache )
import Stack.Build.Haddock ( shouldHaddockDeps )
-import Stack.Package ( resolvePackage )
+import Stack.Package
+ ( buildableBenchmarks, buildableExes, buildableTestSuites
+ , hasBuildableMainLibrary, resolvePackage
+ )
+import Stack.PackageFile ( getPackageFile )
import Stack.Prelude
import Stack.SourceMap
( DumpedGlobalPackage, checkFlagsUsedThrowing
@@ -52,10 +56,11 @@ import Stack.Types.NamedComponent
( NamedComponent (..), isCSubLib, splitComponents )
import Stack.Types.Package
( FileCacheInfo (..), LocalPackage (..), Package (..)
- , PackageConfig (..), PackageLibraries (..)
- , dotCabalGetPath, memoizeRefWith, runMemoizedWith
+ , PackageConfig (..), dotCabalGetPath, memoizeRefWith
+ , runMemoizedWith
)
-import Stack.Types.PackageFile ( PackageWarning, getPackageFiles )
+import Stack.Types.PackageFile
+ ( PackageComponentFile (..), PackageWarning )
import Stack.Types.Platform ( HasPlatform (..) )
import Stack.Types.SourceMap
( CommonPackage (..), DepPackage (..), ProjectPackage (..)
@@ -312,17 +317,17 @@ loadLocalPackage pp = do
let (_s, e, t, b) = splitComponents $ Set.toList comps
in (e, t, b)
Just (TargetAll _packageType) ->
- ( packageExes pkg
+ ( buildableExes pkg
, if boptsTests bopts
&& maybe True (Set.notMember name . curatorSkipTest) mcurator
- then Map.keysSet (packageTests pkg)
+ then buildableTestSuites pkg
else Set.empty
, if boptsBenchmarks bopts
&& maybe
True
(Set.notMember name . curatorSkipBenchmark)
mcurator
- then packageBenchmarks pkg
+ then buildableBenchmarks pkg
else Set.empty
)
Nothing -> mempty
@@ -334,13 +339,9 @@ loadLocalPackage pp = do
-- individual executables or library") is resolved, 'hasLibrary' is only
-- relevant if the library is part of the target spec.
Just _ ->
- let hasLibrary =
- case packageLibraries pkg of
- NoLibraries -> False
- HasLibraries _ -> True
- in hasLibrary
- || not (Set.null nonLibComponents)
- || not (Set.null $ packageSubLibraries pkg)
+ hasBuildableMainLibrary pkg
+ || not (Set.null nonLibComponents)
+ || not (null $ packageSubLibraries pkg)
filterSkippedComponents =
Set.filter (not . (`elem` boptsSkipComponents bopts))
@@ -421,9 +422,9 @@ loadLocalPackage pp = do
-- through component parsing, but the components aren't present, then they
-- must not be buildable.
, lpUnbuildable = toComponents
- (exes `Set.difference` packageExes pkg)
- (tests `Set.difference` Map.keysSet (packageTests pkg))
- (benches `Set.difference` packageBenchmarks pkg)
+ (exes `Set.difference` buildableExes pkg)
+ (tests `Set.difference` buildableTestSuites pkg)
+ (benches `Set.difference` buildableBenchmarks pkg)
}
-- | Compare the current filesystem state to the cached information, and
@@ -499,8 +500,8 @@ getPackageFilesForTargets ::
-> Set NamedComponent
-> RIO env (Map NamedComponent (Set (Path Abs File)), [PackageWarning])
getPackageFilesForTargets pkg cabalFP nonLibComponents = do
- (components',compFiles,otherFiles,warnings) <-
- getPackageFiles (packageFiles pkg) cabalFP
+ PackageComponentFile components' compFiles otherFiles warnings <-
+ getPackageFile pkg cabalFP
let necessaryComponents =
Set.insert CLib $ Set.filter isCSubLib (M.keysSet components')
components = necessaryComponents `Set.union` nonLibComponents
diff --git a/src/Stack/Component.hs b/src/Stack/Component.hs
new file mode 100644
index 0000000000..d1666549b2
--- /dev/null
+++ b/src/Stack/Component.hs
@@ -0,0 +1,216 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DisambiguateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | All utility functions for Components in Stack (library, internal library,
+-- foreign library, executable, tests, benchmarks). In particular, this module
+-- gathers all the Cabal-to-Stack component translations, which previously
+-- occurred in the "Stack.Package" module. See "Stack.Types.Component" for more
+-- details about the design choices.
+
+module Stack.Component
+ ( isComponentBuildable
+ , stackLibraryFromCabal
+ , stackExecutableFromCabal
+ , stackForeignLibraryFromCabal
+ , stackBenchmarkFromCabal
+ , stackTestFromCabal
+ , foldOnNameAndBuildInfo
+ , stackUnqualToQual
+ ) where
+
+import Data.Foldable ( foldr' )
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+import Data.Text ( pack )
+import Distribution.PackageDescription
+ ( Benchmark (..), Executable, ForeignLib, Library (..)
+ , TestSuite (..)
+ )
+import Distribution.Types.BuildInfo ( BuildInfo )
+import Distribution.Types.UnqualComponentName ( UnqualComponentName )
+import Distribution.Package ( mkPackageName )
+import qualified Distribution.PackageDescription as Cabal
+import GHC.Records ( HasField )
+import Stack.Prelude
+import Stack.Types.Component
+ ( HasBuildInfo, StackBenchmark (..), StackBuildInfo (..)
+ , StackExecutable (..), StackForeignLibrary (..)
+ , StackLibrary (..), StackTest (..), StackUnqualCompName (..)
+ )
+import Stack.Types.Dependency ( cabalExeToStackDep, cabalToStackDep )
+import Stack.Types.NamedComponent ( NamedComponent )
+
+fromCabalName :: UnqualComponentName -> StackUnqualCompName
+fromCabalName unqualName =
+ StackUnqualCompName $ pack . Cabal.unUnqualComponentName $ unqualName
+
+stackUnqualToQual ::
+ (Text -> NamedComponent)
+ -> StackUnqualCompName
+ -> NamedComponent
+stackUnqualToQual c (StackUnqualCompName n) = c n
+
+foldOnNameAndBuildInfo ::
+ ( HasField "buildInfo" a StackBuildInfo
+ , HasField "name" a StackUnqualCompName
+ , Foldable c
+ )
+ => c a
+ -> (StackUnqualCompName -> StackBuildInfo -> t -> t)
+ -> t
+ -> t
+foldOnNameAndBuildInfo initialCollection accumulator input =
+ foldr' iterator input initialCollection
+ where
+ iterator comp = accumulator comp.name comp.buildInfo
+
+stackLibraryFromCabal :: Library -> StackLibrary
+stackLibraryFromCabal cabalLib = StackLibrary
+ { name = case cabalLib.libName of
+ LMainLibName -> StackUnqualCompName mempty
+ LSubLibName v -> fromCabalName v
+ , buildInfo = stackBuildInfoFromCabal cabalLib.libBuildInfo
+ , exposedModules = cabalLib.exposedModules
+ }
+
+stackExecutableFromCabal :: Executable -> StackExecutable
+stackExecutableFromCabal cabalExecutable = StackExecutable
+ { name = fromCabalName cabalExecutable.exeName
+ , buildInfo = stackBuildInfoFromCabal cabalExecutable.buildInfo
+ , modulePath = cabalExecutable.modulePath
+ }
+
+stackForeignLibraryFromCabal :: ForeignLib -> StackForeignLibrary
+stackForeignLibraryFromCabal cabalForeignLib = StackForeignLibrary
+ { name = fromCabalName cabalForeignLib.foreignLibName
+ , buildInfo=stackBuildInfoFromCabal cabalForeignLib.foreignLibBuildInfo
+ }
+
+stackBenchmarkFromCabal :: Benchmark -> StackBenchmark
+stackBenchmarkFromCabal cabalBenchmark = StackBenchmark
+ { name = fromCabalName cabalBenchmark.benchmarkName
+ , interface = cabalBenchmark.benchmarkInterface
+ , buildInfo = stackBuildInfoFromCabal cabalBenchmark.benchmarkBuildInfo
+ }
+
+stackTestFromCabal :: TestSuite -> StackTest
+stackTestFromCabal cabalTest = StackTest
+ { name = fromCabalName cabalTest.testName
+ , interface = cabalTest.testInterface
+ , buildInfo = stackBuildInfoFromCabal cabalTest.testBuildInfo
+ }
+
+isComponentBuildable :: HasBuildInfo component => component -> Bool
+isComponentBuildable componentRec = componentRec.buildInfo.sbiBuildable
+
+stackBuildInfoFromCabal :: BuildInfo -> StackBuildInfo
+stackBuildInfoFromCabal buildInfoV = gatherComponentToolsAndDepsFromCabal
+ buildInfoV.buildTools
+ buildInfoV.buildToolDepends
+ buildInfoV.targetBuildDepends
+ StackBuildInfo
+ { sbiBuildable = buildInfoV.buildable
+ , sbiOtherModules = buildInfoV.otherModules
+ , jsSources = buildInfoV.jsSources
+ , hsSourceDirs = buildInfoV.hsSourceDirs
+ , cSources = buildInfoV.cSources
+ , sbiDependency = mempty
+ , sbiUnknownTools = mempty
+ , cppOptions = buildInfoV.cppOptions
+ , targetBuildDepends = buildInfoV.targetBuildDepends
+ , options = buildInfoV.options
+ , allLanguages = Cabal.allLanguages buildInfoV
+ , usedExtensions = Cabal.usedExtensions buildInfoV
+ , includeDirs = buildInfoV.includeDirs
+ , extraLibs = buildInfoV.extraLibs
+ , extraLibDirs = buildInfoV.extraLibDirs
+ , frameworks = buildInfoV.frameworks
+ }
+
+-- | Iterate on all three dependency list given, and transform and sort them
+-- between 'sbiUnknownTools' and legitimate 'DepValue' sbiDependency. Bear in
+-- mind that this only gathers the component level dependencies.
+gatherComponentToolsAndDepsFromCabal
+ :: [Cabal.LegacyExeDependency]
+ -- ^ Legacy build tools dependency from
+ -- [buildTools](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html#t:buildTools).
+ -> [Cabal.ExeDependency]
+ -- ^ Build tools dependency
+ -- [buildToolDepends](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html#t:buildToolDepends)
+ -> [Cabal.Dependency]
+ -- ^ The Cabal defined
+ -- [targetBuildDepends](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html#t:targetBuildDepends),
+ -- these are the simplest dependencies for a component extracted from the
+ -- Cabal file such as:
+ -- @
+ -- build-depends:
+ -- foo ^>= 1.2.3.4,
+ -- bar ^>= 1
+ -- @
+ -> StackBuildInfo
+ -> StackBuildInfo
+gatherComponentToolsAndDepsFromCabal legacyBuildTools buildTools targetDeps =
+ gatherTargetDependency . gatherToolsDependency . gatherUnknownTools
+ where
+ gatherUnknownTools sbi = foldl' processLegacyExeDepency sbi legacyBuildTools
+ gatherToolsDependency sbi = foldl' processExeDependency sbi buildTools
+ gatherTargetDependency sbi = foldl' processDependency sbi targetDeps
+ -- This is similar to
+ -- [desugarBuildTool](https://hackage.haskell.org/package/Cabal/docs/src/Distribution.Simple.BuildToolDepends.html#desugarBuildTool)
+ -- from Cabal, however it uses our own hard-coded map which drops tools
+ -- shipped with GHC (like hsc2hs), and includes some tools from Stackage.
+ processLegacyExeDepency sbi (Cabal.LegacyExeDependency exeName range) =
+ case isKnownLegacyExe exeName of
+ Just pName ->
+ processExeDependency
+ sbi
+ (Cabal.ExeDependency pName (Cabal.mkUnqualComponentName exeName) range)
+ Nothing -> sbi
+ {sbiUnknownTools = Set.insert (pack exeName) $ sbiUnknownTools sbi}
+ processExeDependency sbi exeDep@(Cabal.ExeDependency pName _ _)
+ | isPreInstalledPackages pName = sbi
+ | otherwise = sbi
+ { sbiDependency =
+ Map.insert pName (cabalExeToStackDep exeDep) $ sbiDependency sbi
+ }
+ processDependency sbi dep@(Cabal.Dependency pName _ _) = sbi
+ { sbiDependency =
+ Map.insert pName (cabalToStackDep dep) $ sbiDependency sbi
+ }
+
+-- | A hard-coded map for tool dependencies. If a dependency is within this map
+-- it's considered "known" (the exe will be found at the execution stage).
+-- [It also exists in Cabal](https://hackage.haskell.org/package/Cabal/docs/src/Distribution.Simple.BuildToolDepends.html#local-6989586621679259154)
+isKnownLegacyExe :: String -> Maybe PackageName
+isKnownLegacyExe input = case input of
+ "alex" -> justPck "alex"
+ "happy" -> justPck "happy"
+ "cpphs" -> justPck "cpphs"
+ "greencard" -> justPck "greencard"
+ "c2hs" -> justPck "c2hs"
+ "hscolour" -> justPck "hscolour"
+ "hspec-iscover" -> justPck "hspec-discover"
+ "hsx2hs" -> justPck "hsx2hs"
+ "gtk2hsC2hs" -> justPck "gtk2hs-buildtools"
+ "gtk2hsHookGenerator" -> justPck "gtk2hs-buildtools"
+ "gtk2hsTypeGen" -> justPck "gtk2hs-buildtools"
+ _ -> Nothing
+ where
+ justPck = Just . mkPackageName
+
+-- | Executable-only packages which come pre-installed with GHC and do not need
+-- to be built. Without this exception, we would either end up unnecessarily
+-- rebuilding these packages, or failing because the packages do not appear in
+-- the Stackage snapshot.
+isPreInstalledPackages :: PackageName -> Bool
+isPreInstalledPackages input = case input of
+ "hsc2hs" -> True
+ "haddock" -> True
+ _ -> False
diff --git a/src/Stack/ComponentFile.hs b/src/Stack/ComponentFile.hs
index 896ff119f0..0f0df76ba9 100644
--- a/src/Stack/ComponentFile.hs
+++ b/src/Stack/ComponentFile.hs
@@ -1,5 +1,6 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
-- | A module which exports all component-level file-gathering logic. It also
@@ -7,15 +8,16 @@
module Stack.ComponentFile
( resolveOrWarn
- , libraryFiles
- , executableFiles
- , testFiles
- , benchmarkFiles
, componentOutputDir
, componentBuildDir
, packageAutogenDir
, buildDir
, componentAutogenDir
+ , ComponentFile (..)
+ , stackLibraryFiles
+ , stackExecutableFiles
+ , stackTestFiles
+ , stackBenchmarkFiles
) where
import Control.Exception ( throw )
@@ -27,13 +29,12 @@ import qualified Data.Text as T
import Distribution.ModuleName ( ModuleName )
import qualified Distribution.ModuleName as Cabal
import Distribution.PackageDescription
- ( Benchmark (..), BenchmarkInterface (..), BuildInfo (..)
- , Executable (..), Library (..), TestSuite (..)
- , TestSuiteInterface (..)
- )
+ ( BenchmarkInterface (..), TestSuiteInterface (..) )
import Distribution.Text ( display )
-import Distribution.Utils.Path ( getSymbolicPath )
+import Distribution.Utils.Path
+ ( PackageDir, SourceDir, SymbolicPath, getSymbolicPath )
import Distribution.Version ( mkVersion )
+import GHC.Records ( HasField )
import qualified HiFileParser as Iface
import Path
( (>), filename, isProperPrefixOf, parent, parseRelDir
@@ -50,6 +51,11 @@ import Stack.Constants
, relDirAutogen, relDirBuild, relDirGlobalAutogen
)
import Stack.Prelude hiding ( Display (..) )
+import Stack.Types.Component
+ ( StackBenchmark (..), StackBuildInfo (..)
+ , StackExecutable (..), StackLibrary (..), StackTest (..)
+ , sbiOtherModules, unqualCompToText
+ )
import Stack.Types.Config
( Config (..), HasConfig (..), prettyStackDevL )
import Stack.Types.NamedComponent ( NamedComponent (..) )
@@ -61,83 +67,82 @@ import Stack.Types.PackageFile
import qualified System.Directory as D ( doesFileExist )
import qualified System.FilePath as FilePath
+data ComponentFile = ComponentFile
+ { moduleFileMap :: !(Map ModuleName (Path Abs File))
+ , otherFile :: ![DotCabalPath]
+ , packageWarning :: ![PackageWarning]
+ }
+
-- | Get all files referenced by the benchmark.
-benchmarkFiles ::
- NamedComponent
- -> Benchmark
- -> RIO
- GetPackageFileContext
- (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning])
-benchmarkFiles component bench =
- resolveComponentFiles component build names
+stackBenchmarkFiles ::
+ StackBenchmark
+ -> RIO GetPackageFileContext (NamedComponent, ComponentFile)
+stackBenchmarkFiles bench =
+ resolveComponentFiles (CBench $ unqualCompToText bench.name) build names
where
names = bnames <> exposed
exposed =
- case benchmarkInterface bench of
+ case bench.interface of
BenchmarkExeV10 _ fp -> [DotCabalMain fp]
BenchmarkUnsupported _ -> []
- bnames = map DotCabalModule (otherModules build)
- build = benchmarkBuildInfo bench
+ bnames = map DotCabalModule build.sbiOtherModules
+ build = bench.buildInfo
-- | Get all files referenced by the test.
-testFiles ::
- NamedComponent
- -> TestSuite
- -> RIO
- GetPackageFileContext
- (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning])
-testFiles component test =
- resolveComponentFiles component build names
+stackTestFiles ::
+ StackTest
+ -> RIO GetPackageFileContext (NamedComponent, ComponentFile)
+stackTestFiles test =
+ resolveComponentFiles (CTest $ unqualCompToText test.name) build names
where
names = bnames <> exposed
exposed =
- case testInterface test of
+ case test.interface of
TestSuiteExeV10 _ fp -> [DotCabalMain fp]
TestSuiteLibV09 _ mn -> [DotCabalModule mn]
TestSuiteUnsupported _ -> []
- bnames = map DotCabalModule (otherModules build)
- build = testBuildInfo test
+ bnames = map DotCabalModule build.sbiOtherModules
+ build = test.buildInfo
-- | Get all files referenced by the executable.
-executableFiles ::
- NamedComponent
- -> Executable
- -> RIO
- GetPackageFileContext
- (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning])
-executableFiles component exe =
- resolveComponentFiles component build names
+stackExecutableFiles ::
+ StackExecutable
+ -> RIO GetPackageFileContext (NamedComponent, ComponentFile)
+stackExecutableFiles exe =
+ resolveComponentFiles (CExe $ unqualCompToText exe.name) build names
where
- build = buildInfo exe
+ build = exe.buildInfo
names =
- map DotCabalModule (otherModules build) ++
- [DotCabalMain (modulePath exe)]
+ map DotCabalModule build.sbiOtherModules ++ [DotCabalMain exe.modulePath]
--- | Get all files referenced by the library.
-libraryFiles ::
- NamedComponent
- -> Library
- -> RIO
- GetPackageFileContext
- (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning])
-libraryFiles component lib =
- resolveComponentFiles component build names
+-- | Get all files referenced by the library. Handle all libraries (CLib and
+-- SubLib), based on empty name or not.
+stackLibraryFiles ::
+ StackLibrary
+ -> RIO GetPackageFileContext (NamedComponent, ComponentFile)
+stackLibraryFiles lib =
+ resolveComponentFiles componentName build names
where
- build = libBuildInfo lib
+ componentRawName = unqualCompToText lib.name
+ componentName
+ | componentRawName == mempty = CLib
+ | otherwise = CSubLib componentRawName
+ build = lib.buildInfo
names = bnames ++ exposed
- exposed = map DotCabalModule (exposedModules lib)
- bnames = map DotCabalModule (otherModules build)
+ exposed = map DotCabalModule lib.exposedModules
+ bnames = map DotCabalModule build.sbiOtherModules
-- | Get all files referenced by the component.
resolveComponentFiles ::
- NamedComponent
- -> BuildInfo
+ ( CAndJsSources rec
+ , HasField "hsSourceDirs" rec [SymbolicPath PackageDir SourceDir]
+ )
+ => NamedComponent
+ -> rec
-> [DotCabalDescriptor]
- -> RIO
- GetPackageFileContext
- (Map ModuleName (Path Abs File), [DotCabalPath], [PackageWarning])
+ -> RIO GetPackageFileContext (NamedComponent, ComponentFile)
resolveComponentFiles component build names = do
- dirs <- mapMaybeM (resolveDirOrWarn . getSymbolicPath) (hsSourceDirs build)
+ dirs <- mapMaybeM (resolveDirOrWarn . getSymbolicPath) build.hsSourceDirs
dir <- asks (parent . ctxFile)
agdirs <- autogenDirs
(modules,files,warnings) <-
@@ -146,7 +151,7 @@ resolveComponentFiles component build names = do
((if null dirs then [dir] else dirs) ++ agdirs)
names
cfiles <- buildOtherSources build
- pure (modules, files <> cfiles, warnings)
+ pure (component, ComponentFile modules (files <> cfiles) warnings)
where
autogenDirs = do
cabalVer <- asks ctxCabalVer
@@ -155,10 +160,9 @@ resolveComponentFiles component build names = do
pkgDir = maybeToList $ packageAutogenDir cabalVer distDir
filterM doesDirExist $ compDir : pkgDir
--- | Try to resolve the list of base names in the given directory by
--- looking for unique instances of base names applied with the given
--- extensions, plus find any of their module and TemplateHaskell
--- dependencies.
+-- | Try to resolve the list of base names in the given directory by looking for
+-- unique instances of base names applied with the given extensions, plus find
+-- any of their module and TemplateHaskell dependencies.
resolveFilesAndDeps ::
NamedComponent -- ^ Package component name
-> [Path Abs Dir] -- ^ Directories to look in.
@@ -444,8 +448,14 @@ logPossibilities dirs mn = do
)
dirs
+type CAndJsSources rec =
+ (HasField "cSources" rec [FilePath], HasField "jsSources" rec [FilePath])
+
-- | Get all C sources and extra source files in a build.
-buildOtherSources :: BuildInfo -> RIO GetPackageFileContext [DotCabalPath]
+buildOtherSources ::
+ CAndJsSources rec
+ => rec
+ -> RIO GetPackageFileContext [DotCabalPath]
buildOtherSources build = do
cwd <- liftIO getCurrentDir
dir <- asks (parent . ctxFile)
@@ -458,19 +468,17 @@ buildOtherSources build = do
warnMissingFile "File" cwd fp file
pure Nothing
Just p -> pure $ Just (toCabalPath p)
- csources <- resolveDirFiles (cSources build) DotCabalCFilePath
- jsources <- resolveDirFiles (targetJsSources build) DotCabalFilePath
+ csources <- resolveDirFiles build.cSources DotCabalCFilePath
+ jsources <- resolveDirFiles build.jsSources DotCabalFilePath
pure (csources <> jsources)
--- | Get the target's JS sources.
-targetJsSources :: BuildInfo -> [FilePath]
-targetJsSources = jsSources
-
-- | Resolve file as a child of a specified directory, symlinks
-- don't get followed.
resolveDirFile ::
(MonadIO m, MonadThrow m)
- => Path Abs Dir -> FilePath.FilePath -> m (Maybe (Path Abs File))
+ => Path Abs Dir
+ -> FilePath.FilePath
+ -> m (Maybe (Path Abs File))
resolveDirFile x y = do
-- The standard canonicalizePath does not work for this case
p <- parseCollapsedAbsFile (toFilePath x FilePath.> y)
@@ -542,7 +550,8 @@ buildDir distDir = distDir > relDirBuild
-- component names.
componentNameToDir :: Text -> Path Rel Dir
componentNameToDir name =
- fromMaybe (throw ComponentNotParsedBug) (parseRelDir (T.unpack name))
+ fromMaybe (throw $ ComponentNotParsedBug sName) (parseRelDir sName)
+ where sName = T.unpack name
-- | See 'Distribution.Simple.LocalBuildInfo.componentBuildDir'
componentBuildDir :: Version -> NamedComponent -> Path Abs Dir -> Path Abs Dir
diff --git a/src/Stack/Coverage.hs b/src/Stack/Coverage.hs
index 8f01572c99..f051538f9f 100644
--- a/src/Stack/Coverage.hs
+++ b/src/Stack/Coverage.hs
@@ -40,19 +40,20 @@ import Stack.Constants
, relFileHpcIndexHtml, relFileIndexHtml
)
import Stack.Constants.Config ( distDirFromDir, hpcRelativeDir )
+import Stack.Package ( hasBuildableMainLibrary )
import Stack.Prelude
import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig )
import Stack.Types.BuildConfig
( BuildConfig (..), HasBuildConfig (..) )
import Stack.Types.Compiler ( getGhcVersion )
+import Stack.Types.CompCollection ( getBuildableSetText )
import Stack.Types.BuildOpts ( BuildOptsCLI (..), defaultBuildOptsCLI )
import Stack.Types.EnvConfig
( EnvConfig (..), HasEnvConfig (..), actualCompilerVersionL
, hpcReportDir
)
import Stack.Types.NamedComponent ( NamedComponent (..) )
-import Stack.Types.Package
- ( Package (..), PackageLibraries (..), packageIdentifier )
+import Stack.Types.Package ( Package (..), packageIdentifier )
import Stack.Types.Runner ( Runner )
import Stack.Types.SourceMap
( PackageType (..), SMTargets (..), SMWanted (..)
@@ -181,16 +182,13 @@ generateHpcReport pkgDir package tests = do
let pkgId = packageIdentifierString (packageIdentifier package)
pkgName' = packageNameString $ packageName package
ghcVersion = getGhcVersion compilerVersion
- hasLibrary =
- case packageLibraries package of
- NoLibraries -> False
- HasLibraries _ -> True
+ hasLibrary = hasBuildableMainLibrary package
subLibs = packageSubLibraries package
eincludeName <-
-- Pre-7.8 uses plain PKG-version in tix files.
if ghcVersion < mkVersion [7, 10] then pure $ Right $ Just [pkgId]
-- We don't expect to find a package key if there is no library.
- else if not hasLibrary && Set.null subLibs then pure $ Right Nothing
+ else if not hasLibrary && null subLibs then pure $ Right Nothing
-- Look in the inplace DB for the package key.
-- See https://github.com/commercialhaskell/stack/issues/1181#issuecomment-148968986
else do
@@ -201,7 +199,7 @@ generateHpcReport pkgDir package tests = do
findPackageFieldForBuiltPackage
pkgDir
(packageIdentifier package)
- subLibs
+ (getBuildableSetText subLibs)
hpcNameField
case eincludeName of
Left err -> do
@@ -248,104 +246,113 @@ generateHpcReportInternal ::
-> [String]
-> [String]
-> RIO env (Maybe (Path Abs File))
-generateHpcReportInternal tixSrc reportDir report reportHtml extraMarkupArgs extraReportArgs = do
- -- If a .tix file exists, move it to the HPC output directory and generate a
- -- report for it.
- tixFileExists <- doesFileExist tixSrc
- if not tixFileExists
- then do
- prettyError $
- "[S-4634]"
- <> line
- <> flow "Didn't find"
- <> style File ".tix"
- <> "for"
- <> report
- <> flow "- expected to find it at"
- <> pretty tixSrc <> "."
- pure Nothing
- else (`catch` \(err :: ProcessException) -> do
- logError $ displayShow err
- generateHpcErrorReport reportDir $ display $ sanitize $
- displayException err
- pure Nothing) $
- (`onException`
- prettyError
- ( "[S-8215]"
- <> line
- <> flow "Error occurred while producing"
- <> report <> "."
- )) $ do
- -- Directories for .mix files.
- hpcRelDir <- hpcRelativeDir
- -- Compute arguments used for both "hpc markup" and "hpc report".
- pkgDirs <- view $ buildConfigL.to (map ppRoot . Map.elems . smwProject . bcSMWanted)
- let args =
- -- Use index files from all packages (allows cross-package coverage results).
- concatMap (\x -> ["--srcdir", toFilePathNoTrailingSep x]) pkgDirs ++
- -- Look for index files in the correct dir (relative to each pkgdir).
- ["--hpcdir", toFilePathNoTrailingSep hpcRelDir, "--reset-hpcdirs"]
- prettyInfoL
- [ "Generating"
- , report <> "."
- ]
- -- Strip @\r@ characters because Windows.
- outputLines <- map (L8.filter (/= '\r')) . L8.lines . fst <$>
- proc "hpc"
- ( "report"
- : toFilePath tixSrc
- : (args ++ extraReportArgs)
- )
- readProcess_
- if all ("(0/0)" `L8.isSuffixOf`) outputLines
+generateHpcReportInternal
+ tixSrc
+ reportDir
+ report
+ reportHtml
+ extraMarkupArgs
+ extraReportArgs
+ = do
+ -- If a .tix file exists, move it to the HPC output directory and generate
+ -- a report for it.
+ tixFileExists <- doesFileExist tixSrc
+ if not tixFileExists
then do
- let msgHtml =
- "Error: [S-6829]\n\
- \The "
- <> display reportHtml
- <> " did not consider any code. One possible cause of this is \
- \if your test-suite builds the library code (see Stack \
- \\
- \issue #1008\
- \\
- \). It may also indicate a bug in Stack or the hpc program. \
- \Please report this issue if you think your coverage report \
- \should have meaningful results."
prettyError $
- "[S-6829]"
+ "[S-4634]"
<> line
- <> fillSep
- [ "The"
- , report
- , flow "did not consider any code. One possible cause of this \
- \is if your test-suite builds the library code (see \
- \Stack issue #1008). It may also indicate a bug in \
- \Stack or the hpc program. Please report this issue if \
- \you think your coverage report should have meaningful \
- \results."
- ]
- generateHpcErrorReport reportDir msgHtml
+ <> flow "Didn't find"
+ <> style File ".tix"
+ <> "for"
+ <> report
+ <> flow "- expected to find it at"
+ <> pretty tixSrc <> "."
pure Nothing
- else do
- let reportPath = reportDir > relFileHpcIndexHtml
- -- Print the summary report to the standard output stream.
- putUtf8Builder =<< displayWithColor
- ( fillSep
- [ "Summary"
- , report <> ":"
- ]
- <> line
- )
- forM_ outputLines putStrLn
- -- Generate the HTML markup.
- void $ proc "hpc"
- ( "markup"
+ else (`catch` \(err :: ProcessException) -> do
+ logError $ displayShow err
+ generateHpcErrorReport reportDir $ display $ sanitize $
+ displayException err
+ pure Nothing) $
+ (`onException`
+ prettyError
+ ( "[S-8215]"
+ <> line
+ <> flow "Error occurred while producing"
+ <> report <> "."
+ )) $ do
+ -- Directories for .mix files.
+ hpcRelDir <- hpcRelativeDir
+ -- Compute arguments used for both "hpc markup" and "hpc report".
+ pkgDirs <- view $ buildConfigL.to
+ (map ppRoot . Map.elems . smwProject . bcSMWanted)
+ let args =
+ -- Use index files from all packages (allows cross-package
+ -- coverage results).
+ concatMap (\x -> ["--srcdir", toFilePathNoTrailingSep x]) pkgDirs ++
+ -- Look for index files in the correct dir (relative to each pkgdir).
+ ["--hpcdir", toFilePathNoTrailingSep hpcRelDir, "--reset-hpcdirs"]
+ prettyInfoL
+ [ "Generating"
+ , report <> "."
+ ]
+ -- Strip @\r@ characters because Windows.
+ outputLines <- map (L8.filter (/= '\r')) . L8.lines . fst <$>
+ proc "hpc"
+ ( "report"
: toFilePath tixSrc
- : ("--destdir=" ++ toFilePathNoTrailingSep reportDir)
- : (args ++ extraMarkupArgs)
+ : (args ++ extraReportArgs)
)
readProcess_
- pure (Just reportPath)
+ if all ("(0/0)" `L8.isSuffixOf`) outputLines
+ then do
+ let msgHtml =
+ "Error: [S-6829]\n\
+ \The "
+ <> display reportHtml
+ <> " did not consider any code. One possible cause of this is \
+ \if your test-suite builds the library code (see Stack \
+ \\
+ \issue #1008\
+ \\
+ \). It may also indicate a bug in Stack or the hpc program. \
+ \Please report this issue if you think your coverage report \
+ \should have meaningful results."
+ prettyError $
+ "[S-6829]"
+ <> line
+ <> fillSep
+ [ "The"
+ , report
+ , flow "did not consider any code. One possible cause of this \
+ \is if your test-suite builds the library code (see \
+ \Stack issue #1008). It may also indicate a bug in \
+ \Stack or the hpc program. Please report this issue if \
+ \you think your coverage report should have meaningful \
+ \results."
+ ]
+ generateHpcErrorReport reportDir msgHtml
+ pure Nothing
+ else do
+ let reportPath = reportDir > relFileHpcIndexHtml
+ -- Print the summary report to the standard output stream.
+ putUtf8Builder =<< displayWithColor
+ ( fillSep
+ [ "Summary"
+ , report <> ":"
+ ]
+ <> line
+ )
+ forM_ outputLines putStrLn
+ -- Generate the HTML markup.
+ void $ proc "hpc"
+ ( "markup"
+ : toFilePath tixSrc
+ : ("--destdir=" ++ toFilePathNoTrailingSep reportDir)
+ : (args ++ extraMarkupArgs)
+ )
+ readProcess_
+ pure (Just reportPath)
generateHpcReportForTargets :: HasEnvConfig env
=> HpcReportOpts -> [Text] -> [Text] -> RIO env ()
@@ -415,11 +422,12 @@ generateHpcUnifiedReport = do
outputDir <- hpcReportDir
ensureDir outputDir
(dirs, _) <- listDir outputDir
- tixFiles0 <- fmap (concat . concat) $ forM (filter (("combined" /=) . dirnameString) dirs) $ \dir -> do
- (dirs', _) <- listDir dir
- forM dirs' $ \dir' -> do
- (_, files) <- listDir dir'
- pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files)
+ tixFiles0 <-
+ fmap (concat . concat) $ forM (filter (("combined" /=) . dirnameString) dirs) $ \dir -> do
+ (dirs', _) <- listDir dir
+ forM dirs' $ \dir' -> do
+ (_, files) <- listDir dir'
+ pure (filter ((".tix" `L.isSuffixOf`) . toFilePath) files)
extraTixFiles <- findExtraTixFiles
let tixFiles = tixFiles0 ++ extraTixFiles
reportDir = outputDir > relDirCombined > relDirAll
@@ -463,7 +471,8 @@ generateUnionReport report reportHtml reportDir tixFiles = do
]
<> line
<> bulletedList (map fromString errs :: [StyleDoc])
- tixDest <- (reportDir >) <$> parseRelFile (dirnameString reportDir ++ ".tix")
+ tixDest <-
+ (reportDir >) <$> parseRelFile (dirnameString reportDir ++ ".tix")
ensureDir (parent tixDest)
liftIO $ writeTix (toFilePath tixDest) tix
generateHpcReportInternal tixDest reportDir report reportHtml [] []
@@ -488,10 +497,11 @@ readTixOrLog path = do
]
pure mtix
--- | Module names which contain '/' have a package name, and so they weren't built into the
--- executable.
+-- | Module names which contain '/' have a package name, and so they weren't
+-- built into the executable.
removeExeModules :: Tix -> Tix
-removeExeModules (Tix ms) = Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms)
+removeExeModules (Tix ms) =
+ Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms)
unionTixes :: [Tix] -> ([String], Tix)
unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs))
@@ -500,7 +510,8 @@ unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs))
toMap (Tix ms) = Map.fromList (map (\x@(TixModule k _ _ _) -> (k, Right x)) ms)
merge (Right (TixModule k hash1 len1 tix1))
(Right (TixModule _ hash2 len2 tix2))
- | hash1 == hash2 && len1 == len2 = Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2))
+ | hash1 == hash2 && len1 == len2 =
+ Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2))
merge _ _ = Left ()
generateHpcMarkupIndex :: HasEnvConfig env => RIO env ()
diff --git a/src/Stack/Ghci.hs b/src/Stack/Ghci.hs
index 5de6cb05c4..ceaad19b7d 100644
--- a/src/Stack/Ghci.hs
+++ b/src/Stack/Ghci.hs
@@ -48,9 +48,12 @@ import Stack.Ghci.Script
, scriptToLazyByteString
)
import Stack.Package
- ( PackageDescriptionPair (..), packageFromPackageDescription
+ ( PackageDescriptionPair (..), buildableExes
+ , buildableForeignLibs, hasBuildableMainLibrary
+ , getPackageOpts, packageFromPackageDescription
, readDotBuildinfo, resolvePackageDescription
)
+import Stack.PackageFile ( getPackageFile )
import Stack.Prelude
import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig )
import Stack.Types.Build.Exception
@@ -61,6 +64,7 @@ import Stack.Types.BuildOpts
( ApplyCLIFlag, BenchmarkOpts (..), BuildOpts (..)
, BuildOptsCLI (..), TestOpts (..), defaultBuildOptsCLI
)
+import Stack.Types.CompCollection ( getBuildableListText )
import Stack.Types.CompilerPaths
( CompilerPaths (..), HasCompiler (..) )
import Stack.Types.Config ( Config (..), HasConfig (..), buildOptsL )
@@ -74,10 +78,9 @@ import Stack.Types.NamedComponent
import Stack.Types.Package
( BuildInfoOpts (..), InstallMap, InstalledMap
, LocalPackage (..), Package (..), PackageConfig (..)
- , PackageLibraries (..), dotCabalCFilePath, dotCabalGetPath
- , dotCabalMainPath, getPackageOpts
+ , dotCabalCFilePath, dotCabalGetPath, dotCabalMainPath
)
-import Stack.Types.PackageFile ( getPackageFiles )
+import Stack.Types.PackageFile ( PackageComponentFile (..) )
import Stack.Types.Platform ( HasPlatform (..) )
import Stack.Types.Runner ( HasRunner, Runner )
import Stack.Types.SourceMap
@@ -345,7 +348,7 @@ findFileTargets ::
-> RIO env (Map PackageName Target, Map PackageName [Path Abs File], [Path Abs File])
findFileTargets locals fileTargets = do
filePackages <- forM locals $ \lp -> do
- (_,compFiles,_,_) <- getPackageFiles (packageFiles (lpPackage lp)) (lpCabalFile lp)
+ PackageComponentFile _ compFiles _ _ <- getPackageFile (lpPackage lp) (lpCabalFile lp)
pure (lp, M.map (map dotCabalGetPath) compFiles)
let foundFileTargetComponents :: [(Path Abs File, [(PackageName, NamedComponent)])]
foundFileTargetComponents =
@@ -929,7 +932,8 @@ makeGhciPkgInfo installMap installedMap locals addPkgs mfileTargets pkgDesc = do
cabalfp = ghciDescCabalFp pkgDesc
target = ghciDescTarget pkgDesc
name = packageName pkg
- (mods,files,opts) <- getPackageOpts (packageOpts pkg) installMap installedMap locals addPkgs cabalfp
+ (mods, files, opts) <-
+ getPackageOpts pkg installMap installedMap locals addPkgs cabalfp
let filteredOpts = filterWanted opts
filterWanted = M.filterWithKey (\k _ -> k `S.member` allWanted)
allWanted = wantedPackageComponents bopts target pkg
@@ -957,17 +961,20 @@ makeGhciPkgInfo installMap installedMap locals addPkgs mfileTargets pkgDesc = do
wantedPackageComponents :: BuildOpts -> Target -> Package -> Set NamedComponent
wantedPackageComponents _ (TargetComps cs) _ = cs
wantedPackageComponents bopts (TargetAll PTProject) pkg = S.fromList $
- ( case packageLibraries pkg of
- NoLibraries -> []
- HasLibraries names -> CLib : map CSubLib (S.toList names)
- )
- <> map CExe (S.toList (packageExes pkg))
- <> map CSubLib (S.toList $ packageSubLibraries pkg)
- <> (if boptsTests bopts then map CTest (M.keys (packageTests pkg)) else [])
- <> ( if boptsBenchmarks bopts
- then map CBench (S.toList (packageBenchmarks pkg))
+ ( if hasBuildableMainLibrary pkg
+ then CLib : map CSubLib buildableForeignLibs'
else []
)
+ <> map CExe buildableExes'
+ <> map CSubLib buildableSubLibs
+ <> (if boptsTests bopts then map CTest buildableTestSuites else [])
+ <> (if boptsBenchmarks bopts then map CBench buildableBenchmarks else [])
+ where
+ buildableForeignLibs' = S.toList $ buildableForeignLibs pkg
+ buildableSubLibs = getBuildableListText $ packageSubLibraries pkg
+ buildableExes' = S.toList $ buildableExes pkg
+ buildableTestSuites = getBuildableListText $ packageTestSuites pkg
+ buildableBenchmarks = getBuildableListText $ packageBenchmarks pkg
wantedPackageComponents _ _ _ = S.empty
checkForIssues :: HasTerm env => [GhciPkgInfo] -> RIO env ()
diff --git a/src/Stack/Package.hs b/src/Stack/Package.hs
index a6763e0ee4..06a3332dfd 100644
--- a/src/Stack/Package.hs
+++ b/src/Stack/Package.hs
@@ -1,7 +1,7 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
-- | Dealing with Cabal.
@@ -11,15 +11,24 @@ module Stack.Package
, packageFromPackageDescription
, Package (..)
, PackageDescriptionPair (..)
- , GetPackageOpts (..)
, PackageConfig (..)
, buildLogPath
, PackageException (..)
, resolvePackageDescription
, packageDependencies
, applyForceCustomBuild
+ , hasBuildableMainLibrary
+ , mainLibraryHasExposedModules
+ , packageUnknownTools
+ , buildableForeignLibs
+ , buildableSubLibs
+ , buildableExes
+ , buildableTestSuites
+ , buildableBenchmarks
+ , getPackageOpts
) where
+import Data.Foldable ( Foldable (..) )
import Data.List ( unzip )
import qualified Data.Map.Strict as M
import qualified Data.Set as S
@@ -28,6 +37,7 @@ import Distribution.CabalSpecVersion ( cabalSpecToVersionDigits )
import qualified Distribution.Compat.NonEmptySet as NES
import Distribution.Compiler
( CompilerFlavor (..), PerCompilerFlavor (..) )
+import Distribution.ModuleName ( ModuleName )
import Distribution.Package ( mkPackageName )
import Distribution.PackageDescription
( Benchmark (..), BuildInfo (..), BuildType (..)
@@ -35,9 +45,9 @@ import Distribution.PackageDescription
, Dependency (..), Executable (..), ForeignLib (..)
, GenericPackageDescription (..), HookedBuildInfo
, Library (..), PackageDescription (..), PackageFlag (..)
- , SetupBuildInfo (..), TestSuite (..), allLanguages
- , allLibraries, buildType, depPkgName, depVerRange
- , libraryNameString, maybeToLibraryName, usedExtensions
+ , SetupBuildInfo (..), TestSuite (..), allLibraries
+ , buildType, depPkgName, depVerRange, libraryNameString
+ , maybeToLibraryName
)
import Distribution.Pretty ( prettyShow )
import Distribution.Simple.PackageDescription ( readHookedBuildInfo )
@@ -51,39 +61,53 @@ import Distribution.Utils.Path ( getSymbolicPath )
import Distribution.Verbosity ( silent )
import Distribution.Version
( anyVersion, mkVersion, orLaterVersion )
+import GHC.Records ( getField )
import Path
( (>), parent, parseAbsDir, parseRelDir, parseRelFile
, stripProperPrefix
)
import Path.Extra ( concatAndCollapseAbsDir, toFilePathNoTrailingSep )
-import Stack.Constants (relFileCabalMacrosH, relDirLogs)
-import Stack.Constants.Config ( distDirFromDir )
-import Stack.Prelude hiding ( Display (..) )
+import Stack.Component
+ ( foldOnNameAndBuildInfo, isComponentBuildable
+ , stackBenchmarkFromCabal, stackExecutableFromCabal
+ , stackForeignLibraryFromCabal, stackLibraryFromCabal
+ , stackTestFromCabal, stackUnqualToQual
+ )
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 (..), getProjectWorkDir )
+import Stack.Types.CompCollection
+ ( CompCollection, foldAndMakeCollection
+ , getBuildableSetText
+ )
import Stack.Types.Compiler ( ActualCompiler (..) )
import Stack.Types.CompilerPaths ( cabalVersionL )
+import Stack.Types.Component ( HasBuildInfo )
+import qualified Stack.Types.Component as Component
import Stack.Types.Config ( Config (..), HasConfig (..) )
+import Stack.Types.Dependency ( DepType (..), DepValue (..) )
import Stack.Types.EnvConfig ( HasEnvConfig )
import Stack.Types.GhcPkgId ( ghcPkgIdString )
import Stack.Types.NamedComponent
( NamedComponent (..), subLibComponents )
import Stack.Types.Package
- ( BuildInfoOpts (..), ExeName (..), GetPackageOpts (..)
+ ( BioInput (..), BuildInfoOpts (..), ExeName (..)
, InstallMap, Installed (..), InstalledMap, Package (..)
, PackageConfig (..), PackageException (..)
- , PackageLibraries (..), dotCabalCFilePath, packageIdentifier
+ , dotCabalCFilePath, packageIdentifier
)
+import Stack.Types.PackageFile
+ ( DotCabalPath, PackageComponentFile (..) )
import Stack.Types.Version
( VersionRange, intersectVersionRanges, withinRange )
import System.FilePath ( replaceExtension )
-import Stack.Types.Dependency ( DepValue (..), DepType (..) )
-import Stack.Types.PackageFile ( DotCabalPath , GetPackageFiles (..) )
-import Stack.PackageFile ( getPackageFile )
-- | Read @.buildinfo@ ancillary files produced by some Setup.hs hooks.
-- The file includes Cabal file syntax to be merged into the package description
@@ -108,75 +132,38 @@ packageFromPackageDescription ::
-> [PackageFlag]
-> PackageDescriptionPair
-> Package
-packageFromPackageDescription packageConfig pkgFlags (PackageDescriptionPair pkgNoMod pkg) =
- Package
- { packageName = name
- , packageVersion = pkgVersion pkgId
- , packageLicense = licenseRaw pkg
- , packageDeps = deps
- , packageFiles = pkgFiles
- , packageUnknownTools = unknownTools
- , packageGhcOptions = packageConfigGhcOptions packageConfig
- , packageCabalConfigOpts = packageConfigCabalConfigOpts packageConfig
- , packageFlags = packageConfigFlags packageConfig
- , packageDefaultFlags = M.fromList
- [(flagName flag, flagDefault flag) | flag <- pkgFlags]
- , packageAllDeps = M.keysSet deps
- , packageSubLibDeps = subLibDeps
- , packageLibraries =
- let mlib = do
- lib <- library pkg
- guard $ buildable $ libBuildInfo lib
- Just lib
- in
- case mlib of
- Nothing -> NoLibraries
- Just _ -> HasLibraries foreignLibNames
- , packageSubLibraries = subLibNames
- , packageTests = M.fromList
- [ (T.pack (Cabal.unUnqualComponentName $ testName t), testInterface t)
- | t <- testSuites pkgNoMod
- , buildable (testBuildInfo t)
- ]
- , packageBenchmarks = S.fromList
- [ T.pack (Cabal.unUnqualComponentName $ benchmarkName b)
- | b <- benchmarks pkgNoMod
- , buildable (benchmarkBuildInfo b)
- ]
- -- Same comment about buildable applies here too.
- , packageExes = S.fromList
- [ T.pack (Cabal.unUnqualComponentName $ exeName biBuildInfo)
- | biBuildInfo <- executables pkg
- , buildable (buildInfo biBuildInfo)
- ]
- -- This is an action used to collect info needed for "stack ghci".
- -- This info isn't usually needed, so computation of it is deferred.
- , packageOpts = GetPackageOpts $
- \installMap installedMap omitPkgs addPkgs cabalfp -> do
- (componentsModules,componentFiles, _, _) <- getPackageFiles pkgFiles cabalfp
- let subLibs =
- S.toList $ subLibComponents $ M.keysSet componentsModules
- excludedSubLibs <- mapM (parsePackageNameThrowing . T.unpack) subLibs
- mungedSubLibs <- mapM
- (parsePackageNameThrowing . T.unpack . toInternalPackageMungedName)
- subLibs
- componentsOpts <- generatePkgDescOpts
- installMap
- installedMap
- (excludedSubLibs ++ omitPkgs)
- (mungedSubLibs ++ addPkgs)
- cabalfp
- pkg
- componentFiles
- pure (componentsModules, componentFiles, componentsOpts)
- , packageHasExposedModules = maybe
- False
- (not . null . exposedModules)
- (library pkg)
- , packageBuildType = buildType pkg
- , packageSetupDeps = msetupDeps
- , packageCabalSpec = specVersion pkg
- }
+packageFromPackageDescription
+ packageConfig
+ pkgFlags
+ (PackageDescriptionPair pkgNoMod pkg)
+ = Package
+ { packageName = name
+ , packageVersion = pkgVersion pkgId
+ , packageLicense = licenseRaw pkg
+ , packageDeps = deps
+ , packageGhcOptions = packageConfigGhcOptions packageConfig
+ , packageCabalConfigOpts = packageConfigCabalConfigOpts packageConfig
+ , packageFlags = packageConfigFlags packageConfig
+ , packageDefaultFlags = M.fromList
+ [(flagName flag, flagDefault flag) | flag <- pkgFlags]
+ , packageLibrary = stackLibraryFromCabal <$> library pkg
+ , packageSubLibraries =
+ foldAndMakeCollection stackLibraryFromCabal $ subLibraries pkg
+ , packageForeignLibraries =
+ foldAndMakeCollection stackForeignLibraryFromCabal $ foreignLibs pkg
+ , packageTestSuites =
+ foldAndMakeCollection stackTestFromCabal $ testSuites pkgNoMod
+ , packageBenchmarks =
+ foldAndMakeCollection stackBenchmarkFromCabal $ benchmarks pkgNoMod
+ , packageExecutables =
+ foldAndMakeCollection stackExecutableFromCabal $ executables pkg
+ , packageAllDeps = M.keysSet deps
+ , packageSubLibDeps = subLibDeps
+ , packageBuildType = buildType pkg
+ , packageSetupDeps = msetupDeps
+ , packageCabalSpec = specVersion pkg
+ , packageFile = stackPackageFileFromCabal pkg
+ }
where
extraLibNames = S.union subLibNames foreignLibNames
@@ -195,18 +182,13 @@ packageFromPackageDescription packageConfig pkgFlags (PackageDescriptionPair pkg
$ filter (buildable . foreignLibBuildInfo)
$ foreignLibs pkg
- toInternalPackageMungedName
- = T.pack . prettyShow . MungedPackageName (pkgName pkgId)
- . maybeToLibraryName . Just . Cabal.mkUnqualComponentName . T.unpack
-
-- 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"
- pkgFiles = GetPackageFiles $ getPackageFile pkg
pkgId = package pkg
name = pkgName pkgId
- (unknownTools, knownTools) = packageDescTools pkg
+ (_unknownTools, knownTools) = packageDescTools pkg
deps = M.filterWithKey (const . not . isMe) (M.unionsWith (<>)
[ asLibrary <$> packageDependencies pkg
@@ -240,6 +222,59 @@ packageFromPackageDescription packageConfig pkgFlags (PackageDescriptionPair pkg
isMe name' = name' == name
|| fromString (packageNameString name') `S.member` extraLibNames
+toInternalPackageMungedName :: Package -> Text -> Text
+toInternalPackageMungedName pkg =
+ T.pack
+ . prettyShow
+ . MungedPackageName (packageName pkg)
+ . maybeToLibraryName
+ . Just
+ . Cabal.mkUnqualComponentName
+ . T.unpack
+
+-- | 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 <- mapM (parsePackageNameThrowing . T.unpack) subLibs
+ mungedSubLibs <- mapM
+ ( parsePackageNameThrowing
+ . T.unpack
+ . toInternalPackageMungedName stackPackage
+ )
+ subLibs
+ componentsOpts <- generatePkgDescOpts
+ installMap
+ installedMap
+ (excludedSubLibs ++ omitPkgs)
+ (mungedSubLibs ++ 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 ::
@@ -251,16 +286,22 @@ generatePkgDescOpts ::
-> [PackageName]
-- ^ Packages to add to the "-package" flags
-> Path Abs File
- -> PackageDescription
+ -> Package
-> Map NamedComponent [DotCabalPath]
-> m (Map NamedComponent BuildInfoOpts)
-generatePkgDescOpts installMap installedMap omitPkgs addPkgs cabalfp pkg componentPaths = do
- config <- view configL
- cabalVer <- view cabalVersionL
- distDir <- distDirFromDir cabalDir
- let generate namedComponent binfo =
- ( namedComponent
- , generateBuildInfoOpts BioInput
+generatePkgDescOpts
+ installMap
+ installedMap
+ omitPkgs
+ addPkgs
+ cabalfp
+ pkg
+ componentPaths
+ = do
+ config <- view configL
+ cabalVer <- view cabalVersionL
+ distDir <- distDirFromDir cabalDir
+ let generate namedComponent binfo = generateBuildInfoOpts BioInput
{ biInstallMap = installMap
, biInstalledMap = installedMap
, biCabalDir = cabalDir
@@ -275,73 +316,29 @@ generatePkgDescOpts installMap installedMap omitPkgs addPkgs cabalfp pkg compone
, biComponentName = namedComponent
, biCabalVersion = cabalVer
}
- )
- pure
- ( M.fromList
- ( concat
- [ maybe
- []
- (pure . generate CLib . libBuildInfo)
- (library pkg)
- , mapMaybe
- (\subLib -> do
- let maybeLib =
- CSubLib . T.pack . Cabal.unUnqualComponentName <$>
- (libraryNameString . libName) subLib
- flip generate (libBuildInfo subLib) <$> maybeLib
- )
- (subLibraries pkg)
- , fmap
- (\exe ->
- generate
- (CExe (T.pack (Cabal.unUnqualComponentName (exeName exe))))
- (buildInfo exe)
- )
- (executables pkg)
- , fmap
- (\bench ->
- generate
- (CBench
- (T.pack (Cabal.unUnqualComponentName (benchmarkName bench)))
- )
- (benchmarkBuildInfo bench)
- )
- (benchmarks pkg)
- , fmap
- (\test ->
- generate
- (CTest (T.pack (Cabal.unUnqualComponentName (testName test))))
- (testBuildInfo test)
- )
- (testSuites pkg)
- ]
- )
- )
+ let insertInMap name compVal = M.insert name (generate name compVal)
+ let translatedInsertInMap constructor name =
+ insertInMap (stackUnqualToQual constructor name)
+ let makeBuildInfoOpts selector constructor =
+ foldOnNameAndBuildInfo
+ (selector pkg)
+ (translatedInsertInMap constructor)
+ let aggregateAllBuildInfoOpts =
+ makeBuildInfoOpts packageLibrary (const CLib)
+ . makeBuildInfoOpts packageSubLibraries CSubLib
+ . makeBuildInfoOpts packageExecutables CExe
+ . makeBuildInfoOpts packageBenchmarks CBench
+ . makeBuildInfoOpts packageTestSuites CTest
+ pure $ aggregateAllBuildInfoOpts mempty
where
cabalDir = parent cabalfp
--- | Input to 'generateBuildInfoOpts'
-data BioInput = BioInput
- { biInstallMap :: !InstallMap
- , biInstalledMap :: !InstalledMap
- , biCabalDir :: !(Path Abs Dir)
- , biDistDir :: !(Path Abs Dir)
- , biOmitPackages :: ![PackageName]
- , biAddPackages :: ![PackageName]
- , biBuildInfo :: !BuildInfo
- , biDotCabalPaths :: ![DotCabalPath]
- , biConfigLibDirs :: ![FilePath]
- , biConfigIncludeDirs :: ![FilePath]
- , biComponentName :: !NamedComponent
- , biCabalVersion :: !Version
- }
-
-- | 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 BioInput {..} =
BuildInfoOpts
- { bioOpts = ghcOpts ++ fmap ("-optP" <>) (cppOptions biBuildInfo)
+ { bioOpts = ghcOpts ++ fmap ("-optP" <>) (Component.cppOptions biBuildInfo)
-- 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-
@@ -376,50 +373,54 @@ generateBuildInfoOpts BioInput {..} =
pkgs =
biAddPackages ++
[ name
- | Dependency name _ _ <- targetBuildDepends biBuildInfo
- -- TODO: cabal 3 introduced multiple public libraries in a single dependency
+ | Dependency name _ _ <- Component.targetBuildDepends biBuildInfo
+ -- TODO: Cabal 3.0 introduced multiple public libraries in a single
+ -- dependency
, name `notElem` biOmitPackages
]
- PerCompilerFlavor ghcOpts _ = options biBuildInfo
+ PerCompilerFlavor ghcOpts _ = Component.options biBuildInfo
extOpts =
- map (("-X" ++) . display) (allLanguages biBuildInfo)
- <> map (("-X" ++) . display) (usedExtensions biBuildInfo)
+ map (("-X" ++) . display) (Component.allLanguages biBuildInfo)
+ <> map (("-X" ++) . display) (Component.usedExtensions biBuildInfo)
srcOpts =
map (("-i" <>) . toFilePathNoTrailingSep)
(concat
[ [ componentBuildDir biCabalVersion biComponentName biDistDir ]
, [ biCabalDir
- | null (hsSourceDirs biBuildInfo)
+ | null (Component.hsSourceDirs biBuildInfo)
]
- , mapMaybe (toIncludeDir . getSymbolicPath) (hsSourceDirs biBuildInfo)
+ , mapMaybe
+ (toIncludeDir . getSymbolicPath)
+ (Component.hsSourceDirs biBuildInfo)
, [ componentAutogen ]
, maybeToList (packageAutogenDir biCabalVersion biDistDir)
, [ componentOutputDir biComponentName biDistDir ]
]) ++
[ "-stubdir=" ++ toFilePathNoTrailingSep (buildDir biDistDir) ]
- componentAutogen = componentAutogenDir biCabalVersion biComponentName biDistDir
+ componentAutogen =
+ componentAutogenDir biCabalVersion biComponentName biDistDir
toIncludeDir "." = Just biCabalDir
toIncludeDir relDir = concatAndCollapseAbsDir biCabalDir relDir
includeOpts =
map ("-I" <>) (biConfigIncludeDirs <> pkgIncludeOpts)
pkgIncludeOpts =
[ toFilePathNoTrailingSep absDir
- | dir <- includeDirs biBuildInfo
+ | dir <- Component.includeDirs biBuildInfo
, absDir <- handleDir dir
]
libOpts =
- map ("-l" <>) (extraLibs biBuildInfo) <>
+ map ("-l" <>) (Component.extraLibs biBuildInfo) <>
map ("-L" <>) (biConfigLibDirs <> pkgLibDirs)
pkgLibDirs =
[ toFilePathNoTrailingSep absDir
- | dir <- extraLibDirs biBuildInfo
+ | dir <- Component.extraLibDirs biBuildInfo
, absDir <- handleDir dir
]
handleDir dir = case (parseAbsDir dir, parseRelDir dir) of
(Just ab, _ ) -> [ab]
(_ , Just rel) -> [biCabalDir > rel]
(Nothing, Nothing ) -> []
- fworks = map ("-framework=" <>) (frameworks biBuildInfo)
+ fworks = map ("-framework=" <>) (Component.frameworks biBuildInfo)
-- | Make the .o path from the .c file path for a component. Example:
--
@@ -595,19 +596,28 @@ resolvePackageDescription
go modBuildable = desc
{ library = fmap (resolveConditions rc updateLibDeps) mlib
, subLibraries = map
- (\(n, v) -> (resolveConditions rc updateLibDeps v){libName=LSubLibName n})
+ ( \(n, v) ->
+ (resolveConditions rc updateLibDeps v){libName = LSubLibName n}
+ )
subLibs
, foreignLibs = map
- (\(n, v) -> (resolveConditions rc updateForeignLibDeps v){foreignLibName=n})
+ ( \(n, v) ->
+ (resolveConditions rc updateForeignLibDeps v){foreignLibName = n}
+ )
foreignLibs'
, executables = map
- (\(n, v) -> (resolveConditions rc updateExeDeps v){exeName=n})
+ ( \(n, v) -> (resolveConditions rc updateExeDeps v){exeName = n} )
exes
, testSuites = map
- (\(n, v) -> (resolveConditions rc (updateTestDeps modBuildable) v){testName=n})
+ ( \(n, v) ->
+ (resolveConditions rc (updateTestDeps modBuildable) v){testName = n}
+ )
tests
, benchmarks = map
- (\(n, v) -> (resolveConditions rc (updateBenchmarkDeps modBuildable) v){benchmarkName=n})
+ ( \(n, v) ->
+ (resolveConditions rc (updateBenchmarkDeps modBuildable) v)
+ {benchmarkName = n}
+ )
benches
}
@@ -802,3 +812,52 @@ applyForceCustomBuild cabalVersion package
forceCustomBuild =
packageBuildType package == Simple
&& not (cabalVersion `withinRange` cabalVersionRange)
+
+-- | Check if the package has a main library that is buildable.
+hasBuildableMainLibrary :: Package -> Bool
+hasBuildableMainLibrary package =
+ maybe False isComponentBuildable $ packageLibrary package
+
+-- | 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 . Component.exposedModules) $ packageLibrary package
+
+-- | 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.sbiUnknownTools' for more
+-- information.
+packageUnknownTools :: Package -> Set Text
+packageUnknownTools pkg = lib (bench <> tests <> flib <> sublib <> exe)
+ where
+ lib setT = case packageLibrary pkg of
+ Just libV -> addUnknownTools libV setT
+ Nothing -> setT
+ bench = gatherUnknownTools $ packageBenchmarks pkg
+ tests = gatherUnknownTools $ packageTestSuites pkg
+ flib = gatherUnknownTools $ packageForeignLibraries pkg
+ sublib = gatherUnknownTools $ packageSubLibraries pkg
+ exe = gatherUnknownTools $ packageExecutables pkg
+ addUnknownTools :: HasBuildInfo x => x -> Set Text -> Set Text
+ addUnknownTools = (<>) . Component.sbiUnknownTools . getField @"buildInfo"
+ gatherUnknownTools :: HasBuildInfo x => CompCollection x -> Set Text
+ gatherUnknownTools = foldr' addUnknownTools mempty
+
+buildableForeignLibs :: Package -> Set Text
+buildableForeignLibs pkg = getBuildableSetText (packageForeignLibraries pkg)
+
+buildableSubLibs :: Package -> Set Text
+buildableSubLibs pkg = getBuildableSetText (packageSubLibraries pkg)
+
+buildableExes :: Package -> Set Text
+buildableExes pkg = getBuildableSetText (packageExecutables pkg)
+
+buildableTestSuites :: Package -> Set Text
+buildableTestSuites pkg = getBuildableSetText (packageTestSuites pkg)
+
+buildableBenchmarks :: Package -> Set Text
+buildableBenchmarks pkg = getBuildableSetText (packageBenchmarks pkg)
diff --git a/src/Stack/PackageFile.hs b/src/Stack/PackageFile.hs
index 8ac13bdac7..a7234967be 100644
--- a/src/Stack/PackageFile.hs
+++ b/src/Stack/PackageFile.hs
@@ -4,28 +4,21 @@
-- | A module which exports all package-level file-gathering logic.
module Stack.PackageFile
( getPackageFile
- , packageDescModulesAndFiles
+ , stackPackageFileFromCabal
) where
+import Data.Foldable ( Foldable (..) )
import qualified Data.Map.Strict as M
import qualified Data.Set as S
-import qualified Data.Text as T
import Distribution.CabalSpecVersion ( CabalSpecVersion )
-import Distribution.ModuleName ( ModuleName )
-import Distribution.PackageDescription
- ( BuildType (..), PackageDescription, benchmarkName
- , benchmarks, buildType, dataDir, dataFiles, exeName
- , executables, extraSrcFiles, libName, library
- , libraryNameString, specVersion, subLibraries, testName
- , testSuites )
+import qualified Distribution.PackageDescription as Cabal
import Distribution.Simple.Glob ( matchDirFileGlob )
-import qualified Distribution.Types.UnqualComponentName as Cabal
import Path ( parent, (>) )
import Path.Extra ( forgivingResolveFile, rejectMissingFile )
import Path.IO ( doesFileExist )
import Stack.ComponentFile
- ( benchmarkFiles, executableFiles, libraryFiles
- , resolveOrWarn, testFiles
+ ( ComponentFile (..), resolveOrWarn, stackBenchmarkFiles
+ , stackExecutableFiles, stackLibraryFiles
)
import Stack.Constants
( relFileHpackPackageConfig, relFileSetupHs, relFileSetupLhs
@@ -34,11 +27,12 @@ import Stack.Constants.Config ( distDirFromDir )
import Stack.Prelude
import Stack.Types.BuildConfig ( HasBuildConfig (..) )
import Stack.Types.CompilerPaths ( cabalVersionL )
-import Stack.Types.EnvConfig ( HasEnvConfig )
+import Stack.Types.EnvConfig ( HasEnvConfig (..) )
import Stack.Types.NamedComponent ( NamedComponent (..) )
+import Stack.Types.Package ( Package(..) )
import Stack.Types.PackageFile
- ( DotCabalPath (..), GetPackageFileContext (..)
- , PackageWarning (..)
+ ( GetPackageFileContext (..), PackageComponentFile (..)
+ , StackPackageFile (..)
)
import qualified System.FilePath as FilePath
import System.IO.Error ( isUserError )
@@ -53,70 +47,33 @@ resolveFileOrWarn = resolveOrWarn "File" f
-- | Get all files referenced by the package.
packageDescModulesAndFiles ::
- PackageDescription
+ Package
-> RIO
GetPackageFileContext
- ( Map NamedComponent (Map ModuleName (Path Abs File))
- , Map NamedComponent [DotCabalPath]
- , Set (Path Abs File)
- , [PackageWarning]
- )
+ PackageComponentFile
packageDescModulesAndFiles pkg = do
- (libraryMods, libDotCabalFiles, libWarnings) <-
- maybe
- (pure (M.empty, M.empty, []))
- (asModuleAndFileMap libComponent libraryFiles)
- (library pkg)
- (subLibrariesMods, subLibDotCabalFiles, subLibWarnings) <-
- fmap
- foldTuples
- ( mapM
- (asModuleAndFileMap subLibComponent libraryFiles)
- (subLibraries pkg)
- )
- (executableMods, exeDotCabalFiles, exeWarnings) <-
- fmap
- foldTuples
- ( mapM
- (asModuleAndFileMap exeComponent executableFiles)
- (executables pkg)
- )
- (testMods, testDotCabalFiles, testWarnings) <-
- fmap
- foldTuples
- (mapM (asModuleAndFileMap testComponent testFiles) (testSuites pkg))
- (benchModules, benchDotCabalPaths, benchWarnings) <-
- fmap
- foldTuples
- ( mapM
- (asModuleAndFileMap benchComponent benchmarkFiles)
- (benchmarks pkg)
- )
- dfiles <- resolveGlobFiles
- (specVersion pkg)
- ( extraSrcFiles pkg
- ++ map (dataDir pkg FilePath.>) (dataFiles pkg)
- )
- let modules = libraryMods <> subLibrariesMods <> executableMods <> testMods <>
- benchModules
- files = libDotCabalFiles <> subLibDotCabalFiles <> exeDotCabalFiles <>
- testDotCabalFiles <> benchDotCabalPaths
- warnings = libWarnings <> subLibWarnings <> exeWarnings <> testWarnings <>
- benchWarnings
- pure (modules, files, dfiles, warnings)
- where
- libComponent = const CLib
- subLibComponent =
- CSubLib . T.pack . maybe
- "" Cabal.unUnqualComponentName . libraryNameString . libName
- exeComponent = CExe . T.pack . Cabal.unUnqualComponentName . exeName
- testComponent = CTest . T.pack . Cabal.unUnqualComponentName . testName
- benchComponent = CBench . T.pack . Cabal.unUnqualComponentName . benchmarkName
- asModuleAndFileMap label f lib = do
- (a, b, c) <- f (label lib) lib
- pure (M.singleton (label lib) a, M.singleton (label lib) b, c)
- foldTuples = foldl' (<>) (M.empty, M.empty, [])
+ packageExtraFile <- resolveGlobFilesFromStackPackageFile
+ (packageCabalSpec pkg) (packageFile pkg)
+ let initialValue = mempty{packageExtraFile=packageExtraFile}
+ let accumulator f comp st = (insertComponentFile <$> st) <*> f comp
+ let gatherCompFileCollection createCompFileFn getCompFn res =
+ foldr' (accumulator createCompFileFn) res (getCompFn pkg)
+ gatherCompFileCollection stackLibraryFiles packageLibrary
+ . gatherCompFileCollection stackLibraryFiles packageSubLibraries
+ . gatherCompFileCollection stackExecutableFiles packageExecutables
+ . gatherCompFileCollection stackBenchmarkFiles packageBenchmarks
+ $ pure initialValue
+resolveGlobFilesFromStackPackageFile ::
+ CabalSpecVersion
+ -> StackPackageFile
+ -> RIO GetPackageFileContext (Set (Path Abs File))
+resolveGlobFilesFromStackPackageFile
+ csvV
+ (StackPackageFile extraSrcFilesV dataDirV dataFilesV)
+ = resolveGlobFiles
+ csvV
+ (extraSrcFilesV ++ map (dataDirV FilePath.>) dataFilesV)
-- | Resolve globbing of files (e.g. data files) to absolute paths.
resolveGlobFiles ::
@@ -124,8 +81,7 @@ resolveGlobFiles ::
-> [String]
-> RIO GetPackageFileContext (Set (Path Abs File))
resolveGlobFiles cabalFileVersion =
- fmap (S.fromList . catMaybes . concat) .
- mapM resolve
+ fmap (S.fromList . catMaybes . concat) . mapM resolve
where
resolve name =
if '*' `elem` name
@@ -156,44 +112,59 @@ resolveGlobFiles cabalFileVersion =
-- well as use by "stack ghci"
getPackageFile ::
( HasEnvConfig s, MonadReader s m, MonadThrow m, MonadUnliftIO m )
- => PackageDescription
+ => Package
-> Path Abs File
- -> m ( Map NamedComponent (Map ModuleName (Path Abs File))
- , Map NamedComponent [DotCabalPath]
- , Set (Path Abs File)
- , [PackageWarning]
- )
+ -> m PackageComponentFile
getPackageFile pkg cabalfp =
debugBracket ("getPackageFiles" <+> pretty cabalfp) $ do
let pkgDir = parent cabalfp
distDir <- distDirFromDir pkgDir
bc <- view buildConfigL
cabalVer <- view cabalVersionL
- (componentModules, componentFiles, dataFiles', warnings) <-
+ packageComponentFile <-
runRIO
(GetPackageFileContext cabalfp distDir bc cabalVer)
(packageDescModulesAndFiles pkg)
setupFiles <-
- if buildType pkg == Custom
- then do
- let setupHsPath = pkgDir > relFileSetupHs
- setupLhsPath = pkgDir > relFileSetupLhs
- setupHsExists <- doesFileExist setupHsPath
- if setupHsExists
- then pure (S.singleton setupHsPath)
- else do
- setupLhsExists <- doesFileExist setupLhsPath
- if setupLhsExists
- then pure (S.singleton setupLhsPath)
- else pure S.empty
- else pure S.empty
- buildFiles <- fmap (S.insert cabalfp . S.union setupFiles) $ do
+ if packageBuildType pkg == Cabal.Custom
+ then do
+ let setupHsPath = pkgDir > relFileSetupHs
+ setupLhsPath = pkgDir > relFileSetupLhs
+ setupHsExists <- doesFileExist setupHsPath
+ if setupHsExists
+ then pure (S.singleton setupHsPath)
+ else do
+ setupLhsExists <- doesFileExist setupLhsPath
+ if setupLhsExists
+ then pure (S.singleton setupLhsPath)
+ else pure S.empty
+ else pure S.empty
+ moreBuildFiles <- fmap (S.insert cabalfp . S.union setupFiles) $ do
let hpackPath = pkgDir > relFileHpackPackageConfig
hpackExists <- doesFileExist hpackPath
pure $ if hpackExists then S.singleton hpackPath else S.empty
- pure
- ( componentModules
- , componentFiles
- , buildFiles <> dataFiles'
- , warnings
- )
+ pure packageComponentFile
+ { packageExtraFile =
+ moreBuildFiles <> packageExtraFile packageComponentFile
+ }
+
+stackPackageFileFromCabal :: Cabal.PackageDescription -> StackPackageFile
+stackPackageFileFromCabal cabalPkg =
+ StackPackageFile
+ (Cabal.extraSrcFiles cabalPkg)
+ (Cabal.dataDir cabalPkg)
+ (Cabal.dataFiles cabalPkg)
+
+insertComponentFile ::
+ PackageComponentFile
+ -> (NamedComponent, ComponentFile)
+ -> PackageComponentFile
+insertComponentFile packageCompFile (name, compFile) =
+ PackageComponentFile nCompFile nDotCollec packageExtraFile nWarnings
+ where
+ (ComponentFile moduleFileMap dotCabalFileList warningsCollec) = compFile
+ (PackageComponentFile modules files packageExtraFile warnings) =
+ packageCompFile
+ nCompFile = M.insert name moduleFileMap modules
+ nDotCollec = M.insert name dotCabalFileList files
+ nWarnings = warningsCollec ++ warnings
diff --git a/src/Stack/Types/CompCollection.hs b/src/Stack/Types/CompCollection.hs
new file mode 100644
index 0000000000..d55c63940b
--- /dev/null
+++ b/src/Stack/Types/CompCollection.hs
@@ -0,0 +1,159 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | A package has collections of the types exported by "Stack.Types.Component".
+-- In Cabal, the components are grouped as a list in a @PackageDescription@;
+-- this is Stack's counterpart. See for instance the
+-- [foreign libraries field](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-PackageDescription.html#t:GenericPackageDescription)
+-- in Cabal. Cabal removes all the unbuildable components very early (at the
+-- cost of slightly worse error messages) and it historically used a list for
+-- components. For , as well as set listed for backward compatbility at least,
+-- hence this structure.
+module Stack.Types.CompCollection
+ ( CompCollection
+ , getBuildableSet
+ , getBuildableSetText
+ , getBuildableListText
+ , getBuildableListAs
+ , foldAndMakeCollection
+ , hasBuildableComponent
+ , collectionLookup
+ , collectionKeyValueList
+ , collectionMember
+ ) where
+
+import qualified Data.HashMap.Strict as HM
+import qualified Data.Set as Set
+import Data.Foldable ( Foldable (..) )
+import Stack.Prelude
+import Stack.Types.Component
+ ( HasBuildInfo, HasName, StackBuildInfo (..)
+ , StackUnqualCompName (..), unqualCompToText
+ )
+
+-- | This is a collection discriminating buildable components and non-buildable
+-- ones.
+data CompCollection component = CompCollection
+ { buildableOnes :: {-# UNPACK #-} !(InnerCollection component)
+ , unbuildableOnes :: Set StackUnqualCompName
+ -- ^ Note that the field is lazy beacause it should only serve when users
+ -- explicitely require unbuildable components to be built (and this field
+ -- ensure intelligible error messages).
+ }
+ deriving (Show)
+
+instance Semigroup (CompCollection component) where
+ a <> b = CompCollection
+ { buildableOnes = buildableOnes a <> buildableOnes b
+ , unbuildableOnes = unbuildableOnes a <> unbuildableOnes b
+ }
+
+instance Monoid (CompCollection component) where
+ mempty = CompCollection
+ { buildableOnes = mempty
+ , unbuildableOnes = mempty
+ }
+
+-- | This is a naive collection which trades memory consumption for speed, the
+-- name set is strictly redundant but it takes O(n) to compute it from the map's
+-- keys, hence the redundancy. The assumption is 'asNameSet' is always equal to
+-- @keySet . asNameMap@, but faster because cached. But this assumption is not
+-- checked anywhere, it's simply built into the 'addComponent' function, which
+-- should always be used rather than record's fields for inserting new elements.
+data InnerCollection component = InnerCollection
+ { asNameMap :: !(HashMap StackUnqualCompName component)
+ , asNameSet :: !(Set StackUnqualCompName)
+ }
+ deriving (Show)
+
+instance Semigroup (InnerCollection component) where
+ a <> b = InnerCollection
+ { asNameMap = asNameMap a <> asNameMap b
+ , asNameSet = asNameSet a <> asNameSet b
+ }
+
+instance Monoid (InnerCollection component) where
+ mempty = InnerCollection
+ { asNameMap = mempty
+ , asNameSet = mempty
+ }
+
+instance Foldable CompCollection where
+ foldMap fn collection = foldMap fn (asNameMap $ buildableOnes collection)
+ foldr' fn c collection = HM.foldr' fn c (asNameMap $ buildableOnes collection)
+ null = HM.null . asNameMap . buildableOnes
+
+-- |
+--
+-- >>> :set -XOverloadedStrings
+-- >>> import Stack.Types.Component (StackUnqualCompName(StackUnqualCompName))
+-- >>> data TestComp = TestComp { name :: StackUnqualCompName } deriving (Show)
+-- >>> let ok = TestComp (StackUnqualCompName "some-comp-name")
+-- >>> addComponent ok mempty
+-- InnerCollection {asNameMap = fromList [(StackUnqualCompName "some-comp-name",TestComp {name = StackUnqualCompName "some-comp-name"})], asNameSet = fromList [StackUnqualCompName "some-comp-name"]}
+addComponent ::
+ HasName component
+ => component
+ -> InnerCollection component
+ -> InnerCollection component
+addComponent componentV collection =
+ let nameV = componentV.name
+ in collection
+ { asNameMap=HM.insert nameV componentV (asNameMap collection)
+ , asNameSet=Set.insert nameV (asNameSet collection)
+ }
+
+-- | Iterates on a collection of @compA@ and map each element to @compB@ while
+-- building the corresponding @CompCollection@ out of it.
+foldAndMakeCollection ::
+ (HasBuildInfo compB, HasName compB, Foldable sourceCollection)
+ => (compA -> compB)
+ -> sourceCollection compA
+ -> CompCollection compB
+foldAndMakeCollection mapFn = foldl' compIterator mempty
+ where
+ compIterator existingCollection component =
+ compCreator existingCollection (mapFn component)
+ compCreator existingCollection component
+ | component.buildInfo.sbiBuildable = existingCollection
+ { buildableOnes =
+ addComponent component (buildableOnes existingCollection)
+ }
+ | otherwise = existingCollection
+ { unbuildableOnes =
+ Set.insert component.name (unbuildableOnes existingCollection)
+ }
+
+-- | Get the name set of buildable components from this collection.
+getBuildableSet :: CompCollection component -> Set StackUnqualCompName
+getBuildableSet = asNameSet . buildableOnes
+
+getBuildableSetText :: CompCollection component -> Set Text
+getBuildableSetText = Set.mapMonotonic unqualCompToText . getBuildableSet
+
+getBuildableListText :: CompCollection component -> [Text]
+getBuildableListText = getBuildableListAs unqualCompToText
+
+getBuildableListAs ::
+ (StackUnqualCompName -> something)
+ -> CompCollection component
+ -> [something]
+getBuildableListAs fn = Set.foldr' (\v l -> fn v:l) [] . getBuildableSet
+
+hasBuildableComponent :: CompCollection component -> Bool
+hasBuildableComponent = not . null . getBuildableSet
+
+collectionLookup :: Text -> CompCollection component -> Maybe component
+collectionLookup needle haystack =
+ HM.lookup (StackUnqualCompName needle) (asNameMap $ buildableOnes haystack)
+
+collectionKeyValueList :: CompCollection component -> [(Text, component)]
+collectionKeyValueList haystack =
+ (\(StackUnqualCompName k, !v) -> (k, v))
+ <$> HM.toList (asNameMap $ buildableOnes haystack)
+
+collectionMember :: Text -> CompCollection component -> Bool
+collectionMember needle haystack = isJust $ collectionLookup needle haystack
diff --git a/src/Stack/Types/Component.hs b/src/Stack/Types/Component.hs
new file mode 100644
index 0000000000..dcb447ba7d
--- /dev/null
+++ b/src/Stack/Types/Component.hs
@@ -0,0 +1,169 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | All component-related types in Stack (library, internal library, foreign
+-- library, executable, tests and benchmarks). The chosen design replicates many
+-- of Cabal existing things but in simplified and sometimes more typed versions.
+-- It's a work in progress to bring Stack to a more componentized design, and
+-- closer to Cabal.
+module Stack.Types.Component
+ ( HasName
+ , HasBuildInfo
+ , StackBenchmark (..)
+ , StackBuildInfo (..)
+ , StackExecutable (..)
+ , StackForeignLibrary (..)
+ , StackLibrary (..)
+ , StackTest (..)
+ , StackUnqualCompName (..)
+ , unqualCompToText
+ ) where
+
+import Distribution.Compiler ( PerCompilerFlavor )
+import Distribution.ModuleName ( ModuleName )
+import Distribution.PackageDescription
+ ( BenchmarkInterface, Dependency, TestSuiteInterface )
+import Distribution.Simple ( Extension, Language )
+import Distribution.Utils.Path ( PackageDir, SourceDir, SymbolicPath )
+import GHC.Records ( HasField )
+import Stack.Prelude
+import Stack.Types.Dependency ( DepValue )
+
+type HasName component = HasField "name" component StackUnqualCompName
+
+type HasBuildInfo component = HasField "buildInfo" component StackBuildInfo
+
+-- | A main or sub library. We do not keep the
+-- [Cabal ADT name distinction](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-LibraryName.html#t:LibraryName)
+-- because in Cabal 3.0 it's
+-- [likely](https://github.com/haskell/cabal/issues/8567) that the main/sub
+-- distinction doesn't make sense anymore. Besides, the missing name from main
+-- lib can simply be encoded as an empty string for backward compatibility
+-- without loosing info. Through this simplification we get a clean name
+-- interface for all components (they all have a potentially mempty name of the
+-- same type).
+--
+-- The Cabal equivalent is
+-- [Library](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Library.html).
+data StackLibrary = StackLibrary
+ { name :: StackUnqualCompName
+ , buildInfo :: !StackBuildInfo
+ , exposedModules :: [ModuleName]
+ -- |^ This is only used for gathering the files related to this component.
+ }
+ deriving (Show, Typeable)
+
+-- Stack foreign libraries.
+--
+-- The Cabal equivalent is
+-- [ForeignLib](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Foreign-Libraries.html).
+data StackForeignLibrary = StackForeignLibrary
+ { name :: StackUnqualCompName
+ , buildInfo :: !StackBuildInfo
+ }
+ deriving (Show, Typeable)
+
+-- Stack executable.
+--
+-- The Cabal equivalent is
+-- [Executable](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Executable.html).
+data StackExecutable = StackExecutable
+ { name :: StackUnqualCompName
+ , buildInfo :: !StackBuildInfo
+ , modulePath :: FilePath
+ }
+ deriving (Show, Typeable)
+
+-- Stack test.
+--
+-- The Cabal equivalent is
+-- [TestSuite](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-TestSuite.html).
+data StackTest = StackTest
+ { name :: StackUnqualCompName
+ , buildInfo :: !StackBuildInfo
+ , interface :: !TestSuiteInterface
+ }
+ deriving (Show, Typeable)
+
+-- Stack benchmark.
+--
+-- The Cabal equivalent is
+-- [Benchmark](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Benchmark.html).
+data StackBenchmark = StackBenchmark
+ { name :: StackUnqualCompName
+ , buildInfo :: StackBuildInfo
+ , interface :: BenchmarkInterface
+ -- ^ This is only used for gathering the files related to this component.
+ }
+ deriving (Show, Typeable)
+
+-- | Name of an executable.
+newtype ExeName = ExeName Text
+ deriving (Data, Eq, Hashable, IsString, Generic, NFData, Ord, Show, Typeable)
+
+-- | The name of an unqualified component (that is, it can be an executable, a
+-- library, anything). The Cabal equivalent is
+-- [UnqualComponentName](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-UnqualComponentName.html#t:UnqualComponentName).
+-- Ideally, we'd want to use the Cabal type behind this newtype and not Text to
+-- avoid unnecessary work, but for now this is too much refactoring (also there
+-- is no Hashable instance for UnqualComponentName yet).
+newtype StackUnqualCompName = StackUnqualCompName Text
+ deriving (Data, Eq, Hashable, IsString, Generic, NFData, Ord, Show, Typeable)
+
+unqualCompToText :: StackUnqualCompName -> Text
+unqualCompToText (StackUnqualCompName v) = v
+
+-- | This is the Stack equivalent of Cabal's
+-- [BuildInfo](https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-BuildInfo.html).
+-- We don't use the Cabal version because Cabal provides a list of dependencies
+-- (and we need a Map), and we only need a tiny subset of all the Cabal-provided
+-- info. It's also the decomposition of @Package@ based information in prior
+-- versions of Stack, to enable component based builds and backpack. The file
+-- gathering related fields are lazy because not always needed.
+data StackBuildInfo = StackBuildInfo
+ { sbiBuildable :: !Bool
+ -- ^ From BuildInfo in Cabal.
+ , sbiDependency :: !(Map PackageName DepValue)
+ -- ^ From targetBuildDepends in BuildInfo in Cabal, and known legacy
+ -- specified build tools (buildTool).
+ , sbiUnknownTools :: Set Text
+ -- ^ From buildTool in Cabal, we only keep the legacy build tool depends
+ -- that we know (from a hardcoded list). We only use the deduplication
+ -- aspect of the Set here, as this field is only used for error reporting in
+ -- the end. This is kept lazy because it's an error reporting field only.
+ , sbiOtherModules :: [ModuleName]
+ -- ^ Only used in file gathering. See usage in "Stack.ComponentFile" module.
+ , jsSources :: [FilePath]
+ -- ^ Only used in file gathering. See usage in "Stack.ComponentFile" module.
+ , hsSourceDirs :: [SymbolicPath PackageDir SourceDir]
+ -- ^ Only used in file & opts gathering. See usage in "Stack.ComponentFile"
+ -- module for fle gathering.
+ , cSources :: [FilePath]
+ -- ^ Only used in file gathering. See usage in "Stack.ComponentFile" module.
+ , cppOptions :: [String]
+ -- ^ Only used in opts gathering. See usage in "Stack.Package" module.
+ , targetBuildDepends :: [Dependency]
+ -- ^ Only used in opts gathering.
+ , options :: PerCompilerFlavor [String]
+ -- ^ Only used in opts gathering.
+ , allLanguages :: [Language]
+ -- ^ Only used in opts gathering.
+ , usedExtensions :: [Extension]
+ -- ^ Only used in opts gathering.
+ , includeDirs :: [FilePath]
+ -- ^ Only used in opts gathering.
+ , extraLibs :: [String]
+ -- ^ Only used in opts gathering.
+ , extraLibDirs :: [String]
+ -- ^ Only used in opts gathering.
+ , frameworks :: [String]
+ -- ^ Only used in opts gathering.
+ }
+ deriving (Show)
diff --git a/src/Stack/Types/Dependency.hs b/src/Stack/Types/Dependency.hs
index 892ab2911f..3985b8871e 100644
--- a/src/Stack/Types/Dependency.hs
+++ b/src/Stack/Types/Dependency.hs
@@ -3,13 +3,15 @@
module Stack.Types.Dependency
( DepValue (..)
, DepType (..)
+ , cabalToStackDep
+ , cabalExeToStackDep
) where
+import qualified Distribution.PackageDescription as Cabal
import Distribution.Types.VersionRange ( VersionRange )
import Stack.Prelude
import Stack.Types.Version ( intersectVersionRanges )
-
-- | The value for a map from dependency name. This contains both the version
-- range and the type of dependency, and provides a semigroup instance.
data DepValue = DepValue
@@ -32,3 +34,10 @@ data DepType
instance Semigroup DepType where
AsLibrary <> _ = AsLibrary
AsBuildTool <> x = x
+
+cabalToStackDep :: Cabal.Dependency -> DepValue
+cabalToStackDep (Cabal.Dependency _ verRange _libNameSet) =
+ DepValue{dvVersionRange = verRange, dvType = AsLibrary}
+cabalExeToStackDep :: Cabal.ExeDependency -> DepValue
+cabalExeToStackDep (Cabal.ExeDependency _ _name verRange) =
+ DepValue{dvVersionRange = verRange, dvType = AsBuildTool}
diff --git a/src/Stack/Types/Package.hs b/src/Stack/Types/Package.hs
index c2aa3a19d0..058ab582a2 100644
--- a/src/Stack/Types/Package.hs
+++ b/src/Stack/Types/Package.hs
@@ -1,12 +1,12 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
module Stack.Types.Package
- ( BuildInfoOpts (..)
+ ( BioInput (..)
+ , BuildInfoOpts (..)
, ExeName (..)
, FileCacheInfo (..)
- , GetPackageOpts (..)
, InstallLocation (..)
, InstallMap
, Installed (..)
@@ -19,7 +19,6 @@ module Stack.Types.Package
, PackageDatabase (..)
, PackageDbVariety (..)
, PackageException (..)
- , PackageLibraries (..)
, PackageSource (..)
, dotCabalCFilePath
, dotCabalGetPath
@@ -49,19 +48,23 @@ import Distribution.Parsec ( PError (..), PWarning (..), showPos )
import qualified Distribution.SPDX.License as SPDX
import Distribution.License ( License )
import Distribution.ModuleName ( ModuleName )
-import Distribution.PackageDescription
- ( TestSuiteInterface, BuildType )
+import Distribution.PackageDescription ( BuildType )
import Distribution.System ( Platform (..) )
import qualified RIO.Text as T
import Stack.Prelude
+import Stack.Types.CompCollection ( CompCollection )
import Stack.Types.Compiler ( ActualCompiler )
+import Stack.Types.Component
+ ( StackBenchmark, StackBuildInfo, StackExecutable
+ , StackForeignLibrary, StackLibrary, StackTest
+ )
import Stack.Types.Dependency ( DepValue )
import Stack.Types.EnvConfig ( EnvConfig, HasEnvConfig (..) )
import Stack.Types.GhcPkgId ( GhcPkgId )
import Stack.Types.NamedComponent ( NamedComponent )
import Stack.Types.PackageFile
- ( GetPackageFiles (..), DotCabalDescriptor (..)
- , DotCabalPath (..)
+ ( DotCabalDescriptor (..), DotCabalPath (..)
+ , StackPackageFile
)
import Stack.Types.SourceMap ( CommonPackage, FromSnapshot )
import Stack.Types.Version ( VersionRange )
@@ -77,7 +80,7 @@ data PackageException
| MismatchedCabalIdentifier !PackageIdentifierRevision !PackageIdentifier
| CabalFileNameParseFail FilePath
| CabalFileNameInvalidPackageName FilePath
- | ComponentNotParsedBug
+ | ComponentNotParsedBug String
deriving (Show, Typeable)
instance Exception PackageException where
@@ -132,16 +135,12 @@ instance Exception PackageException where
\extension, the following is invalid: "
, fp
]
- displayException ComponentNotParsedBug = bugReport "[S-4623]"
- "Component names should always parse as directory names."
-
--- | Libraries in a package. Since Cabal 2.0, sub-libraries are a thing.
-data PackageLibraries
- = NoLibraries
- | HasLibraries !(Set Text)
- -- ^ the foreign library names, sub-libraries get built automatically
- -- without explicit component name passing
- deriving (Show, Typeable)
+ displayException (ComponentNotParsedBug name) = bugReport "[S-4623]"
+ ( "Component names should always parse as directory names. The component \
+ \name without a directory is '"
+ <> name
+ <> "'."
+ )
-- | Name of an executable.
newtype ExeName
@@ -156,13 +155,9 @@ data Package = Package
-- ^ Version of the package
, packageLicense :: !(Either SPDX.License License)
-- ^ The license the package was released under.
- , packageFiles :: !GetPackageFiles
- -- ^ Get all files of the package.
, packageDeps :: !(Map PackageName DepValue)
- -- ^ Packages that the package depends on, both as libraries and build tools.
- , packageUnknownTools :: !(Set ExeName)
- -- ^ Build tools specified in the legacy manner (build-tools:) that failed
- -- the hard-coded lookup.
+ -- ^ Packages that the package depends on, both as libraries and build
+ -- tools.
, packageAllDeps :: !(Set PackageName)
-- ^ Original dependencies (not sieved).
, packageSubLibDeps :: !(Map MungedPackageName DepValue)
@@ -175,26 +170,27 @@ data Package = Package
-- ^ Flags used on package.
, packageDefaultFlags :: !(Map FlagName Bool)
-- ^ Defaults for unspecified flags.
- , packageLibraries :: !PackageLibraries
- -- ^ does the package have a buildable library stanza?
- , packageSubLibraries :: !(Set Text)
- -- ^ Names of sub-libraries
- , packageTests :: !(Map Text TestSuiteInterface)
- -- ^ names and interfaces of test suites
- , packageBenchmarks :: !(Set Text)
- -- ^ names of benchmarks
- , packageExes :: !(Set Text)
- -- ^ names of executables
- , packageOpts :: !GetPackageOpts
- -- ^ Args to pass to GHC.
- , packageHasExposedModules :: !Bool
- -- ^ Does the package have exposed modules?
+ , packageLibrary :: !(Maybe StackLibrary)
+ -- ^ Does the package have a buildable main library stanza?
+ , packageSubLibraries :: !(CompCollection StackLibrary)
+ -- ^ The sub-libraries of the package.
+ , packageForeignLibraries :: !(CompCollection StackForeignLibrary)
+ -- ^ The foreign libraries of the package.
+ , packageTestSuites :: !(CompCollection StackTest)
+ -- ^ The test suites of the package.
+ , packageBenchmarks :: !(CompCollection StackBenchmark)
+ -- ^ The benchmarks of the package.
+ , packageExecutables :: !(CompCollection StackExecutable)
+ -- ^ The executables of the package.
, packageBuildType :: !BuildType
-- ^ Package build-type.
, packageSetupDeps :: !(Maybe (Map PackageName VersionRange))
-- ^ If present: custom-setup dependencies
, packageCabalSpec :: !CabalSpecVersion
-- ^ Cabal spec range
+ , packageFile :: StackPackageFile
+ -- ^ The cabal sourced files related to the package at the package level
+ -- The components may have file information in their own types
}
deriving (Show, Typeable)
@@ -209,25 +205,6 @@ packageDefinedFlags = M.keysSet . packageDefaultFlags
-- or mutable) and package versions.
type InstallMap = Map PackageName (InstallLocation, Version)
--- | Files that the package depends on, relative to package directory.
--- Argument is the location of the Cabal file
-newtype GetPackageOpts = GetPackageOpts
- { getPackageOpts :: forall env. HasEnvConfig env
- => InstallMap
- -> InstalledMap
- -> [PackageName]
- -> [PackageName]
- -> Path Abs File
- -> RIO env
- ( Map NamedComponent (Map ModuleName (Path Abs File))
- , Map NamedComponent [DotCabalPath]
- , Map NamedComponent BuildInfoOpts
- )
- }
-
-instance Show GetPackageOpts where
- show _ = ""
-
-- | GHC options based on cabal information and ghc-options.
data BuildInfoOpts = BuildInfoOpts
{ bioOpts :: [String]
@@ -498,3 +475,19 @@ installedVersion :: Installed -> Version
installedVersion i =
let PackageIdentifier _ version = installedPackageIdentifier i
in version
+
+-- | Type representing inputs to 'Stack.Package.generateBuildInfoOpts'.
+data BioInput = BioInput
+ { biInstallMap :: !InstallMap
+ , biInstalledMap :: !InstalledMap
+ , biCabalDir :: !(Path Abs Dir)
+ , biDistDir :: !(Path Abs Dir)
+ , biOmitPackages :: ![PackageName]
+ , biAddPackages :: ![PackageName]
+ , biBuildInfo :: !StackBuildInfo
+ , biDotCabalPaths :: ![DotCabalPath]
+ , biConfigLibDirs :: ![FilePath]
+ , biConfigIncludeDirs :: ![FilePath]
+ , biComponentName :: !NamedComponent
+ , biCabalVersion :: !Version
+ }
diff --git a/src/Stack/Types/PackageFile.hs b/src/Stack/Types/PackageFile.hs
index e42a6fce8e..64b6a1c3ea 100644
--- a/src/Stack/Types/PackageFile.hs
+++ b/src/Stack/Types/PackageFile.hs
@@ -9,8 +9,9 @@ module Stack.Types.PackageFile
( GetPackageFileContext (..)
, DotCabalPath (..)
, DotCabalDescriptor (..)
- , GetPackageFiles (..)
, PackageWarning (..)
+ , StackPackageFile (..)
+ , PackageComponentFile (..)
) where
import Distribution.ModuleName ( ModuleName )
@@ -19,7 +20,6 @@ import Stack.Prelude
import Stack.Types.BuildConfig
( BuildConfig (..), HasBuildConfig (..) )
import Stack.Types.Config ( HasConfig (..) )
-import Stack.Types.EnvConfig ( HasEnvConfig )
import Stack.Types.GHCVariant ( HasGHCVariant (..) )
import Stack.Types.NamedComponent ( NamedComponent )
import Stack.Types.Platform ( HasPlatform (..) )
@@ -91,21 +91,6 @@ data DotCabalDescriptor
| DotCabalCFile !FilePath
deriving (Eq, Ord, Show)
--- | Files that the package depends on, relative to package directory.
--- Argument is the location of the Cabal file
-newtype GetPackageFiles = GetPackageFiles
- { getPackageFiles :: forall env. HasEnvConfig env
- => Path Abs File
- -> RIO env
- ( Map NamedComponent (Map ModuleName (Path Abs File))
- , Map NamedComponent [DotCabalPath]
- , Set (Path Abs File)
- , [PackageWarning]
- )
- }
-instance Show GetPackageFiles where
- show _ = ""
-
-- | Warning generated when reading a package
data PackageWarning
= UnlistedModulesWarning NamedComponent [ModuleName]
@@ -116,3 +101,27 @@ data PackageWarning
| MissingModulesWarning (Path Abs File) (Maybe String) [ModuleName]
-- ^ Modules not found in file system, which are listed in Cabal file
-}
+
+-- | This is the information from Cabal we need at the package level to track
+-- files.
+data StackPackageFile = StackPackageFile
+ { extraSrcFiles :: [FilePath]
+ , dataDir :: FilePath
+ , dataFiles :: [FilePath]
+ }
+ deriving (Show, Typeable)
+
+-- | Files that the package depends on, relative to package directory.
+data PackageComponentFile = PackageComponentFile
+ { modulePathMap :: Map NamedComponent (Map ModuleName (Path Abs File))
+ , cabalFileMap :: !(Map NamedComponent [DotCabalPath])
+ , packageExtraFile :: Set (Path Abs File)
+ , warnings :: [PackageWarning]
+ }
+
+instance Semigroup PackageComponentFile where
+ PackageComponentFile x1 x2 x3 x4 <> PackageComponentFile y1 y2 y3 y4 =
+ PackageComponentFile (x1 <> y1) (x2 <> y2) (x3 <> y3) (x4 <> y4)
+
+instance Monoid PackageComponentFile where
+ mempty = PackageComponentFile mempty mempty mempty mempty
diff --git a/stack.cabal b/stack.cabal
index fde4aadaff..9fae0428e8 100644
--- a/stack.cabal
+++ b/stack.cabal
@@ -326,7 +326,10 @@ library
Paths_stack
other-modules:
GHC.Utils.GhcPkg.Main.Compat
+ Stack.Component
Stack.Config.ConfigureScript
+ Stack.Types.CompCollection
+ Stack.Types.Component
Stack.Types.FileDigestCache
autogen-modules:
Build_stack