Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3be28ab
Monitor imported cabal.project files
hasufell Mar 4, 2026
ef279b9
Satisfy fourmolu
philderbeast May 25, 2026
b2a15a8
Undo removal of export of lookupLocalPackageConfig
philderbeast May 25, 2026
da8c72c
Promote comment to haddocks
philderbeast May 26, 2026
a24dfef
Reformat that fourmolu accepts
philderbeast May 26, 2026
de0ca84
Change the arg order for partial application
philderbeast May 26, 2026
bd324b8
Rename lookupPerPkgOption' to perPkgOption
philderbeast May 26, 2026
54f3575
Change arg order with perPkgOptionFlag, def first
philderbeast May 26, 2026
1eb62bc
Use function composition for perPkgOption*
philderbeast May 26, 2026
e7e1cca
Add type sigs for all perPkgOption* functions
philderbeast May 26, 2026
8e2994d
Inline each perPkgOption* only used once
philderbeast May 26, 2026
ae43cd5
Use -XViewPatterns in perPkgOptionLibExeFlag
philderbeast May 26, 2026
a432271
Follow hlint suggestion: move bracket to avoid $
philderbeast May 26, 2026
daec305
Partially apply then use lookupPerPkgOption
philderbeast May 26, 2026
b0a0340
Move Map.fromList in list comprehension
philderbeast May 26, 2026
f67ff64
Use -XViewPattern in Just pkgId branch
philderbeast May 26, 2026
41a3517
Defer (TestStanzas,) & (BenechStanzas,) tuples
philderbeast May 26, 2026
29586be
Move awkwardly formatted TODO comment
philderbeast May 26, 2026
720c59f
Mark a comment for REVIEW
philderbeast May 26, 2026
89b155c
Don't use cabal.project.[foo|bar] in note
philderbeast May 26, 2026
e3b5df3
Don't change haddocks, but word wrap
philderbeast May 26, 2026
18ce209
Take more care and expand monitoring notes
philderbeast May 26, 2026
3af2c7d
Follow hlint suggestion: use list comprehension
philderbeast May 26, 2026
9c8f107
Reduce diff
philderbeast May 26, 2026
1de51d8
Remove lookupLocalPackageConfig
philderbeast May 26, 2026
86a51e4
Add changelog entry
philderbeast May 26, 2026
3e0f928
Exclude .local and .freeze if imported
philderbeast May 26, 2026
f4dbfe4
Use filter rather than List.(\\)
philderbeast May 27, 2026
850c698
Always be monitoring .local + .freeze?
philderbeast May 27, 2026
05d9e65
Mark unused parameters
philderbeast May 27, 2026
42f2ea9
Remove unused parameters form read...Gen
philderbeast May 27, 2026
3919a69
Remove -Wno-unused-matches
philderbeast May 27, 2026
988b26d
Only monitor imports for main project
philderbeast May 27, 2026
3ad5e89
Filter .freeze and .local, they're read separately
philderbeast May 27, 2026
cc57ff1
Fix up some bad comment list formatting
philderbeast May 27, 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
115 changes: 67 additions & 48 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 @@ -263,28 +262,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 @@ -767,7 +744,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 @@ -839,32 +816,74 @@ readProjectLocalFreezeConfig verbosity parserOption httpTransport distDirLayout
"freeze"
"project freeze file"

-- | 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 -> String -> String -> (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
verbosity
httpTransport
dir
extensionName
extensionDescription
:: DistDirLayout
-> String
-- ^ "" for the main project file, or one of "local" or "freeze"
-> (FilePath -> IO ProjectConfigSkeleton)
-> Rebuild ProjectConfigSkeleton
readProjectFileSkeletonGen
DistDirLayout{distProjectFile, distProjectRootDirectory}
extensionName@(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 (null extensionName) $ do
monitorFiles
[ monitorFileHashed path
| let projFile = makeAbsolute . distProjectFile
, path <-
filter (`notElem` [extensionFile, projFile "freeze", projFile "local"]) $
ordNub
[ p
| (Nothing, makeAbsolute . currentProjectConfigPath -> p) <- projectSkeletonImports pcs
]
]

return pcs
else do
monitorFiles [monitorNonExistentFile extensionFile]
return mempty
where
extensionFile = distProjectFile dir extensionName
-- 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 @@ -895,15 +914,15 @@ readProjectFileSkeleton option =
-- | Read a project file using the legacy parser.
readProjectFileSkeletonLegacy :: Verbosity -> HttpTransport -> DistDirLayout -> String -> String -> Rebuild ProjectConfigSkeleton
readProjectFileSkeletonLegacy verbosity httpTransport distDirLayout extensionName extensionDescription = do
readProjectFileSkeletonGen verbosity httpTransport distDirLayout extensionName extensionDescription $ \fp -> do
readProjectFileSkeletonGen distDirLayout extensionName $ \fp -> do
debug verbosity "Reading project file using the legacy parser"
parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout extensionName extensionDescription fp
>>= liftIO . reportParseResult verbosity extensionDescription fp

-- | Read a project file using the parsec parser, but if that fails, it falls back to the legacy parser.
readProjectFileSkeletonFallback :: Verbosity -> HttpTransport -> DistDirLayout -> String -> String -> Rebuild ProjectConfigSkeleton
readProjectFileSkeletonFallback verbosity httpTransport distDirLayout extensionName extensionDescription = do
readProjectFileSkeletonGen verbosity httpTransport distDirLayout extensionName extensionDescription $ \fp -> do
readProjectFileSkeletonGen distDirLayout extensionName $ \fp -> do
debug verbosity "Reading project file using the fallback parser"
(res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout extensionName extensionDescription fp
let (_, pres) = runParseResult res
Expand All @@ -926,21 +945,21 @@ readProjectFileSkeletonFallback verbosity httpTransport distDirLayout extensionN
-- | Read a project file using the parsec parser.
readProjectFileSkeletonParsec :: Verbosity -> HttpTransport -> DistDirLayout -> String -> String -> Rebuild ProjectConfigSkeleton
readProjectFileSkeletonParsec verbosity httpTransport distDirLayout extensionName extensionDescription = do
readProjectFileSkeletonGen verbosity httpTransport distDirLayout extensionName extensionDescription $ \fp -> do
readProjectFileSkeletonGen distDirLayout extensionName $ \fp -> do
debug verbosity "Reading project file using the parsec parser"
(res, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout extensionName extensionDescription fp
liftIO $ reportParseResultParsec verbosity fp bs res

readProjectFileSkeletonCompare :: Verbosity -> HttpTransport -> DistDirLayout -> String -> String -> Rebuild ProjectConfigSkeleton
readProjectFileSkeletonCompare verbosity httpTransport distDirLayout extensionName extensionDescription = do
readProjectFileSkeletonGen verbosity httpTransport distDirLayout extensionName extensionDescription $ \fp -> do
readProjectFileSkeletonGen distDirLayout extensionName $ \fp -> do
debug verbosity "Reading project file using the comparative parser"
(pres, bs) <- parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout extensionName extensionDescription fp
lres <- parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout extensionName extensionDescription fp
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 @@ -963,7 +982,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 @@ -976,20 +995,20 @@ reportParseResultParsec verbosity fpath contents pr = do

-- | Reads a named extended (with imports and conditionals) config file in the given project root dir, or returns empty.
parseProjectFileSkeletonLegacy :: Verbosity -> HttpTransport -> DistDirLayout -> String -> String -> FilePath -> IO (OldParser.ProjectParseResult ProjectConfigSkeleton)
parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout extensionName extensionDescription extensionFile = do
parseProjectFileSkeletonLegacy verbosity httpTransport distDirLayout _extName _extDescription extensionFile = do
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 -> String -> String -> FilePath -> IO (Parsec.ParseResult ProjectFileSource ProjectConfigSkeleton, BS.ByteString)
parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout extensionName extensionDescription extensionFile = do
parseProjectFileSkeletonParsec verbosity httpTransport distDirLayout _extName _extDescription extensionFile = do
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