From 362ad704c33733617bec378ddbdecc3afc021b2d Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Fri, 11 Jul 2025 12:59:05 +1000 Subject: [PATCH 1/7] Solver: shorten the skipping message if needed Also add a test to prevent this regressing. Closes: https://github.com/haskell/cabal/issues/4251 --- .../src/Distribution/Solver/Modular/Message.hs | 6 +++--- .../UnitTests/Distribution/Solver/Modular/Solver.hs | 13 +++++++++++++ changelog.d/pr-11062 | 8 ++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 changelog.d/pr-11062 diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 9cc4234e66e..35c51789b7d 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -282,7 +282,7 @@ showOption qpn@(Q _pp pn) (POption i linkedTo) = -- >>> showOptions foobarQPN [k1, k2] -- "foo-bar; foo-bar~>bazqux.foo-bar-1, foo-bar~>bazqux.foo-bar-2" -- >>> showOptions foobarQPN [v0, i1, k2] --- "foo-bar; 0, 1/installed-inplace, foo-bar~>bazqux.foo-bar-2" +-- "foo-bar; 0, 1/installed-inplace, foo-bar~>bazqux.foo-bar-2 and earlier versions" showOptions :: QPN -> [POption] -> String showOptions _ [] = "unexpected empty list of versions" showOptions q [x] = showOption q x @@ -290,8 +290,8 @@ showOptions q xs = showQPN q ++ "; " ++ (L.intercalate ", " [if isJust linkedTo then showOption q x else showI i -- Don't show the package, just the version - | x@(POption i linkedTo) <- xs - ]) + | x@(POption i linkedTo) <- take 3 xs + ] ++ if length xs >= 3 then " and earlier versions" else "") showGR :: QGoalReason -> String showGR UserGoal = " (user goal)" diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 691d9b1d39e..8aab7550cf9 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -986,6 +986,19 @@ tests = skipping = "skipping: A; 2.0.0/installed-2.0.0, 1.0.0/installed-1.0.0" in mkTest db "show skipping versions list, installed" ["B"] $ solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) + , runTest $ + let db = + [ Right $ exAv "A" 1 [] + , Right $ exAv "A" 2 [] + , Right $ exAv "A" 3 [] + , Right $ exAv "A" 4 [] + , Right $ exAv "A" 5 [] + , Right $ exAv "B" 1 [ExFix "A" 6] + ] + rejecting = "rejecting: A-5.0.0 (conflict: B => A==6.0.0)" + skipping = "skipping: A; 4.0.0, 3.0.0, 2.0.0 and earlier versions (has" + in mkTest db "show summarized skipping versions list" ["B"] $ + solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) ] ] ] diff --git a/changelog.d/pr-11062 b/changelog.d/pr-11062 new file mode 100644 index 00000000000..50dfa1919f1 --- /dev/null +++ b/changelog.d/pr-11062 @@ -0,0 +1,8 @@ +synopsis: Solver: shorten the skipping message if needed +packages: cabal-install-solver +prs: #11062 + +When the solver fails to find a solution, it can print out a long list +of package versions which failed to meet the requirements. This PR +shortens the message to at most 3 versions which failed to meet the +requriements. From 750b2d5e426ce1205eeab8e65cb29c7ee184251a Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Tue, 15 Jul 2025 08:27:44 +1000 Subject: [PATCH 2/7] Change earlier to other in skipped versions msg --- cabal-install-solver/src/Distribution/Solver/Modular/Message.hs | 2 +- .../tests/UnitTests/Distribution/Solver/Modular/Solver.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 35c51789b7d..2a3f10e37e1 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -291,7 +291,7 @@ showOptions q xs = showQPN q ++ "; " ++ (L.intercalate ", " then showOption q x else showI i -- Don't show the package, just the version | x@(POption i linkedTo) <- take 3 xs - ] ++ if length xs >= 3 then " and earlier versions" else "") + ] ++ if length xs >= 3 then " and other versions" else "") showGR :: QGoalReason -> String showGR UserGoal = " (user goal)" diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 8aab7550cf9..1e9157f5091 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -996,7 +996,7 @@ tests = , Right $ exAv "B" 1 [ExFix "A" 6] ] rejecting = "rejecting: A-5.0.0 (conflict: B => A==6.0.0)" - skipping = "skipping: A; 4.0.0, 3.0.0, 2.0.0 and earlier versions (has" + skipping = "skipping: A; 4.0.0, 3.0.0, 2.0.0 and other versions (has" in mkTest db "show summarized skipping versions list" ["B"] $ solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) ] From 95f82cfa7ac8b20c04873a96fba4911e8efabc89 Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Tue, 15 Jul 2025 13:46:31 +1000 Subject: [PATCH 3/7] Plumb verbosity level down to where its needed Use `setVerbose` in two of the unit tests in preparation for upcoming changes to the `skipping ...` message. --- .../src/Distribution/Solver/Modular.hs | 2 +- .../Distribution/Solver/Modular/Message.hs | 55 ++++++++++--------- .../src/Distribution/Client/Dependency.hs | 2 +- .../Distribution/Solver/Modular/Solver.hs | 10 ++-- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular.hs b/cabal-install-solver/src/Distribution/Solver/Modular.hs index a4baebf496c..850b06ea007 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular.hs @@ -200,7 +200,7 @@ solve' sc cinfo idx pkgConfigDB pprefs gcs pns = -- original goal order. goalOrder' = preferGoalsFromConflictSet cs <> fromMaybe mempty (goalOrder sc) - in unlines ("Could not resolve dependencies:" : map renderSummarizedMessage (messages (toProgress (runSolver True sc')))) + in unlines ("Could not resolve dependencies:" : map (renderSummarizedMessage (solverVerbosity sc)) (messages (toProgress (runSolver True sc')))) printFullLog = solverVerbosity sc >= verbose diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 2a3f10e37e1..b27e52713ac 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -54,6 +54,7 @@ import Distribution.Types.LibraryName ( LibraryName(LSubLibName, LMainLibName) ) import Distribution.Types.UnqualComponentName ( unUnqualComponentName ) +import Distribution.Verbosity (Verbosity, verbose) import Text.PrettyPrint ( nest, render ) @@ -69,32 +70,32 @@ data Message = | Success | Failure ConflictSet FailReason -renderSummarizedMessage :: SummarizedMessage -> String -renderSummarizedMessage (SummarizedMsg i) = displayMessageAtLevel i -renderSummarizedMessage (StringMsg s) = s +renderSummarizedMessage :: Verbosity -> SummarizedMessage -> String +renderSummarizedMessage verb (SummarizedMsg i) = displayMessageAtLevel verb i +renderSummarizedMessage _ (StringMsg s) = s -displayMessageAtLevel :: EntryAtLevel -> String -displayMessageAtLevel (AtLevel l msg) = +displayMessageAtLevel :: Verbosity -> EntryAtLevel -> String +displayMessageAtLevel verb (AtLevel l msg) = let s = show l - in "[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ displayMessage msg - -displayMessage :: Entry -> String -displayMessage (EntryPackageGoal qpn gr) = "next goal: " ++ showQPN qpn ++ showGR gr -displayMessage (EntryRejectF qfn b c fr) = "rejecting: " ++ showQFNBool qfn b ++ showFR c fr -displayMessage (EntryRejectS qsn b c fr) = "rejecting: " ++ showQSNBool qsn b ++ showFR c fr -displayMessage (EntrySkipping cs) = "skipping: " ++ showConflicts cs -displayMessage (EntryTryingF qfn b) = "trying: " ++ showQFNBool qfn b -displayMessage (EntryTryingP qpn i) = "trying: " ++ showOption qpn i -displayMessage (EntryTryingNewP qpn i gr) = "trying: " ++ showOption qpn i ++ showGR gr -displayMessage (EntryTryingS qsn b) = "trying: " ++ showQSNBool qsn b -displayMessage (EntryUnknownPackage qpn gr) = "unknown package: " ++ showQPN qpn ++ showGR gr -displayMessage EntrySuccess = "done" -displayMessage (EntryFailure c fr) = "fail" ++ showFR c fr -displayMessage (EntrySkipMany qsn b cs) = "skipping: " ++ showOptions qsn b ++ " " ++ showConflicts cs + in "[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ displayMessage verb msg + +displayMessage :: Verbosity -> Entry -> String +displayMessage _ (EntryPackageGoal qpn gr) = "next goal: " ++ showQPN qpn ++ showGR gr +displayMessage _ (EntryRejectF qfn b c fr) = "rejecting: " ++ showQFNBool qfn b ++ showFR c fr +displayMessage _ (EntryRejectS qsn b c fr) = "rejecting: " ++ showQSNBool qsn b ++ showFR c fr +displayMessage _ (EntrySkipping cs) = "skipping: " ++ showConflicts cs +displayMessage _ (EntryTryingF qfn b) = "trying: " ++ showQFNBool qfn b +displayMessage _ (EntryTryingP qpn i) = "trying: " ++ showOption qpn i +displayMessage _ (EntryTryingNewP qpn i gr) = "trying: " ++ showOption qpn i ++ showGR gr +displayMessage _ (EntryTryingS qsn b) = "trying: " ++ showQSNBool qsn b +displayMessage _ (EntryUnknownPackage qpn gr) = "unknown package: " ++ showQPN qpn ++ showGR gr +displayMessage _ EntrySuccess = "done" +displayMessage _ (EntryFailure c fr) = "fail" ++ showFR c fr +displayMessage verb (EntrySkipMany qsn b cs) = "skipping: " ++ showOptions verb qsn b ++ " " ++ showConflicts cs -- Instead of displaying `aeson-1.0.2.1, aeson-1.0.2.0, aeson-1.0.1.0, ...`, -- the following line aims to display `aeson: 1.0.2.1, 1.0.2.0, 1.0.1.0, ...`. -- -displayMessage (EntryRejectMany qpn is c fr) = "rejecting: " ++ showOptions qpn is ++ showFR c fr +displayMessage verb (EntryRejectMany qpn is c fr) = "rejecting: " ++ showOptions verb qpn is ++ showFR c fr -- | Transforms the structured message type to actual messages (SummarizedMessage s). -- @@ -283,15 +284,15 @@ showOption qpn@(Q _pp pn) (POption i linkedTo) = -- "foo-bar; foo-bar~>bazqux.foo-bar-1, foo-bar~>bazqux.foo-bar-2" -- >>> showOptions foobarQPN [v0, i1, k2] -- "foo-bar; 0, 1/installed-inplace, foo-bar~>bazqux.foo-bar-2 and earlier versions" -showOptions :: QPN -> [POption] -> String -showOptions _ [] = "unexpected empty list of versions" -showOptions q [x] = showOption q x -showOptions q xs = showQPN q ++ "; " ++ (L.intercalate ", " +showOptions :: Verbosity -> QPN -> [POption] -> String +showOptions _ _ [] = "unexpected empty list of versions" +showOptions _ q [x] = showOption q x +showOptions verb q xs = showQPN q ++ "; " ++ (L.intercalate ", " [if isJust linkedTo then showOption q x else showI i -- Don't show the package, just the version - | x@(POption i linkedTo) <- take 3 xs - ] ++ if length xs >= 3 then " and other versions" else "") + | x@(POption i linkedTo) <- if verb >= verbose then xs else take 3 xs + ] ++ if verb < verbose && length xs >= 3 then " and other versions" else "") showGR :: QGoalReason -> String showGR UserGoal = " (user goal)" diff --git a/cabal-install/src/Distribution/Client/Dependency.hs b/cabal-install/src/Distribution/Client/Dependency.hs index a65c41cb046..f0b1e68ef7d 100644 --- a/cabal-install/src/Distribution/Client/Dependency.hs +++ b/cabal-install/src/Distribution/Client/Dependency.hs @@ -864,7 +864,7 @@ resolveDependencies platform comp pkgConfigDB params = else dontInstallNonReinstallablePackages params formatProgress :: Progress SummarizedMessage String a -> Progress String String a - formatProgress p = foldProgress (\x xs -> Step (renderSummarizedMessage x) xs) Fail Done p + formatProgress p = foldProgress (\x xs -> Step (renderSummarizedMessage (depResolverVerbosity params) x) xs) Fail Done p preferences :: PackageName -> PackagePreferences preferences = interpretPackagesPreference targets defpref prefs diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 1e9157f5091..d3ea5041c6d 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -973,8 +973,9 @@ tests = ] rejecting = "rejecting: A-3.0.0" skipping = "skipping: A; 2.0.0, 1.0.0" - in mkTest db "show skipping versions list" ["B"] $ - solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) + in setVerbose $ + mkTest db "show skipping versions list" ["B"] $ + solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) , runTest $ let db = [ Left $ exInst "A" 1 "A-1.0.0" [] @@ -984,8 +985,9 @@ tests = ] rejecting = "rejecting: A-3.0.0/installed-3.0.0" skipping = "skipping: A; 2.0.0/installed-2.0.0, 1.0.0/installed-1.0.0" - in mkTest db "show skipping versions list, installed" ["B"] $ - solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) + in setVerbose $ + mkTest db "show skipping versions list, installed" ["B"] $ + solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) , runTest $ let db = [ Right $ exAv "A" 1 [] From df816b8d1932079a03ddfda76cf285154439286e Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Thu, 7 Aug 2025 17:46:00 +1000 Subject: [PATCH 4/7] Reduce show skipped versions from 3 to 1 And fix associated unit test expected string. --- .../src/Distribution/Solver/Modular/Message.hs | 4 ++-- .../tests/UnitTests/Distribution/Solver/Modular/Solver.hs | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index b27e52713ac..9312f27a629 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -291,8 +291,8 @@ showOptions verb q xs = showQPN q ++ "; " ++ (L.intercalate ", " [if isJust linkedTo then showOption q x else showI i -- Don't show the package, just the version - | x@(POption i linkedTo) <- if verb >= verbose then xs else take 3 xs - ] ++ if verb < verbose && length xs >= 3 then " and other versions" else "") + | x@(POption i linkedTo) <- if verb >= verbose then xs else take 1 xs + ] ++ if verb < verbose && length xs >= 1 then " and other versions" else "") showGR :: QGoalReason -> String showGR UserGoal = " (user goal)" diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index d3ea5041c6d..2b06dde0be0 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -993,12 +993,10 @@ tests = [ Right $ exAv "A" 1 [] , Right $ exAv "A" 2 [] , Right $ exAv "A" 3 [] - , Right $ exAv "A" 4 [] - , Right $ exAv "A" 5 [] - , Right $ exAv "B" 1 [ExFix "A" 6] + , Right $ exAv "B" 1 [ExFix "A" 4] ] - rejecting = "rejecting: A-5.0.0 (conflict: B => A==6.0.0)" - skipping = "skipping: A; 4.0.0, 3.0.0, 2.0.0 and other versions (has" + rejecting = "rejecting: A-3.0.0 (conflict: B => A==4.0.0)" + skipping = "skipping: A; 2.0.0 and other versions (has" in mkTest db "show summarized skipping versions list" ["B"] $ solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) ] From de2a39cc5b2594da664d9b1b0d4d28ef7732c914 Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Mon, 11 Aug 2025 10:33:58 +1000 Subject: [PATCH 5/7] Update 'and other versions' message --- cabal-install-solver/src/Distribution/Solver/Modular/Message.hs | 2 +- .../tests/UnitTests/Distribution/Solver/Modular/Solver.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 9312f27a629..01f479bbe96 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -292,7 +292,7 @@ showOptions verb q xs = showQPN q ++ "; " ++ (L.intercalate ", " then showOption q x else showI i -- Don't show the package, just the version | x@(POption i linkedTo) <- if verb >= verbose then xs else take 1 xs - ] ++ if verb < verbose && length xs >= 1 then " and other versions" else "") + ] ++ if verb < verbose && length xs > 1 then " and " ++ show (length xs - 1) ++" other versions" else "") showGR :: QGoalReason -> String showGR UserGoal = " (user goal)" diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 2b06dde0be0..4a8adc57e8a 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -996,7 +996,7 @@ tests = , Right $ exAv "B" 1 [ExFix "A" 4] ] rejecting = "rejecting: A-3.0.0 (conflict: B => A==4.0.0)" - skipping = "skipping: A; 2.0.0 and other versions (has" + skipping = "skipping: A; 2.0.0 and 1 other versions (has" in mkTest db "show summarized skipping versions list" ["B"] $ solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) ] From 8e90d5f71f61ece059ebb625878a9fd5e7bbf5c1 Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Mon, 11 Aug 2025 10:36:09 +1000 Subject: [PATCH 6/7] Update changelog message --- changelog.d/pr-11062 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.d/pr-11062 b/changelog.d/pr-11062 index 50dfa1919f1..5798a87a69d 100644 --- a/changelog.d/pr-11062 +++ b/changelog.d/pr-11062 @@ -4,5 +4,5 @@ prs: #11062 When the solver fails to find a solution, it can print out a long list of package versions which failed to meet the requirements. This PR -shortens the message to at most 3 versions which failed to meet the -requriements. +shortens the message price the one version that failed and the number +of other versions which failed to meet the requriements. From c9b5ca35bf7344e13b12c0bac374418362e15b78 Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Tue, 19 Aug 2025 17:20:18 +1000 Subject: [PATCH 7/7] verbosity --- .../src/Distribution/Solver/Modular/Message.hs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 01f479bbe96..5a91e51ffce 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -268,22 +268,22 @@ showOption qpn@(Q _pp pn) (POption i linkedTo) = -- | Shows a mixed list of instances and versions in a human-friendly way, -- abbreviated. --- >>> showOptions foobarQPN [v0, v1] +-- >>> showOptions verbose foobarQPN [v0, v1] -- "foo-bar; 0, 1" --- >>> showOptions foobarQPN [v0] +-- >>> showOptions verbose foobarQPN [v0] -- "foo-bar-0" --- >>> showOptions foobarQPN [i0, i1] +-- >>> showOptions verbose foobarQPN [i0, i1] -- "foo-bar; 0/installed-inplace, 1/installed-inplace" --- >>> showOptions foobarQPN [i0, v1] +-- >>> showOptions verbose foobarQPN [i0, v1] -- "foo-bar; 0/installed-inplace, 1" --- >>> showOptions foobarQPN [v0, i1] +-- >>> showOptions verbose foobarQPN [v0, i1] -- "foo-bar; 0, 1/installed-inplace" --- >>> showOptions foobarQPN [] +-- >>> showOptions verbose foobarQPN [] -- "unexpected empty list of versions" --- >>> showOptions foobarQPN [k1, k2] +-- >>> showOptions verbose foobarQPN [k1, k2] -- "foo-bar; foo-bar~>bazqux.foo-bar-1, foo-bar~>bazqux.foo-bar-2" --- >>> showOptions foobarQPN [v0, i1, k2] --- "foo-bar; 0, 1/installed-inplace, foo-bar~>bazqux.foo-bar-2 and earlier versions" +-- >>> showOptions verbose foobarQPN [v0, i1, k2] +-- "foo-bar; 0, 1/installed-inplace, foo-bar~>bazqux.foo-bar-2" showOptions :: Verbosity -> QPN -> [POption] -> String showOptions _ _ [] = "unexpected empty list of versions" showOptions _ q [x] = showOption q x