From ed01de6ec33fcc98f924c51b15337f65ff51b28c Mon Sep 17 00:00:00 2001 From: gbaz <gershomb@gmail.com> Date: Tue, 7 Sep 2021 02:07:31 -0400 Subject: [PATCH] handle conditionals in duplicate module checks (#7616) * handle conditionals in duplicate module checks * fix silly bug * changelog * Update Check.hs Co-authored-by: Gershom Bazerman <gershom@arista.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../Distribution/PackageDescription/Check.hs | 56 +++++++++---------- Cabal/src/Distribution/Types/CondTree.hs | 13 +++++ changelog.d/pr-7616 | 10 ++++ 3 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 changelog.d/pr-7616 diff --git a/Cabal/src/Distribution/PackageDescription/Check.hs b/Cabal/src/Distribution/PackageDescription/Check.hs index fc1c196f06..449c535583 100644 --- a/Cabal/src/Distribution/PackageDescription/Check.hs +++ b/Cabal/src/Distribution/PackageDescription/Check.hs @@ -155,6 +155,7 @@ checkPackage gpkg mpkg = ++ checkUnicodeXFields gpkg ++ checkPathsModuleExtensions pkg ++ checkSetupVersions gpkg + ++ checkDuplicateModules gpkg where pkg = fromMaybe (flattenPackageDescription gpkg) mpkg @@ -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" @@ -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 @@ -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)) $ @@ -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 = @@ -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 " @@ -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 @@ -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 " @@ -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 @@ -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 + 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 + (\(_,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 -- ------------------------------------------------------------ diff --git a/Cabal/src/Distribution/Types/CondTree.hs b/Cabal/src/Distribution/Types/CondTree.hs index b99ac8f166..8fd233658f 100644 --- a/Cabal/src/Distribution/Types/CondTree.hs +++ b/Cabal/src/Distribution/Types/CondTree.hs @@ -10,6 +10,7 @@ module Distribution.Types.CondTree ( CondBranch(..), condIfThen, condIfThenElse, + foldCondTree, mapCondTree, mapTreeConstrs, mapTreeConds, @@ -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) diff --git a/changelog.d/pr-7616 b/changelog.d/pr-7616 new file mode 100644 index 0000000000..2d8520f19f --- /dev/null +++ b/changelog.d/pr-7616 @@ -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. + +} -- GitLab