Skip to content

Commit 331f3cd

Browse files
committed
Add separate cache for getPkgConfigDb
Querying pkg-config for the version of every module can be a very expensive operation on some systems. This change adds a separate, per-project, cache for PkgConfigDB; reducing the cost from "every plan change" to "every pkg-config-db change per project". The cache key is composed by the pkg-config configured program and the list of directories reported by pkg-config's pc_path variable.
1 parent e1f73a4 commit 331f3cd

File tree

11 files changed

+128
-38
lines changed

11 files changed

+128
-38
lines changed

cabal-install/src/Distribution/Client/ProjectConfig.hs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ module Distribution.Client.ProjectConfig
5151
, resolveSolverSettings
5252
, BuildTimeSettings (..)
5353
, resolveBuildTimeSettings
54+
, resolveProgramDb
5455

5556
-- * Checking configuration
5657
, checkBadPerPackageCompilerPaths
@@ -158,6 +159,12 @@ import Distribution.Simple.InstallDirs
158159
import Distribution.Simple.Program
159160
( ConfiguredProgram (..)
160161
)
162+
import Distribution.Simple.Program.Db
163+
( ProgramDb
164+
, defaultProgramDb
165+
, prependProgramSearchPath
166+
, userSpecifyPaths
167+
)
161168
import Distribution.Simple.Setup
162169
( Flag (Flag)
163170
, flagToList
@@ -507,6 +514,14 @@ resolveBuildTimeSettings
507514
| isParallelBuild buildSettingNumJobs = False
508515
| otherwise = False
509516

517+
-- | ProgramDb with user specified paths
518+
resolveProgramDb :: Verbosity -> PackageConfig -> IO ProgramDb
519+
resolveProgramDb verbosity packageConfig = do
520+
let extraPath = fromNubList (packageConfigProgramPathExtra packageConfig)
521+
programDb <- prependProgramSearchPath verbosity extraPath [] defaultProgramDb
522+
let paths = Map.toList $ getMapLast (packageConfigProgramPaths packageConfig)
523+
return $ userSpecifyPaths paths programDb
524+
510525
---------------------------------------------
511526
-- Reading and writing project config files
512527
--

cabal-install/src/Distribution/Client/ProjectPlanning.hs

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,7 @@ configureCompiler
459459
, projectConfigHcPath
460460
, projectConfigHcPkg
461461
}
462-
, projectConfigLocalPackages =
463-
PackageConfig
464-
{ packageConfigProgramPaths
465-
, packageConfigProgramPathExtra
466-
}
462+
, projectConfigLocalPackages
467463
} = do
468464
let fileMonitorCompiler = newFileMonitor $ distProjectCacheFile "compiler"
469465

@@ -475,35 +471,26 @@ configureCompiler
475471
, hcPath
476472
, hcPkg
477473
, progsearchpath
478-
, packageConfigProgramPaths
479-
, packageConfigProgramPathExtra
474+
, projectConfigLocalPackages
480475
)
481476
$ do
482477
liftIO $ info verbosity "Compiler settings changed, reconfiguring..."
483-
let extraPath = fromNubList packageConfigProgramPathExtra
484-
progdb <- liftIO $ prependProgramSearchPath verbosity extraPath [] defaultProgramDb
485-
let progdb' = userSpecifyPaths (Map.toList (getMapLast packageConfigProgramPaths)) progdb
486-
(comp, plat, progdb'') <-
487-
liftIO $
488-
Cabal.configCompilerEx
489-
hcFlavor
490-
hcPath
491-
hcPkg
492-
progdb'
493-
verbosity
478+
progdb <- liftIO $ resolveProgramDb verbosity projectConfigLocalPackages
479+
(comp, plat, progdb') <-
480+
liftIO $ Cabal.configCompilerEx hcFlavor hcPath hcPkg progdb verbosity
494481

495482
-- Note that we added the user-supplied program locations and args
496483
-- for /all/ programs, not just those for the compiler prog and
497484
-- compiler-related utils. In principle we don't know which programs
498485
-- the compiler will configure (and it does vary between compilers).
499486
-- We do know however that the compiler will only configure the
500487
-- programs it cares about, and those are the ones we monitor here.
501-
monitorFiles (programsMonitorFiles progdb'')
488+
monitorFiles (programsMonitorFiles progdb')
502489

503490
-- Configure the unconfigured programs in the program database,
504491
-- as we can't serialise unconfigured programs.
505492
-- See also #2241 and #9840.
506-
finalProgDb <- liftIO $ configureAllKnownPrograms verbosity progdb''
493+
finalProgDb <- liftIO $ configureAllKnownPrograms verbosity progdb'
507494

508495
return (comp, plat, finalProgDb)
509496
where
@@ -555,9 +542,14 @@ rebuildInstallPlan
555542
{ cabalStoreDirLayout
556543
} = \projectConfig localPackages mbInstalledPackages ->
557544
runRebuild distProjectRootDirectory $ do
558-
progsearchpath <- liftIO $ getSystemSearchPath
545+
progsearchpath <- liftIO getSystemSearchPath
559546
let projectConfigMonitored = projectConfig{projectConfigBuildOnly = mempty}
560547

548+
progdb <- liftIO $ resolveProgramDb verbosity (projectConfigLocalPackages projectConfig)
549+
monitorFiles (programsMonitorFiles progdb)
550+
551+
pkgConfigDB <- getPkgConfigDb verbosity distDirLayout progdb
552+
561553
-- The overall improved plan is cached
562554
rerunIfChanged
563555
verbosity
@@ -578,15 +570,15 @@ rebuildInstallPlan
578570
$ do
579571
compilerEtc <- phaseConfigureCompiler projectConfig
580572
_ <- phaseConfigurePrograms projectConfig compilerEtc
581-
(solverPlan, pkgConfigDB, totalIndexState, activeRepos) <-
573+
(solverPlan, totalIndexState, activeRepos) <-
582574
phaseRunSolver
583575
projectConfig
584576
compilerEtc
577+
pkgConfigDB
585578
localPackages
586579
(fromMaybe mempty mbInstalledPackages)
587-
( elaboratedPlan
588-
, elaboratedShared
589-
) <-
580+
581+
(elaboratedPlan, elaboratedShared) <-
590582
phaseElaboratePlan
591583
projectConfig
592584
compilerEtc
@@ -620,7 +612,8 @@ rebuildInstallPlan
620612
phaseConfigureCompiler
621613
:: ProjectConfig
622614
-> Rebuild (Compiler, Platform, ProgramDb)
623-
phaseConfigureCompiler = configureCompiler verbosity distDirLayout
615+
phaseConfigureCompiler projectConfig =
616+
configureCompiler verbosity distDirLayout projectConfig
624617

625618
-- Configuring other programs.
626619
--
@@ -660,15 +653,17 @@ rebuildInstallPlan
660653
phaseRunSolver
661654
:: ProjectConfig
662655
-> (Compiler, Platform, ProgramDb)
656+
-> PkgConfigDb
663657
-> [PackageSpecifier UnresolvedSourcePackage]
664658
-> InstalledPackageIndex
665-
-> Rebuild (SolverInstallPlan, PkgConfigDb, IndexUtils.TotalIndexState, IndexUtils.ActiveRepos)
659+
-> Rebuild (SolverInstallPlan, IndexUtils.TotalIndexState, IndexUtils.ActiveRepos)
666660
phaseRunSolver
667661
projectConfig@ProjectConfig
668662
{ projectConfigShared
669663
, projectConfigBuildOnly
670664
}
671665
(compiler, platform, progdb)
666+
pkgConfigDB
672667
localPackages
673668
installedPackages =
674669
rerunIfChanged
@@ -695,7 +690,6 @@ rebuildInstallPlan
695690
withRepoCtx
696691
(solverSettingIndexState solverSettings)
697692
(solverSettingActiveRepos solverSettings)
698-
pkgConfigDB <- getPkgConfigDb verbosity progdb
699693

700694
-- TODO: [code cleanup] it'd be better if the Compiler contained the
701695
-- ConfiguredPrograms that it needs, rather than relying on the progdb
@@ -720,7 +714,7 @@ rebuildInstallPlan
720714
Left msg -> do
721715
reportPlanningFailure projectConfig compiler platform localPackages
722716
dieWithException verbosity $ PhaseRunSolverErr msg
723-
Right plan -> return (plan, pkgConfigDB, tis, ar)
717+
Right plan -> return (plan, tis, ar)
724718
where
725719
corePackageDbs :: [PackageDB]
726720
corePackageDbs =
@@ -1008,13 +1002,23 @@ getSourcePackages verbosity withRepoCtx idxState activeRepos = do
10081002
$ repos
10091003
return sourcePkgDbWithTIS
10101004

1011-
getPkgConfigDb :: Verbosity -> ProgramDb -> Rebuild PkgConfigDb
1012-
getPkgConfigDb verbosity progdb = do
1013-
dirs <- liftIO $ getPkgConfigDbDirs verbosity progdb
1014-
-- Just monitor the dirs so we'll notice new .pc files.
1015-
-- Alternatively we could monitor all the .pc files too.
1016-
traverse_ monitorDirectoryStatus dirs
1017-
liftIO $ readPkgConfigDb verbosity progdb
1005+
getPkgConfigDb :: Verbosity -> DistDirLayout -> ProgramDb -> Rebuild PkgConfigDb
1006+
getPkgConfigDb verbosity distDirLayout progdb = do
1007+
mpkgConfig <- liftIO $ needProgram verbosity pkgConfigProgram progdb
1008+
case mpkgConfig of
1009+
Nothing -> do
1010+
liftIO $ info verbosity "Cannot find pkg-config program. Cabal will continue without solving for pkg-config constraints."
1011+
return NoPkgConfigDb
1012+
Just (pkgConfig, progdb') -> do
1013+
dirs <- liftIO $ getPkgConfigDbDirs verbosity progdb'
1014+
rerunIfChanged verbosity fileMonitorPkgConfigDb (pkgConfig, dirs) $ do
1015+
-- By monitoring the dirs, we'll notice new .pc files. We do not monitor changes in the .pc files themselves.
1016+
traverse_ monitorDirectoryStatus dirs
1017+
liftIO $ do
1018+
info verbosity "Querying pkg-config database..."
1019+
readPkgConfigDb verbosity progdb'
1020+
where
1021+
fileMonitorPkgConfigDb = newFileMonitor $ distProjectCacheFile distDirLayout "pkg-config-db"
10181022

10191023
-- | Select the config values to monitor for changes package source hashes.
10201024
packageLocationsSignature

cabal-testsuite/PackageTests/ExtraProgPath/setup.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# cabal v2-build
22
Warning: cannot determine version of <ROOT>/pkg-config :
33
""
4+
Warning: cannot determine version of <ROOT>/pkg-config :
5+
""
46
Resolving dependencies...
57
Error: [Cabal-7107]
68
Could not resolve dependencies:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module P where
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# cabal v2-build
2+
# cabal v2-build
3+
# cabal v2-build
4+
# cabal v2-build
5+
# cabal v2-build
6+
# cabal v2-build
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
packages: .
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Distribution.Compat.Environment (setEnv)
2+
import System.Directory (copyFile, createDirectoryIfMissing, removeDirectoryRecursive)
3+
import Test.Cabal.Prelude
4+
5+
main = cabalTest $ do
6+
env <- getTestEnv
7+
8+
cabal' "v2-build" ["--dry-run", "p", "-v2"]
9+
>>= assertOutputContains "Querying pkg-config database..."
10+
11+
cabal' "v2-build" ["--dry-run", "p", "-v2"]
12+
>>= assertOutputDoesNotContain "Querying pkg-config database..."
13+
14+
-- Check that changing PKG_CONFIG_PATH invalidates the cache
15+
16+
let pkgConfigPath = testWorkDir env </> "pkgconfig"
17+
liftIO $ do
18+
createDirectoryIfMissing True pkgConfigPath
19+
setEnv "PKG_CONFIG_PATH" pkgConfigPath
20+
21+
cabal' "v2-build" ["--dry-run", "p", "-v2"]
22+
>>= assertOutputContains "Querying pkg-config database..."
23+
24+
cabal' "v2-build" ["--dry-run", "p", "-v2"]
25+
>>= assertOutputDoesNotContain "Querying pkg-config database..."
26+
27+
-- Check that changing a file in PKG_CONFIG_PATH invalidates the cache
28+
29+
liftIO $ copyFile (testCurrentDir env </> "test.pc") (pkgConfigPath </> "test.pc")
30+
31+
cabal' "v2-build" ["--dry-run", "p", "-v2"]
32+
>>= assertOutputContains "Querying pkg-config database..."
33+
34+
cabal' "v2-build" ["--dry-run", "p", "-v2"]
35+
>>= assertOutputDoesNotContain "Querying pkg-config database..."
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: p
2+
version: 1.0
3+
license: BSD3
4+
author: Somebody
5+
maintainer: [email protected]
6+
build-type: Simple
7+
cabal-version: >=1.10
8+
9+
library
10+
exposed-modules: P
11+
build-depends: base
12+
default-language: Haskell2010
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Name: test
2+
Version: 0
3+
Description: a test .pc file

cabal-testsuite/PackageTests/NewUpdate/RejectFutureIndexStates/cabal.test.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ testBody = withProjectFile "cabal.project" $ withRemoteRepo "repo" $ do
1717
. resultOutput
1818
<$> recordMode DoNotRecord (cabal' "update" [])
1919
-- update golden output with actual timestamp
20-
shell "cp" ["cabal.out.in", "cabal.out"]
21-
shell "sed" ["-i''", "-e", "s/REPLACEME/" <> output <> "/g", "cabal.out"]
20+
shell "sed" ["-e", "s/REPLACEME/" <> output <> "/g; w cabal.out", "cabal.out.in"]
2221
-- This shall fail with an error message as specified in `cabal.out`
2322
fails $ cabal "build" ["--index-state=4000-01-01T00:00:00Z", "fake-pkg"]
2423
-- This shall fail by not finding the package, what indicates that it

changelog.d/pr-9422

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
synopsis: Add separate cache for getPkgConfigDb
2+
packages: cabal-install
3+
prs: #9422
4+
issues: #8930
5+
6+
description: {
7+
Querying pkg-config for the version of every module can be a very expensive
8+
operation on some systems. This change adds a separate, per-project, cache for
9+
pkgConfigDB; reducing the cost from "every plan change" to "every pkg-config-db
10+
change per project". A notice is also presented to the user when refreshing the
11+
packagedb.
12+
}

0 commit comments

Comments
 (0)