Skip to content

Commit b12f4c8

Browse files
alt-romesandreabedini
authored andcommitted
HPC artifacts are written and read from pkg-db
This commit re-designs the mechanism by which we make the .mix files of libraries available to produce the Haskell Program Coverage report after running testsuites. The idea, for the Cabal library, is: * Cabal builds libraries with -fhpc, and store the hpc artifacts in build </> `extraCompilationArtifacts` * At Cabal install time, `extraCompilationArtifacts` is copied into the package database * At Cabal configure time, we both - receive as --coverage-for flags unit-ids of library components from the same package (ultimately, when #9493 is resolved, we will receive unit ids of libraries in other packages in the same project too), - and, when configuring a whole package instead of just a testsuite component, we determine the unit-ids of libraries in the package these unit-ids are written into `configCoverageFor` in `ConfigFlags` * At Cabal test time, for each library to cover (stored in `configCoverageFor`), we look in the package database for the hpc dirs, which we eventually pass along to the `hpc markup` call as `--hpcdir` flags As for cabal-install: * After a plan has been elaborated, we select the packages which can be covered and pass them to Cabal's ./Setup configure as --coverage-for=<unit-id> flags. - Notably, valid libraries are non-indefinite and non-instantiations, since HPC does not support backpack. - Furthermore, we only include libraries in the same package as the component being configured, despite possibly there being more library components in other packages of the same project. When #9493 is resolved, we could lift this restriction and pass all libraries local to the package as --coverage-for. See `determineCoverageFor` and `shouldCoverPkg` in Distribution.Client.ProjectPlanning. Detail: We no longer pass the path to the testsuite's mix dirs to `hpc markup` because we only ever include modules in libraries, which means they were previously unused. Fixes #6440 (internal libs coverage), #6397 (backpack breaks coverage), doesn't yet fix #8609 (multi-package coverage report) which is tracked in #9493, and fixes in a new way the previously fixed #4798, #5213.
1 parent 073ccc8 commit b12f4c8

File tree

41 files changed

+424
-155
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+424
-155
lines changed

Cabal-tests/tests/UnitTests/Distribution/Utils/Structured.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@ md5CheckLocalBuildInfo proxy = md5Check proxy
4343
#if MIN_VERSION_base(4,19,0)
4444
0x23942cff98237dc167ef90d64d7ef893
4545
#else
46-
0xa4e9f8a7e1583906880d6ec2d1bbb14b
46+
0xc6c0cc122cc60ce7943764cbaaacdc2d
4747
#endif

Cabal/src/Distribution/Simple/Configure.hs

+40-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{-# LANGUAGE DeriveDataTypeable #-}
22
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
{-# LANGUAGE NamedFieldPuns #-}
35
{-# LANGUAGE OverloadedStrings #-}
46
{-# LANGUAGE RankNTypes #-}
57
{-# LANGUAGE RecordWildCards #-}
@@ -44,6 +46,7 @@ module Distribution.Simple.Configure
4446
, localBuildInfoFile
4547
, getInstalledPackages
4648
, getInstalledPackagesMonitorFiles
49+
, getInstalledPackagesById
4750
, getPackageDBContents
4851
, configCompilerEx
4952
, configCompilerAuxEx
@@ -56,6 +59,7 @@ module Distribution.Simple.Configure
5659
, platformDefines
5760
) where
5861

62+
import Control.Monad
5963
import Distribution.Compat.Prelude
6064
import Prelude ()
6165

@@ -78,7 +82,7 @@ import Distribution.Simple.BuildTarget
7882
import Distribution.Simple.BuildToolDepends
7983
import Distribution.Simple.Compiler
8084
import Distribution.Simple.LocalBuildInfo
81-
import Distribution.Simple.PackageIndex (InstalledPackageIndex)
85+
import Distribution.Simple.PackageIndex (InstalledPackageIndex, lookupUnitId)
8286
import qualified Distribution.Simple.PackageIndex as PackageIndex
8387
import Distribution.Simple.PreProcess
8488
import Distribution.Simple.Program
@@ -162,6 +166,7 @@ import qualified Data.Maybe as M
162166
import qualified Data.Set as Set
163167
import qualified Distribution.Compat.NonEmptySet as NES
164168
import Distribution.Simple.Errors
169+
import Distribution.Simple.Flag (mergeListFlag)
165170
import Distribution.Types.AnnotatedId
166171

167172
type UseExternalInternalDeps = Bool
@@ -877,10 +882,21 @@ configure (pkg_descr0, pbi) cfg = do
877882
Map.empty
878883
buildComponents
879884

885+
-- For whole-package configure, we have to determine the additional
886+
-- configCoverageFor of the main lib and sub libs here.
887+
let extraCoverageFor :: [UnitId] = case enabled of
888+
-- Whole package configure, add package libs
889+
ComponentRequestedSpec{} -> mapMaybe (\case LibComponentLocalBuildInfo{componentUnitId} -> Just componentUnitId; _ -> Nothing) buildComponents
890+
-- Component configure, no need to do anything
891+
OneComponentRequestedSpec{} -> []
892+
893+
-- TODO: Should we also enforce something here on that --coverage-for cannot
894+
-- include indefinite components or instantiations?
895+
880896
let lbi =
881897
(setCoverageLBI . setProfLBI)
882898
LocalBuildInfo
883-
{ configFlags = cfg
899+
{ configFlags = cfg{configCoverageFor = mergeListFlag (configCoverageFor cfg) (toFlag extraCoverageFor)}
884900
, flagAssignment = flags
885901
, componentEnabledSpec = enabled
886902
, extraConfigArgs = [] -- Currently configure does not
@@ -1747,6 +1763,28 @@ getInstalledPackagesMonitorFiles verbosity comp packageDBs progdb platform =
17471763
++ prettyShow other
17481764
return []
17491765

1766+
-- | Looks up the 'InstalledPackageInfo' of the given 'UnitId's from the
1767+
-- 'PackageDBStack' in the 'LocalBuildInfo'.
1768+
getInstalledPackagesById
1769+
:: (Exception (VerboseException exception), Show exception, Typeable exception)
1770+
=> Verbosity
1771+
-> LocalBuildInfo
1772+
-> (UnitId -> exception)
1773+
-- ^ Construct an exception that is thrown if a
1774+
-- unit-id is not found in the installed packages,
1775+
-- from the unit-id that is missing.
1776+
-> [UnitId]
1777+
-- ^ The unit ids to lookup in the installed packages
1778+
-> IO [InstalledPackageInfo]
1779+
getInstalledPackagesById verbosity LocalBuildInfo{compiler, withPackageDB, withPrograms} mkException unitids = do
1780+
ipindex <- getInstalledPackages verbosity compiler withPackageDB withPrograms
1781+
mapM
1782+
( \uid -> case lookupUnitId ipindex uid of
1783+
Nothing -> dieWithException verbosity (mkException uid)
1784+
Just ipkg -> return ipkg
1785+
)
1786+
unitids
1787+
17501788
-- | The user interface specifies the package dbs to use with a combination of
17511789
-- @--global@, @--user@ and @--package-db=global|user|clear|$file@.
17521790
-- This function combines the global/user flag and interprets the package-db

Cabal/src/Distribution/Simple/Errors.hs

+6
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ data CabalException
170170
| NoProgramFound String VersionRange
171171
| BadVersionDb String Version VersionRange FilePath
172172
| UnknownVersionDb String VersionRange FilePath
173+
| MissingCoveredInstalledLibrary UnitId
173174
deriving (Show, Typeable)
174175

175176
exceptionCode :: CabalException -> Int
@@ -301,6 +302,7 @@ exceptionCode e = case e of
301302
NoProgramFound{} -> 7620
302303
BadVersionDb{} -> 8038
303304
UnknownVersionDb{} -> 1008
305+
MissingCoveredInstalledLibrary{} -> 9341
304306

305307
versionRequirement :: VersionRange -> String
306308
versionRequirement range
@@ -791,3 +793,7 @@ exceptionMessage e = case e of
791793
++ " is required but the version of "
792794
++ locationPath
793795
++ " could not be determined."
796+
MissingCoveredInstalledLibrary unitId ->
797+
"Failed to find the installed unit '"
798+
++ prettyShow unitId
799+
++ "' in package database stack."

Cabal/src/Distribution/Simple/GHC/BuildGeneric.hs

+2-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import Distribution.PackageDescription.Utils (cabalBug)
1919
import Distribution.Pretty
2020
import Distribution.Simple.BuildPaths
2121
import Distribution.Simple.Compiler
22-
import Distribution.Simple.Flag (Flag (..), fromFlag, toFlag)
2322
import Distribution.Simple.GHC.Build
2423
( checkNeedsRecompilation
2524
, componentGhcOptions
@@ -39,7 +38,7 @@ import Distribution.Simple.LocalBuildInfo
3938
import qualified Distribution.Simple.PackageIndex as PackageIndex
4039
import Distribution.Simple.Program
4140
import Distribution.Simple.Program.GHC
42-
import Distribution.Simple.Setup.Config
41+
import Distribution.Simple.Setup.Common
4342
import Distribution.Simple.Setup.Repl
4443
import Distribution.Simple.Utils
4544
import Distribution.System
@@ -399,10 +398,9 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do
399398
-- Determine if program coverage should be enabled and if so, what
400399
-- '-hpcdir' should be.
401400
let isCoverageEnabled = exeCoverage lbi
402-
distPref = fromFlag $ configDistPref $ configFlags lbi
403401
hpcdir way
404402
| gbuildIsRepl bm = mempty -- HPC is not supported in ghci
405-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
403+
| isCoverageEnabled = toFlag $ Hpc.mixDir (tmpDir </> extraCompilationArtifacts) way
406404
| otherwise = mempty
407405

408406
rpaths <- getRPaths lbi clbi

Cabal/src/Distribution/Simple/GHC/BuildOrRepl.hs

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import Distribution.Package
99
import Distribution.PackageDescription as PD
1010
import Distribution.Simple.BuildPaths
1111
import Distribution.Simple.Compiler
12-
import Distribution.Simple.Flag (Flag (..), fromFlag, toFlag)
1312
import Distribution.Simple.GHC.Build
1413
( checkNeedsRecompilation
1514
, componentGhcOptions
@@ -27,7 +26,7 @@ import Distribution.Simple.Program
2726
import qualified Distribution.Simple.Program.Ar as Ar
2827
import Distribution.Simple.Program.GHC
2928
import qualified Distribution.Simple.Program.Ld as Ld
30-
import Distribution.Simple.Setup.Config
29+
import Distribution.Simple.Setup.Common
3130
import Distribution.Simple.Setup.Repl
3231
import Distribution.Simple.Utils
3332
import Distribution.System
@@ -96,10 +95,9 @@ buildOrReplLib mReplFlags verbosity numJobs pkg_descr lbi lib clbi = do
9695
-- Determine if program coverage should be enabled and if so, what
9796
-- '-hpcdir' should be.
9897
let isCoverageEnabled = libCoverage lbi
99-
distPref = fromFlag $ configDistPref $ configFlags lbi
10098
hpcdir way
10199
| forRepl = mempty -- HPC is not supported in ghci
102-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
100+
| isCoverageEnabled = toFlag $ Hpc.mixDir (libTargetDir </> extraCompilationArtifacts) way
103101
| otherwise = mempty
104102

105103
createDirectoryIfMissingVerbose verbosity True libTargetDir

Cabal/src/Distribution/Simple/GHCJS.hs

+3-5
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import Distribution.Simple.Program
7272
import Distribution.Simple.Program.GHC
7373
import qualified Distribution.Simple.Program.HcPkg as HcPkg
7474
import qualified Distribution.Simple.Program.Strip as Strip
75-
import Distribution.Simple.Setup.Config
75+
import Distribution.Simple.Setup.Common
7676
import Distribution.Simple.Utils
7777
import Distribution.System
7878
import Distribution.Types.ComponentLocalBuildInfo
@@ -515,10 +515,9 @@ buildOrReplLib mReplFlags verbosity numJobs _pkg_descr lbi lib clbi = do
515515
-- Determine if program coverage should be enabled and if so, what
516516
-- '-hpcdir' should be.
517517
let isCoverageEnabled = libCoverage lbi
518-
distPref = fromFlag $ configDistPref $ configFlags lbi
519518
hpcdir way
520519
| forRepl = mempty -- HPC is not supported in ghci
521-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
520+
| isCoverageEnabled = toFlag $ Hpc.mixDir (libTargetDir </> extraCompilationArtifacts) way
522521
| otherwise = mempty
523522

524523
createDirectoryIfMissingVerbose verbosity True libTargetDir
@@ -1235,10 +1234,9 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do
12351234
-- Determine if program coverage should be enabled and if so, what
12361235
-- '-hpcdir' should be.
12371236
let isCoverageEnabled = exeCoverage lbi
1238-
distPref = fromFlag $ configDistPref $ configFlags lbi
12391237
hpcdir way
12401238
| gbuildIsRepl bm = mempty -- HPC is not supported in ghci
1241-
| isCoverageEnabled = toFlag $ Hpc.mixDir distPref way
1239+
| isCoverageEnabled = toFlag $ Hpc.mixDir (tmpDir </> extraCompilationArtifacts) way
12421240
| otherwise = mempty
12431241

12441242
rpaths <- getRPaths lbi clbi

Cabal/src/Distribution/Simple/Hpc.hs

+16-8
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,26 @@ module Distribution.Simple.Hpc
2222
, mixDir
2323
, tixDir
2424
, tixFilePath
25+
, HPCMarkupInfo (..)
2526
, markupPackage
2627
) where
2728

2829
import Distribution.Compat.Prelude
2930
import Prelude ()
3031

31-
import Distribution.ModuleName (main)
32+
import Distribution.ModuleName (ModuleName, main)
3233
import Distribution.PackageDescription
3334
( TestSuite (..)
3435
, testModules
3536
)
3637
import qualified Distribution.PackageDescription as PD
3738
import Distribution.Pretty
38-
import Distribution.Simple.Flag (fromFlagOrDefault)
3939
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo (..))
4040
import Distribution.Simple.Program
4141
( hpcProgram
4242
, requireProgramVersion
4343
)
4444
import Distribution.Simple.Program.Hpc (markup, union)
45-
import Distribution.Simple.Setup (TestFlags (..))
4645
import Distribution.Simple.Utils (notice)
4746
import Distribution.Types.UnqualComponentName
4847
import Distribution.Verbosity (Verbosity ())
@@ -112,17 +111,27 @@ guessWay lbi
112111
| withDynExe lbi = Dyn
113112
| otherwise = Vanilla
114113

114+
-- | Haskell Program Coverage information required to produce a valid HPC
115+
-- report through the `hpc markup` call for the package libraries.
116+
data HPCMarkupInfo = HPCMarkupInfo
117+
{ pathsToLibsArtifacts :: [FilePath]
118+
-- ^ The paths to the library components whose modules are included in the
119+
-- coverage report
120+
, libsModulesToInclude :: [ModuleName]
121+
-- ^ The modules to include in the coverage report
122+
}
123+
115124
-- | Generate the HTML markup for a package's test suites.
116125
markupPackage
117126
:: Verbosity
118-
-> TestFlags
127+
-> HPCMarkupInfo
119128
-> LocalBuildInfo
120129
-> FilePath
121130
-- ^ Testsuite \"dist/\" prefix
122131
-> PD.PackageDescription
123132
-> [TestSuite]
124133
-> IO ()
125-
markupPackage verbosity TestFlags{testCoverageDistPrefs, testCoverageLibsModules} lbi testDistPref pkg_descr suites = do
134+
markupPackage verbosity HPCMarkupInfo{pathsToLibsArtifacts, libsModulesToInclude} lbi testDistPref pkg_descr suites = do
126135
let tixFiles = map (tixFilePath testDistPref way) testNames
127136
tixFilesExist <- traverse doesFileExist tixFiles
128137
when (and tixFilesExist) $ do
@@ -160,13 +169,12 @@ markupPackage verbosity TestFlags{testCoverageDistPrefs, testCoverageLibsModules
160169
union hpc verbosity tixFiles summedTixFile excluded
161170
return summedTixFile
162171

163-
markup hpc hpcVer verbosity tixFile mixDirs htmlDir' included
172+
markup hpc hpcVer verbosity tixFile mixDirs htmlDir' libsModulesToInclude
164173
notice verbosity $
165174
"Package coverage report written to "
166175
++ htmlDir'
167176
</> "hpc_index.html"
168177
where
169178
way = guessWay lbi
170179
testNames = fmap (unUnqualComponentName . testName) suites
171-
mixDirs = map (`mixDir` way) (fromFlagOrDefault [] testCoverageDistPrefs)
172-
included = fromFlagOrDefault [] testCoverageLibsModules
180+
mixDirs = map (`mixDir` way) pathsToLibsArtifacts

Cabal/src/Distribution/Simple/Setup/Config.hs

+23
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import Distribution.Types.DumpBuildInfo
5454
import Distribution.Types.GivenComponent
5555
import Distribution.Types.Module
5656
import Distribution.Types.PackageVersionConstraint
57+
import Distribution.Types.UnitId
5758
import Distribution.Utils.NubList
5859
import Distribution.Verbosity
5960
import qualified Text.PrettyPrint as Disp
@@ -220,6 +221,11 @@ data ConfigFlags = ConfigFlags
220221
-- ^ Allow depending on private sublibraries. This is used by external
221222
-- tools (like cabal-install) so they can add multiple-public-libraries
222223
-- compatibility to older ghcs by checking visibility externally.
224+
, configCoverageFor :: Flag [UnitId]
225+
-- ^ The list of libraries to be included in the hpc coverage report for
226+
-- testsuites run with @--enable-coverage@. Notably, this list must exclude
227+
-- indefinite libraries and instantiations because HPC does not support
228+
-- backpack (Nov. 2023).
223229
}
224230
deriving (Generic, Read, Show, Typeable)
225231

@@ -288,6 +294,7 @@ instance Eq ConfigFlags where
288294
&& equal configDebugInfo
289295
&& equal configDumpBuildInfo
290296
&& equal configUseResponseFiles
297+
&& equal configCoverageFor
291298
where
292299
equal f = on (==) f a b
293300

@@ -828,6 +835,22 @@ configureOptions showOrParseArgs =
828835
configAllowDependingOnPrivateLibs
829836
(\v flags -> flags{configAllowDependingOnPrivateLibs = v})
830837
trueArg
838+
, option
839+
""
840+
["coverage-for"]
841+
"A list of unit-ids of libraries to include in the Haskell Program Coverage report."
842+
configCoverageFor
843+
( \v flags ->
844+
flags
845+
{ configCoverageFor =
846+
mergeListFlag (configCoverageFor flags) v
847+
}
848+
)
849+
( reqArg'
850+
"UNITID"
851+
(Flag . (: []) . fromString)
852+
(fmap prettyShow . fromFlagOrDefault [])
853+
)
831854
]
832855
where
833856
liftInstallDirs =

0 commit comments

Comments
 (0)