Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d41a64d
Monitor imported cabal.project files
hasufell Mar 4, 2026
4b0a413
Satisfy fourmolu
philderbeast May 25, 2026
8062726
Undo removal of export of lookupLocalPackageConfig
philderbeast May 25, 2026
76cf6ec
Promote comment to haddocks
philderbeast May 26, 2026
e6065e6
Reformat that fourmolu accepts
philderbeast May 26, 2026
a7807ce
Change the arg order for partial application
philderbeast May 26, 2026
86492e6
Rename lookupPerPkgOption' to perPkgOption
philderbeast May 26, 2026
cbb8a4b
Change arg order with perPkgOptionFlag, def first
philderbeast May 26, 2026
280b1b6
Use function composition for perPkgOption*
philderbeast May 26, 2026
bbc376b
Add type sigs for all perPkgOption* functions
philderbeast May 26, 2026
856efff
Inline each perPkgOption* only used once
philderbeast May 26, 2026
70def63
Use -XViewPatterns in perPkgOptionLibExeFlag
philderbeast May 26, 2026
1bbd5ac
Follow hlint suggestion: move bracket to avoid $
philderbeast May 26, 2026
9a92c44
Partially apply then use lookupPerPkgOption
philderbeast May 26, 2026
53d4471
Move Map.fromList in list comprehension
philderbeast May 26, 2026
2ba7258
Use -XViewPattern in Just pkgId branch
philderbeast May 26, 2026
c1dbdc4
Defer (TestStanzas,) & (BenechStanzas,) tuples
philderbeast May 26, 2026
49cb085
Move awkwardly formatted TODO comment
philderbeast May 26, 2026
d5f9655
Mark a comment for REVIEW
philderbeast May 26, 2026
d8c0e40
Don't use cabal.project.[foo|bar] in note
philderbeast May 26, 2026
0feb8f9
Don't change haddocks, but word wrap
philderbeast May 26, 2026
93bd6a6
Take more care and expand monitoring notes
philderbeast May 26, 2026
c1ce02d
Follow hlint suggestion: use list comprehension
philderbeast May 26, 2026
8fb6d5c
Reduce diff
philderbeast May 26, 2026
003dae2
Remove lookupLocalPackageConfig
philderbeast May 26, 2026
0f49370
Add changelog entry
philderbeast May 26, 2026
44c7217
Exclude .local and .freeze if imported
philderbeast May 26, 2026
84bf410
Use filter rather than List.(\\)
philderbeast May 27, 2026
1c8dc30
Always be monitoring .local + .freeze?
philderbeast May 27, 2026
a30c607
Mark unused parameters
philderbeast May 27, 2026
a28ec45
Remove unused parameters form read...Gen
philderbeast May 27, 2026
2dcecdb
Remove -Wno-unused-matches
philderbeast May 27, 2026
268ca5e
Only monitor imports for main project
philderbeast May 27, 2026
9b55ea7
Filter .freeze and .local, they're read separately
philderbeast May 27, 2026
5cb8c80
Fix up some bad comment list formatting
philderbeast May 27, 2026
9a743c9
Fixup after rebase
philderbeast Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 72 additions & 55 deletions cabal-install/src/Distribution/Client/ProjectConfig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-unused-matches #-}
{-# LANGUAGE ViewPatterns #-}

-- | Handling project configuration.
module Distribution.Client.ProjectConfig
Expand Down Expand Up @@ -59,7 +59,6 @@ module Distribution.Client.ProjectConfig
, fetchAndReadSourcePackages

-- * Resolving configuration
, lookupLocalPackageConfig
, projectConfigWithBuilderRepoContext
, projectConfigWithSolverRepoContext
, SolverSettings (..)
Expand Down Expand Up @@ -264,28 +263,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
Expand Down Expand Up @@ -768,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
Expand Down Expand Up @@ -845,31 +822,69 @@ 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.
readProjectFileSkeletonGen :: Verbosity -> HttpTransport -> DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton
-- | 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 :: DistDirLayout -> ProjectFileKey -> (FilePath -> IO ProjectConfigSkeleton) -> Rebuild ProjectConfigSkeleton
readProjectFileSkeletonGen
verbosity
httpTransport
dir
key
DistDirLayout{distProjectFile, distProjectRootDirectory}
key@(distProjectFile -> possiblyRelativeExtensionFile)
parseConfig =
do
exists <- liftIO $ doesFileExist extensionFile
if exists
then do
monitorFiles [monitorFileHashed extensionFile]
pcs <- liftIO $ parseConfig extensionFile
monitorFiles
[ monitorFileHashed (projectConfigPathRoot path)
| (Nothing, path) <- projectSkeletonImports pcs
]

-- 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.
--
-- 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" :| []
--
-- '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.
when (key == ProjectFileKeyMain) $ do
monitorFiles
[ monitorFileHashed path
| let projFile = makeAbsolute . distProjectFile
, path <-
filter (`notElem` [extensionFile, projFile ProjectFileKeyFreeze, projFile ProjectFileKeyLocal]) $
ordNub
[ p
| (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs
]
]

return pcs
else do
monitorFiles [monitorNonExistentFile extensionFile]
return mempty
where
extensionFile = distProjectFile dir key
-- 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
Expand Down Expand Up @@ -900,24 +915,24 @@ 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
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.
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
(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.
Expand All @@ -931,21 +946,21 @@ 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
(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 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
(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
(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.
Expand All @@ -968,7 +983,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
Expand All @@ -980,21 +995,23 @@ 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
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.
--
Expand Down
Loading
Loading