Skip to content

handle conditionals in duplicate module checks #7616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 25 additions & 31 deletions Cabal/src/Distribution/PackageDescription/Check.hs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ checkPackage gpkg mpkg =
++ checkUnicodeXFields gpkg
++ checkPathsModuleExtensions pkg
++ checkSetupVersions gpkg
++ checkDuplicateModules gpkg
where
pkg = fromMaybe (flattenPackageDescription gpkg) mpkg

Expand Down Expand Up @@ -241,13 +242,8 @@ checkLibrary :: PackageDescription -> Library -> [PackageCheck]
checkLibrary pkg lib =
catMaybes [

check (not (null moduleDuplicates)) $
PackageBuildImpossible $
"Duplicate modules in library: "
++ commaSep (map prettyShow moduleDuplicates)

-- TODO: This check is bogus if a required-signature was passed through
, check (null (explicitLibModules lib) && null (reexportedModules lib)) $
check (null (explicitLibModules lib) && null (reexportedModules lib)) $
PackageDistSuspiciousWarn $
showLibraryName (libName lib) ++ " does not expose any modules"

Expand Down Expand Up @@ -278,10 +274,6 @@ checkLibrary pkg lib =
| specVersion pkg >= ver = Nothing
| otherwise = check cond pc

-- TODO: not sure if this check is always right in Backpack
moduleDuplicates = dups (explicitLibModules lib ++
map moduleReexportName (reexportedModules lib))

allExplicitIncludes :: L.HasBuildInfo a => a -> [FilePath]
allExplicitIncludes x = view L.includes x ++ view L.installIncludes x

Expand All @@ -307,11 +299,6 @@ checkExecutable pkg exe =
"The package uses a C/C++/obj-C source file for the 'main-is' field. "
++ "To use this feature you must specify 'cabal-version: >= 1.18'."

, check (not (null moduleDuplicates)) $
PackageBuildImpossible $
"Duplicate modules in executable '" ++ prettyShow (exeName exe) ++ "': "
++ commaSep (map prettyShow moduleDuplicates)

-- check that all autogen-modules appear on other-modules
, check
(not $ and $ map (flip elem (exeModules exe)) (exeModulesAutogen exe)) $
Expand All @@ -324,8 +311,6 @@ checkExecutable pkg exe =
(not $ and $ map (flip elem (view L.includes exe)) (view L.autogenIncludes exe)) $
PackageBuildImpossible "An include in 'autogen-includes' is not in 'includes'."
]
where
moduleDuplicates = dups (exeModules exe)

checkTestSuite :: PackageDescription -> TestSuite -> [PackageCheck]
checkTestSuite pkg test =
Expand All @@ -345,11 +330,6 @@ checkTestSuite pkg test =
++ commaSep (map prettyShow knownTestTypes)
_ -> Nothing

, check (not $ null moduleDuplicates) $
PackageBuildImpossible $
"Duplicate modules in test suite '" ++ prettyShow (testName test) ++ "': "
++ commaSep (map prettyShow moduleDuplicates)

, check mainIsWrongExt $
PackageBuildImpossible $
"The 'main-is' field must specify a '.hs' or '.lhs' file "
Expand All @@ -374,8 +354,6 @@ checkTestSuite pkg test =
PackageBuildImpossible "An include in 'autogen-includes' is not in 'includes'."
]
where
moduleDuplicates = dups $ testModules test

mainIsWrongExt = case testInterface test of
TestSuiteExeV10 _ f -> not $ fileExtensionSupportedLanguage f
_ -> False
Expand All @@ -402,11 +380,6 @@ checkBenchmark _pkg bm =
++ commaSep (map prettyShow knownBenchmarkTypes)
_ -> Nothing

, check (not $ null moduleDuplicates) $
PackageBuildImpossible $
"Duplicate modules in benchmark '" ++ prettyShow (benchmarkName bm) ++ "': "
++ commaSep (map prettyShow moduleDuplicates)

, check mainIsWrongExt $
PackageBuildImpossible $
"The 'main-is' field must specify a '.hs' or '.lhs' file "
Expand All @@ -425,8 +398,6 @@ checkBenchmark _pkg bm =
PackageBuildImpossible "An include in 'autogen-includes' is not in 'includes'."
]
where
moduleDuplicates = dups $ benchmarkModules bm

mainIsWrongExt = case benchmarkInterface bm of
BenchmarkExeV10 _ f -> takeExtension f `notElem` [".hs", ".lhs"]
_ -> False
Expand Down Expand Up @@ -2158,6 +2129,29 @@ checkSetupVersions pkg =
++ "not sure what upper bound to use then use the next major "
++ "version."

checkDuplicateModules :: GenericPackageDescription -> [PackageCheck]
checkDuplicateModules pkg =
concatMap checkLib (maybe id (:) (condLibrary pkg) . map snd $ condSubLibraries pkg)
++ concatMap checkExe (map snd $ condExecutables pkg)
++ concatMap checkTest (map snd $ condTestSuites pkg)
++ concatMap checkBench (map snd $ condBenchmarks pkg)
where
-- the duplicate modules check is has not been thoroughly vetted for backpack
Copy link
Member

Choose a reason for hiding this comment

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

We can leave a TODO for that in the issues list if anyone wants to check it out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this was me migrating the old comment -- the current logic is no worse than before, but i didn't want to lose a potential warning here :-)

checkLib = checkDups "library" (\l -> explicitLibModules l ++ map moduleReexportName (reexportedModules l))
checkExe = checkDups "executable" exeModules
checkTest = checkDups "test suite" testModules
checkBench = checkDups "benchmark" benchmarkModules
checkDups s getModules t =
let libMap = foldCondTree Map.empty
Copy link
Member

Choose a reason for hiding this comment

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

Why such an enormous indent? (Also, two spaces after arrow on the line below, but only one space before comments (feel free to ignore).)

(\(_,v) -> Map.fromListWith (+) . map (\x -> (x,(1::Int))) $ getModules v )
(Map.unionWith (+)) -- if a module may occur in nonexclusive branches count it twice
(Map.unionWith max) -- a module occurs the max of times it might appear in exclusive branches
t
dupLibs = Map.keys $ Map.filter (>1) libMap
in if null dupLibs
then []
else [PackageBuildImpossible $ "Duplicate modules in " ++ s ++ ": " ++ commaSep (map prettyShow dupLibs)]

-- ------------------------------------------------------------
-- * Utils
-- ------------------------------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions Cabal/src/Distribution/Types/CondTree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Distribution.Types.CondTree (
CondBranch(..),
condIfThen,
condIfThenElse,
foldCondTree,
mapCondTree,
mapTreeConstrs,
mapTreeConds,
Expand Down Expand Up @@ -179,3 +180,15 @@ ignoreConditions :: (Semigroup a, Semigroup c) => CondTree v c a -> (a, c)
ignoreConditions (CondNode a c ifs) = foldl (<>) (a, c) $ concatMap f ifs
where f (CondBranch _ t me) = ignoreConditions t
: maybeToList (fmap ignoreConditions me)


-- | Flatten a CondTree. This will traverse the CondTree by taking all
-- possible paths into account, but merging inclusive when two paths
-- may co-exist, and exclusively when the paths are an if/else
foldCondTree :: forall b c a v. b -> ((c, a) -> b) -> (b -> b -> b) -> (b -> b -> b) -> CondTree v c a -> b
foldCondTree e u mergeInclusive mergeExclusive = goTree
where
goTree :: CondTree v c a -> b
goTree (CondNode a c ifs) = u (c, a) `mergeInclusive` foldl goBranch e ifs
goBranch :: b -> CondBranch v c a -> b
goBranch acc (CondBranch _ t mt) = mergeInclusive acc (maybe (goTree t) (mergeExclusive (goTree t) . goTree) mt)
10 changes: 10 additions & 0 deletions changelog.d/pr-7616
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
synopsis: Handle conditionals in duplicate module checks
packages: Cabal
prs: #7616
issues: #4629 #7525

description: {

Improves `cabal check` logic for duplicate modules to take into account conditional branches. If a module appears on both sides of an `if/else` clause in a cabal file, it is now correctly not reported as a duplicate.

}