Skip to content

Commit 5749297

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 06f4155 commit 5749297

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
@@ -153,6 +154,12 @@ import Distribution.Simple.InstallDirs
153154
import Distribution.Simple.Program
154155
( ConfiguredProgram (..)
155156
)
157+
import Distribution.Simple.Program.Db
158+
( ProgramDb
159+
, defaultProgramDb
160+
, prependProgramSearchPath
161+
, userSpecifyPaths
162+
)
156163
import Distribution.Simple.Setup
157164
( Flag (Flag)
158165
, flagToList
@@ -525,6 +532,14 @@ resolveBuildTimeSettings
525532
| isParallelBuild buildSettingNumJobs = False
526533
| otherwise = False
527534

535+
-- | ProgramDb with user specified paths
536+
resolveProgramDb :: Verbosity -> PackageConfig -> IO ProgramDb
537+
resolveProgramDb verbosity packageConfig = do
538+
let extraPath = fromNubList (packageConfigProgramPathExtra packageConfig)
539+
programDb <- prependProgramSearchPath verbosity extraPath [] defaultProgramDb
540+
let paths = Map.toList $ getMapLast (packageConfigProgramPaths packageConfig)
541+
return $ userSpecifyPaths paths programDb
542+
528543
---------------------------------------------
529544
-- Reading and writing project config files
530545
--

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

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -461,11 +461,7 @@ configureCompiler
461461
, projectConfigHcPath
462462
, projectConfigHcPkg
463463
}
464-
, projectConfigLocalPackages =
465-
PackageConfig
466-
{ packageConfigProgramPaths
467-
, packageConfigProgramPathExtra
468-
}
464+
, projectConfigLocalPackages
469465
} = do
470466
let fileMonitorCompiler = newFileMonitor $ distProjectCacheFile "compiler"
471467

@@ -477,35 +473,26 @@ configureCompiler
477473
, hcPath
478474
, hcPkg
479475
, progsearchpath
480-
, packageConfigProgramPaths
481-
, packageConfigProgramPathExtra
476+
, projectConfigLocalPackages
482477
)
483478
$ do
484479
liftIO $ info verbosity "Compiler settings changed, reconfiguring..."
485-
let extraPath = fromNubList packageConfigProgramPathExtra
486-
progdb <- liftIO $ prependProgramSearchPath verbosity extraPath [] defaultProgramDb
487-
let progdb' = userSpecifyPaths (Map.toList (getMapLast packageConfigProgramPaths)) progdb
488-
(comp, plat, progdb'') <-
489-
liftIO $
490-
Cabal.configCompilerEx
491-
hcFlavor
492-
hcPath
493-
hcPkg
494-
progdb'
495-
verbosity
480+
progdb <- liftIO $ resolveProgramDb verbosity projectConfigLocalPackages
481+
(comp, plat, progdb') <-
482+
liftIO $ Cabal.configCompilerEx hcFlavor hcPath hcPkg progdb verbosity
496483

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

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

510497
return (comp, plat, finalProgDb)
511498
where
@@ -557,9 +544,14 @@ rebuildInstallPlan
557544
{ cabalStoreDirLayout
558545
} = \projectConfig localPackages mbInstalledPackages ->
559546
runRebuild distProjectRootDirectory $ do
560-
progsearchpath <- liftIO $ getSystemSearchPath
547+
progsearchpath <- liftIO getSystemSearchPath
561548
let projectConfigMonitored = projectConfig{projectConfigBuildOnly = mempty}
562549

550+
progdb <- liftIO $ resolveProgramDb verbosity (projectConfigLocalPackages projectConfig)
551+
monitorFiles (programsMonitorFiles progdb)
552+
553+
pkgConfigDB <- getPkgConfigDb verbosity distDirLayout progdb
554+
563555
-- The overall improved plan is cached
564556
rerunIfChanged
565557
verbosity
@@ -580,15 +572,15 @@ rebuildInstallPlan
580572
$ do
581573
compilerEtc <- phaseConfigureCompiler projectConfig
582574
_ <- phaseConfigurePrograms projectConfig compilerEtc
583-
(solverPlan, pkgConfigDB, totalIndexState, activeRepos) <-
575+
(solverPlan, totalIndexState, activeRepos) <-
584576
phaseRunSolver
585577
projectConfig
586578
compilerEtc
579+
pkgConfigDB
587580
localPackages
588581
(fromMaybe mempty mbInstalledPackages)
589-
( elaboratedPlan
590-
, elaboratedShared
591-
) <-
582+
583+
(elaboratedPlan, elaboratedShared) <-
592584
phaseElaboratePlan
593585
projectConfig
594586
compilerEtc
@@ -622,7 +614,8 @@ rebuildInstallPlan
622614
phaseConfigureCompiler
623615
:: ProjectConfig
624616
-> Rebuild (Compiler, Platform, ProgramDb)
625-
phaseConfigureCompiler = configureCompiler verbosity distDirLayout
617+
phaseConfigureCompiler projectConfig =
618+
configureCompiler verbosity distDirLayout projectConfig
626619

627620
-- Configuring other programs.
628621
--
@@ -662,15 +655,17 @@ rebuildInstallPlan
662655
phaseRunSolver
663656
:: ProjectConfig
664657
-> (Compiler, Platform, ProgramDb)
658+
-> PkgConfigDb
665659
-> [PackageSpecifier UnresolvedSourcePackage]
666660
-> InstalledPackageIndex
667-
-> Rebuild (SolverInstallPlan, PkgConfigDb, IndexUtils.TotalIndexState, IndexUtils.ActiveRepos)
661+
-> Rebuild (SolverInstallPlan, IndexUtils.TotalIndexState, IndexUtils.ActiveRepos)
668662
phaseRunSolver
669663
projectConfig@ProjectConfig
670664
{ projectConfigShared
671665
, projectConfigBuildOnly
672666
}
673667
(compiler, platform, progdb)
668+
pkgConfigDB
674669
localPackages
675670
installedPackages =
676671
rerunIfChanged
@@ -697,7 +692,6 @@ rebuildInstallPlan
697692
withRepoCtx
698693
(solverSettingIndexState solverSettings)
699694
(solverSettingActiveRepos solverSettings)
700-
pkgConfigDB <- getPkgConfigDb verbosity progdb
701695

702696
-- TODO: [code cleanup] it'd be better if the Compiler contained the
703697
-- ConfiguredPrograms that it needs, rather than relying on the progdb
@@ -722,7 +716,7 @@ rebuildInstallPlan
722716
Left msg -> do
723717
reportPlanningFailure projectConfig compiler platform localPackages
724718
dieWithException verbosity $ PhaseRunSolverErr msg
725-
Right plan -> return (plan, pkgConfigDB, tis, ar)
719+
Right plan -> return (plan, tis, ar)
726720
where
727721
corePackageDbs :: [PackageDB]
728722
corePackageDbs =
@@ -1010,13 +1004,23 @@ getSourcePackages verbosity withRepoCtx idxState activeRepos = do
10101004
$ repos
10111005
return sourcePkgDbWithTIS
10121006

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

10211025
-- | Select the config values to monitor for changes package source hashes.
10221026
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)