diff --git a/cabal-install-solver/cabal-install-solver.cabal b/cabal-install-solver/cabal-install-solver.cabal index 151f46fa09a..ab41ac7786b 100644 --- a/cabal-install-solver/cabal-install-solver.cabal +++ b/cabal-install-solver/cabal-install-solver.cabal @@ -72,6 +72,7 @@ library Distribution.Solver.Modular.Solver Distribution.Solver.Modular.Tree Distribution.Solver.Modular.Validate + Distribution.Solver.Modular.ValidateDependencies Distribution.Solver.Modular.Var Distribution.Solver.Modular.Version Distribution.Solver.Modular.WeightedPSQ diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Assignment.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Assignment.hs index d1ae64e5b38..00d5666ba8f 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Assignment.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Assignment.hs @@ -54,7 +54,7 @@ toCPs (A pa fa sa) rdm = cvm :: QPN -> Maybe Vertex -- Note that the RevDepMap contains duplicate dependencies. Therefore the nub. (g, vm, cvm) = graphFromEdges (L.map (\ (x, xs) -> ((), x, nub xs)) - (M.toList rdm)) + (M.toList $ revDeps rdm)) tg :: Graph Component tg = transposeG g -- Topsort the dependency graph, yielding a list of pkgs in the right order. diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs index 95373cbd473..d9a2a7b5e2a 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Builder.hs @@ -81,10 +81,19 @@ extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs -- instance for the setup script. We may need to track other -- self-dependencies once we implement component-based solving. case c of - ComponentSetup -> go (M.adjust (addIfAbsent (ComponentSetup, qpn')) qpn g) o ngs + ComponentSetup -> go g{revDeps = M.adjust (addIfAbsent (ComponentSetup, qpn')) qpn (revDeps g)} o ngs _ -> go g o ngs - | qpn `M.member` g = go (M.adjust (addIfAbsent (c, qpn')) qpn g) o ngs - | otherwise = go (M.insert qpn [(c, qpn')] g) (PkgGoal qpn (DependencyGoal dr) : o) ngs + | qpn `M.member` (revDeps g) + = go g{ revDeps = M.adjust (addIfAbsent (c, qpn')) qpn (revDeps g) + -- If qpn is already a member of revDeps, it is already in the privScopes mapping. + } o ngs + | otherwise + = go g{ revDeps = M.insert qpn [(c, qpn')] (revDeps g) + , privScopes = case qpn of + Q (PackagePath _ ql@QualAlias{}) _ -> + M.insertWith(<>) ql (S.singleton qpn) (privScopes g) + _ -> privScopes g + } (PkgGoal qpn (DependencyGoal dr) : o) ngs -- code above is correct; insert/adjust have different arg order go g o ((Simple (LDep _dr (Ext _ext )) _) : ngs) = go g o ngs go g o ((Simple (LDep _dr (Lang _lang))_) : ngs) = go g o ngs @@ -252,7 +261,7 @@ buildTree idx (IndependentGoals ind) igs = build Linker { buildState = BS { index = idx - , rdeps = M.fromList (L.map (\ qpn -> (qpn, [])) qpns) + , rdeps = RevDepMap { revDeps = M.fromList (L.map (\ qpn -> (qpn, [])) qpns), privScopes = mempty } , open = L.map topLevelGoal qpns , next = Goals , qualifyOptions = defaultQualifyOptions idx diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/ConflictSet.hs b/cabal-install-solver/src/Distribution/Solver/Modular/ConflictSet.hs index 6c9b4280986..00cf15b466f 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/ConflictSet.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/ConflictSet.hs @@ -74,15 +74,6 @@ data Conflict = -- means that package y's constraint 'x >= 2.0' excluded some version of x. | VersionConflict QPN OrderedVersionRange - -- | The conflict set variable represents a package that was excluded for - -- violating the closure property of a private-scope, because that package is part of - -- the closure of the private scope, but it itself is not - -- included in it. For example, the conflict set entry '(P pkgC, - -- PrivateScopeClosureConflict pkgA:lib:G0:pkgB pkgA:lib:G0:pkgD)' means - -- that pkgC is in the (private-deps) closure from pkgA:lib:G0:pkgB to - -- pkgA:lib:G0:pkgD, but pkgC is not included in the private scope pkgA:lib:G0. - | PrivateScopeClosureConflict QPN QPN - -- | Any other conflict. | OtherConflict deriving (Eq, Ord, Show) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Cycles.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Cycles.hs index b82e39a0d26..eaf0b1bb789 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Cycles.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Cycles.hs @@ -1,6 +1,7 @@ +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} module Distribution.Solver.Modular.Cycles ( - detectCyclesPhase + findCycles ) where import Prelude hiding (cycle) @@ -8,51 +9,20 @@ import qualified Data.Map as M import qualified Data.Set as S import qualified Distribution.Compat.Graph as G -import Distribution.Simple.Utils (ordNub) import Distribution.Solver.Modular.Dependency -import Distribution.Solver.Modular.Flag import Distribution.Solver.Modular.Tree import qualified Distribution.Solver.Modular.ConflictSet as CS -import Distribution.Solver.Types.ComponentDeps (Component) import Distribution.Solver.Types.PackagePath +import Distribution.Solver.Modular.ValidateDependencies --- | Find and reject any nodes with cyclic dependencies -detectCyclesPhase :: Tree d c -> Tree d c -detectCyclesPhase = go - where - -- Only check children of choice nodes. - go :: Tree d c -> Tree d c - go (PChoice qpn rdm gr cs) = - PChoice qpn rdm gr $ fmap (checkChild qpn) (fmap go cs) - go (FChoice qfn@(FN qpn _) rdm gr w m d cs) = - FChoice qfn rdm gr w m d $ fmap (checkChild qpn) (fmap go cs) - go (SChoice qsn@(SN qpn _) rdm gr w cs) = - SChoice qsn rdm gr w $ fmap (checkChild qpn) (fmap go cs) - go (GoalChoice rdm cs) = GoalChoice rdm (fmap go cs) - go x@(Fail _ _) = x - go x@(Done _ _) = x - - checkChild :: QPN -> Tree d c -> Tree d c - checkChild qpn x@(PChoice _ rdm _ _) = failIfCycle qpn rdm x - checkChild qpn x@(FChoice _ rdm _ _ _ _ _) = failIfCycle qpn rdm x - checkChild qpn x@(SChoice _ rdm _ _ _) = failIfCycle qpn rdm x - checkChild qpn x@(GoalChoice rdm _) = failIfCycle qpn rdm x - checkChild _ x@(Fail _ _) = x - checkChild qpn x@(Done rdm _) = failIfCycle qpn rdm x - - failIfCycle :: QPN -> RevDepMap -> Tree d c -> Tree d c - failIfCycle qpn rdm x = - case findCycles qpn rdm of - Nothing -> x - Just relSet -> Fail relSet CyclicDependencies -- | Given the reverse dependency map from a node in the tree, check -- if the solution is cyclic. If it is, return the conflict set containing -- all decisions that could potentially break the cycle. -- -- TODO: The conflict set should also contain flag and stanza variables. -findCycles :: QPN -> RevDepMap -> Maybe ConflictSet -findCycles pkg rdm = +findCycles :: QPN -> RevDepMap -> Maybe (ConflictSet, FailReason) +findCycles pkg rdm = (,CyclicDependencies) <$> -- This function has two parts: a faster cycle check that is called at every -- step and a slower calculation of the conflict set. -- @@ -102,19 +72,9 @@ findCycles pkg rdm = else foldl go (S.insert x s) $ neighbors x neighbors :: QPN -> [QPN] - neighbors x = case x `M.lookup` rdm of + neighbors x = case x `M.lookup` revDeps rdm of Nothing -> findCyclesError "cannot find node" Just xs -> map snd xs findCyclesError = error . ("Distribution.Solver.Modular.Cycles.findCycles: " ++) -data RevDepMapNode = RevDepMapNode QPN [(Component, QPN)] - -instance G.IsNode RevDepMapNode where - type Key RevDepMapNode = QPN - nodeKey (RevDepMapNode qpn _) = qpn - nodeNeighbors (RevDepMapNode _ ns) = ordNub $ map snd ns - -revDepMapToGraph :: RevDepMap -> G.Graph RevDepMapNode -revDepMapToGraph rdm = G.fromDistinctList - [RevDepMapNode qpn ns | (qpn, ns) <- M.toList rdm] diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Dependency.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Dependency.hs index 195c9b71646..91fb6715fcf 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Dependency.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Dependency.hs @@ -25,7 +25,7 @@ module Distribution.Solver.Modular.Dependency ( , qualifyDeps , unqualifyDeps -- * Reverse dependency map - , RevDepMap + , RevDepMap(..) -- * Goals , Goal(..) , GoalReason(..) @@ -226,7 +226,7 @@ qualifyDeps QO{..} rdm (Q pp@(PackagePath ns q) pn) = go -- with the package-path of the package that introduced -- this dependency, which will match if this dependency is -- included in the same private scope. - case M.lookup (Q pp pnx) rdm of + case M.lookup (Q pp pnx) (revDeps rdm) of Just _x -> q -- found, use same private qualifier Nothing -> QualToplevel -- not found, use top level qual @@ -268,7 +268,12 @@ unqualifyDeps = go -- | A map containing reverse dependencies between qualified -- package names. -type RevDepMap = Map QPN [(Component, QPN)] +data RevDepMap = RevDepMap + { revDeps :: Map QPN [(Component, QPN)] + -- ^ The reverse dependencies + , privScopes :: Map Qualifier {- a private qualifier -} (S.Set QPN) {- caches the packages in this private scope -} + -- ^ Information related to reverse dependency mapped additionally cached here. + } {------------------------------------------------------------------------------- Goals diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs index fe54752771d..cf1544bc0fd 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Explore.hs @@ -274,7 +274,6 @@ exploreLog mbj enableBj fineGrainedConflicts (CountConflicts countConflicts) idx couldBeResolved :: CS.Conflict -> Maybe ConflictSet couldBeResolved CS.OtherConflict = Nothing - couldBeResolved (CS.PrivateScopeClosureConflict _ _) = Nothing -- Could we optimise here? couldBeResolved (CS.GoalConflict conflictingDep) = -- Check whether this package instance also has 'conflictingDep' -- as a dependency (ignoring flag and stanza choices). diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 8f1b05c23e9..475cd1a982b 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -181,7 +181,6 @@ showConflicts conflicts = Just (qpn, MergedPackageConflict False [v] Nothing) toMergedConflict (CS.VersionConflict qpn (CS.OrderedVersionRange vr)) = Just (qpn, MergedPackageConflict False [] (Just vr)) - toMergedConflict (CS.PrivateScopeClosureConflict _ _) = Nothing toMergedConflict CS.OtherConflict = Nothing showConflict :: QPN -> MergedPackageConflict -> String @@ -303,7 +302,7 @@ showFR c Backjump = " (backjumping, conflict set: " ++ s showFR _ MultipleInstances = " (multiple instances)" showFR c (DependenciesNotLinked msg) = " (dependencies not linked: " ++ msg ++ "; conflict set: " ++ showConflictSet c ++ ")" showFR c CyclicDependencies = " (cyclic dependencies; conflict set: " ++ showConflictSet c ++ ")" -showFR c (InvalidPrivateScope qual) = " (private scopes must contain its closure, but package " ++ showConflictSet c ++ " is not included in the private scope " ++ prettyShow qual ++ ")" +showFR c (InvalidPrivateScope qual) = " (private scopes must contain its closure, but packages " ++ showConflictSet c ++ " are not included in the private scope " ++ prettyShow qual ++ ")" showFR _ (UnsupportedSpecVer ver) = " (unsupported spec-version " ++ prettyShow ver ++ ")" -- The following are internal failures. They should not occur. In the -- interest of not crashing unnecessarily, we still just print an error diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/PrivateScopeClosure.hs b/cabal-install-solver/src/Distribution/Solver/Modular/PrivateScopeClosure.hs index 82e3c4f5637..74d1add7668 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/PrivateScopeClosure.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/PrivateScopeClosure.hs @@ -1,126 +1,249 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE ViewPatterns #-} module Distribution.Solver.Modular.PrivateScopeClosure where -import Control.Exception (assert) +import Data.Maybe +import qualified Data.Set as S import Prelude hiding (cycle) import qualified Data.Map as M import Distribution.Solver.Modular.Dependency -import Distribution.Solver.Modular.Flag import Distribution.Solver.Modular.Tree import qualified Distribution.Solver.Modular.ConflictSet as CS import Distribution.Solver.Types.PackagePath --- | Find and reject any nodes that would violate the private-dependencies --- closure property, which states that all packages within the closure of a --- private scope must also be included in the private scope. -detectInvalidPrivateScopesPhase :: Tree d c -> Tree d c -detectInvalidPrivateScopesPhase = go - where - -- Similar to detectCyclesPhase, maybe we could deduplicate - go :: Tree d c -> Tree d c - go (PChoice qpn rdm gr cs) = - PChoice qpn rdm gr $ fmap (checkChild qpn) (fmap go cs) - go (FChoice qfn@(FN qpn _) rdm gr w m d cs) = - FChoice qfn rdm gr w m d $ fmap (checkChild qpn) (fmap go cs) - go (SChoice qsn@(SN qpn _) rdm gr w cs) = - SChoice qsn rdm gr w $ fmap (checkChild qpn) (fmap go cs) - go (GoalChoice rdm cs) = GoalChoice rdm (fmap go cs) - go x@(Fail _ _) = x - go x@(Done _ _) = x - - checkChild :: QPN -> Tree d c -> Tree d c - checkChild qpn x@(PChoice _ rdm _ _) = failIfBadClosure qpn rdm x - checkChild qpn x@(FChoice _ rdm _ _ _ _ _) = failIfBadClosure qpn rdm x - checkChild qpn x@(SChoice _ rdm _ _ _) = failIfBadClosure qpn rdm x - checkChild qpn x@(GoalChoice rdm _) = failIfBadClosure qpn rdm x - checkChild _ x@(Fail _ _) = x - checkChild qpn x@(Done rdm _) = failIfBadClosure qpn rdm x - - failIfBadClosure :: QPN -> RevDepMap -> Tree d c -> Tree d c - -- An already qualified package can't violate the closure property - failIfBadClosure (Q (PackagePath _ (QualAlias _ _ _)) _) _ x = x - failIfBadClosure qpn rdm x = - case findBadClosures qpn rdm of - Nothing -> x - Just (relSet, qual) -> Fail relSet (InvalidPrivateScope qual) +import Control.Exception (assert) -- | Given the reverse dependency map from a node in the tree, check if the --- solution has any bad closures. If it is, return the conflict set containing +-- solution has any bad closures. If it does, return the conflict set containing -- the variables violating private deps closures. -findBadClosures :: QPN -> RevDepMap -> Maybe (ConflictSet, Qualifier) -findBadClosures pkg rdm = - case concatMap (\root@(Q (PackagePath _ ps) _) -> (root,) <$> concatMap (step ps False . snd) (findRevDepsTopLevel root)) roots of - (closureBegin@(Q (PackagePath _ ql) _), closureEnd@(Q (PackagePath _ ql') _)):_ -> - assert (ql == ql') $ - return (CS.singletonWithConflict (P pkg) (CS.PrivateScopeClosureConflict closureBegin closureEnd), ql) - [] -> Nothing +-- +-- In practice, for every package that is added to the solution (the 'QPN'), we ask: +-- Does choosing this package violate the closure property? +-- By case analysis: +-- +-- (a) If the package X introduced is in a private scope S, then S may now +-- violate the closure property. To determine so, +-- * We take the list of packages in that same private scope S[1] (the +-- so-called "roots" of the check) +-- +-- * And compute, for each root, the up-to-private reverse-dependency +-- closure[2] of that package X qualified with QualTopLevel instead of the +-- private scope[3]. +-- +-- * If we were able to reach the private scope S from the "special closure" of the +-- top-level-qualified roots, the closure property is violated! +-- +-- (b) If the package X introduced is NOT in a private scope, we need to +-- determine all packages Ys in private scopes PSs that depend on this package X. +-- * We do so by traversing upwards from X, ie computing the up-to-private +-- reverse-dependency closure[2] of X. +-- +-- * For every private scope PS reached from traversing upwards from X +-- (possibleBadPrivateScopes), we get the list of packages in that private +-- scope PS (see [1] again), the "roots" of this branch of the check. +-- +-- * For every package (aka root) in that private scope, we traverse upwards +-- from the top-level-qualified root[3] until we find another private scope +-- or until we find the "original" package X from which we originally +-- computed the reachable roots. +-- +-- This is similar to up-to-private reverse-dependency closure[2] of the +-- root but we also stop traversing upwards if we find the package X. +-- +-- * If the package X is reachable from any of the private scopes' roots +-- then the property is violated. That is, if we can reach PS from the +-- (non-private) X, and one of the roots of PS can reach X too, then X sits +-- unqualified between two packages in the private scope (violating the +-- closure property) +-- +-- Additionally, when the private closure property is violated for some private +-- qualifier Q, we compute the packages missing from the private scope for it +-- to be valid (see 'computeBadPkgs'). +-- +-- [1] We associate private scopes to the set of packages contained in it in a +-- cache in the 'RevDepMap' (the 'privScopes' field). We insert into this +-- mapping around the same time a package is introduced in the reverse +-- dependency map, if the package has a private scope. +-- +-- This cache is needed for the 'findBadPrivClosures' check to be fast, because +-- we always need to find the set of packages in a given private scope to perform the check. +-- +-- [2] The up-to-private reverse-dependency closure of X is the +-- reverse-dependency closure of X, constructed by traversing upwards the +-- reverse dependency map, with the twist of stopping at any +-- privately-qualified reverse dependency. +-- +-- The function which computes this 'upwardsClosure' local to 'findBadPrivClosures'. +-- When the first argument is 'True', we additionally stop when the package X is reached. +-- +-- We use this "special closure" because dependencies introduced by packages in +-- a private scope can only violate the property of that scope (it is +-- impossible for them to violate the property of scopes further upward) +-- +-- [3] When we traverse upwards from a privately-qualified root, we want to +-- first unqualify the root and search the unqualified space. The local function +-- 'findRevDepsTopLevel' explains in more detail with an example. +-- +-- INVARIANT: The RevDepMap forms an acyclic graph (guaranteed by this check running after 'findCycles') +findBadPrivClosures :: QPN + -- ^ Newly added package that I need to check if violates property + -> RevDepMap + -> Maybe (ConflictSet, FailReason) +findBadPrivClosures pkg rdm = + case pkg of + Q (PackagePath _ ql@QualAlias{}) _ -> do + -- (a) case + let + roots = pkgsInPrivScope ql + reachableFromTopLevel = upwardsClosure False (concatMap findRevDepsTopLevel roots) + isViolated = + -- From the top level roots there can be no package in the private scope reachable + (any ((== Just ql) . fmap snd . getPrivateQual) reachableFromTopLevel) + + if isViolated + then Just + (CS.fromList $ map P $ computeBadPkgs ql, InvalidPrivateScope ql) + else Nothing + Q (PackagePath _ _) _ -> do + -- (b) case + let + possibleBadPrivateScopes = + -- All the privately qualified packages you can reach from pkg + S.fromList $ + mapMaybe getPrivateQual $ + S.toList $ + upwardsClosure False [pkg] + roots = + S.filter (\p -> not $ p `S.member` S.map fst possibleBadPrivateScopes) $ + foldr (\ps acc -> acc <> pkgsInPrivScope ps) S.empty (S.map snd possibleBadPrivateScopes) + reachable = S.map (\r -> (upwardsClosure True . findRevDepsTopLevel $ r, getQual r)) roots + isViolated = + S.toList $ + S.filter (any (== pkg) . fst) reachable + in + case isViolated of + (_,ql'):_ -> Just (CS.fromList $ map P $ computeBadPkgs ql', InvalidPrivateScope ql') + [] -> Nothing + where + pkgsInPrivScope :: Qualifier -> S.Set QPN + pkgsInPrivScope ql' = fromMaybe (error "all private scopes should be cached") $ M.lookup ql' (privScopes rdm) + + -- Find the reverse dependencies of this QPN, but in the top-level scope. + -- + -- Recall that the private closure property may only be violated by pkgA + -- depending privately on pkgB and pkgD, but not on pkgC, when + -- pkgB --top-level-depends-> pkgC + -- pkgC --top-level-depends-> pkgD + -- + -- In the reverse dependency map we'd have + -- pkgD(private) --rd-> pkgA(top) + -- pkgB(private) --rd-> pkgA(top) + -- pkgC(top) --rd-> pkgB(private) + -- pkgD(top) --rd-> pkgC(top) + -- + -- So, for each pkg with a private scope (like pkgD or pkgB), we look for + -- the top-level equivalent of that package (this function), and will + -- traverse up until we find (or not) that there is a reverse dep on a + -- package in the same private scope as the root from which we started. + findRevDepsTopLevel (Q (PackagePath namespace _) pn) = + case (Q (PackagePath namespace QualToplevel) pn) `M.lookup` revDeps rdm of + Nothing -> + -- If this package wasn't at the top-level means there is no + -- top-level package publicly depending on it. Nothing to worry about then. + [] + Just rdeps -> map snd rdeps + + getQual (Q (PackagePath _ ql) _) = ql + + getPrivateQual p@(Q (PackagePath _ ql@QualAlias{}) _) = Just (p,ql) + getPrivateQual _ = Nothing - -- Roots of the rev dep map with QualAlias/private scope - roots :: [QPN] - roots = flip M.foldMapWithKey rdm $ - \key _ -> case key of - Q (PackagePath _ (QualAlias _ _ _)) _ -> [key] - _ -> [] - - -- Traverse up from a root until a reverse dep in the same private scope is - -- found. We only traverse up until we find another private dep in the same - -- scope because that is sufficient to complete a "local closure", and - -- because we traverse from all root deps in private scopes, we will - -- traverse all the "local" closures thus the full closure of each scope... REWRITE and RENAME - step :: Qualifier -- ^ This root's qualifier/private scope - -> Bool -- ^ Have we found the "goal" package in the "local" closure - -> QPN -- ^ Next package in the closure traversal + hasPrivateQual :: QPN -> Bool + hasPrivateQual = \case + Q (PackagePath _ QualAlias{}) _ -> True + _ -> False + + upwardsClosure :: Bool {-^ Whether to stop traversing when `pkg` is found -} + -> [QPN] {-^ ps roots in qualtoplevel -} + -> S.Set QPN {-^ all the things you can reach (stopping at pkg and on any private thing) -} + upwardsClosure stopAtPkg = foldl go S.empty + where + go :: S.Set QPN -> QPN -> S.Set QPN + go s x + | x `S.member` s + = s + | hasPrivateQual x + = S.insert x s + | stopAtPkg && x == pkg + = S.insert x s + | otherwise + = foldl go (S.insert x s) $ neighbors x + + neighbors :: QPN -> [QPN] + neighbors x = case x `M.lookup` revDeps rdm of + Nothing -> error $ "cannot find node: " ++ show x + Just xs -> map snd xs + + -- Determine the packages which need to be in the private scope for it to + -- be valid, to report in the ConflictSet. + -- + -- We compute the bad pkgs by going up and down from the pkg which + -- invalidated the scope until two ends of a scope are found + -- + -- This is much slower than the rest of the checks because we need to + -- compute all the roots to traverse upwards from, but this cost is only + -- paid for when there is an invalid scope. + -- + -- This is (c). + computeBadPkgs :: Qualifier -> [QPN] + computeBadPkgs bad_scope_qual = badPkgs where + + -- The pkgs which need to be part of the private scope + badPkgs :: [QPN] + badPkgs = + [ badpkg + | root <- roots + , rootDirectRevDep <- findRevDepsTopLevel root + , badpkg <- go [] rootDirectRevDep + ] + + -- Roots of the rev dep map with QualAlias/private scope + roots :: [QPN] + roots = S.toList $ pkgsInPrivScope bad_scope_qual + + -- Traverse up from a root until a reverse dep in the same private scope is + -- found. We only traverse up until we find another private dep in the same + -- scope because that is sufficient to complete a "local closure". + go :: [QPN] -- ^ Accumulate packages in this closure not in the private scope + -> QPN -- ^ Next package in the closure traversal -> [QPN] - -- ^ The terminal nodes for each closure violated by this package. + -- ^ The terminal nodes for each closure possibly violated by this package. -- Empty if the closure property is kept. - step rootQual hasFoundGoal next - -- We stop at any qualified reverse dep, even if it does not belong to - -- the same scope as the one we are checking for the closure property. - -- By case analysis: - -- * If it is the same scope, we've reached the end of the local - -- closure, and if the package has been seen as non-qualified then the - -- property is violated - -- - -- * If it is not the same scope, that means "next" in that branch is a - -- dep of a private scope goal, but it may not violate the closure - -- property for that one. Even if it were to violate the property - -- outside of a nested private scope, it doesn't matter because within a - -- (nested) private scope it just has to be consistent in - -- itself......... - | Q (PackagePath _ ps@(QualAlias _ _ _)) _ <- next - = if ps == rootQual && hasFoundGoal - then [next] - else [] - | otherwise - = case findRevDepsTopLevel next of - -- If there are no more deps (meaning we didn't stop at any rev-dep in - -- a private scope), then we don't have a private scope closure and the - -- property is preserved. - [] -> [] - -- Step through all the next reverse deps, failing (by adding terminal - -- nodes to the result) if any of the steps violates the closure - -- property - xs -> - -- If the next pkg is our goal, we recurse with "hasFoundGoal = - -- True", otherwise with what we had previously - let hasFoundGoal' = next == pkg || hasFoundGoal - in concatMap (step rootQual hasFoundGoal' . snd) xs + -- At the last iteration it is either empty or has the first QPN which + -- closed an invalid closure. + go acc next + -- We stop at any private-qualified reverse dep, even if it does not + -- belong to the same scope as the one we are checking for the closure + -- property. + -- By case analysis: + -- * If it is the same scope, we've reached the end of the local + -- closure and all packages so far are bad + -- + -- * If it is not the same private scope, but in one nonetheless, + -- we've reached an end and none of the packages accumulated. + | Q (PackagePath _ ps@(QualAlias _ _ _)) _ <- next + = if ps == bad_scope_qual + then assert (not $ null acc) acc + else [] + | otherwise + = case findRevDepsTopLevel next of + -- If there are no more deps (meaning we didn't stop at any rev-dep in + -- a private scope), then we don't have a closed private scope for this branch. + [] -> [] + -- Step through all the next reverse deps + xs -> concatMap (go (next:acc)) xs - -- Find the reverse dependencies of this QPN, but in the top-level scope. - -- When constructing the closure, starting from a qualified root, we need - -- to take into account that the dependencies introduced by the - -- private-scoped-depends will be in the top level scope... - findRevDepsTopLevel qpn@(Q (PackagePath namespace _) pn) = - case (Q (PackagePath namespace QualToplevel) pn) `M.lookup` rdm of - Nothing -> - -- This means the package we are looking up in the map has only been - -- introduced qualified, not at the QualToplevel. This means we look - -- it up as is. - case qpn `M.lookup` rdm of - Nothing -> findError "cannot find node" - Just rdeps -> rdeps - Just rdeps -> rdeps - - findError = error . ("Distribution.Solver.Modular.PrivateScopeClosure.findBadClosures: " ++) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs index 98498813126..a1e57408670 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs @@ -36,6 +36,7 @@ import Distribution.Solver.Modular.Log import Distribution.Solver.Modular.Message import Distribution.Solver.Modular.Package import qualified Distribution.Solver.Modular.Preference as P +import Distribution.Solver.Modular.ValidateDependencies import Distribution.Solver.Modular.PrivateScopeClosure import Distribution.Solver.Modular.Validate import Distribution.Solver.Modular.Linking @@ -99,10 +100,11 @@ solve :: SolverConfig -- ^ solver parameters -> RetryLog Message SolverFailure (Assignment, RevDepMap) solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = explorePhase . - traceTree "invalid-scopes.json" id . - detectInvalidPrivateScopesPhase . - traceTree "cycles.json" id . - detectCycles . + traceTree "invalid-dep-graph.json" id . + detectInvalidDepGraphPhase + [ findCycles -- this first + , findBadPrivClosures -- then this + ] . traceTree "heuristics.json" id . trav ( heuristicsPhase . @@ -121,7 +123,6 @@ solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = (fineGrainedConflicts sc) (countConflicts sc) idx - detectCycles = detectCyclesPhase heuristicsPhase = let sortGoals = case goalOrder sc of diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/ValidateDependencies.hs b/cabal-install-solver/src/Distribution/Solver/Modular/ValidateDependencies.hs new file mode 100644 index 00000000000..37edcfefc81 --- /dev/null +++ b/cabal-install-solver/src/Distribution/Solver/Modular/ValidateDependencies.hs @@ -0,0 +1,66 @@ +{-# LANGUAGE TypeFamilies #-} +module Distribution.Solver.Modular.ValidateDependencies ( + detectInvalidDepGraphPhase + , RevDepMapNode(..) + , revDepMapToGraph + ) where + +import Prelude hiding (cycle) +import qualified Data.Map as M + +import qualified Distribution.Compat.Graph as G +import Distribution.Simple.Utils (ordNub) +import Distribution.Solver.Modular.Dependency +import Distribution.Solver.Modular.Flag +import Distribution.Solver.Modular.Tree +import Distribution.Solver.Types.ComponentDeps (Component) +import Distribution.Solver.Types.PackagePath + +-- | Run checks on the dependencies at every node, such as the cycle check +-- 'findCycles' and the private scope closure check 'findBadPrivClosures' +detectInvalidDepGraphPhase :: [QPN -> RevDepMap -> Maybe (ConflictSet, FailReason)] + -- ^ The list of checks to run at every node, run left to right. + -> Tree d c -> Tree d c +detectInvalidDepGraphPhase checks = go + where + -- Only check children of choice nodes. + go :: Tree d c -> Tree d c + go (PChoice qpn rdm gr cs) = + PChoice qpn rdm gr $ fmap (checkChild qpn) (fmap go cs) + go (FChoice qfn@(FN qpn _) rdm gr w m d cs) = + FChoice qfn rdm gr w m d $ fmap (checkChild qpn) (fmap go cs) + go (SChoice qsn@(SN qpn _) rdm gr w cs) = + SChoice qsn rdm gr w $ fmap (checkChild qpn) (fmap go cs) + go (GoalChoice rdm cs) = GoalChoice rdm (fmap go cs) + go x@(Fail _ _) = x + go x@(Done _ _) = x + + checkChild :: QPN -> Tree d c -> Tree d c + checkChild qpn x@(PChoice _ rdm _ _) = failIfBad qpn rdm x + checkChild qpn x@(FChoice _ rdm _ _ _ _ _) = failIfBad qpn rdm x + checkChild qpn x@(SChoice _ rdm _ _ _) = failIfBad qpn rdm x + checkChild qpn x@(GoalChoice rdm _) = failIfBad qpn rdm x + checkChild _ x@(Fail _ _) = x + checkChild qpn x@(Done rdm _) = failIfBad qpn rdm x + + failIfBad :: QPN -> RevDepMap -> Tree d c -> Tree d c + failIfBad qpn rdm x = maybe x (uncurry Fail) (goCheck checks) where + goCheck :: [QPN -> RevDepMap -> Maybe (ConflictSet, FailReason)] -> Maybe (ConflictSet, FailReason) + goCheck [] = Nothing -- no more check, succeed + goCheck (c:cs) = + case c qpn rdm of + Nothing -> + -- success for this check, but we need to run all checks + goCheck cs + Just f -> Just f + +data RevDepMapNode = RevDepMapNode QPN [(Component, QPN)] + +instance G.IsNode RevDepMapNode where + type Key RevDepMapNode = QPN + nodeKey (RevDepMapNode qpn _) = qpn + nodeNeighbors (RevDepMapNode _ ns) = ordNub $ map snd ns + +revDepMapToGraph :: RevDepMap -> G.Graph RevDepMapNode +revDepMapToGraph rdm = G.fromDistinctList + [RevDepMapNode qpn ns | (qpn, ns) <- M.toList $ revDeps rdm] diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 4d577b32df7..58076140df7 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BlockArguments #-} {-# LANGUAGE OverloadedStrings #-} -- | This is a set of unit tests for the dependency solver, @@ -7,10 +8,15 @@ module UnitTests.Distribution.Solver.Modular.Solver (tests) where -- base + +import Control.Monad import Data.List (isInfixOf) import qualified Distribution.Version as V +-- mtl +import Control.Monad.State + -- test-framework import Test.Tasty as TF import Test.Tasty.ExpectedFailure @@ -24,6 +30,7 @@ import Language.Haskell.Extension -- cabal-install +import Data.Bifunctor import qualified Distribution.Solver.Types.ComponentDeps as P import Distribution.Solver.Types.Flag import Distribution.Solver.Types.OptionalStanza @@ -881,6 +888,9 @@ tests = , runTest privDep13 , runTest privDep14 , runTest privDep15 + -- These tests should only be run manually! (Not enabled by default) + -- , runTest $ _manualPrivDepsStressTest True + -- , runTest $ _manualPrivDepsStressTestWithBacktracking True ] , -- Tests for the contents of the solver's log testGroup @@ -2846,6 +2856,87 @@ privDep15 = setVerbose $ mkTest priv_db15 "private-dependencies-15" ["P"] (solverSuccess [("A", 1), ("P", 1)]) +uniqPkgName :: State Int String +uniqPkgName = do + i <- get + modify' (+ 1) + return ("pkgN" ++ show i) +uniqScope :: State Int String +uniqScope = do + i <- get + modify' (+ 1) + return ("S" ++ show i) + +-- The stress test for private dependencies without backtracking +manual_priv_stress :: Bool + -- ^ Use private scopes? + -> ExampleDb +manual_priv_stress usePrivScopes = (`evalState` 1) do + (deps, depsOfDeps) <- bimap concat concat . unzip <$> forM [1 .. param] (const $ mkPkg param) + return ([Right $ exAv "P" 1 $ map (ExAny . exAvName) deps] ++ map Right (deps ++ depsOfDeps)) + where + param :: Int + param = 6 + + mkPkg :: Int -> State Int ([ExampleAvailable], [ExampleAvailable]) + mkPkg 0 = return ([], []) + mkPkg i = do + (depPkgs, depsOfDeps) <- bimap concat concat . unzip <$> forM [1 .. param] (const $ mkPkg (i - 1)) + deps <- + concat <$> forM (zip depPkgs [(1 :: Int) ..]) \(p, ix) -> + if even ix && usePrivScopes + then do + sc <- uniqScope + return [ExAnyPriv sc (exAvName p)] + else + return [ExAny (exAvName p)] -- note, no backtracking so no constraints. + pkgid <- uniqPkgName + return ([exAv pkgid 1 deps], (depPkgs ++ depsOfDeps)) + +-- The stress test for private dependencies with backtracking +manual_priv_stress_with_bt :: Bool + -- ^ Use private scopes? + -> ExampleDb +manual_priv_stress_with_bt usePrivScopes = (`evalState` 1) do + (deps, depsOfDeps) <- bimap concat concat . unzip <$> forM [1 .. param] (const $ mkPkg param) + return ([Right $ exAv "P" 1 $ map (ExAny . exAvName) deps] ++ map Right (deps ++ depsOfDeps)) + where + param :: Int + param = 5 + + mkPkg :: Int -> State Int ([ExampleAvailable], [ExampleAvailable]) + mkPkg 0 = return ([], []) + mkPkg i = do + (depPkgs, depsOfDeps) <- bimap concat concat . unzip <$> forM [1 .. param] (const $ mkPkg (i - 1)) + let nn n = + concat <$> forM (zip depPkgs [(1 :: Int) ..]) \(p, ix) -> + let ver = if ix `mod` n == 0 then 1 else 2 + in if even ix && usePrivScopes + then do + sc <- uniqScope + return [ExFixPriv sc (exAvName p) ver] + else + return [ExFix (exAvName p) ver] + deps <- nn 13 + deps' <- nn 14 + pkgid <- uniqPkgName + return ([exAv pkgid 1 deps, exAv pkgid 2 deps'], (depPkgs ++ depsOfDeps)) + +-- | This is a stress test of private dependencies which should be manually +-- enabled for local testing, but not enabled in CI. +_manualPrivDepsStressTest :: Bool -> SolverTest +_manualPrivDepsStressTest b = + setVerbose $ + mkTest (manual_priv_stress b) "manual-private-dependencies-stress-test" ["P"] (solverSuccess [("P", 1)]) + +-- | This is a stress test of private dependencies which should be manually +-- enabled for local testing, but not enabled in CI. +-- It is quite hard to provoke backtracking in a procedural test, but here's an attempt. +_manualPrivDepsStressTestWithBacktracking :: Bool -> SolverTest +_manualPrivDepsStressTestWithBacktracking b = + setVerbose $ + mkTest (manual_priv_stress_with_bt b) "manual-private-dependencies-stress-test-backtrack" ["P"] (solverSuccess [("P", 1)]) + -- | Returns true if the second list contains all elements of the first list, in -- order. containsInOrder :: Eq a => [a] -> [a] -> Bool diff --git a/cabal-testsuite/PackageTests/PrivateDeps/closure-property-test/cabal.test.hs b/cabal-testsuite/PackageTests/PrivateDeps/closure-property-test/cabal.test.hs index 13215233402..0760e84a43b 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/closure-property-test/cabal.test.hs +++ b/cabal-testsuite/PackageTests/PrivateDeps/closure-property-test/cabal.test.hs @@ -7,7 +7,7 @@ main = do withProjectFile "cabal.project.1" $ withRepo "repo" $ fails (cabal' "v2-build" ["libA"]) - >>= assertOutputContains "private scopes must contain its closure, but package libC is not included in the private scope libA:lib:G0" + >>= assertOutputContains "private scopes must contain its closure, but packages libC are not included in the private scope libA:lib:G0" -- Must pick libC == 0.1 withProjectFile "cabal.project.2" $ diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/cabal.test.hs b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/cabal.test.hs index 293588f009e..8128995e676 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/cabal.test.hs +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/cabal.test.hs @@ -16,16 +16,14 @@ because of the conflict too main = cabalTest $ recordMode DoNotRecord $ do - withProjectFile "cabal.project.scenea" $ + -- withProjectFile "cabal.project.scenea" $ -- For some reason this doesn't work... withRepo "repo" $ do -- Succeeds because SameName from pkgA and SameName from pkgB do not collide. - cabal "build" ["pkgA"] - withProjectFile "cabal.project.sceneb" $ + cabal "build" ["pkgA", "--project-file=cabal.project.scenea"] withRepo "repo" $ do -- Fails because SameName from pkgC in its two separate components - cabal "build" ["pkgC"] - withProjectFile "cabal.project.scenec" $ + cabal "build" ["pkgC", "--project-file=cabal.project.sceneb"] withRepo "repo" $ do -- Fails because SameName from pkgD in the same component - cabal "build" ["pkgD"] + cabal "build" ["pkgD", "--project-file=cabal.project.scenec"] diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/CHANGELOG.md b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/CHANGELOG.md deleted file mode 100644 index 6c1d0fed1d1..00000000000 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Revision history for pkgA - -## 0.1.0.0 -- YYYY-mm-dd - -* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/pkgA.cabal b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/pkgA.cabal index 1c667562329..b081ea9530b 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/pkgA.cabal +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgA/pkgA.cabal @@ -3,7 +3,6 @@ name: pkgA version: 0.1.0.0 license: NONE build-type: Simple -extra-doc-files: CHANGELOG.md common warnings ghc-options: -Wall diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/CHANGELOG.md b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/CHANGELOG.md deleted file mode 100644 index 6c1d0fed1d1..00000000000 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Revision history for pkgA - -## 0.1.0.0 -- YYYY-mm-dd - -* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/pkgB.cabal b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/pkgB.cabal index d1cef9a5ecd..d1c0dff0489 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/pkgB.cabal +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgB/pkgB.cabal @@ -3,7 +3,6 @@ name: pkgB version: 0.1.0.0 license: NONE build-type: Simple -extra-doc-files: CHANGELOG.md common warnings ghc-options: -Wall diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/CHANGELOG.md b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/CHANGELOG.md deleted file mode 100644 index 6c1d0fed1d1..00000000000 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Revision history for pkgA - -## 0.1.0.0 -- YYYY-mm-dd - -* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/app/Main.hs b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/app/Main.hs new file mode 100644 index 00000000000..76a9bdb5d48 --- /dev/null +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/app/Main.hs @@ -0,0 +1 @@ +main = pure () diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/pkgC.cabal b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/pkgC.cabal index 7a75f05b208..a2798b523ee 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/pkgC.cabal +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgC/pkgC.cabal @@ -3,7 +3,6 @@ name: pkgC version: 0.1.0.0 license: NONE build-type: Simple -extra-doc-files: CHANGELOG.md common warnings ghc-options: -Wall @@ -18,6 +17,7 @@ library executable myexe main-is: Main.hs + hs-source-dirs: app private-build-depends: SameName with (libA == 0.2.0.0) default-language: Haskell2010 build-depends: base diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/CHANGELOG.md b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/CHANGELOG.md deleted file mode 100644 index 6c1d0fed1d1..00000000000 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Revision history for pkgA - -## 0.1.0.0 -- YYYY-mm-dd - -* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/pkgD.cabal b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/pkgD.cabal index e3f6fe5644f..83b11345752 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/pkgD.cabal +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/pkgD/pkgD.cabal @@ -3,7 +3,6 @@ name: pkgD version: 0.1.0.0 license: NONE build-type: Simple -extra-doc-files: CHANGELOG.md common warnings ghc-options: -Wall diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/CHANGELOG.md b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/CHANGELOG.md deleted file mode 100644 index 4e6fa27dd90..00000000000 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Revision history for libA - -## 0.1.0.0 -- YYYY-mm-dd - -* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/libA.cabal b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/libA.cabal index ced6e1bbe92..8162c86fb8f 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/libA.cabal +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.1.0.0/libA.cabal @@ -3,7 +3,6 @@ name: libA version: 0.1.0.0 license: NONE build-type: Simple -extra-doc-files: CHANGELOG.md common warnings ghc-options: -Wall diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/CHANGELOG.md b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/CHANGELOG.md deleted file mode 100644 index 4e6fa27dd90..00000000000 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Revision history for libA - -## 0.1.0.0 -- YYYY-mm-dd - -* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/libA.cabal b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/libA.cabal index 45452d179b1..84a8664ca62 100644 --- a/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/libA.cabal +++ b/cabal-testsuite/PackageTests/PrivateDeps/same-scope-name/repo/libA-0.2.0.0/libA.cabal @@ -3,7 +3,6 @@ name: libA version: 0.2.0.0 license: NONE build-type: Simple -extra-doc-files: CHANGELOG.md common warnings ghc-options: -Wall