From d41a64d680fe3f48d7a8b0df058fff67bfd0d5f2 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 4 Mar 2026 21:25:18 +0800 Subject: [PATCH 01/45] Monitor imported cabal.project files - Fixes #10255 - Fix assertion failure wrt #11568 'lookupLocalPackageConfig' would ignore --'projectConfigAllPackages' (`package *`) and thus diverge from 'lookupPerPkgOption'. This would then cause further divergence between 'elabStanzasRequested' and 'elabStanzasAvailable'. - Add test for #10255 - Add bootstrap-jsons-ghcup Makefile rule - Be stricter about URI imports - This behavior is documented, but not enforced. We redesign the 'ProjectConfigPath' type to better express the properties we expect. - Revert Cabal-syntax network-uri - Revert boostrap/*.json and Makefile - Revert ProjectConfigPath - Revert ParserTests - Revert ProjectConfig/[Legacy|Parsec] - Revert Compat/Orphans - Revert TreeDiffInstances - Revert deletion of ProjectFileParseError - Revert Arbitrary ProjectConfigProvenance change - Revert Errors/Parser prettyShow removal - Get ProjectConfig compiling --- .../src/Distribution/Client/ProjectConfig.hs | 24 +++- .../Distribution/Client/ProjectPlanning.hs | 109 ++++++++++-------- .../ProjectImport/FileMonitoring/app/Main.hs | 4 + .../FileMonitoring/cabal-project-repro.cabal | 101 ++++++++++++++++ .../FileMonitoring/cabal.project | 6 + .../FileMonitoring/cabal.test.hs | 16 +++ .../nested/deeply-nested/hop.config | 1 + .../FileMonitoring/nested/hop.config | 2 + .../ProjectImport/FileMonitoring/test/Main.hs | 4 + .../FileMonitoring/test/tests-toggle.config | 2 + 10 files changed, 221 insertions(+), 48 deletions(-) create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/app/Main.hs create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal-project-repro.cabal create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.project create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/deeply-nested/hop.config create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/hop.config create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/Main.hs create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/tests-toggle.config diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 2249c4c7147..bee7782f316 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -4,6 +4,7 @@ {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -Wno-unused-matches #-} -- | Handling project configuration. @@ -59,7 +60,6 @@ module Distribution.Client.ProjectConfig , fetchAndReadSourcePackages -- * Resolving configuration - , lookupLocalPackageConfig , projectConfigWithBuilderRepoContext , projectConfigWithSolverRepoContext , SolverSettings (..) @@ -858,17 +858,35 @@ readProjectFileSkeletonGen exists <- liftIO $ doesFileExist extensionFile if exists then do + -- Monitor the "main" project file (this could be e.g. 'cabal.project', 'cabal.project.freeze' or + -- 'cabal.project.local'. monitorFiles [monitorFileHashed extensionFile] pcs <- liftIO $ parseConfig extensionFile + + -- We also need to monitor all the (possibly recursive) imports. + -- 'projectSkeletonImports' is a path per imported project file that starts with the leaf + -- and ends with the main project file that is the root. E.g. if 'cabal.project' imports + -- 'cabal.project.foo', which imports 'cabal.project.bar', then we get two paths: + -- + -- > ("cabal.project.bar" :| ["cabal.project.foo", "cabal.project"]) + -- > ("cabal.project.foo" :| ["cabal.project"]) + -- + -- Consequently, we just take the heads of all the paths. monitorFiles - [ monitorFileHashed (projectConfigPathRoot path) - | (Nothing, path) <- projectSkeletonImports pcs + + [ monitorFileHashed $ makeAbsolute path + | (Nothing, currentProjectConfigPath -> path) <- projectSkeletonImports pcs ] + return pcs else do monitorFiles [monitorNonExistentFile extensionFile] return mempty where + -- Do we prefer absolute paths for cache monitoring? + makeAbsolute f + | isAbsolute f = f + | otherwise = distProjectRootDirectory dir f extensionFile = distProjectFile dir key -- There are 3 different variants of the project parsing function. diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index f3054b98099..49160836460 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -809,6 +809,9 @@ rebuildInstallPlan projectConfig@ProjectConfig { projectConfigShared , projectConfigBuildOnly + , projectConfigAllPackages + , projectConfigLocalPackages + , projectConfigSpecificPackage } (compiler, platform, progdb) localPackages @@ -887,27 +890,32 @@ rebuildInstallPlan -- and packages explicitly mentioned in the project -- let pkgname = pkgSpecifierTarget pkg - testsEnabled = - lookupLocalPackageConfig - packageConfigTests - projectConfig - pkgname - benchmarksEnabled = - lookupLocalPackageConfig - packageConfigBenchmarks - projectConfig - pkgname - isLocal = isJust (shouldBeLocal pkg) - stanzas - | isLocal = - Map.fromList $ - [ (TestStanzas, enabled) - | enabled <- flagToList testsEnabled - ] - ++ [ (BenchStanzas, enabled) - | enabled <- flagToList benchmarksEnabled - ] - | otherwise = Map.fromList [(TestStanzas, False), (BenchStanzas, False)] + stanzas = case shouldBeLocal pkg of + Just pkgId -> + let testsEnabled = + lookupPerPkgOption + pkgId + packageConfigTests + projectConfigAllPackages + projectConfigLocalPackages + (getMapMappend projectConfigSpecificPackage) + (const True) + benchmarksEnabled = + lookupPerPkgOption + pkgId + packageConfigBenchmarks + projectConfigAllPackages + projectConfigLocalPackages + (getMapMappend projectConfigSpecificPackage) + (const True) + in Map.fromList $ + [ (TestStanzas, enabled) + | enabled <- flagToList testsEnabled + ] + ++ [ (BenchStanzas, enabled) + | enabled <- flagToList benchmarksEnabled + ] + Nothing -> Map.fromList [(TestStanzas, False), (BenchStanzas, False)] ] -- Elaborate the solver's install plan to get a fully detailed plan. This @@ -2502,39 +2510,27 @@ elaborateInstallPlan perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] - perPkgOptionFlag pkgid def f = fromFlagOrDefault def (lookupPerPkgOption pkgid f) - perPkgOptionMaybe pkgid f = flagToMaybe (lookupPerPkgOption pkgid f) - perPkgOptionList pkgid f = lookupPerPkgOption pkgid f - perPkgOptionNubList pkgid f = fromNubList (lookupPerPkgOption pkgid f) - perPkgOptionMapLast pkgid f = getMapLast (lookupPerPkgOption pkgid f) - perPkgOptionMapMappend pkgid f = getMapMappend (lookupPerPkgOption pkgid f) + perPkgOptionFlag pkgid def f = fromFlagOrDefault def (lookupPerPkgOption' pkgid f) + perPkgOptionMaybe pkgid f = flagToMaybe (lookupPerPkgOption' pkgid f) + perPkgOptionList pkgid f = lookupPerPkgOption' pkgid f + perPkgOptionNubList pkgid f = fromNubList (lookupPerPkgOption' pkgid f) + perPkgOptionMapLast pkgid f = getMapLast (lookupPerPkgOption' pkgid f) + perPkgOptionMapMappend pkgid f = getMapMappend (lookupPerPkgOption' pkgid f) perPkgOptionLibExeFlag pkgid def fboth flib = (exe, lib) where exe = fromFlagOrDefault def bothflag lib = fromFlagOrDefault def (bothflag <> libflag) - bothflag = lookupPerPkgOption pkgid fboth - libflag = lookupPerPkgOption pkgid flib + bothflag = lookupPerPkgOption' pkgid fboth + libflag = lookupPerPkgOption' pkgid flib - lookupPerPkgOption + lookupPerPkgOption' :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m - lookupPerPkgOption pkg f = - -- This is where we merge the options from the project config that - -- apply to all packages, all project local packages, and to specific - -- named packages - global <> local <> perpkg - where - global = f allPackagesConfig - local - | isLocalToProject pkg = - f localPackagesConfig - | otherwise = - mempty - perpkg = maybe mempty f (Map.lookup (packageName pkg) perPackageConfig) + lookupPerPkgOption' pkg f = lookupPerPkgOption pkg f allPackagesConfig localPackagesConfig perPackageConfig isLocalToProject inplacePackageDbs = corePackageDbs @@ -2650,8 +2646,8 @@ elaborateInstallPlan fromFlagOrDefault compilerShouldUseProfilingLibByDefault (profBothFlag <> profLibFlag) where pkgid = packageId pkg - profBothFlag = lookupPerPkgOption pkgid packageConfigProf - profLibFlag = lookupPerPkgOption pkgid packageConfigProfLib + profBothFlag = lookupPerPkgOption' pkgid packageConfigProf + profLibFlag = lookupPerPkgOption' pkgid packageConfigProfLib pkgsUseProfilingLibraryShared :: Set PackageId pkgsUseProfilingLibraryShared = @@ -4691,3 +4687,26 @@ determineCoverageFor configuredPkg plan = isIndefiniteOrInstantiation :: ModuleShape -> Bool isIndefiniteOrInstantiation = not . Set.null . modShapeRequires + +lookupPerPkgOption + :: (Package pkg, Monoid m) + => pkg + -> (PackageConfig -> m) + -> PackageConfig + -> PackageConfig + -> Map PackageName PackageConfig + -> (pkg -> Bool) + -> m +lookupPerPkgOption pkg f allPackagesConfig localPackagesConfig perPackageConfig isLocalPkg = + -- This is where we merge the options from the project config that + -- apply to all packages, all project local packages, and to specific + -- named packages + global `mappend` local `mappend` perpkg + where + global = f allPackagesConfig + local + | isLocalPkg pkg = + f localPackagesConfig + | otherwise = + mempty + perpkg = maybe mempty f (Map.lookup (packageName pkg) perPackageConfig) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/app/Main.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/app/Main.hs new file mode 100644 index 00000000000..f16f6f53396 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/app/Main.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = putStrLn "Hello, Haskell!" diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal-project-repro.cabal b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal-project-repro.cabal new file mode 100644 index 00000000000..fc3fbfd2d15 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal-project-repro.cabal @@ -0,0 +1,101 @@ +cabal-version: 3.0 +-- The cabal-version field refers to the version of the .cabal specification, +-- and can be different from the cabal-install (the tool) version and the +-- Cabal (the library) version you are using. As such, the Cabal (the library) +-- version used must be equal or greater than the version stated in this field. +-- Starting from the specification version 2.2, the cabal-version field must be +-- the first thing in the cabal file. + +-- Initial package description 'cabal-project-repro' generated by +-- 'cabal init'. For further documentation, see: +-- http://haskell.org/cabal/users-guide/ +-- +-- The name of the package. +name: cabal-project-repro + +-- The package version. +-- See the Haskell package versioning policy (PVP) for standards +-- guiding when and how versions should be incremented. +-- https://pvp.haskell.org +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 0.1.0.0 + +-- A short (one-line) description of the package. +-- synopsis: + +-- A longer description of the package. +-- description: + +-- The license under which the package is released. +license: BSD-3-Clause + +-- The file containing the license text. +license-file: LICENSE + +-- The package author(s). +author: Julian Ospald + +-- An email address to which users can send suggestions, bug reports, and patches. +maintainer: hasufell@posteo.de + +-- A copyright notice. +-- copyright: +build-type: Simple + +-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README. +extra-doc-files: CHANGELOG.md + +-- Extra source files to be distributed with the package, such as examples, or a tutorial module. +-- extra-source-files: + +common warnings + ghc-options: -Wall + +executable filemonitor-test + -- Import common warning flags. + import: warnings + + -- .hs or .lhs file containing the Main module. + main-is: Main.hs + + -- Modules included in this executable, other than Main. + -- other-modules: + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + + -- Other library packages from which modules are imported. + build-depends: base, filepath + + -- Directories containing source files. + hs-source-dirs: app + + -- Base language which the package is written in. + default-language: Haskell2010 + +test-suite cabal-project-repro-test + -- Import common warning flags. + import: warnings + + -- Base language which the package is written in. + default-language: Haskell2010 + + -- Modules included in this executable, other than Main. + -- other-modules: + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + + -- The interface type and version of the test suite. + type: exitcode-stdio-1.0 + + -- Directories containing source files. + hs-source-dirs: test + + -- The entrypoint to the test suite. + main-is: Main.hs + + -- Test dependencies. + build-depends: base diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.project b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.project new file mode 100644 index 00000000000..469e652adaf --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.project @@ -0,0 +1,6 @@ +packages: ./cabal-project-repro.cabal + +package * + Tests: True + +import: nested/hop.config diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs new file mode 100644 index 00000000000..bb4b9504190 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -0,0 +1,16 @@ +import Test.Cabal.Prelude +import System.Exit (ExitCode(ExitFailure)) +import System.IO + +main = cabalTest (withProjectFile "cabal.project" $ do + result <- fails $ recordMode DoNotRecord $ cabal' "build" [] + assertExitCode (ExitFailure 1) result + assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." result + + -- change the imported project file + test_dir <- fmap testTmpDir getTestEnv + liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" + result' <- recordMode DoNotRecord $ cabal' "build" [] + assertOutputDoesNotContain "Test suite not yet implement" result' + assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." result' + ) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/deeply-nested/hop.config b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/deeply-nested/hop.config new file mode 100644 index 00000000000..14a44ec1b90 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/deeply-nested/hop.config @@ -0,0 +1 @@ +import: ../../test/tests-toggle.config diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/hop.config b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/hop.config new file mode 100644 index 00000000000..5023e425930 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/nested/hop.config @@ -0,0 +1,2 @@ +import: deeply-nested/hop.config + diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/Main.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/Main.hs new file mode 100644 index 00000000000..7e9a17db22b --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/Main.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = puStrLn "Test suite not yet implemented." diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/tests-toggle.config b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/tests-toggle.config new file mode 100644 index 00000000000..653cd6b9660 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/test/tests-toggle.config @@ -0,0 +1,2 @@ +package * + Tests: True From 4b0a41314019fbf09fa428172bfbf0ca295f2372 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Mon, 25 May 2026 16:46:05 -0400 Subject: [PATCH 02/45] Satisfy fourmolu --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index bee7782f316..74a1ebaae5b 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -873,7 +873,6 @@ readProjectFileSkeletonGen -- -- Consequently, we just take the heads of all the paths. monitorFiles - [ monitorFileHashed $ makeAbsolute path | (Nothing, currentProjectConfigPath -> path) <- projectSkeletonImports pcs ] From 8062726996a9eedf65d2cb47c74e7f36eeb86c09 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Mon, 25 May 2026 16:54:52 -0400 Subject: [PATCH 03/45] Undo removal of export of lookupLocalPackageConfig --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 74a1ebaae5b..39550221210 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -60,6 +60,7 @@ module Distribution.Client.ProjectConfig , fetchAndReadSourcePackages -- * Resolving configuration + , lookupLocalPackageConfig , projectConfigWithBuilderRepoContext , projectConfigWithSolverRepoContext , SolverSettings (..) From 76cf6ece069d69b505aca44165ad77fdf6c6838f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 09:49:44 -0400 Subject: [PATCH 04/45] Promote comment to haddocks --- cabal-install/src/Distribution/Client/ProjectPlanning.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 49160836460..7a07b1ca31b 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -4688,6 +4688,8 @@ determineCoverageFor configuredPkg plan = isIndefiniteOrInstantiation :: ModuleShape -> Bool isIndefiniteOrInstantiation = not . Set.null . modShapeRequires +-- | Look up and merge the options from the project config that apply to all +-- packages, all project local packages, and to specific named packages. lookupPerPkgOption :: (Package pkg, Monoid m) => pkg @@ -4698,9 +4700,6 @@ lookupPerPkgOption -> (pkg -> Bool) -> m lookupPerPkgOption pkg f allPackagesConfig localPackagesConfig perPackageConfig isLocalPkg = - -- This is where we merge the options from the project config that - -- apply to all packages, all project local packages, and to specific - -- named packages global `mappend` local `mappend` perpkg where global = f allPackagesConfig From e6065e634ccb50451fe3b6c88a11d87056dc1e50 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 09:50:17 -0400 Subject: [PATCH 05/45] Reformat that fourmolu accepts --- cabal-install/src/Distribution/Client/ProjectPlanning.hs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 7a07b1ca31b..9dcddc51ceb 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -4704,8 +4704,6 @@ lookupPerPkgOption pkg f allPackagesConfig localPackagesConfig perPackageConfig where global = f allPackagesConfig local - | isLocalPkg pkg = - f localPackagesConfig - | otherwise = - mempty + | isLocalPkg pkg = f localPackagesConfig + | otherwise = mempty perpkg = maybe mempty f (Map.lookup (packageName pkg) perPackageConfig) From a7807ce73da39d9ed6fe53a3cf3b09dfb1aaaf9c Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 10:03:24 -0400 Subject: [PATCH 06/45] Change the arg order for partial application --- .../Distribution/Client/ProjectPlanning.hs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 9dcddc51ceb..5d6fe5d74ac 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -894,20 +894,20 @@ rebuildInstallPlan Just pkgId -> let testsEnabled = lookupPerPkgOption - pkgId - packageConfigTests + (const True) projectConfigAllPackages projectConfigLocalPackages (getMapMappend projectConfigSpecificPackage) - (const True) + pkgId + packageConfigTests benchmarksEnabled = lookupPerPkgOption - pkgId - packageConfigBenchmarks + (const True) projectConfigAllPackages projectConfigLocalPackages (getMapMappend projectConfigSpecificPackage) - (const True) + pkgId + packageConfigBenchmarks in Map.fromList $ [ (TestStanzas, enabled) | enabled <- flagToList testsEnabled @@ -2525,12 +2525,8 @@ elaborateInstallPlan bothflag = lookupPerPkgOption' pkgid fboth libflag = lookupPerPkgOption' pkgid flib - lookupPerPkgOption' - :: (Package pkg, Monoid m) - => pkg - -> (PackageConfig -> m) - -> m - lookupPerPkgOption' pkg f = lookupPerPkgOption pkg f allPackagesConfig localPackagesConfig perPackageConfig isLocalToProject + lookupPerPkgOption' :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m + lookupPerPkgOption' = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig inplacePackageDbs = corePackageDbs @@ -4692,14 +4688,14 @@ determineCoverageFor configuredPkg plan = -- packages, all project local packages, and to specific named packages. lookupPerPkgOption :: (Package pkg, Monoid m) - => pkg - -> (PackageConfig -> m) + => (pkg -> Bool) -> PackageConfig -> PackageConfig -> Map PackageName PackageConfig - -> (pkg -> Bool) + -> pkg + -> (PackageConfig -> m) -> m -lookupPerPkgOption pkg f allPackagesConfig localPackagesConfig perPackageConfig isLocalPkg = +lookupPerPkgOption isLocalPkg allPackagesConfig localPackagesConfig perPackageConfig pkg f = global `mappend` local `mappend` perpkg where global = f allPackagesConfig From 86492e6b862e309c9424e7f9771da20adb6c33bb Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 10:12:27 -0400 Subject: [PATCH 07/45] Rename lookupPerPkgOption' to perPkgOption --- .../Distribution/Client/ProjectPlanning.hs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 5d6fe5d74ac..fdd9b4f1b65 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2506,27 +2506,26 @@ elaborateInstallPlan elabBenchmarkOptions = perPkgOptionList pkgid packageConfigBenchmarkOptions + perPkgOption :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m + perPkgOption = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig + perPkgOptionFlag :: PackageId -> a -> (PackageConfig -> Flag a) -> a perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] - - perPkgOptionFlag pkgid def f = fromFlagOrDefault def (lookupPerPkgOption' pkgid f) - perPkgOptionMaybe pkgid f = flagToMaybe (lookupPerPkgOption' pkgid f) - perPkgOptionList pkgid f = lookupPerPkgOption' pkgid f - perPkgOptionNubList pkgid f = fromNubList (lookupPerPkgOption' pkgid f) - perPkgOptionMapLast pkgid f = getMapLast (lookupPerPkgOption' pkgid f) - perPkgOptionMapMappend pkgid f = getMapMappend (lookupPerPkgOption' pkgid f) + perPkgOptionFlag pkgid def f = fromFlagOrDefault def (perPkgOption pkgid f) + perPkgOptionMaybe pkgid f = flagToMaybe (perPkgOption pkgid f) + perPkgOptionList pkgid f = perPkgOption pkgid f + perPkgOptionNubList pkgid f = fromNubList (perPkgOption pkgid f) + perPkgOptionMapLast pkgid f = getMapLast (perPkgOption pkgid f) + perPkgOptionMapMappend pkgid f = getMapMappend (perPkgOption pkgid f) perPkgOptionLibExeFlag pkgid def fboth flib = (exe, lib) where exe = fromFlagOrDefault def bothflag lib = fromFlagOrDefault def (bothflag <> libflag) - bothflag = lookupPerPkgOption' pkgid fboth - libflag = lookupPerPkgOption' pkgid flib - - lookupPerPkgOption' :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m - lookupPerPkgOption' = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig + bothflag = perPkgOption pkgid fboth + libflag = perPkgOption pkgid flib inplacePackageDbs = corePackageDbs @@ -2642,8 +2641,8 @@ elaborateInstallPlan fromFlagOrDefault compilerShouldUseProfilingLibByDefault (profBothFlag <> profLibFlag) where pkgid = packageId pkg - profBothFlag = lookupPerPkgOption' pkgid packageConfigProf - profLibFlag = lookupPerPkgOption' pkgid packageConfigProfLib + profBothFlag = perPkgOption pkgid packageConfigProf + profLibFlag = perPkgOption pkgid packageConfigProfLib pkgsUseProfilingLibraryShared :: Set PackageId pkgsUseProfilingLibraryShared = From cbb8a4b401957e90de8f23394e8fae7e463062a8 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 10:17:02 -0400 Subject: [PATCH 08/45] Change arg order with perPkgOptionFlag, def first --- .../Distribution/Client/ProjectPlanning.hs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index fdd9b4f1b65..181a2305c80 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2339,7 +2339,7 @@ elaborateInstallPlan elabHaddockTargets = [] elabBuildHaddocks = - perPkgOptionFlag pkgid False packageConfigDocumentation + perPkgOptionFlag False pkgid packageConfigDocumentation -- `documentation: true` should imply `-haddock` for GHC addHaddockIfDocumentationEnabled :: ConfiguredProgram -> ConfiguredProgram @@ -2387,35 +2387,35 @@ elaborateInstallPlan -- 'elabBuildOptions' accurately reflects what will actually be built. elabBuildOptionsRaw = LBC.BuildOptions - { withVanillaLib = perPkgOptionFlag pkgid True packageConfigVanillaLib -- TODO: [required feature]: also needs to be handled recursively + { withVanillaLib = perPkgOptionFlag True pkgid packageConfigVanillaLib -- TODO: [required feature]: also needs to be handled recursively , withSharedLib = canBuildSharedLibs && pkgid `Set.member` pkgsUseSharedLibrary - , withStaticLib = perPkgOptionFlag pkgid False packageConfigStaticLib + , withStaticLib = perPkgOptionFlag False pkgid packageConfigStaticLib , withDynExe = - perPkgOptionFlag pkgid False packageConfigDynExe + perPkgOptionFlag False pkgid packageConfigDynExe -- We can't produce a dynamic executable if the user -- wants to enable executable profiling but the -- compiler doesn't support prof+dyn. && (okProfDyn || not profExe) - , withFullyStaticExe = perPkgOptionFlag pkgid False packageConfigFullyStaticExe - , withGHCiLib = perPkgOptionFlag pkgid False packageConfigGHCiLib -- TODO: [required feature] needs to default to enabled on windows still + , withFullyStaticExe = perPkgOptionFlag False pkgid packageConfigFullyStaticExe + , withGHCiLib = perPkgOptionFlag False pkgid packageConfigGHCiLib -- TODO: [required feature] needs to default to enabled on windows still , withProfExe = profExe , withProfLib = canBuildProfilingLibs && pkgid `Set.member` pkgsUseProfilingLibrary , withProfLibShared = canBuildProfilingSharedLibs && pkgid `Set.member` pkgsUseProfilingLibraryShared - , withBytecodeLib = perPkgOptionFlag pkgid False packageConfigBytecodeLib - , exeCoverage = perPkgOptionFlag pkgid False packageConfigCoverage - , libCoverage = perPkgOptionFlag pkgid False packageConfigCoverage - , withOptimization = perPkgOptionFlag pkgid NormalOptimisation packageConfigOptimization - , splitObjs = perPkgOptionFlag pkgid False packageConfigSplitObjs - , splitSections = perPkgOptionFlag pkgid False packageConfigSplitSections - , stripLibs = perPkgOptionFlag pkgid False packageConfigStripLibs - , stripExes = perPkgOptionFlag pkgid False packageConfigStripExes - , withDebugInfo = perPkgOptionFlag pkgid NoDebugInfo packageConfigDebugInfo - , relocatable = perPkgOptionFlag pkgid False packageConfigRelocatable + , withBytecodeLib = perPkgOptionFlag False pkgid packageConfigBytecodeLib + , exeCoverage = perPkgOptionFlag False pkgid packageConfigCoverage + , libCoverage = perPkgOptionFlag False pkgid packageConfigCoverage + , withOptimization = perPkgOptionFlag NormalOptimisation pkgid packageConfigOptimization + , splitObjs = perPkgOptionFlag False pkgid packageConfigSplitObjs + , splitSections = perPkgOptionFlag False pkgid packageConfigSplitSections + , stripLibs = perPkgOptionFlag False pkgid packageConfigStripLibs + , stripExes = perPkgOptionFlag False pkgid packageConfigStripExes + , withDebugInfo = perPkgOptionFlag NoDebugInfo pkgid packageConfigDebugInfo + , relocatable = perPkgOptionFlag False pkgid packageConfigRelocatable , withProfLibDetail = elabProfExeDetail , withProfExeDetail = elabProfLibDetail } okProfDyn = profilingDynamicSupportedOrUnknown compiler - profExe = perPkgOptionFlag pkgid False packageConfigProf + profExe = perPkgOptionFlag False pkgid packageConfigProf elabBuildOptions = Cabal.adjustBuildOptions compiler compilerProgDb elabBuildOptionsRaw @@ -2428,7 +2428,7 @@ elaborateInstallPlan packageConfigProfDetail packageConfigProfLibDetail - elabDumpBuildInfo = perPkgOptionFlag pkgid NoDumpBuildInfo packageConfigDumpBuildInfo + elabDumpBuildInfo = perPkgOptionFlag NoDumpBuildInfo pkgid packageConfigDumpBuildInfo -- Combine the configured compiler prog settings with the user-supplied -- config. For the compiler progs any user-supplied config was taken @@ -2476,32 +2476,32 @@ elaborateInstallPlan elabProgPrefix = perPkgOptionMaybe pkgid packageConfigProgPrefix elabProgSuffix = perPkgOptionMaybe pkgid packageConfigProgSuffix - elabHaddockHoogle = perPkgOptionFlag pkgid False packageConfigHaddockHoogle - elabHaddockHtml = perPkgOptionFlag pkgid False packageConfigHaddockHtml + elabHaddockHoogle = perPkgOptionFlag False pkgid packageConfigHaddockHoogle + elabHaddockHtml = perPkgOptionFlag False pkgid packageConfigHaddockHtml elabHaddockHtmlLocation = perPkgOptionMaybe pkgid packageConfigHaddockHtmlLocation - elabHaddockForeignLibs = perPkgOptionFlag pkgid False packageConfigHaddockForeignLibs - elabHaddockForHackage = perPkgOptionFlag pkgid Cabal.ForDevelopment packageConfigHaddockForHackage - elabHaddockExecutables = perPkgOptionFlag pkgid False packageConfigHaddockExecutables - elabHaddockTestSuites = perPkgOptionFlag pkgid False packageConfigHaddockTestSuites - elabHaddockBenchmarks = perPkgOptionFlag pkgid False packageConfigHaddockBenchmarks - elabHaddockInternal = perPkgOptionFlag pkgid False packageConfigHaddockInternal + elabHaddockForeignLibs = perPkgOptionFlag False pkgid packageConfigHaddockForeignLibs + elabHaddockForHackage = perPkgOptionFlag Cabal.ForDevelopment pkgid packageConfigHaddockForHackage + elabHaddockExecutables = perPkgOptionFlag False pkgid packageConfigHaddockExecutables + elabHaddockTestSuites = perPkgOptionFlag False pkgid packageConfigHaddockTestSuites + elabHaddockBenchmarks = perPkgOptionFlag False pkgid packageConfigHaddockBenchmarks + elabHaddockInternal = perPkgOptionFlag False pkgid packageConfigHaddockInternal elabHaddockCss = perPkgOptionMaybe pkgid packageConfigHaddockCss - elabHaddockLinkedSource = perPkgOptionFlag pkgid False packageConfigHaddockLinkedSource - elabHaddockQuickJump = perPkgOptionFlag pkgid False packageConfigHaddockQuickJump + elabHaddockLinkedSource = perPkgOptionFlag False pkgid packageConfigHaddockLinkedSource + elabHaddockQuickJump = perPkgOptionFlag False pkgid packageConfigHaddockQuickJump elabHaddockHscolourCss = perPkgOptionMaybe pkgid packageConfigHaddockHscolourCss elabHaddockContents = perPkgOptionMaybe pkgid packageConfigHaddockContents elabHaddockIndex = perPkgOptionMaybe pkgid packageConfigHaddockIndex elabHaddockBaseUrl = perPkgOptionMaybe pkgid packageConfigHaddockBaseUrl elabHaddockResourcesDir = perPkgOptionMaybe pkgid packageConfigHaddockResourcesDir elabHaddockOutputDir = perPkgOptionMaybe pkgid packageConfigHaddockOutputDir - elabHaddockUseUnicode = perPkgOptionFlag pkgid False packageConfigHaddockUseUnicode + elabHaddockUseUnicode = perPkgOptionFlag False pkgid packageConfigHaddockUseUnicode elabTestMachineLog = perPkgOptionMaybe pkgid packageConfigTestMachineLog elabTestHumanLog = perPkgOptionMaybe pkgid packageConfigTestHumanLog elabTestShowDetails = perPkgOptionMaybe pkgid packageConfigTestShowDetails - elabTestKeepTix = perPkgOptionFlag pkgid False packageConfigTestKeepTix + elabTestKeepTix = perPkgOptionFlag False pkgid packageConfigTestKeepTix elabTestWrapper = perPkgOptionMaybe pkgid packageConfigTestWrapper - elabTestFailWhenNoTestSuites = perPkgOptionFlag pkgid False packageConfigTestFailWhenNoTestSuites + elabTestFailWhenNoTestSuites = perPkgOptionFlag False pkgid packageConfigTestFailWhenNoTestSuites elabTestTestOptions = perPkgOptionList pkgid packageConfigTestTestOptions elabBenchmarkOptions = perPkgOptionList pkgid packageConfigBenchmarkOptions @@ -2509,10 +2509,10 @@ elaborateInstallPlan perPkgOption :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m perPkgOption = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig - perPkgOptionFlag :: PackageId -> a -> (PackageConfig -> Flag a) -> a + perPkgOptionFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> a perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] - perPkgOptionFlag pkgid def f = fromFlagOrDefault def (perPkgOption pkgid f) + perPkgOptionFlag def pkgid f = fromFlagOrDefault def (perPkgOption pkgid f) perPkgOptionMaybe pkgid f = flagToMaybe (perPkgOption pkgid f) perPkgOptionList pkgid f = perPkgOption pkgid f perPkgOptionNubList pkgid f = fromNubList (perPkgOption pkgid f) From 280b1b6e296bab3091b4e5454ce99a14d75de6e2 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 10:33:17 -0400 Subject: [PATCH 09/45] Use function composition for perPkgOption* --- .../src/Distribution/Client/ProjectPlanning.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 181a2305c80..160f06447be 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2512,12 +2512,12 @@ elaborateInstallPlan perPkgOptionFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> a perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] - perPkgOptionFlag def pkgid f = fromFlagOrDefault def (perPkgOption pkgid f) - perPkgOptionMaybe pkgid f = flagToMaybe (perPkgOption pkgid f) - perPkgOptionList pkgid f = perPkgOption pkgid f - perPkgOptionNubList pkgid f = fromNubList (perPkgOption pkgid f) - perPkgOptionMapLast pkgid f = getMapLast (perPkgOption pkgid f) - perPkgOptionMapMappend pkgid f = getMapMappend (perPkgOption pkgid f) + perPkgOptionFlag def = fmap (fromFlagOrDefault def) . perPkgOption + perPkgOptionMaybe = fmap flagToMaybe . perPkgOption + perPkgOptionList = perPkgOption + perPkgOptionNubList = fmap fromNubList . perPkgOption + perPkgOptionMapLast = fmap getMapLast . perPkgOption + perPkgOptionMapMappend = fmap getMapMappend . perPkgOption perPkgOptionLibExeFlag pkgid def fboth flib = (exe, lib) where From bbc376bb27c08dce6d61e16f9aaa0f973997a85b Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 10:54:47 -0400 Subject: [PATCH 10/45] Add type sigs for all perPkgOption* functions --- .../src/Distribution/Client/ProjectPlanning.hs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 160f06447be..b17eb854c0f 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2423,8 +2423,8 @@ elaborateInstallPlan , elabProfLibDetail ) = perPkgOptionLibExeFlag - pkgid ProfDetailDefault + pkgid packageConfigProfDetail packageConfigProfLibDetail @@ -2510,16 +2510,25 @@ elaborateInstallPlan perPkgOption = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig perPkgOptionFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> a - perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a - perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] perPkgOptionFlag def = fmap (fromFlagOrDefault def) . perPkgOption + + perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a perPkgOptionMaybe = fmap flagToMaybe . perPkgOption + + perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] perPkgOptionList = perPkgOption + + perPkgOptionNubList :: PackageId -> (PackageConfig -> NubList FilePath) -> [FilePath] perPkgOptionNubList = fmap fromNubList . perPkgOption + + perPkgOptionMapLast :: Ord k => PackageId -> (PackageConfig -> MapLast k v) -> Map k v perPkgOptionMapLast = fmap getMapLast . perPkgOption + + perPkgOptionMapMappend :: (Ord k, Semigroup v) => PackageId -> (PackageConfig -> MapMappend k v) -> Map k v perPkgOptionMapMappend = fmap getMapMappend . perPkgOption - perPkgOptionLibExeFlag pkgid def fboth flib = (exe, lib) + perPkgOptionLibExeFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> (PackageConfig -> Flag a) -> (a, a) + perPkgOptionLibExeFlag def pkgid fboth flib = (exe, lib) where exe = fromFlagOrDefault def bothflag lib = fromFlagOrDefault def (bothflag <> libflag) From 856efff89069e299fdc46f7e7bc7fb22edd4e914 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 11:02:10 -0400 Subject: [PATCH 11/45] Inline each perPkgOption* only used once --- .../src/Distribution/Client/ProjectPlanning.hs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index b17eb854c0f..e006a46f913 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2440,7 +2440,8 @@ elaborateInstallPlan [ (programId prog, programPath prog) | prog <- configuredPrograms compilerProgDb ] - <> perPkgOptionMapLast pkgid packageConfigProgramPaths + <> (getMapLast $ perPkgOption pkgid packageConfigProgramPaths) + elabProgramArgs = -- Workaround for -- @@ -2465,8 +2466,9 @@ elaborateInstallPlan , not (null args) ] ) - (perPkgOptionMapMappend pkgid packageConfigProgramArgs) - elabProgramPathExtra = perPkgOptionNubList pkgid packageConfigProgramPathExtra + (getMapMappend $ perPkgOption pkgid packageConfigProgramArgs) + + elabProgramPathExtra = fromNubList $ perPkgOption pkgid packageConfigProgramPathExtra elabConfiguredPrograms = configuredPrograms compilerProgDb elabConfigureScriptArgs = perPkgOptionList pkgid packageConfigConfigureArgs elabExtraLibDirs = perPkgOptionList pkgid packageConfigExtraLibDirs @@ -2518,15 +2520,6 @@ elaborateInstallPlan perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a] perPkgOptionList = perPkgOption - perPkgOptionNubList :: PackageId -> (PackageConfig -> NubList FilePath) -> [FilePath] - perPkgOptionNubList = fmap fromNubList . perPkgOption - - perPkgOptionMapLast :: Ord k => PackageId -> (PackageConfig -> MapLast k v) -> Map k v - perPkgOptionMapLast = fmap getMapLast . perPkgOption - - perPkgOptionMapMappend :: (Ord k, Semigroup v) => PackageId -> (PackageConfig -> MapMappend k v) -> Map k v - perPkgOptionMapMappend = fmap getMapMappend . perPkgOption - perPkgOptionLibExeFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> (PackageConfig -> Flag a) -> (a, a) perPkgOptionLibExeFlag def pkgid fboth flib = (exe, lib) where From 70def63264dc43fe18a3a90adcd7a6fed985707d Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 11:12:32 -0400 Subject: [PATCH 12/45] Use -XViewPatterns in perPkgOptionLibExeFlag --- .../src/Distribution/Client/ProjectPlanning.hs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index e006a46f913..8ea5f74370a 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -6,6 +6,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ViewPatterns #-} -- | -- /Elaborated: worked out with great care and nicety of detail; executed with great minuteness: elaborate preparations; elaborate care./ @@ -2521,13 +2522,8 @@ elaborateInstallPlan perPkgOptionList = perPkgOption perPkgOptionLibExeFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> (PackageConfig -> Flag a) -> (a, a) - perPkgOptionLibExeFlag def pkgid fboth flib = (exe, lib) - where - exe = fromFlagOrDefault def bothflag - lib = fromFlagOrDefault def (bothflag <> libflag) - - bothflag = perPkgOption pkgid fboth - libflag = perPkgOption pkgid flib + perPkgOptionLibExeFlag (fromFlagOrDefault -> f) pkgid (perPkgOption pkgid -> both) (perPkgOption pkgid -> lib) = + (f both, f (both <> lib)) inplacePackageDbs = corePackageDbs From 1bbd5ac874eaae434f7f39a84132cba2875a8afa Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 11:26:16 -0400 Subject: [PATCH 13/45] Follow hlint suggestion: move bracket to avoid $ --- cabal-install/src/Distribution/Client/ProjectPlanning.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 8ea5f74370a..5e731e0ee64 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2441,7 +2441,7 @@ elaborateInstallPlan [ (programId prog, programPath prog) | prog <- configuredPrograms compilerProgDb ] - <> (getMapLast $ perPkgOption pkgid packageConfigProgramPaths) + <> getMapLast (perPkgOption pkgid packageConfigProgramPaths) elabProgramArgs = -- Workaround for From 9a92c448ce51a56744678616aceb35c1b9c404fa Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 11:36:01 -0400 Subject: [PATCH 14/45] Partially apply then use lookupPerPkgOption --- .../src/Distribution/Client/ProjectPlanning.hs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 5e731e0ee64..e5118bb785c 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -893,28 +893,19 @@ rebuildInstallPlan let pkgname = pkgSpecifierTarget pkg stanzas = case shouldBeLocal pkg of Just pkgId -> - let testsEnabled = + let perPkgOption = lookupPerPkgOption (const True) projectConfigAllPackages projectConfigLocalPackages (getMapMappend projectConfigSpecificPackage) pkgId - packageConfigTests - benchmarksEnabled = - lookupPerPkgOption - (const True) - projectConfigAllPackages - projectConfigLocalPackages - (getMapMappend projectConfigSpecificPackage) - pkgId - packageConfigBenchmarks in Map.fromList $ [ (TestStanzas, enabled) - | enabled <- flagToList testsEnabled + | enabled <- flagToList $ perPkgOption packageConfigTests ] ++ [ (BenchStanzas, enabled) - | enabled <- flagToList benchmarksEnabled + | enabled <- flagToList $ perPkgOption packageConfigBenchmarks ] Nothing -> Map.fromList [(TestStanzas, False), (BenchStanzas, False)] ] From 53d44716a8e19fc95cc11d88b8372d7f4ba97d2b Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 11:57:04 -0400 Subject: [PATCH 15/45] Move Map.fromList in list comprehension --- .../Distribution/Client/ProjectPlanning.hs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index e5118bb785c..a1c27dfd8d0 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -5,6 +5,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} @@ -881,10 +882,11 @@ rebuildInstallPlan solverSettings = resolveSolverSettings projectConfig logMsg message rest = debugNoWrap verbosity message >> rest + perPkgOption = lookupPerPkgOption (const True) projectConfigAllPackages projectConfigLocalPackages (getMapMappend projectConfigSpecificPackage) localPackagesEnabledStanzas = Map.fromList - [ (pkgname, stanzas) + [ (pkgname, Map.fromList $ stanzas) | pkg <- localPackages , -- TODO: misnomer: we should separate -- builtin/global/inplace/local packages @@ -893,21 +895,9 @@ rebuildInstallPlan let pkgname = pkgSpecifierTarget pkg stanzas = case shouldBeLocal pkg of Just pkgId -> - let perPkgOption = - lookupPerPkgOption - (const True) - projectConfigAllPackages - projectConfigLocalPackages - (getMapMappend projectConfigSpecificPackage) - pkgId - in Map.fromList $ - [ (TestStanzas, enabled) - | enabled <- flagToList $ perPkgOption packageConfigTests - ] - ++ [ (BenchStanzas, enabled) - | enabled <- flagToList $ perPkgOption packageConfigBenchmarks - ] - Nothing -> Map.fromList [(TestStanzas, False), (BenchStanzas, False)] + ((TestStanzas,) <$> flagToList (perPkgOption pkgId packageConfigTests)) + ++ ((BenchStanzas,) <$> flagToList (perPkgOption pkgId packageConfigBenchmarks)) + Nothing -> [(TestStanzas, False), (BenchStanzas, False)] ] -- Elaborate the solver's install plan to get a fully detailed plan. This From 2ba725812c3067006c5856dcabccf4b445b2367f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 12:11:30 -0400 Subject: [PATCH 16/45] Use -XViewPattern in Just pkgId branch --- cabal-install/src/Distribution/Client/ProjectPlanning.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index a1c27dfd8d0..92e43ceecdb 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -886,7 +886,7 @@ rebuildInstallPlan localPackagesEnabledStanzas = Map.fromList - [ (pkgname, Map.fromList $ stanzas) + [ (pkgname, Map.fromList stanzas) | pkg <- localPackages , -- TODO: misnomer: we should separate -- builtin/global/inplace/local packages @@ -894,9 +894,9 @@ rebuildInstallPlan -- let pkgname = pkgSpecifierTarget pkg stanzas = case shouldBeLocal pkg of - Just pkgId -> - ((TestStanzas,) <$> flagToList (perPkgOption pkgId packageConfigTests)) - ++ ((BenchStanzas,) <$> flagToList (perPkgOption pkgId packageConfigBenchmarks)) + Just (fmap flagToList . perPkgOption -> f) -> + ((TestStanzas,) <$> f packageConfigTests) + ++ ((BenchStanzas,) <$> f packageConfigBenchmarks) Nothing -> [(TestStanzas, False), (BenchStanzas, False)] ] From c1dbdc4a1700e75f03959d06b8f9ab00195773db Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 12:19:39 -0400 Subject: [PATCH 17/45] Defer (TestStanzas,) & (BenechStanzas,) tuples --- .../src/Distribution/Client/ProjectPlanning.hs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 92e43ceecdb..8517429b0d0 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -886,18 +886,16 @@ rebuildInstallPlan localPackagesEnabledStanzas = Map.fromList - [ (pkgname, Map.fromList stanzas) + [ (pkgname, Map.fromList $ ((TestStanzas,) <$> tests) ++ ((BenchStanzas,) <$> benches)) | pkg <- localPackages , -- TODO: misnomer: we should separate -- builtin/global/inplace/local packages -- and packages explicitly mentioned in the project -- let pkgname = pkgSpecifierTarget pkg - stanzas = case shouldBeLocal pkg of - Just (fmap flagToList . perPkgOption -> f) -> - ((TestStanzas,) <$> f packageConfigTests) - ++ ((BenchStanzas,) <$> f packageConfigBenchmarks) - Nothing -> [(TestStanzas, False), (BenchStanzas, False)] + (tests, benches) = case shouldBeLocal pkg of + Just (fmap flagToList . perPkgOption -> f) -> (f packageConfigTests, f packageConfigBenchmarks) + Nothing -> ([False], [False]) ] -- Elaborate the solver's install plan to get a fully detailed plan. This From 49cb085cc801e82b5c621eccc4d3c43483dc4722 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 12:21:42 -0400 Subject: [PATCH 18/45] Move awkwardly formatted TODO comment --- .../src/Distribution/Client/ProjectPlanning.hs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 8517429b0d0..d5f198d63c5 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -884,18 +884,17 @@ rebuildInstallPlan logMsg message rest = debugNoWrap verbosity message >> rest perPkgOption = lookupPerPkgOption (const True) projectConfigAllPackages projectConfigLocalPackages (getMapMappend projectConfigSpecificPackage) + -- TODO: "local" misnomer: we should separate + -- builtin/global/inplace/local packages and packages explicitly + -- mentioned in the project. localPackagesEnabledStanzas = Map.fromList [ (pkgname, Map.fromList $ ((TestStanzas,) <$> tests) ++ ((BenchStanzas,) <$> benches)) | pkg <- localPackages - , -- TODO: misnomer: we should separate - -- builtin/global/inplace/local packages - -- and packages explicitly mentioned in the project - -- - let pkgname = pkgSpecifierTarget pkg - (tests, benches) = case shouldBeLocal pkg of - Just (fmap flagToList . perPkgOption -> f) -> (f packageConfigTests, f packageConfigBenchmarks) - Nothing -> ([False], [False]) + , let pkgname = pkgSpecifierTarget pkg + , let (tests, benches) = case shouldBeLocal pkg of + Just (fmap flagToList . perPkgOption -> f) -> (f packageConfigTests, f packageConfigBenchmarks) + Nothing -> ([False], [False]) ] -- Elaborate the solver's install plan to get a fully detailed plan. This From d5f96553b512d837861ba9d8003177a3ef149202 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 12:23:49 -0400 Subject: [PATCH 19/45] Mark a comment for REVIEW --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 39550221210..cc9d77cd61b 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -883,7 +883,7 @@ readProjectFileSkeletonGen monitorFiles [monitorNonExistentFile extensionFile] return mempty where - -- Do we prefer absolute paths for cache monitoring? + -- REVIEW: Do we prefer absolute paths for cache monitoring? makeAbsolute f | isAbsolute f = f | otherwise = distProjectRootDirectory dir f From d8c0e40f4617a660cf6b5548a34a3ab587cb199f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 12:28:15 -0400 Subject: [PATCH 20/45] Don't use cabal.project.[foo|bar] in note --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index cc9d77cd61b..4b019099ea2 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -867,10 +867,10 @@ readProjectFileSkeletonGen -- We also need to monitor all the (possibly recursive) imports. -- 'projectSkeletonImports' is a path per imported project file that starts with the leaf -- and ends with the main project file that is the root. E.g. if 'cabal.project' imports - -- 'cabal.project.foo', which imports 'cabal.project.bar', then we get two paths: + -- 'importee-1.config', which imports 'importee-2.config', then we get two paths: -- - -- > ("cabal.project.bar" :| ["cabal.project.foo", "cabal.project"]) - -- > ("cabal.project.foo" :| ["cabal.project"]) + -- > ("importee-2.config" :| ["importee-1.config", "cabal.project"]) + -- > ("importee-1.config" :| ["cabal.project"]) -- -- Consequently, we just take the heads of all the paths. monitorFiles From 0feb8f983ce07e1f995c9ab3ff52031567cc3d6d Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 12:29:25 -0400 Subject: [PATCH 21/45] Don't change haddocks, but word wrap --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 4b019099ea2..ad0a97b156e 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -846,8 +846,9 @@ readProjectLocalFreezeConfig verbosity parserOption httpTransport distDirLayout distDirLayout ProjectFileKeyFreeze --- | Reads a named extended (with imports and conditionals) config file in the given project root dir, or returns empty. --- This function is generic and can be used with the legacy or parsec parser, or a combination of both. +-- | Reads a named extended (with imports and conditionals) config file in the +-- given project root dir, or returns empty. This function is generic and can +-- be used with the legacy or parsec parser, or a combination of both. readProjectFileSkeletonGen :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonGen verbosity From 93bd6a6159759869f6c30f4747e318e41081d977 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 13:19:54 -0400 Subject: [PATCH 22/45] Take more care and expand monitoring notes --- .../src/Distribution/Client/ProjectConfig.hs | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index ad0a97b156e..6f9d64138a0 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -854,29 +854,48 @@ readProjectFileSkeletonGen verbosity httpTransport dir - key + key@(distProjectFile dir -> extensionFile) parseConfig = do exists <- liftIO $ doesFileExist extensionFile if exists then do - -- Monitor the "main" project file (this could be e.g. 'cabal.project', 'cabal.project.freeze' or - -- 'cabal.project.local'. - monitorFiles [monitorFileHashed extensionFile] - pcs <- liftIO $ parseConfig extensionFile - - -- We also need to monitor all the (possibly recursive) imports. - -- 'projectSkeletonImports' is a path per imported project file that starts with the leaf - -- and ends with the main project file that is the root. E.g. if 'cabal.project' imports - -- 'importee-1.config', which imports 'importee-2.config', then we get two paths: + pcs@(projectSkeletonImports -> allProjectFiles) <- liftIO $ parseConfig extensionFile + + -- If we have with .local or .freeze extension, these + -- aren't normally imported but there's nothing stopping the user from + -- importing them, so we throw them in with the other project files. + let additional = + if (isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName) + then [extensionFile] + else [] + + -- We need to monitor the project and all of its local imports, We + -- can't monitor remote URI imports. + -- + -- We don't allow duplicate import paths but we do allow multiple + -- imports of the same file by different paths so we'll want to take + -- care to only monitor each file once. There should only ever be one + -- root 'cabal.project' file. + -- + -- In the simple case, if 'cabal.project' imports 'importee-1.config', + -- which imports 'importee-2.config', then we get these paths from + -- 'projectSkeletonImports': -- -- > ("importee-2.config" :| ["importee-1.config", "cabal.project"]) -- > ("importee-1.config" :| ["cabal.project"]) + -- > ("cabal.project" :| []) -- - -- Consequently, we just take the heads of all the paths. + -- 'currentProjectConfigPath' gives us the head of the path, an + -- importee or the root project file. monitorFiles - [ monitorFileHashed $ makeAbsolute path - | (Nothing, currentProjectConfigPath -> path) <- projectSkeletonImports pcs + [ monitorFileHashed path + | path <- + ordNub $ + additional + ++ [ p + | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles + ] ] return pcs @@ -888,7 +907,6 @@ readProjectFileSkeletonGen makeAbsolute f | isAbsolute f = f | otherwise = distProjectRootDirectory dir f - extensionFile = distProjectFile dir key -- There are 3 different variants of the project parsing function. -- 1. readProjectFileSkeletonLegacy: always uses the legacy parser From c1ce02d995ce1a0c7d9889ad0c9b847d6a97399a Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 13:20:56 -0400 Subject: [PATCH 23/45] Follow hlint suggestion: use list comprehension --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 6f9d64138a0..bd951746f9f 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -866,9 +866,7 @@ readProjectFileSkeletonGen -- aren't normally imported but there's nothing stopping the user from -- importing them, so we throw them in with the other project files. let additional = - if (isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName) - then [extensionFile] - else [] + ([extensionFile | isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName]) -- We need to monitor the project and all of its local imports, We -- can't monitor remote URI imports. From 8fb6d5ce98db568e40155faca29802efe077c24b Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 13:34:43 -0400 Subject: [PATCH 24/45] Reduce diff --- cabal-install/src/Distribution/Client/ProjectPlanning.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index d5f198d63c5..650c25a437e 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -2487,9 +2487,6 @@ elaborateInstallPlan elabBenchmarkOptions = perPkgOptionList pkgid packageConfigBenchmarkOptions - perPkgOption :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m - perPkgOption = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig - perPkgOptionFlag :: a -> PackageId -> (PackageConfig -> Flag a) -> a perPkgOptionFlag def = fmap (fromFlagOrDefault def) . perPkgOption @@ -2503,6 +2500,9 @@ elaborateInstallPlan perPkgOptionLibExeFlag (fromFlagOrDefault -> f) pkgid (perPkgOption pkgid -> both) (perPkgOption pkgid -> lib) = (f both, f (both <> lib)) + perPkgOption :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m + perPkgOption = lookupPerPkgOption isLocalToProject allPackagesConfig localPackagesConfig perPackageConfig + inplacePackageDbs = corePackageDbs ++ [distPackageDB (compilerId compiler)] From 003dae25fc47194689e1b33e1362318680fe211e Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 13:42:12 -0400 Subject: [PATCH 25/45] Remove lookupLocalPackageConfig --- .../src/Distribution/Client/ProjectConfig.hs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index bd951746f9f..0f976569ea8 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -60,7 +60,6 @@ module Distribution.Client.ProjectConfig , fetchAndReadSourcePackages -- * Resolving configuration - , lookupLocalPackageConfig , projectConfigWithBuilderRepoContext , projectConfigWithSolverRepoContext , SolverSettings (..) @@ -265,28 +264,6 @@ import Distribution.Solver.Types.ProjectConfigPath -- Resolving configuration to settings -- --- | Look up a 'PackageConfig' field in the 'ProjectConfig' for a specific --- 'PackageName'. This returns the configuration that applies to all local --- packages plus any package-specific configuration for this package. -lookupLocalPackageConfig - :: Monoid a - => (PackageConfig -> a) - -> ProjectConfig - -> PackageName - -> a -lookupLocalPackageConfig - field - ProjectConfig - { projectConfigLocalPackages - , projectConfigSpecificPackage - } - pkgname = - field projectConfigLocalPackages - <> maybe - mempty - field - (Map.lookup pkgname (getMapMappend projectConfigSpecificPackage)) - -- | Use a 'RepoContext' based on the 'BuildTimeSettings'. projectConfigWithBuilderRepoContext :: Verbosity From 0f493706e51ebaba50bcc48231392d7417bdbf8b Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 13:49:13 -0400 Subject: [PATCH 26/45] Add changelog entry --- changelog.d/11884.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 changelog.d/11884.md diff --git a/changelog.d/11884.md b/changelog.d/11884.md new file mode 100644 index 00000000000..82ab94bcf21 --- /dev/null +++ b/changelog.d/11884.md @@ -0,0 +1,12 @@ +--- +synopsis: Fix project file monitoring +packages: [cabal-install] +prs: 11884 +issues: 11567 +--- + +Watch for changes in all project files, the root `cabal.project` and all local +files it imports. We don't monitor remote URI imports. + +Remove `lookupLocalPackageConfig`, an export from module +`Distribution.Client.ProjectConfig`. From 44c72171c930c0e22bf1514ff2a8da18c5e6b774 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 15:38:55 -0400 Subject: [PATCH 27/45] Exclude .local and .freeze if imported --- .../src/Distribution/Client/ProjectConfig.hs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 0f976569ea8..dca5454d8ee 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -230,6 +230,7 @@ import Control.Exception (handle) import Control.Monad.Trans (liftIO) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS +import Data.List ((\\)) import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map import qualified Data.Set as Set @@ -837,12 +838,13 @@ readProjectFileSkeletonGen exists <- liftIO $ doesFileExist extensionFile if exists then do + monitorFiles [monitorFileHashed extensionFile] pcs@(projectSkeletonImports -> allProjectFiles) <- liftIO $ parseConfig extensionFile -- If we have with .local or .freeze extension, these -- aren't normally imported but there's nothing stopping the user from - -- importing them, so we throw them in with the other project files. - let additional = + -- importing them. We're already monitoring this one. + let localOrFreeze = ([extensionFile | isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName]) -- We need to monitor the project and all of its local imports, We @@ -867,10 +869,10 @@ readProjectFileSkeletonGen [ monitorFileHashed path | path <- ordNub $ - additional - ++ [ p - | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles - ] + [ p + | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles + ] + \\ localOrFreeze ] return pcs From 84bf410b5e60b2c02b7af9103bb8a92a9ee697a3 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 20:17:49 -0400 Subject: [PATCH 28/45] Use filter rather than List.(\\) --- .../src/Distribution/Client/ProjectConfig.hs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index dca5454d8ee..66ecc0a50d1 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -230,7 +230,6 @@ import Control.Exception (handle) import Control.Monad.Trans (liftIO) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS -import Data.List ((\\)) import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map import qualified Data.Set as Set @@ -841,12 +840,6 @@ readProjectFileSkeletonGen monitorFiles [monitorFileHashed extensionFile] pcs@(projectSkeletonImports -> allProjectFiles) <- liftIO $ parseConfig extensionFile - -- If we have with .local or .freeze extension, these - -- aren't normally imported but there's nothing stopping the user from - -- importing them. We're already monitoring this one. - let localOrFreeze = - ([extensionFile | isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName]) - -- We need to monitor the project and all of its local imports, We -- can't monitor remote URI imports. -- @@ -865,14 +858,20 @@ readProjectFileSkeletonGen -- -- 'currentProjectConfigPath' gives us the head of the path, an -- importee or the root project file. + -- + -- If we have with .local or .freeze extension, these + -- aren't normally imported but there's nothing stopping the user from + -- importing them. We're already monitoring this one. monitorFiles [ monitorFileHashed path - | path <- - ordNub $ - [ p - | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles - ] - \\ localOrFreeze + | let localOrFreeze = isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName + , let excludeExtension = if localOrFreeze then id else filter (/= makeAbsolute extensionFile) + , path <- + excludeExtension $ + ordNub + [ p + | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles + ] ] return pcs From 1c8dc306b3920a1b06a0d77d8a8da96686998f4b Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 20:53:27 -0400 Subject: [PATCH 29/45] Always be monitoring .local + .freeze? --- .../src/Distribution/Client/ProjectConfig.hs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 66ecc0a50d1..a9fa8dd4838 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -830,15 +830,18 @@ readProjectFileSkeletonGen :: Verbosity -> HttpTransport -> DistDirLayout -> Pro readProjectFileSkeletonGen verbosity httpTransport - dir - key@(distProjectFile dir -> extensionFile) + DistDirLayout{distProjectFile, distProjectRootDirectory} + key@(distProjectFile -> extensionFile) + parseConfig = do - exists <- liftIO $ doesFileExist extensionFile + exists <- liftIO $ doesFileExist absExtensionFile if exists then do - monitorFiles [monitorFileHashed extensionFile] - pcs@(projectSkeletonImports -> allProjectFiles) <- liftIO $ parseConfig extensionFile + -- REVIEW: Shouldn't we always be monitoring the main project, its + -- freeze file and its local file? + monitorFiles [monitorFileHashed absExtensionFile] + pcs@(projectSkeletonImports -> allProjectFiles) <- liftIO $ parseConfig absExtensionFile -- We need to monitor the project and all of its local imports, We -- can't monitor remote URI imports. @@ -859,15 +862,13 @@ readProjectFileSkeletonGen -- 'currentProjectConfigPath' gives us the head of the path, an -- importee or the root project file. -- - -- If we have with .local or .freeze extension, these - -- aren't normally imported but there's nothing stopping the user from - -- importing them. We're already monitoring this one. + -- If we have extensionName of "local or "freeze", these aren't + -- normally imported but there's nothing stopping the user from + -- importing them. monitorFiles [ monitorFileHashed path - | let localOrFreeze = isExtensionOf "local" extensionName || isExtensionOf "freeze" extensionName - , let excludeExtension = if localOrFreeze then id else filter (/= makeAbsolute extensionFile) - , path <- - excludeExtension $ + | path <- + filter (/= absExtensionFile) $ ordNub [ p | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles @@ -882,7 +883,8 @@ readProjectFileSkeletonGen -- REVIEW: Do we prefer absolute paths for cache monitoring? makeAbsolute f | isAbsolute f = f - | otherwise = distProjectRootDirectory dir f + | otherwise = distProjectRootDirectory f + absExtensionFile = makeAbsolute extensionFile -- There are 3 different variants of the project parsing function. -- 1. readProjectFileSkeletonLegacy: always uses the legacy parser From a30c60781b26d51c3493b3051a21f8bd8b11bf0f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 20:54:05 -0400 Subject: [PATCH 30/45] Mark unused parameters --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index a9fa8dd4838..e4d862d4f77 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -828,8 +828,8 @@ readProjectLocalFreezeConfig verbosity parserOption httpTransport distDirLayout -- be used with the legacy or parsec parser, or a combination of both. readProjectFileSkeletonGen :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonGen - verbosity - httpTransport + _verbosity + _httpTransport DistDirLayout{distProjectFile, distProjectRootDirectory} key@(distProjectFile -> extensionFile) From a28ec45aa18ecb462cb0864e287599c20709249f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 20:57:32 -0400 Subject: [PATCH 31/45] Remove unused parameters form read...Gen --- .../src/Distribution/Client/ProjectConfig.hs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index e4d862d4f77..5d358598e3c 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -826,13 +826,10 @@ readProjectLocalFreezeConfig verbosity parserOption httpTransport distDirLayout -- | Reads a named extended (with imports and conditionals) config file in the -- given project root dir, or returns empty. This function is generic and can -- be used with the legacy or parsec parser, or a combination of both. -readProjectFileSkeletonGen :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton +readProjectFileSkeletonGen :: DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonGen - _verbosity - _httpTransport DistDirLayout{distProjectFile, distProjectRootDirectory} key@(distProjectFile -> extensionFile) - parseConfig = do exists <- liftIO $ doesFileExist absExtensionFile @@ -915,7 +912,7 @@ readProjectFileSkeleton option = -- | Read a project file using the legacy parser. readProjectFileSkeletonLegacy :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key = do - readProjectFileSkeletonGen verbosity httpTransport distDirLayout key $ \fp -> do + readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the legacy parser" parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key fp >>= liftIO . reportParseResult verbosity (extensionDescription key) fp @@ -923,7 +920,7 @@ readProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key = do -- | Read a project file using the parsec parser, but if that fails, it falls back to the legacy parser. readProjectFileSkeletonFallback :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonFallback verbosity httpTransport distDirLayout key = do - readProjectFileSkeletonGen verbosity httpTransport distDirLayout key $ \fp -> do + readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the fallback parser" (res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key fp let (_, pres) = runParseResult res @@ -946,14 +943,14 @@ readProjectFileSkeletonFallback verbosity httpTransport distDirLayout key = do -- | Read a project file using the parsec parser. readProjectFileSkeletonParsec :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonParsec verbosity httpTransport distDirLayout key = do - readProjectFileSkeletonGen verbosity httpTransport distDirLayout key $ \fp -> do + readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the parsec parser" (res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key fp liftIO $ reportParseResultParsec verbosity fp bs res readProjectFileSkeletonCompare :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonCompare verbosity httpTransport distDirLayout key = do - readProjectFileSkeletonGen verbosity httpTransport distDirLayout key $ \fp -> do + readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the comparative parser" (pres, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key fp lres <- parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key fp From 2dcecdbe48f76787ece30882c057bf521dc66670 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 21:03:25 -0400 Subject: [PATCH 32/45] Remove -Wno-unused-matches --- .../src/Distribution/Client/ProjectConfig.hs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 5d358598e3c..8424ac2e8e9 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -5,7 +5,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} -{-# OPTIONS_GHC -Wno-unused-matches #-} -- | Handling project configuration. module Distribution.Client.ProjectConfig @@ -746,7 +745,7 @@ readProjectConfig -> Flag FilePath -> DistDirLayout -> Rebuild ProjectConfigSkeleton -readProjectConfig verbosity parserOption _ (Flag True) configFileFlag _ = do +readProjectConfig verbosity _parserOption _ (Flag True) configFileFlag _ = do global <- singletonProjectConfigSkeleton <$> readGlobalConfig verbosity configFileFlag return (global <> singletonProjectConfigSkeleton defaultImplicitProjectConfig) readProjectConfig verbosity parserOption httpTransport _ configFileFlag distDirLayout = do @@ -957,7 +956,7 @@ readProjectFileSkeletonCompare verbosity httpTransport distDirLayout key = do let (_, ppres) = runParseResult pres case (lres, ppres) of -- 1. Both succeed, compare the results - (OldParser.ProjectParseOk lwarns lpcs, Right ppcs) -> do + (OldParser.ProjectParseOk _lwarns lpcs, Right ppcs) -> do unless (lpcs == ppcs) (dieWithException verbosity $ LegacyAndParsecParseResultsDiffer fp (show lpcs) (show ppcs)) liftIO $ reportParseResultParsec verbosity fp bs pres -- 2. The legacy parser failed, but the parsec parser succeeded. @@ -980,7 +979,7 @@ reportParseResultParsec -> BS.ByteString -> Parsec.ParseResult ProjectFileSource a -> IO a -reportParseResultParsec verbosity fpath contents pr = do +reportParseResultParsec verbosity fpath _contents pr = do let (warnings, result) = runParseResult pr case result of Right x -> do @@ -1005,8 +1004,8 @@ parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key extensi bs <- BS.readFile extensionFile res <- Parsec.parseProject extensionFile (distDownloadSrcDirectory distDirLayout) httpTransport verbosity $ ProjectConfigToParse bs case snd $ runParseResult res of - x@(Right skeleton) -> reportDuplicateImports verbosity skeleton >> pure (res, bs) - x@Left{} -> pure (res, bs) + Right skeleton -> reportDuplicateImports verbosity skeleton >> pure (res, bs) + Left{} -> pure (res, bs) -- | Render the 'ProjectConfig' format. -- From 268ca5e7db99b12230611f67311f63fc88270e4f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 21:44:54 -0400 Subject: [PATCH 33/45] Only monitor imports for main project --- .../src/Distribution/Client/ProjectConfig.hs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 8424ac2e8e9..9c5234efec3 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -834,11 +834,10 @@ readProjectFileSkeletonGen exists <- liftIO $ doesFileExist absExtensionFile if exists then do - -- REVIEW: Shouldn't we always be monitoring the main project, its - -- freeze file and its local file? monitorFiles [monitorFileHashed absExtensionFile] - pcs@(projectSkeletonImports -> allProjectFiles) <- liftIO $ parseConfig absExtensionFile + pcs <- liftIO $ parseConfig absExtensionFile + -- If its the main project then we have the local imports to monitor. -- We need to monitor the project and all of its local imports, We -- can't monitor remote URI imports. -- @@ -851,9 +850,9 @@ readProjectFileSkeletonGen -- which imports 'importee-2.config', then we get these paths from -- 'projectSkeletonImports': -- - -- > ("importee-2.config" :| ["importee-1.config", "cabal.project"]) - -- > ("importee-1.config" :| ["cabal.project"]) - -- > ("cabal.project" :| []) + -- > ("importee-2.config" :| ["importee-1.config", "cabal.project"]) > + -- ("importee-1.config" :| ["cabal.project"]) > ("cabal.project" :| + -- []) -- -- 'currentProjectConfigPath' gives us the head of the path, an -- importee or the root project file. @@ -861,15 +860,16 @@ readProjectFileSkeletonGen -- If we have extensionName of "local or "freeze", these aren't -- normally imported but there's nothing stopping the user from -- importing them. - monitorFiles - [ monitorFileHashed path - | path <- - filter (/= absExtensionFile) $ - ordNub - [ p - | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- allProjectFiles - ] - ] + when (key == ProjectFileKeyMain) $ do + monitorFiles + [ monitorFileHashed path + | path <- + filter (/= absExtensionFile) $ + ordNub + [ p + | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs + ] + ] return pcs else do From 9b55ea78e1ba56aba45f4f2f562a995a32fdb966 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 22:02:20 -0400 Subject: [PATCH 34/45] Filter .freeze and .local, they're read separately --- .../src/Distribution/Client/ProjectConfig.hs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 9c5234efec3..9da9b74571b 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -831,11 +831,11 @@ readProjectFileSkeletonGen key@(distProjectFile -> extensionFile) parseConfig = do - exists <- liftIO $ doesFileExist absExtensionFile + exists <- liftIO $ doesFileExist extensionFile if exists then do - monitorFiles [monitorFileHashed absExtensionFile] - pcs <- liftIO $ parseConfig absExtensionFile + monitorFiles [monitorFileHashed extensionFile] + pcs <- liftIO $ parseConfig extensionFile -- If its the main project then we have the local imports to monitor. -- We need to monitor the project and all of its local imports, We @@ -857,14 +857,18 @@ readProjectFileSkeletonGen -- 'currentProjectConfigPath' gives us the head of the path, an -- importee or the root project file. -- - -- If we have extensionName of "local or "freeze", these aren't + -- If we have an extensionName of "" it is still possible for the main + -- project to import the .local or .freeze explicitly. These aren't -- normally imported but there's nothing stopping the user from - -- importing them. + -- importing them. They're read separately and we don't want to + -- monitor them twice, so we filter them out. We're already monitoring + -- the main project file (above), so we filter that out. when (key == ProjectFileKeyMain) $ do monitorFiles [ monitorFileHashed path - | path <- - filter (/= absExtensionFile) $ + | let projFile = makeAbsolute . distProjectFile + , path <- + filter (`notElem` [extensionFile, projFile "freeze", projFile "local"]) $ ordNub [ p | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs @@ -880,7 +884,7 @@ readProjectFileSkeletonGen makeAbsolute f | isAbsolute f = f | otherwise = distProjectRootDirectory f - absExtensionFile = makeAbsolute extensionFile + extensionFile = makeAbsolute possiblyRelativeExtensionFile -- There are 3 different variants of the project parsing function. -- 1. readProjectFileSkeletonLegacy: always uses the legacy parser From 5cb8c8072b1d7f7cdbef9238839f80c7585e5bb2 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Tue, 26 May 2026 22:04:23 -0400 Subject: [PATCH 35/45] Fix up some bad comment list formatting --- cabal-install/src/Distribution/Client/ProjectConfig.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 9da9b74571b..cef73ebdebc 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -850,9 +850,9 @@ readProjectFileSkeletonGen -- which imports 'importee-2.config', then we get these paths from -- 'projectSkeletonImports': -- - -- > ("importee-2.config" :| ["importee-1.config", "cabal.project"]) > - -- ("importee-1.config" :| ["cabal.project"]) > ("cabal.project" :| - -- []) + -- "importee-2.config" :| ["importee-1.config", "cabal.project"] + -- "importee-1.config" :| ["cabal.project"] + -- "cabal.project" :| [] -- -- 'currentProjectConfigPath' gives us the head of the path, an -- importee or the root project file. From 9a743c964daefa98c29c315ca95d13ec4e3341f4 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Wed, 24 Jun 2026 07:08:48 -0400 Subject: [PATCH 36/45] Fixup after rebase --- .../src/Distribution/Client/ProjectConfig.hs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index cef73ebdebc..16aebab0295 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -828,7 +828,7 @@ readProjectLocalFreezeConfig verbosity parserOption httpTransport distDirLayout readProjectFileSkeletonGen :: DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonGen DistDirLayout{distProjectFile, distProjectRootDirectory} - key@(distProjectFile -> extensionFile) + key@(distProjectFile -> possiblyRelativeExtensionFile) parseConfig = do exists <- liftIO $ doesFileExist extensionFile @@ -868,7 +868,7 @@ readProjectFileSkeletonGen [ monitorFileHashed path | let projFile = makeAbsolute . distProjectFile , path <- - filter (`notElem` [extensionFile, projFile "freeze", projFile "local"]) $ + filter (`notElem` [extensionFile, projFile ProjectFileKeyFreeze, projFile ProjectFileKeyLocal]) $ ordNub [ p | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs @@ -917,7 +917,7 @@ readProjectFileSkeletonLegacy :: Verbosity -> HttpTransport -> DistDirLayout -> readProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key = do readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the legacy parser" - parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key fp + parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key >>= liftIO . reportParseResult verbosity (extensionDescription key) fp -- | Read a project file using the parsec parser, but if that fails, it falls back to the legacy parser. @@ -925,14 +925,14 @@ readProjectFileSkeletonFallback :: Verbosity -> HttpTransport -> DistDirLayout - readProjectFileSkeletonFallback verbosity httpTransport distDirLayout key = do readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the fallback parser" - (res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key fp + (res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key let (_, pres) = runParseResult res case pres of -- 1. Successful parse with parsec parser, handle the result as normal. Right{} -> liftIO $ reportParseResultParsec verbosity fp bs res -- 2. The parse failed with the parsec parser, fallback to the legacy parser. Left{} -> do - lres <- parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key fp + lres <- parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key case lres of -- 3a. The legacy parser worked, but the parsec parser failed! -- Report a warning to the user that this happened. @@ -948,15 +948,15 @@ readProjectFileSkeletonParsec :: Verbosity -> HttpTransport -> DistDirLayout -> readProjectFileSkeletonParsec verbosity httpTransport distDirLayout key = do readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the parsec parser" - (res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key fp + (res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key liftIO $ reportParseResultParsec verbosity fp bs res readProjectFileSkeletonCompare :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonCompare verbosity httpTransport distDirLayout key = do readProjectFileSkeletonGen distDirLayout key $ \fp -> do debug verbosity "Reading project file using the comparative parser" - (pres, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key fp - lres <- parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key fp + (pres, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key + lres <- parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key let (_, ppres) = runParseResult pres case (lres, ppres) of -- 1. Both succeed, compare the results @@ -995,16 +995,18 @@ reportParseResultParsec verbosity fpath _contents pr = do dieWithException verbosity $ ProjectConfigParseFailure $ ProjectConfigParseError errors warnings -- | Reads a named extended (with imports and conditionals) config file in the given project root dir, or returns empty. -parseProjectFileSkeletonLegacy :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> FilePath -> IO (OldParser.ProjectParseResult ProjectConfigSkeleton) -parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key extensionFile = do +parseProjectFileSkeletonLegacy :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> IO (OldParser.ProjectParseResult ProjectConfigSkeleton) +parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout key = do + let extensionFile = distProjectFile distDirLayout key bs <- BS.readFile extensionFile res <- parseProject extensionFile (distDownloadSrcDirectory distDirLayout) httpTransport verbosity $ ProjectConfigToParse bs case res of x@(OldParser.ProjectParseOk _ skeleton) -> reportDuplicateImports verbosity skeleton >> pure x x@OldParser.ProjectParseFailed{} -> pure x -parseProjectFileSkeletonParsec :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> FilePath -> IO (Parsec.ParseResult ProjectFileSource ProjectConfigSkeleton, BS.ByteString) -parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key extensionFile = do +parseProjectFileSkeletonParsec :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> IO (Parsec.ParseResult ProjectFileSource ProjectConfigSkeleton, BS.ByteString) +parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout key = do + let extensionFile = distProjectFile distDirLayout key bs <- BS.readFile extensionFile res <- Parsec.parseProject extensionFile (distDownloadSrcDirectory distDirLayout) httpTransport verbosity $ ProjectConfigToParse bs case snd $ runParseResult res of From 9ee1e1e8f73e2dfd0b36901ae26d01a0fa4bac4a Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sat, 27 Jun 2026 07:44:01 -0400 Subject: [PATCH 37/45] Filter the other key project before monitoring --- .../src/Distribution/Client/DistDirLayout.hs | 2 +- .../src/Distribution/Client/ProjectConfig.hs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cabal-install/src/Distribution/Client/DistDirLayout.hs b/cabal-install/src/Distribution/Client/DistDirLayout.hs index 0e8fd2026a5..9dd5e160846 100644 --- a/cabal-install/src/Distribution/Client/DistDirLayout.hs +++ b/cabal-install/src/Distribution/Client/DistDirLayout.hs @@ -87,7 +87,7 @@ data ProjectFileKey = ProjectFileKeyMain | ProjectFileKeyLocal | ProjectFileKeyFreeze - deriving (Eq, Ord, Show) + deriving (Eq, Ord, Show, Bounded, Enum) -- | The layout of the project state directory. Traditionally this has been -- called the @dist@ directory. diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 16aebab0295..2df4ef75291 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -857,18 +857,20 @@ readProjectFileSkeletonGen -- 'currentProjectConfigPath' gives us the head of the path, an -- importee or the root project file. -- - -- If we have an extensionName of "" it is still possible for the main - -- project to import the .local or .freeze explicitly. These aren't - -- normally imported but there's nothing stopping the user from - -- importing them. They're read separately and we don't want to - -- monitor them twice, so we filter them out. We're already monitoring - -- the main project file (above), so we filter that out. + -- It is possible for the main project file to import the .local or + -- .freeze file explicitly. It is also possible for the user to edit + -- the .local or .freeze file to import the main project file. There's + -- nothing stopping the user from setting up these imports. + + -- Each of the main project file, the .local and .freeze file are read + -- separately. We don't want to monitor them twice, so we filter them + -- out. when (key == ProjectFileKeyMain) $ do monitorFiles [ monitorFileHashed path | let projFile = makeAbsolute . distProjectFile , path <- - filter (`notElem` [extensionFile, projFile ProjectFileKeyFreeze, projFile ProjectFileKeyLocal]) $ + filter (`notElem` [projFile k | k <- filter (/= key) [minBound .. maxBound]]) $ ordNub [ p | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs From 44b5e5de34a6cd0a643fc3dd4be1cfa0b11e1192 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sat, 27 Jun 2026 07:58:48 -0400 Subject: [PATCH 38/45] Don't make absolute - Remove stray where --- .../src/Distribution/Client/ProjectConfig.hs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 2df4ef75291..8483a4cd91b 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -827,8 +827,8 @@ readProjectLocalFreezeConfig verbosity parserOption httpTransport distDirLayout -- be used with the legacy or parsec parser, or a combination of both. readProjectFileSkeletonGen :: DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton readProjectFileSkeletonGen - DistDirLayout{distProjectFile, distProjectRootDirectory} - key@(distProjectFile -> possiblyRelativeExtensionFile) + DistDirLayout{distProjectFile} + key@(distProjectFile -> extensionFile) parseConfig = do exists <- liftIO $ doesFileExist extensionFile @@ -868,12 +868,12 @@ readProjectFileSkeletonGen when (key == ProjectFileKeyMain) $ do monitorFiles [ monitorFileHashed path - | let projFile = makeAbsolute . distProjectFile + | let projFile = distProjectFile , path <- filter (`notElem` [projFile k | k <- filter (/= key) [minBound .. maxBound]]) $ ordNub [ p - | (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs + | (Nothing, currentProjectConfigPath -> p) <- projectSkeletonImports pcs ] ] @@ -881,12 +881,6 @@ readProjectFileSkeletonGen else do monitorFiles [monitorNonExistentFile extensionFile] return mempty - where - -- REVIEW: Do we prefer absolute paths for cache monitoring? - makeAbsolute f - | isAbsolute f = f - | otherwise = distProjectRootDirectory f - extensionFile = makeAbsolute possiblyRelativeExtensionFile -- There are 3 different variants of the project parsing function. -- 1. readProjectFileSkeletonLegacy: always uses the legacy parser From 059b444247971db6c4d58a3590c530c0e2667782 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sat, 27 Jun 2026 08:37:06 -0400 Subject: [PATCH 39/45] Rework test --- .../ProjectImport/FileMonitoring/cabal.out | 33 +++++++++++++++++++ .../FileMonitoring/cabal.test.hs | 31 ++++++++++++----- 2 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.out diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.out new file mode 100644 index 00000000000..3e8dce69f7b --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.out @@ -0,0 +1,33 @@ +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +# cabal build +Up to date +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index bb4b9504190..a06b70f4c74 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -2,15 +2,28 @@ import Test.Cabal.Prelude import System.Exit (ExitCode(ExitFailure)) import System.IO -main = cabalTest (withProjectFile "cabal.project" $ do - result <- fails $ recordMode DoNotRecord $ cabal' "build" [] - assertExitCode (ExitFailure 1) result - assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." result +-- If tests are enabled then we get this output: +-- [1 of 1] Compiling Main +-- test/Main.hs:4:8: error: [GHC-88464] +-- Variable not in scope: puStrLn :: [Char] -> IO () +-- Suggested fix: Perhaps use ‘putStrLn’ (imported from Prelude) +-- | +-- 4 | main = puStrLn "Test suite not yet implemented." +-- | +main = cabalTest . recordMode RecordMarked $ do + projEnabledTests <- fails $ cabal' "build" [] + assertExitCode (ExitFailure 1) projEnabledTests + assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests - -- change the imported project file + cmdDisabledTests <- cabal' "build" ["--disable-tests"] + assertOutputDoesNotContain "Test suite not yet implement" cmdDisabledTests + + -- Change the imported project file with "Tests: False" test_dir <- fmap testTmpDir getTestEnv liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" - result' <- recordMode DoNotRecord $ cabal' "build" [] - assertOutputDoesNotContain "Test suite not yet implement" result' - assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." result' - ) + projDisabledTests <- cabal' "build" [] + assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests + assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests + + cmdEnabledTests <- fails $ cabal' "build" ["--enable-tests"] + assertOutputContains "Test suite not yet implement" cmdEnabledTests From cd4579dde14afe049c51a53c436634fbca3909e4 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sun, 28 Jun 2026 07:23:17 -0400 Subject: [PATCH 40/45] Direct output to cabal.main-project.out --- .../FileMonitoring/{cabal.out => cabal.main-project.out} | 0 .../PackageTests/ProjectImport/FileMonitoring/cabal.test.hs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/{cabal.out => cabal.main-project.out} (100%) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out similarity index 100% rename from cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.out rename to cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index a06b70f4c74..1e48a8faf17 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -10,7 +10,7 @@ import System.IO -- | -- 4 | main = puStrLn "Test suite not yet implemented." -- | -main = cabalTest . recordMode RecordMarked $ do +main = cabalTest' "main-project" . recordMode RecordMarked $ do projEnabledTests <- fails $ cabal' "build" [] assertExitCode (ExitFailure 1) projEnabledTests assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests From 7af335c1ac7057724b75d3a71495a94edc2ff9b9 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sun, 28 Jun 2026 07:57:33 -0400 Subject: [PATCH 41/45] Add tests for .local and .freeze monitoring --- .../FileMonitoring/cabal.freeze-only.out | 31 ++++++++++++++ .../FileMonitoring/cabal.freeze-only.project | 2 + .../cabal.freeze-only.project.freeze | 6 +++ .../FileMonitoring/cabal.local-only.out | 31 ++++++++++++++ .../FileMonitoring/cabal.local-only.project | 2 + .../cabal.local-only.project.local | 6 +++ .../FileMonitoring/cabal.main-project.out | 5 ++- .../FileMonitoring/cabal.test.hs | 40 ++++++++++++------- 8 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project.freeze create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project create mode 100644 cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project.local diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out new file mode 100644 index 00000000000..0275cb6da72 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out @@ -0,0 +1,31 @@ +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project new file mode 100644 index 00000000000..d2a7aaa3993 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project @@ -0,0 +1,2 @@ +-- NOTE: Intentionally empty as we can't specify +-- --project-file=cabal.freeze-only.project when that file doesn't exist. diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project.freeze b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project.freeze new file mode 100644 index 00000000000..469e652adaf --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.project.freeze @@ -0,0 +1,6 @@ +packages: ./cabal-project-repro.cabal + +package * + Tests: True + +import: nested/hop.config diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out new file mode 100644 index 00000000000..0275cb6da72 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out @@ -0,0 +1,31 @@ +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project new file mode 100644 index 00000000000..84b597f1aae --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project @@ -0,0 +1,2 @@ +-- NOTE: Intentionally empty as we can't specify +-- --project-file=cabal.local-only.project when that file doesn't exist. diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project.local b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project.local new file mode 100644 index 00000000000..469e652adaf --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.project.local @@ -0,0 +1,6 @@ +packages: ./cabal-project-repro.cabal + +package * + Tests: True + +import: nested/hop.config diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out index 3e8dce69f7b..412100a1253 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out @@ -20,8 +20,6 @@ Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' wh Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... # cabal build -Up to date -# cabal build Resolving dependencies... Build profile: -w ghc- -O1 In order, the following will be built: @@ -31,3 +29,6 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# cabal build +Resolving dependencies... +Up to date diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index 1e48a8faf17..0cac9f4c139 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -10,20 +10,32 @@ import System.IO -- | -- 4 | main = puStrLn "Test suite not yet implemented." -- | -main = cabalTest' "main-project" . recordMode RecordMarked $ do - projEnabledTests <- fails $ cabal' "build" [] - assertExitCode (ExitFailure 1) projEnabledTests - assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests +main = do + cabalTest' "main-project" . recordMode RecordMarked $ + runTest ["--project-file=cabal.project"] True + cabalTest' "local-only" . recordMode RecordMarked $ + runTest ["--project-file=cabal.local-only.project"] False + cabalTest' "freeze-only" . recordMode RecordMarked $ + runTest ["--project-file=cabal.freeze-only.project"] False + where + testNotYetImplemented = "Test suite not yet implemented" - cmdDisabledTests <- cabal' "build" ["--disable-tests"] - assertOutputDoesNotContain "Test suite not yet implement" cmdDisabledTests + runTest projOpts toggle = do + projEnabledTests <- fails $ cabal' "build" projOpts + assertExitCode (ExitFailure 1) projEnabledTests + assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests - -- Change the imported project file with "Tests: False" - test_dir <- fmap testTmpDir getTestEnv - liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" - projDisabledTests <- cabal' "build" [] - assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests - assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests + cmdDisabledTests <- cabal' "build" (projOpts ++ ["--disable-tests"]) + assertOutputDoesNotContain testNotYetImplemented cmdDisabledTests - cmdEnabledTests <- fails $ cabal' "build" ["--enable-tests"] - assertOutputContains "Test suite not yet implement" cmdEnabledTests + cmdEnabledTests <- fails $ cabal' "build" (projOpts ++ ["--enable-tests"]) + assertOutputContains testNotYetImplemented cmdEnabledTests + + when toggle $ do + -- Change the imported project file with "Tests: False" + test_dir <- fmap testTmpDir getTestEnv + liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" + projDisabledTests <- cabal' "build" projOpts + assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests + assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests + liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: True" From da140cfbc19ab433079cc4413b7f62774c0a6943 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sun, 28 Jun 2026 08:04:26 -0400 Subject: [PATCH 42/45] Add log messages --- .../ProjectImport/FileMonitoring/cabal.freeze-only.out | 3 +++ .../ProjectImport/FileMonitoring/cabal.local-only.out | 3 +++ .../ProjectImport/FileMonitoring/cabal.main-project.out | 4 ++++ .../PackageTests/ProjectImport/FileMonitoring/cabal.test.hs | 5 +++++ 4 files changed, 15 insertions(+) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out index 0275cb6da72..1f93fb9a4db 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out @@ -1,3 +1,4 @@ +# As-is, the project should not build # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -10,6 +11,7 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# Disabling tests on the command line # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -19,6 +21,7 @@ Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +# Enabling tests on the command line # cabal build Resolving dependencies... Build profile: -w ghc- -O1 diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out index 0275cb6da72..1f93fb9a4db 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out @@ -1,3 +1,4 @@ +# As-is, the project should not build # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -10,6 +11,7 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# Disabling tests on the command line # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -19,6 +21,7 @@ Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +# Enabling tests on the command line # cabal build Resolving dependencies... Build profile: -w ghc- -O1 diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out index 412100a1253..114d67031af 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out @@ -1,3 +1,4 @@ +# As-is, the project should not build # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -10,6 +11,7 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# Disabling tests on the command line # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -19,6 +21,7 @@ Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +# Enabling tests on the command line # cabal build Resolving dependencies... Build profile: -w ghc- -O1 @@ -29,6 +32,7 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# Rewriting an imported project file to disable tests # cabal build Resolving dependencies... Up to date diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index 0cac9f4c139..8334836fdfa 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -19,20 +19,25 @@ main = do runTest ["--project-file=cabal.freeze-only.project"] False where testNotYetImplemented = "Test suite not yet implemented" + log = recordHeader . pure runTest projOpts toggle = do + log "As-is, the project should not build" projEnabledTests <- fails $ cabal' "build" projOpts assertExitCode (ExitFailure 1) projEnabledTests assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests + log "Disabling tests on the command line" cmdDisabledTests <- cabal' "build" (projOpts ++ ["--disable-tests"]) assertOutputDoesNotContain testNotYetImplemented cmdDisabledTests + log "Enabling tests on the command line" cmdEnabledTests <- fails $ cabal' "build" (projOpts ++ ["--enable-tests"]) assertOutputContains testNotYetImplemented cmdEnabledTests when toggle $ do -- Change the imported project file with "Tests: False" + log "Rewriting an imported project file to disable tests" test_dir <- fmap testTmpDir getTestEnv liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" projDisabledTests <- cabal' "build" projOpts From 4d7b4ebb923efee6a97478e9f105d6114cea8455 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sun, 28 Jun 2026 08:07:03 -0400 Subject: [PATCH 43/45] Remove toggle and clean --- .../FileMonitoring/cabal.freeze-only.out | 11 ++++++++ .../FileMonitoring/cabal.local-only.out | 11 ++++++++ .../FileMonitoring/cabal.main-project.out | 9 ++++++- .../FileMonitoring/cabal.test.hs | 26 +++++++++---------- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out index 1f93fb9a4db..23cf1c66aea 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out @@ -32,3 +32,14 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# Rewriting an imported project file to disable tests +# cabal clean +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out index 1f93fb9a4db..23cf1c66aea 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out @@ -32,3 +32,14 @@ Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1. Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# Rewriting an imported project file to disable tests +# cabal clean +# cabal build +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out index 114d67031af..23cf1c66aea 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out @@ -33,6 +33,13 @@ Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0.. Error: [Cabal-7125] Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. # Rewriting an imported project file to disable tests +# cabal clean # cabal build Resolving dependencies... -Up to date +Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) +Configuring executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... +Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index 8334836fdfa..17077d25751 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -12,16 +12,16 @@ import System.IO -- | main = do cabalTest' "main-project" . recordMode RecordMarked $ - runTest ["--project-file=cabal.project"] True + runTest ["--project-file=cabal.project"] cabalTest' "local-only" . recordMode RecordMarked $ - runTest ["--project-file=cabal.local-only.project"] False + runTest ["--project-file=cabal.local-only.project"] cabalTest' "freeze-only" . recordMode RecordMarked $ - runTest ["--project-file=cabal.freeze-only.project"] False + runTest ["--project-file=cabal.freeze-only.project"] where testNotYetImplemented = "Test suite not yet implemented" log = recordHeader . pure - runTest projOpts toggle = do + runTest projOpts = do log "As-is, the project should not build" projEnabledTests <- fails $ cabal' "build" projOpts assertExitCode (ExitFailure 1) projEnabledTests @@ -35,12 +35,12 @@ main = do cmdEnabledTests <- fails $ cabal' "build" (projOpts ++ ["--enable-tests"]) assertOutputContains testNotYetImplemented cmdEnabledTests - when toggle $ do - -- Change the imported project file with "Tests: False" - log "Rewriting an imported project file to disable tests" - test_dir <- fmap testTmpDir getTestEnv - liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" - projDisabledTests <- cabal' "build" projOpts - assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests - assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests - liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: True" + -- Change the imported project file with "Tests: False" + log "Rewriting an imported project file to disable tests" + test_dir <- fmap testTmpDir getTestEnv + liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" + projDisabledTests <- cabal' "clean" projOpts + projDisabledTests <- cabal' "build" projOpts + assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests + assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests + liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: True" From c115cb2b2c2b373a128cccaf997800eea0cfe973 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sun, 28 Jun 2026 08:14:09 -0400 Subject: [PATCH 44/45] Split command tests from project tests --- .../FileMonitoring/cabal.freeze-only.out | 25 +++++++------- .../FileMonitoring/cabal.local-only.out | 25 +++++++------- .../FileMonitoring/cabal.main-project.out | 25 +++++++------- .../FileMonitoring/cabal.test.hs | 33 +++++++++++-------- 4 files changed, 56 insertions(+), 52 deletions(-) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out index 23cf1c66aea..b635a962832 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.freeze-only.out @@ -1,16 +1,3 @@ -# As-is, the project should not build -# cabal build -Resolving dependencies... -Build profile: -w ghc- -O1 -In order, the following will be built: - - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) - - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) -Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. -Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Error: [Cabal-7125] -Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. # Disabling tests on the command line # cabal build Resolving dependencies... @@ -25,6 +12,18 @@ Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... # cabal build Resolving dependencies... Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) +Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# As-is, the project should not build +# cabal build +Build profile: -w ghc- -O1 In order, the following will be built: - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out index 23cf1c66aea..b635a962832 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.local-only.out @@ -1,16 +1,3 @@ -# As-is, the project should not build -# cabal build -Resolving dependencies... -Build profile: -w ghc- -O1 -In order, the following will be built: - - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) - - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) -Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. -Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Error: [Cabal-7125] -Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. # Disabling tests on the command line # cabal build Resolving dependencies... @@ -25,6 +12,18 @@ Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... # cabal build Resolving dependencies... Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) +Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# As-is, the project should not build +# cabal build +Build profile: -w ghc- -O1 In order, the following will be built: - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out index 23cf1c66aea..b635a962832 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.main-project.out @@ -1,16 +1,3 @@ -# As-is, the project should not build -# cabal build -Resolving dependencies... -Build profile: -w ghc- -O1 -In order, the following will be built: - - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) - - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (first run) -Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. -Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... -Error: [Cabal-7125] -Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. # Disabling tests on the command line # cabal build Resolving dependencies... @@ -25,6 +12,18 @@ Building executable 'filemonitor-test' for cabal-project-repro-0.1.0.0... # cabal build Resolving dependencies... Build profile: -w ghc- -O1 +In order, the following will be built: + - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) + - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) +Configuring test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Warning: [unknown-file] The 'license-file' field refers to the file 'LICENSE' which does not exist. +Preprocessing test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Building test suite 'cabal-project-repro-test' for cabal-project-repro-0.1.0.0... +Error: [Cabal-7125] +Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test. +# As-is, the project should not build +# cabal build +Build profile: -w ghc- -O1 In order, the following will be built: - cabal-project-repro-0.1.0.0 (test:cabal-project-repro-test) (first run) - cabal-project-repro-0.1.0.0 (exe:filemonitor-test) (configuration changed) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index 17077d25751..092dfd272d3 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -11,22 +11,23 @@ import System.IO -- 4 | main = puStrLn "Test suite not yet implemented." -- | main = do - cabalTest' "main-project" . recordMode RecordMarked $ - runTest ["--project-file=cabal.project"] - cabalTest' "local-only" . recordMode RecordMarked $ - runTest ["--project-file=cabal.local-only.project"] - cabalTest' "freeze-only" . recordMode RecordMarked $ - runTest ["--project-file=cabal.freeze-only.project"] + cabalTest' "main-project" . recordMode RecordMarked $ do + let opts = ["--project-file=cabal.project"] + runCommandTest opts + runProjectTest opts + cabalTest' "local-only" . recordMode RecordMarked $ do + let opts = ["--project-file=cabal.local-only.project"] + runCommandTest opts + runProjectTest opts + cabalTest' "freeze-only" . recordMode RecordMarked $ do + let opts = ["--project-file=cabal.freeze-only.project"] + runCommandTest opts + runProjectTest opts where testNotYetImplemented = "Test suite not yet implemented" log = recordHeader . pure - runTest projOpts = do - log "As-is, the project should not build" - projEnabledTests <- fails $ cabal' "build" projOpts - assertExitCode (ExitFailure 1) projEnabledTests - assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests - + runCommandTest projOpts = do log "Disabling tests on the command line" cmdDisabledTests <- cabal' "build" (projOpts ++ ["--disable-tests"]) assertOutputDoesNotContain testNotYetImplemented cmdDisabledTests @@ -35,11 +36,17 @@ main = do cmdEnabledTests <- fails $ cabal' "build" (projOpts ++ ["--enable-tests"]) assertOutputContains testNotYetImplemented cmdEnabledTests + runProjectTest projOpts = do + log "As-is, the project should not build" + projEnabledTests <- fails $ cabal' "build" projOpts + assertExitCode (ExitFailure 1) projEnabledTests + assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests + -- Change the imported project file with "Tests: False" log "Rewriting an imported project file to disable tests" test_dir <- fmap testTmpDir getTestEnv liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" - projDisabledTests <- cabal' "clean" projOpts + _ <- cabal' "clean" projOpts projDisabledTests <- cabal' "build" projOpts assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests From 7ce4a96fa5b4e7660251a50b554152d9d5b5871f Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Sun, 28 Jun 2026 08:16:42 -0400 Subject: [PATCH 45/45] Add failureMsg --- .../ProjectImport/FileMonitoring/cabal.test.hs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs index 092dfd272d3..aeabed2a064 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/FileMonitoring/cabal.test.hs @@ -24,23 +24,24 @@ main = do runCommandTest opts runProjectTest opts where - testNotYetImplemented = "Test suite not yet implemented" + testNotYetImplementedMsg = "Test suite not yet implemented" + failureMsg = "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." log = recordHeader . pure runCommandTest projOpts = do log "Disabling tests on the command line" cmdDisabledTests <- cabal' "build" (projOpts ++ ["--disable-tests"]) - assertOutputDoesNotContain testNotYetImplemented cmdDisabledTests + assertOutputDoesNotContain testNotYetImplementedMsg cmdDisabledTests log "Enabling tests on the command line" cmdEnabledTests <- fails $ cabal' "build" (projOpts ++ ["--enable-tests"]) - assertOutputContains testNotYetImplemented cmdEnabledTests + assertOutputContains testNotYetImplementedMsg cmdEnabledTests runProjectTest projOpts = do log "As-is, the project should not build" projEnabledTests <- fails $ cabal' "build" projOpts assertExitCode (ExitFailure 1) projEnabledTests - assertOutputContains "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projEnabledTests + assertOutputContains failureMsg projEnabledTests -- Change the imported project file with "Tests: False" log "Rewriting an imported project file to disable tests" @@ -48,6 +49,6 @@ main = do liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: False" _ <- cabal' "clean" projOpts projDisabledTests <- cabal' "build" projOpts - assertOutputDoesNotContain "Test suite not yet implement" projDisabledTests - assertOutputDoesNotContain "Failed to build cabal-project-repro-0.1.0.0-inplace-cabal-project-repro-test." projDisabledTests + assertOutputDoesNotContain testNotYetImplementedMsg projDisabledTests + assertOutputDoesNotContain failureMsg projDisabledTests liftIO $ writeFile (test_dir "test" "tests-toggle.config") "package *\n Tests: True"