From 8ee7fd3dfd8f5d9e5073134b94c089b746d9ce55 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Thu, 30 Jul 2026 15:07:33 -0400 Subject: [PATCH 1/2] Filter flag constraints, keep version constraints - Add test for mistaking flag for version - Add tests for -any and -none - Add doctests to isNoVersion && isSpecificVersion - Update tests for rejecting -none and <0 - Reject -none and <0 as version constrained - Add =all and shorten the failure message - Minor correction of haddock rendering --- Cabal-syntax/src/Distribution/Version.hs | 14 +++++++-- .../Distribution/Solver/Modular/Message.hs | 2 +- .../src/Distribution/Solver/Modular/Solver.hs | 30 +++++++++++++++---- .../RequireExplicit/FlagNotVersion/a.cabal | 7 +++++ .../FlagNotVersion/cabal.any-flag.out | 14 +++++++++ .../FlagNotVersion/cabal.any-version.out | 14 +++++++++ .../FlagNotVersion/cabal.none-flag.out | 14 +++++++++ .../FlagNotVersion/cabal.none-version.out | 14 +++++++++ .../RequireExplicit/FlagNotVersion/cabal.out | 14 +++++++++ .../FlagNotVersion/cabal.project | 3 ++ .../FlagNotVersion/cabal.test.hs | 24 +++++++++++++++ .../RequireExplicit/FlagNotVersion/repo | 1 + changelog.d/12191.md | 11 +++++++ 13 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/a.cabal create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-flag.out create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-version.out create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-flag.out create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-version.out create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.out create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.project create mode 100644 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.test.hs create mode 120000 cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/repo create mode 100644 changelog.d/12191.md diff --git a/Cabal-syntax/src/Distribution/Version.hs b/Cabal-syntax/src/Distribution/Version.hs index 2abf88100a8..a53a85b80fb 100644 --- a/Cabal-syntax/src/Distribution/Version.hs +++ b/Cabal-syntax/src/Distribution/Version.hs @@ -108,9 +108,13 @@ import Distribution.Types.VersionRange -- | This is the converse of 'isAnyVersion'. It check if the version range is -- empty, if there is no possible version that satisfies the version range. -- --- For example this is @True@ (for all @v@): +-- For example, @\v@ is no version for all @v@. -- --- > isNoVersion (EarlierVersion v `IntersectVersionRanges` LaterVersion v) +-- >>> ordNub [isNoVersion (earlierVersion v `intersectVersionRanges` laterVersion v) | v <- mkVersion <$> [[0],[1]]] +-- [True] +-- +-- >>> isNoVersion <$> [noVersion, anyVersion] +-- [True,False] isNoVersion :: VersionRange -> Bool isNoVersion vr = case asVersionIntervals vr of [] -> True @@ -118,8 +122,11 @@ isNoVersion vr = case asVersionIntervals vr of -- | Is this version range in fact just a specific version? -- --- For example the version range @\">= 3 && <= 3\"@ contains only the version +-- For example the version range @\>= 3 && <= 3@ contains only the version -- @3@. +-- +-- >>> isSpecificVersion (orLaterVersion (mkVersion [3]) `intersectVersionRanges` orEarlierVersion (mkVersion [3])) +-- Just (mkVersion [3]) isSpecificVersion :: VersionRange -> Maybe Version isSpecificVersion vr = case asVersionIntervals vr of [VersionInterval (LowerBound v InclusiveBound) (UpperBound v' InclusiveBound)] @@ -222,5 +229,6 @@ transformCaretLower = hyloVersionRange embed projectVersionRange -- >>> :set -XScopedTypeVariables -- >>> import Distribution.Parsec -- >>> import Distribution.Pretty +-- >>> import Distribution.Utils.Generic (ordNub) -- >>> -- >>> mapVR f xs = [pretty $ f v| Just v <- simpleParsec <$> xs] diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 1b6fa0b3af5..16a1b1ff13b 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -309,7 +309,7 @@ showFR _ (PackageRequiresMissingComponent qpn comp) = " (requires " ++ showExpos showFR _ (PackageRequiresPrivateComponent qpn comp) = " (requires " ++ showExposedComponent comp ++ " from " ++ showQPN qpn ++ ", but the component is private)" showFR _ (PackageRequiresUnbuildableComponent qpn comp) = " (requires " ++ showExposedComponent comp ++ " from " ++ showQPN qpn ++ ", but the component is not buildable in the current environment)" showFR _ CannotReinstall = " (avoiding to reinstall a package with same version but new dependencies)" -showFR _ NotExplicit = " (not a user-provided goal nor mentioned as a constraint, but reject-unconstrained-dependencies was set)" +showFR _ NotExplicit = " (not a user-provided goal nor mentioned as a constraint when reject-unconstrained-dependencies=all)" showFR _ Shadowed = " (shadowed by another installed package with same version)" showFR _ (Broken u) = " (package is broken, missing dependency " ++ prettyShow u ++ ")" showFR _ UnknownPackage = " (unknown package)" diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs index cb9b204b5cb..f2197379076 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Solver.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE ViewPatterns #-} #ifdef DEBUG_TRACETREE {-# OPTIONS_GHC -Wno-orphans #-} #endif @@ -18,6 +19,7 @@ import Distribution.Verbosity import Distribution.Compiler (CompilerInfo) +import Distribution.Version import Distribution.Solver.Types.PackagePath import Distribution.Solver.Types.PackagePreferences import Distribution.Solver.Types.PkgConfigDb (PkgConfigDb) @@ -41,6 +43,7 @@ import Distribution.Solver.Modular.PSQ (PSQ) import Distribution.Solver.Modular.RetryLog import Distribution.Solver.Modular.Tree import qualified Distribution.Solver.Modular.PSQ as PSQ +import Distribution.Solver.Types.PackageConstraint import Distribution.Simple.Setup (BooleanFlag(..)) @@ -140,15 +143,13 @@ solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = prunePhase = (if asBool (avoidReinstalls sc) then P.avoidReinstalls (const True) else id) . (case onlyConstrained sc of OnlyConstrainedAll -> - P.onlyConstrained pkgIsExplicit + P.onlyConstrained (`S.member` versionConstrainedOrGoals) OnlyConstrainedNone -> id) buildPhase = buildTree idx (independentGoals sc) (S.toList userGoals) - allExplicit = M.keysSet userConstraints `S.union` userGoals - - pkgIsExplicit :: PN -> Bool - pkgIsExplicit pn = S.member pn allExplicit + versionConstrained = filterVersion isVersionConstrained userConstraints + versionConstrainedOrGoals = versionConstrained `S.union` userGoals -- When --reorder-goals is set, we use preferReallyEasyGoalChoices, which -- prefers (keeps) goals only if the have 0 or 1 enabled choice. @@ -166,6 +167,25 @@ solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = | asBool (reorderGoals sc) = P.preferReallyEasyGoalChoices | otherwise = id {- P.firstGoal -} +-- | Keep package names of constraints that satisfy the predicate. +filterVersion :: (LabeledPackageConstraint -> Bool) -> M.Map PN [LabeledPackageConstraint] -> Set PN +filterVersion versionFilter = M.keysSet . M.filter (not . null) . M.map (filter versionFilter) + +normalise :: VersionRange -> VersionRange +normalise = fromVersionIntervals . toVersionIntervals + +-- | Unconstrained with a version range @>=0@ or @<0@ or their flag equivalents +-- and constrained by other versions ranges. +-- +-- Both the @-any@ and @-none@ flags are considered unconstrained, because they +-- don't actually constrain the version of the package. The @-any@ flag allows +-- any version, and the @-none@ flag effectively excludes a package. +isVersionConstrained :: LabeledPackageConstraint -> Bool +isVersionConstrained (LabeledPackageConstraint (PackageConstraint _ c) _) = case c of + PackagePropertyVersion (normalise -> vr) -> not (isAnyVersion vr || isNoVersion vr) + -- `PackagePropertyFlags` @-any@ and @-none@ are covered below. + _ -> False + -- | Dump solver tree to a file (in debugging mode) -- -- This only does something if the @debug-tracetree@ configure argument was diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/a.cabal b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/a.cabal new file mode 100644 index 00000000000..5d9797ccf7d --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/a.cabal @@ -0,0 +1,7 @@ +cabal-version: 2.2 +name: a +version: 0 + +library + build-depends: + some-lib diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-flag.out b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-flag.out new file mode 100644 index 00000000000..49872a08df4 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-flag.out @@ -0,0 +1,14 @@ +# tar +# tar +# tar +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-build +Resolving dependencies... +Error: [Cabal-7107] +Could not resolve dependencies: +[__0] trying: a-0 (user goal) +[__1] next goal: some-lib (dependency of a) +[__1] fail (not a user-provided goal nor mentioned as a constraint when reject-unconstrained-dependencies=all) +[__1] fail (backjumping, conflict set: a, some-lib) +After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: a (2), some-lib (1) diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-version.out b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-version.out new file mode 100644 index 00000000000..49872a08df4 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.any-version.out @@ -0,0 +1,14 @@ +# tar +# tar +# tar +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-build +Resolving dependencies... +Error: [Cabal-7107] +Could not resolve dependencies: +[__0] trying: a-0 (user goal) +[__1] next goal: some-lib (dependency of a) +[__1] fail (not a user-provided goal nor mentioned as a constraint when reject-unconstrained-dependencies=all) +[__1] fail (backjumping, conflict set: a, some-lib) +After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: a (2), some-lib (1) diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-flag.out b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-flag.out new file mode 100644 index 00000000000..49872a08df4 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-flag.out @@ -0,0 +1,14 @@ +# tar +# tar +# tar +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-build +Resolving dependencies... +Error: [Cabal-7107] +Could not resolve dependencies: +[__0] trying: a-0 (user goal) +[__1] next goal: some-lib (dependency of a) +[__1] fail (not a user-provided goal nor mentioned as a constraint when reject-unconstrained-dependencies=all) +[__1] fail (backjumping, conflict set: a, some-lib) +After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: a (2), some-lib (1) diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-version.out b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-version.out new file mode 100644 index 00000000000..49872a08df4 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.none-version.out @@ -0,0 +1,14 @@ +# tar +# tar +# tar +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-build +Resolving dependencies... +Error: [Cabal-7107] +Could not resolve dependencies: +[__0] trying: a-0 (user goal) +[__1] next goal: some-lib (dependency of a) +[__1] fail (not a user-provided goal nor mentioned as a constraint when reject-unconstrained-dependencies=all) +[__1] fail (backjumping, conflict set: a, some-lib) +After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: a (2), some-lib (1) diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.out b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.out new file mode 100644 index 00000000000..49872a08df4 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.out @@ -0,0 +1,14 @@ +# tar +# tar +# tar +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-build +Resolving dependencies... +Error: [Cabal-7107] +Could not resolve dependencies: +[__0] trying: a-0 (user goal) +[__1] next goal: some-lib (dependency of a) +[__1] fail (not a user-provided goal nor mentioned as a constraint when reject-unconstrained-dependencies=all) +[__1] fail (backjumping, conflict set: a, some-lib) +After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: a (2), some-lib (1) diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.project b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.project new file mode 100644 index 00000000000..167c82a6219 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.project @@ -0,0 +1,3 @@ +packages: . +constraints: some-lib +some-flag +reject-unconstrained-dependencies: all diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.test.hs b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.test.hs new file mode 100644 index 00000000000..044f5d36759 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/cabal.test.hs @@ -0,0 +1,24 @@ +import Test.Cabal.Prelude +main = do + cabalTest . recordMode RecordMarked $ withRepo "repo" $ do + res <- fails $ cabal' "v2-build" ["all", "--dry-run"] + assertOutputContains "not a user-provided goal" res + + -- The following all check version ranges that don't have a version + -- constrained dependency one way or another. + + cabalTest' "any-flag" . recordMode RecordMarked $ withRepo "repo" $ do + res <- fails $ cabal' "v2-build" ["all", "--dry-run", "--constraint", "some-lib -any"] + assertOutputContains "not a user-provided goal" res + + cabalTest' "any-version" . recordMode RecordMarked $ withRepo "repo" $ do + res <- fails $ cabal' "v2-build" ["all", "--dry-run", "--constraint", "some-lib >=0"] + assertOutputContains "not a user-provided goal" res + + cabalTest' "none-flag" . recordMode RecordMarked $ withRepo "repo" $ do + res <- fails $ cabal' "v2-build" ["all", "--dry-run", "--constraint", "some-lib -none"] + assertOutputContains "not a user-provided goal" res + + cabalTest' "none-version" . recordMode RecordMarked $ withRepo "repo" $ do + res <- fails $ cabal' "v2-build" ["all", "--dry-run", "--constraint", "some-lib <0"] + assertOutputContains "not a user-provided goal" res diff --git a/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/repo b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/repo new file mode 120000 index 00000000000..2dd2305a132 --- /dev/null +++ b/cabal-testsuite/PackageTests/RequireExplicit/FlagNotVersion/repo @@ -0,0 +1 @@ +../repo/ \ No newline at end of file diff --git a/changelog.d/12191.md b/changelog.d/12191.md new file mode 100644 index 00000000000..c5b2555b20a --- /dev/null +++ b/changelog.d/12191.md @@ -0,0 +1,11 @@ +--- +synopsis: Reject dependencies with unconstrained versions +packages: [cabal-install] +prs: 12191 +issues: 12190 +--- + +Change the behaviour of `--reject-unconstrained-dependencies=all` so that it is +not satisfied by flag constraints. Only version constraints should satisfy this +check and even then these need to reject at least some versions so `>=0` or +`-any` and `<0` or `-none` as version constraint will also fail the check. From bcbbcff43d8e45d4a000cbee32fb425b8cba3940 Mon Sep 17 00:00:00 2001 From: Phil de Joux Date: Thu, 13 Aug 2026 19:22:49 -0400 Subject: [PATCH 2/2] Update test expectation for failure message --- .../tests/UnitTests/Distribution/Solver/Modular/Solver.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index b3780e2a345..4b377fb8a40 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -264,8 +264,8 @@ tests = "Could not resolve dependencies:\n" ++ "[__0] trying: A-3 (user goal)\n" ++ "[__1] next goal: C (dependency of A)\n" - ++ "[__1] fail (not a user-provided goal nor mentioned as a constraint, " - ++ "but reject-unconstrained-dependencies was set)\n" + ++ "[__1] fail (not a user-provided goal nor mentioned as a constraint" + ++ " when reject-unconstrained-dependencies=all)\n" ++ "[__1] fail (backjumping, conflict set: A, C)\n" ++ "After searching the rest of the dependency tree exhaustively, " ++ "these were the goals I've had most trouble fulfilling: A, C, B"