Skip to content

Commit 2d3b1f8

Browse files
committed
Error on duplicate imports
1 parent 0ffb925 commit 2d3b1f8

File tree

3 files changed

+45
-15
lines changed

3 files changed

+45
-15
lines changed

cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Distribution.Solver.Types.ProjectConfigPath
1212
-- * Messages
1313
, docProjectConfigPath
1414
, cyclicalImportMsg
15+
, duplicateImportMsg
1516
, docProjectConfigPathFailReason
1617

1718
-- * Checks and Normalization
@@ -72,6 +73,15 @@ cyclicalImportMsg path@(ProjectConfigPath (duplicate :| _)) =
7273
, nest 2 (docProjectConfigPath path)
7374
]
7475

76+
-- | A message for a duplicate import.
77+
duplicateImportMsg :: FilePath -> ProjectConfigPath -> [(FilePath, ProjectConfigPath)] -> Doc
78+
duplicateImportMsg duplicate path dupImportsBy =
79+
vcat
80+
[ text "duplicate import of" <+> text duplicate <> semi
81+
, nest 2 (docProjectConfigPath path)
82+
, nest 2 (vcat [docProjectConfigPath dib | (_, dib) <- dupImportsBy])
83+
]
84+
7585
docProjectConfigPathFailReason :: VR -> ProjectConfigPath -> Doc
7686
docProjectConfigPathFailReason vr pcp
7787
| ProjectConfigPath (p :| []) <- pcp =

cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE ConstraintKinds #-}
22
{-# LANGUAGE DataKinds #-}
33
{-# LANGUAGE DeriveGeneric #-}
4+
{-# LANGUAGE MultiWayIf #-}
45
{-# LANGUAGE NamedFieldPuns #-}
56
{-# LANGUAGE RecordWildCards #-}
67
{-# LANGUAGE ScopedTypeVariables #-}
@@ -33,6 +34,7 @@ module Distribution.Client.ProjectConfig.Legacy
3334
) where
3435

3536
import Data.Coerce (coerce)
37+
import Data.IORef
3638
import Distribution.Client.Compat.Prelude
3739

3840
import Distribution.Types.Flag (FlagName, parsecFlagAssignment)
@@ -245,41 +247,51 @@ parseProject
245247
parseProject rootPath cacheDir httpTransport verbosity configToParse = do
246248
let (dir, projectFileName) = splitFileName rootPath
247249
projectDir <- makeAbsolute dir
248-
projectPath <- canonicalizeConfigPath projectDir (ProjectConfigPath $ projectFileName :| [])
249-
parseProjectSkeleton cacheDir httpTransport verbosity projectDir projectPath configToParse
250+
projectPath@(ProjectConfigPath (canonicalRoot :| _)) <- canonicalizeConfigPath projectDir (ProjectConfigPath $ projectFileName :| [])
251+
importsBy :: IORef [(FilePath, ProjectConfigPath)] <- newIORef [(canonicalRoot, projectPath)]
252+
parseProjectSkeleton cacheDir httpTransport verbosity importsBy projectDir projectPath configToParse
250253

251254
parseProjectSkeleton
252255
:: FilePath
253256
-> HttpTransport
254257
-> Verbosity
258+
-> IORef [(FilePath, ProjectConfigPath)]
255259
-> FilePath
256260
-- ^ The directory of the project configuration, typically the directory of cabal.project
257261
-> ProjectConfigPath
258262
-- ^ The path of the file being parsed, either the root or an import
259263
-> ProjectConfigToParse
260264
-- ^ The contents of the file to parse
261265
-> IO (ParseResult ProjectConfigSkeleton)
262-
parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (ProjectConfigToParse bs) =
266+
parseProjectSkeleton cacheDir httpTransport verbosity importsBy projectDir source (ProjectConfigToParse bs) =
263267
(sanityWalkPCS False =<<) <$> liftPR (go []) (ParseUtils.readFields bs)
264268
where
265269
go :: [ParseUtils.Field] -> [ParseUtils.Field] -> IO (ParseResult ProjectConfigSkeleton)
266270
go acc (x : xs) = case x of
267271
(ParseUtils.F _ "import" importLoc) -> do
268272
let importLocPath = importLoc `consProjectConfigPath` source
269273

270-
-- Once we canonicalize the import path, we can check for cyclical imports
271-
normLocPath <- canonicalizeConfigPath projectDir importLocPath
274+
-- Once we canonicalize the import path, we can check for cyclical and duplicate imports
275+
normLocPath@(ProjectConfigPath (uniqueImport :| _)) <- canonicalizeConfigPath projectDir importLocPath
276+
seenImportsBy@(fmap fst -> seenImports) <- atomicModifyIORef' importsBy (\ibs -> (nub $ (uniqueImport, normLocPath) : ibs, ibs))
272277

273278
debug verbosity $ "\nimport path, normalized\n=======================\n" ++ render (docProjectConfigPath normLocPath)
274-
275-
if isCyclicConfigPath normLocPath
276-
then pure . parseFail $ ParseUtils.FromString (render $ cyclicalImportMsg normLocPath) Nothing
277-
else do
278-
normSource <- canonicalizeConfigPath projectDir source
279-
let fs = (\z -> CondNode z [normLocPath] mempty) <$> fieldsToConfig normSource (reverse acc)
280-
res <- parseProjectSkeleton cacheDir httpTransport verbosity projectDir importLocPath . ProjectConfigToParse =<< fetchImportConfig normLocPath
281-
rest <- go [] xs
282-
pure . fmap mconcat . sequence $ [fs, res, rest]
279+
debug verbosity "\nseen unique paths\n================="
280+
mapM_ (debug verbosity . fst) seenImportsBy
281+
debug verbosity "\n"
282+
283+
if
284+
| isCyclicConfigPath normLocPath ->
285+
pure . parseFail $ ParseUtils.FromString (render $ cyclicalImportMsg normLocPath) Nothing
286+
| uniqueImport `elem` seenImports -> do
287+
let dupImportsBy = filter ((uniqueImport ==) . fst) seenImportsBy
288+
pure . parseFail $ ParseUtils.FromString (render $ duplicateImportMsg uniqueImport normLocPath dupImportsBy) Nothing
289+
| otherwise -> do
290+
normSource <- canonicalizeConfigPath projectDir source
291+
let fs = (\z -> CondNode z [normLocPath] mempty) <$> fieldsToConfig normSource (reverse acc)
292+
res <- parseProjectSkeleton cacheDir httpTransport verbosity importsBy projectDir importLocPath . ProjectConfigToParse =<< fetchImportConfig normLocPath
293+
rest <- go [] xs
294+
pure . fmap mconcat . sequence $ [fs, res, rest]
283295
(ParseUtils.Section l "if" p xs') -> do
284296
subpcs <- go [] xs'
285297
let fs = singletonProjectConfigSkeleton <$> fieldsToConfig source (reverse acc)

cabal-testsuite/PackageTests/ConditionalAndImport/cabal.out

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,15 @@ Could not resolve dependencies:
126126
After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: hashable (3), oops (2)
127127
# checking that we detect when the same config is imported via many different paths
128128
# cabal v2-build
129-
Up to date
129+
Error: [Cabal-7090]
130+
Error parsing project file <ROOT>/yops-0.project:
131+
duplicate import of yops/yops-3.config;
132+
yops/yops-3.config
133+
imported by: yops-0.project
134+
yops/yops-3.config
135+
imported by: yops-2.config
136+
imported by: yops/yops-1.config
137+
imported by: yops-0.project
130138
# checking bad conditional
131139
# cabal v2-build
132140
Error: [Cabal-7090]

0 commit comments

Comments
 (0)