Skip to content

Add separate cache for getPkgConfigDb - #9422

Open
andreabedini wants to merge 1 commit into
masterfrom
andrea/pkgconfig-cache
Open

Add separate cache for getPkgConfigDb#9422
andreabedini wants to merge 1 commit into
masterfrom
andrea/pkgconfig-cache

Conversation

@andreabedini

@andreabedini andreabedini commented Nov 8, 2023

Copy link
Copy Markdown
Collaborator

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".

No input key is required since getPkgConfigDb already specifies the directories to monitor for changes. These are obtained from pkg-config itself as pkg-config --variable pc_path pkg-config as documented in pkg-config(1).

A notice is presented to the user when refreshing the packagedb.

Closes #8930 and subsumes #9360.

changelog entry to follow

@geekosaur geekosaur left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth adding a test that ran cabal twice and verified that the cached pkg-config output is used the second time?

@andreabedini

Copy link
Copy Markdown
Collaborator Author

Would it be worth adding a test that ran cabal twice and verified that the cached pkg-config output is used the second time?

I think it would be, I am not sure whether we have anything similar already. I'll check.

@geekosaur

Copy link
Copy Markdown
Collaborator

Not for caching, but there's a test that verifies that a package reinstall causes a warning IIRC.

@gbaz

gbaz commented Nov 8, 2023

Copy link
Copy Markdown
Collaborator

this looks excellent, thanks!

Comment thread changelog.d/pr-9422 Outdated
@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch 2 times, most recently from 6170fe3 to eaf490a Compare November 8, 2023 13:03
@mpickering

Copy link
Copy Markdown
Collaborator

Can you describe the cache invalidation strategy?

Does the cache get invalidated when PKG_CONFIG_PATH environment variable gets changed?

It would be good to add some tests testing the different invalidation mechanisms.

@andreabedini

andreabedini commented Nov 9, 2023

Copy link
Copy Markdown
Collaborator Author

Can you describe the cache invalidation strategy?

Yes, I am relying on what getPkgConfigDb does currently:

getPkgConfigDb :: Verbosity -> ProgramDb -> Rebuild PkgConfigDb
getPkgConfigDb verbosity progdb = do
  dirs <- liftIO $ getPkgConfigDbDirs verbosity progdb
  -- Just monitor the dirs so we'll notice new .pc files.
  -- Alternatively we could monitor all the .pc files too.
  traverse_ monitorDirectoryStatus dirs
  liftIO $ readPkgConfigDb verbosity progdb

It obtains PKG_CONFIG_PATH and for monitors each directory for changes. As the comment says, it does not monitor each individual files.

Perhaps it could be worth monitoring the result of getPkgConfigDbDirs itself?

It would be good to add some tests testing the different invalidation mechanisms.

I agree, I am planning to write some.

@andreabedini

Copy link
Copy Markdown
Collaborator Author

I did some rework and added tests but one of the tests (ExtraProgPath) seems to have picked up a small difference in behaviour.

@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch 2 times, most recently from 03e8346 to 89d3ee1 Compare November 27, 2023 07:55
@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch from 89d3ee1 to 0f81808 Compare December 4, 2023 10:01
Comment thread cabal-testsuite/PackageTests/ExtraProgPath/setup.out
Comment thread cabal-testsuite/PackageTests/Backpack/Includes2/cabal-external-target.out Outdated
@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch 4 times, most recently from 74e96f0 to 9cf9a17 Compare December 5, 2023 03:48
@andreabedini

Copy link
Copy Markdown
Collaborator Author

@mpickering do you think the tests are sufficient? I am checking the search path (for the pkg-config executable) and PKG_CONFIG_PATH. I don't think it is a perfect solution but it seems to be the approach taken elsewhere.

@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch from 9cf9a17 to f97fb81 Compare December 6, 2023 07:12
@sebright sebright added the re: pkg-config Concerning pkg-config and pkgconfig-depends constraints label Dec 7, 2023
@andreabedini

Copy link
Copy Markdown
Collaborator Author

@jasagredo does 3ae0488 look alright to you? I tried to follow what you did in #9527

@andreabedini

Copy link
Copy Markdown
Collaborator Author
Actual output differs from expected:

stderr:
--- /home/runner/work/cabal/cabal/cabal-testsuite/PackageTests/ExtraProgPath/setup.dist/setup.out.normalized	2024-01-24 14:53:40.341551022 +0000
+++ /home/runner/work/cabal/cabal/cabal-testsuite/PackageTests/ExtraProgPath/setup.dist/setup.comp.out.normalized	2024-01-24 14:53:40.341551022 +0000
@@ -1,8 +1,6 @@
 # cabal v2-build
 Warning: cannot determine version of <ROOT>/./pkg-config :
 ""
-Warning: cannot determine version of <ROOT>/./pkg-config :
-""
 Resolving dependencies...
 Error: [Cabal-7107]
 Could not resolve dependencies:
*** Exception: ExitFailure 1

*** unexpected failure for PackageTests/ExtraProgPath/setup.test.hs

🧐

@fgaz

fgaz commented Feb 2, 2024

Copy link
Copy Markdown
Member

@michaelpj

So from a Nix perspective, the thing that should force cache invalidation is that we have a different pkg-config executable in the two cases

the pkg-config executable is the same in all my shells. What changes is the non-standard variable $PKG_CONFIG_PATH_FOR_TARGET, confirmed by https://github.com/NixOS/nixpkgs/blob/55ca7939944136731b34316a4c6fc2d4a11da122/pkgs/build-support/pkg-config-wrapper/pkg-config-wrapper.sh

I think there's nothing we can do here unfortunately.


@andreabedini I tried again and the result is identical

@andreabedini

Copy link
Copy Markdown
Collaborator Author

the pkg-config executable is the same in all my shells. What changes is the non-standard variable $PKG_CONFIG_PATH_FOR_TARGET, confirmed

oh, in my tests the path to the wrapper was changing :-/

@michaelpj

michaelpj commented Feb 5, 2024

Copy link
Copy Markdown
Collaborator

the pkg-config executable is the same in all my shells. What changes is the non-standard variable $PKG_CONFIG_PATH_FOR_TARGET

Right, and in the nix world this is all detectable because the derivation that produces the pkg-config executable is changing, but it's hard for us to perform as fine-grained a check in cabal. It wouldn't be enough to hash the executable, since we also need to know about stuff it uses transitively...

Some ideas:

  • Add an environment variable to turn off the caching, tell Nix folks to set it
  • Make the cache per-project rather than global. Then somewhat by coincidence this will work out, since the granularity at which Nix folks tend to have different pkg-config executables is per-project. Also I guess a bit more consistent with cabal's other caches, and easily cleanable with cabal clean It already is per-project
  • Try and at least advertise that we are getting cached results in solver output, so people know something is up. Especially if thee is a failure...

EDIT: since it's per-project the damage seems less bad, but it maybe forces Nix users to cabal clean when they update their pkg-config dependencies...

@andreabedini

Copy link
Copy Markdown
Collaborator Author

I don't have much time to dedicate to this so I would appreciate if anybody could help pushing this over the line. It's complete but I had issues with the tests.

@andreabedini andreabedini added attention: needs-help Help wanted with this issue/PR and removed attention: needs-review labels Feb 6, 2024
@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch 3 times, most recently from 547f70c to 5b71c97 Compare February 18, 2024 13:53
@jasagredo

jasagredo commented Jun 10, 2024

Copy link
Copy Markdown
Collaborator

So I tried to run this on Windows again. Some remarks:

  1. There are symlinks here. I would advise against that, they do not work on windows, one has to do weird magic tricks to support them.
  2. A test is actually failing: PackageTests\CCompilerOverride\setup.test.hs. I will look into it eventually. It says it cannot find the C compiler. It failed because I didn't have clang in my UCRT environment.
  3. My ./validate.sh process dies abruptly with this same error I reported time ago: Running the test-suite terminates abruptly with fd:5: hGetLine: end of file in MSYS2 Windows #9571

@jasagredo

Copy link
Copy Markdown
Collaborator

I got this error on my machine:

fatal: 'C:\msys64\tmp\repos-67360\src' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Unit Tests
  UnitTests.Distribution.Client.Get
    forkPackages, network tests
      git clone:                                                                                                                       FAIL (2.06s)
        tests\UnitTests\Distribution\Client\Get.hs:219:
        expected a file to exist: C:\msys64\tmp\repos-67360\zlib1/zlib.cabal
        Use -p '/git clone/' to rerun this test only.

1 out of 543 tests failed (77.03s)
<<< C:\Users\Javier\code\cabal\dist-newstyle-validate-ghc-9.8.2\build\x86_64-windows\ghc-9.8.2\cabal-install-3.11.0.0\t\unit-tests\build\unit-tests\unit-tests.exe -j1 --hide-successes (87/104 sec, 1)
<<< C:\Users\Javier\code\cabal\dist-newstyle-validate-ghc-9.8.2\build\x86_64-windows\ghc-9.8.2\cabal-install-3.11.0.0\t\unit-tests\build\unit-tests\unit-tests.exe -j1 --hide-successes (87/104 sec, 1)

@jasagredo

Copy link
Copy Markdown
Collaborator

^ This was fixed in #9915. Needs a rebase

@jasagredo

jasagredo commented Jun 17, 2024

Copy link
Copy Markdown
Collaborator

All tests pass in my machine if using:

  • Rebased on top of update zlib.cabal location #9915
  • No symlinks
  • CLANG64 environment
  • Installed pkgconf from mingw-w64-clang64-x86_64-pkgconf
  • Used --io-manager=native in a couple of test-suites

@jasagredo

Copy link
Copy Markdown
Collaborator

FTR: I'm using this to undo-redo the symlinks on Windows

@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch from 5b71c97 to 331f3cd Compare June 17, 2024 15:13
@andreabedini

Copy link
Copy Markdown
Collaborator Author

I did the rebase but I some tests failed locally. Perhaps the code has bit-rotted a bit.

@jasagredo

Copy link
Copy Markdown
Collaborator

You will have to add a step somewhere to install pkg-config on Windows:

Cannot find pkg-config program. Cabal will continue without solving for

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a dedicated per-project cache for the pkg-config database to avoid re-querying pkg-config on every plan change, reducing repeated solver overhead (especially on systems where pkg-config queries are expensive).

Changes:

  • Add a new getPkgConfigDb caching layer stored in the project cache directory, with a user-facing notice when the pkg-config DB is queried/refreshed.
  • Refactor project planning to compute and pass pkgConfigDB into the solver/elaboration phases.
  • Add a testsuite case to validate pkg-config DB caching and invalidation behavior (and adjust an existing test’s golden-output update step).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
cabal-install/src/Distribution/Client/ProjectPlanning.hs Adds per-project file monitor for pkg-config DB and threads cached pkgConfigDB into planning phases.
cabal-install/src/Distribution/Client/ProjectConfig.hs Exports and adds resolveProgramDb helper for program path overrides.
cabal-testsuite/PackageTests/MonitorPkgConfig/* New test validating pkg-config DB cache reuse + invalidation signals.
cabal-testsuite/PackageTests/NewUpdate/RejectFutureIndexStates/cabal.test.hs Simplifies golden-output rewriting to avoid platform-specific sed -i handling.
changelog.d/pr-9422 Changelog entry describing the new pkg-config DB caching behavior.
Comments suppressed due to low confidence (2)

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

  • The improved/elaborated plan caches do not depend on the pkg-config DB cache. Since pkgConfigDB is computed outside rerunIfChanged and is not included in the cache keys, changes in pkg-config dirs/PKG_CONFIG_PATH can leave fileMonitorImprovedPlan/fileMonitorElaboratedPlan returning a stale plan without re-running the solver/elaboration. Include a pkg-config signature (or a monitored file) in these cache keys so pkg-config changes invalidate the plan caches.
      -- The overall improved plan is cached
      rerunIfChanged
        verbosity
        fileMonitorImprovedPlan
        -- react to changes in the project config,
        -- the package .cabal files and the path
        (projectConfigMonitored, localPackages, progsearchpath)
        $ do

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

  • fileMonitorSolverPlan’s cache key does not include pkgConfigDB, even though planPackages depends on it. If the pkg-config DB changes, the solver plan can be reused incorrectly. Add pkgConfigDB (or a stable signature derived from it) to the rerunIfChanged key, or otherwise ensure this monitor is invalidated when the pkg-config DB cache refreshes.
          rerunIfChanged
            verbosity
            fileMonitorSolverPlan
            ( solverSettings
            , localPackages
            , localPackagesEnabledStanzas
            , compiler
            , platform
            , programDbSignature progdb
            )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +662 to +663

progdb <- liftIO $ resolveProgramDb verbosity (projectConfigLocalPackages projectConfig)

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveProgramDb is built only from projectConfigLocalPackages, so it ignores the shared/global extra-prog-path (projectConfigProgPathExtra) that configureCompiler includes. This can regress locating pkg-config when it is only available via the shared config; consider constructing this ProgramDb from both shared + local program path settings (or reuse the same logic as configureCompiler).

Suggested change
progdb <- liftIO $ resolveProgramDb verbosity (projectConfigLocalPackages projectConfig)
projectConfigPrograms =
projectConfigShared projectConfig
<> projectConfigLocalPackages projectConfig
progdb <- liftIO $ resolveProgramDb verbosity projectConfigPrograms

Copilot uses AI. Check for mistakes.
Comment on lines +663 to +665
progdb <- liftIO $ resolveProgramDb verbosity (projectConfigLocalPackages projectConfig)
monitorFiles (programsMonitorFiles progdb)

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

monitorFiles (programsMonitorFiles progdb) is executed outside any rerunIfChanged action, so these monitored files are not recorded in any file monitor cache and won’t trigger rebuilds. If you intended program changes (e.g. pkg-config executable) to invalidate caches, move the monitoring into the relevant rerunIfChanged body (or add the signature to the key) instead of doing it here.

Copilot uses AI. Check for mistakes.
Comment on lines +575 to +581
-- | ProgramDb with user specified paths
resolveProgramDb :: Verbosity -> PackageConfig -> IO ProgramDb
resolveProgramDb verbosity packageConfig = do
let extraPath = fromNubList (packageConfigProgramPathExtra packageConfig)
programDb <- prependProgramSearchPath verbosity extraPath [] defaultProgramDb
let paths = Map.toList $ getMapLast (packageConfigProgramPaths packageConfig)
return $ userSpecifyPaths paths programDb

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveProgramDb only applies packageConfigProgramPathExtra / packageConfigProgramPaths. Callers that need the full project program search path (including ProjectConfigShared.projectConfigProgPathExtra) will miss it. Either extend this helper to incorporate the shared/global extra-prog-path, or rename/document it as “package config only” to avoid accidental misuse (as in ProjectPlanning’s pkg-config lookup).

Copilot uses AI. Check for mistakes.
@@ -11,8 +11,7 @@ main = skipIfCIAndWindows 10230 >> cabalTest (flakyIfCI 9530 $ withProjectFile "
. resultOutput
<$> recordMode DoNotRecord (cabal' "update" [])
-- update golden output with actual timestamp

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sed invocation uses the w cabal.out command, which appends if cabal.out already exists. That can make the test output accumulate across reruns in the same directory. Prefer writing via stdout redirection (e.g. sed ... cabal.out.in > cabal.out) or explicitly removing/truncating cabal.out first.

Suggested change
-- update golden output with actual timestamp
-- update golden output with actual timestamp
shell "rm" ["-f", "cabal.out"]

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +21
liftIO $ do
createDirectoryIfMissing True pkgConfigPath
setEnv "PKG_CONFIG_PATH" pkgConfigPath

cabal' "v2-build" ["--dry-run", "p", "-v2"]

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test mutates the process environment via setEnv, which can leak into subsequent tests in the same process. Prefer using the testsuite’s withEnv helper so the env override is scoped to the cabal invocations (and is automatically restored afterwards).

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,35 @@
import Distribution.Compat.Environment (setEnv)
import System.Directory (copyFile, createDirectoryIfMissing, removeDirectoryRecursive)

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeDirectoryRecursive is imported but never used in this test, which adds noise and can produce warnings under -Wall. Remove the unused import (or use it if cleanup is intended).

Suggested change
import System.Directory (copyFile, createDirectoryIfMissing, removeDirectoryRecursive)
import System.Directory (copyFile, createDirectoryIfMissing)

Copilot uses AI. Check for mistakes.
@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch from 3f54ae6 to 0101ae9 Compare May 28, 2026 08:19
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.

Co-authored-by: andreabedini <69135+andreabedini@users.noreply.github.com>

fix: skip MonitorPkgConfig test on Windows (no pkg-config available)

fix(MonitorPkgConfig): provide fake pkg-config for Windows CI

fix: address review comments
@andreabedini
andreabedini force-pushed the andrea/pkgconfig-cache branch from 0101ae9 to b42d236 Compare May 28, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

attention: needs-help Help wanted with this issue/PR re: pkg-config Concerning pkg-config and pkgconfig-depends constraints

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cabal invokes pkg-config on every installed package, so cabal run or cabal install takes 20 minutes on my system