Skip to content
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

Refactor cabal-install solver config log output #9159

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

yvan-sraka
Copy link
Collaborator

@yvan-sraka yvan-sraka commented Aug 3, 2023

This PR does not modify cabal behaviour and is about code refactoring. It's part of the initiative described in this RFC #8939.

Special thanks to @andreabedini for refactoring and authoring the displayMessage' function. This significantly enhanced the readability of Distribution.Solver.Modular.Message. Moreover, the pair-programming sessions were invaluable in helping me kick off the implementation of a PATCH to address this issue :)

Checklist:

@michaelpj
Copy link
Collaborator

Can you show us some examples?

@yvan-sraka
Copy link
Collaborator Author

I've reworked the commit message a bit:

[WIP] Fix #4251: Simplify verbose "rejecting" message in solver

This commit makes the following changes:

- Enhancements to the codebase:
  * Adds several TODO/FIXME/HELP comments, laying the groundwork for
    improved solver output as described in issue #8939 ;
  * Refactors the `showMessages` function to split the logic of
    building the output (now as a `Message'` enumeration) and the
    string representation of it (now formatted by `displayMessage'`).

- Modifications to the solver output:
  * If the `-v3` or `--minimize-conflict-set` flags are not set, it
    now prompts the user to consider using them in cases of the
    "Could not resolve dependencies: ..." error ;
  * The message "(has the same characteristics that caused the
    previous version to fail: ...)" has been rephrased to
    "all other available packages. They are excluded due to the same
    constraint that caused the last version attempted to fail: ..." ;
  * Package lists are now grouped by name. For example, instead of
    displaying `aeson-1.0.2.1, aeson-1.0.2.0, aeson-1.0.1.0, ...`, it
    now shows `aeson: 1.0.2.1, 1.0.2.0, 1.0.1.0, ...`.

Before the changes …

Resolving dependencies...
Error: cabal: Could not resolve dependencies:
[__0] trying: hello-1.0.0.2 (user goal)
[__1] next goal: base (dependency of hello)
[__1] rejecting: base-4.16.4.0/installed-4.16.4.0 (conflict: hello => base>=5)
[__1] skipping: base-4.18.0.0, base-4.17.2.0, base-4.17.1.0, base-4.17.0.0,
base-4.16.4.0, base-4.16.3.0, base-4.16.2.0, base-4.16.1.0, base-4.16.0.0,
base-4.15.1.0, base-4.15.0.0, base-4.14.3.0, base-4.14.2.0, base-4.14.1.0,
base-4.14.0.0, base-4.13.0.0, base-4.12.0.0, base-4.11.1.0, base-4.11.0.0,
base-4.10.1.0, base-4.10.0.0, base-4.9.1.0, base-4.9.0.0, base-4.8.2.0,
base-4.8.1.0, base-4.8.0.0, base-4.7.0.2, base-4.7.0.1, base-4.7.0.0,
base-4.6.0.1, base-4.6.0.0, base-4.5.1.0, base-4.5.0.0, base-4.4.1.0,
base-4.4.0.0, base-4.3.1.0, base-4.3.0.0, base-4.2.0.2, base-4.2.0.1,
base-4.2.0.0, base-4.1.0.0, base-4.0.0.0, base-3.0.3.2, base-3.0.3.1 (has the
same characteristics that caused the previous version to fail: excluded by
constraint '>=5' from 'hello')
[__1] fail (backjumping, conflict set: base, hello)
After searching the rest of the dependency tree exhaustively, these were the
goals I've had most trouble fulfilling: hello, base

… and after the changes:

Resolving dependencies...
Error: cabal: Could not resolve dependencies:
[__0] trying: hello-1.0.0.2 (user goal)
[__1] next goal: base (dependency of hello)
[__1] rejecting: base-4.16.4.0/installed: 4.16.4.0 (conflict: new package does
not match existing constraint hello => base>=5)
[__1] skipping: all other available packages. They are excluded by the same
constraint that caused the last version tried to fail:
excluded by constraint '>=5' from 'hello'
[__1] fail (backjumping, conflict set: base, hello)
For detailed error messages, please rerun with the `-v3' flag.
To improve the solver output, consider running with the
`--minimize-conflict-set' option.
After searching the rest of the dependency tree exhaustively, these were the
goals I've had most trouble fulfilling: hello, base

There are many open questions left as code comments. I would greatly appreciate any feedback on them :)

@michaelpj
Copy link
Collaborator

all other available packages

Could this perhaps say "all other available versions of package "? We're not rejecting other packages and now the message doesn't tell us which package it is talking about, so when it later references a constraint it is unclear what it refers to.

@michaelpj
Copy link
Collaborator

Also, if we run with higher verbosity we should perhaps go back to listing all the package versions that get rejected.

Alternatively, I wonder if we could do something more like footnotes, e.g. with verbose output produce

Resolving dependencies...
Error: cabal: Could not resolve dependencies:
[__0] trying: hello-1.0.0.2 (user goal)
[__1] next goal: base (dependency of hello)
[__1] rejecting: base-4.16.4.0/installed: 4.16.4.0 (conflict: new package does
not match existing constraint hello => base>=5)
[__1] skipping: all other available packages (1). They are excluded by the same
constraint that caused the last version tried to fail:
excluded by constraint '>=5' from 'hello'
[__1] fail (backjumping, conflict set: base, hello)

(1): base-X, base-Y, ...

Or maybe we even print this on normal verbosity (i.e. max of two additional package versions, then an ellipsis) and then print the full list only with higher verbosity.

@grayjay
Copy link
Collaborator

grayjay commented Aug 30, 2023

Thanks for working on the solver error messages. I haven't had time to read the code carefully yet, but I have a few comments.

  • It looks like this PR combines multiple independent improvements, so I think it would be better to split them into separate PRs. It would also help to split the displayMessage' refactoring into its own commit or PR that doesn't change behavior.
  • I think that we should add a dedicated flag for showing better solver output (if it's too expensive to show it by default), rather than suggest that users rerun with -v3. -v3 shows maximum detail for debugging, such as multiple runs of the solver with --minimize-conflict-set, which isn't always helpful for users that are trying to understand conflicts.
  • I'm not sure we should change the logic for suggesting --minimize-conflict-set. It is very unlikely to improve the solver output for small conflict sets.
  • I'm not sure I understand the change to the message "has the same characteristics that caused the previous version to fail". The new message seems to be much more specific, so I don't think that it is correct in all cases. Maybe this PR should add a special case for when one version of a package is rejected by an existing version constraint, and then one or more of the following versions are rejected by the same constraint, as in the example with base above. That is a very common case, so I think that it is worth giving it a better message.

@yvan-sraka yvan-sraka force-pushed the fix-4251 branch 4 times, most recently from 6c95e3b to 52f087e Compare October 17, 2023 06:38
@yvan-sraka yvan-sraka changed the title [WIP] Fix #4251: Solver "rejecting" message is too verbose [WIP] Refactor cabal-install solver config log output (and add a --json machine-readable option) Oct 23, 2023
@yvan-sraka
Copy link
Collaborator Author

I've reworked this PR a bit: It now aims to add a --json flag (though I'm uncertain about the name) to the solver configuration arguments (this is the last part currently not implemented). This allows the cabal-install solver's log output to be mechanized via an external command, e.g., similar to the tree view that nix-output-monitor provides for nix-build --log-format internal-json -v.

It would be greatly appreciated if this PR could receive another round of reviews, as it significantly refactors the solver logging logic: Instead of relying on strings, it introduces a new data structure representation of logs that's utilized in several places in the code before being serialized with show.

I'm seeking advice on:

  • what's the best method to serialize this data structure into JSON (should I just use aeson?)
  • how to introduce a new user-facing solver configuration parameter in the CLI without having to modify a significant amount of code…

@andreabedini
Copy link
Collaborator

There are TODOs and clean ups left to do, but I think this is looking great. The solver is now giving structured information (SolverTrace) and cabal can take that information and give it to the user in the desired format.

SolverTrace has also a Show instance that replicates exactly how the solver was rendering its trace before so this is a drop-in replacement that should not cause any unintended change.

@yvan-sraka

what's the best method to serialize this data structure into JSON (should I just use aeson?)

for serialising JSON we already have Distribution.Client.Utils.Json.

how to introduce a new user-facing solver configuration parameter in the CLI without having to modify a significant amount of code…

Right, this hits the legacy parsing path for ProjectConfig but we can hope be able to avoid the worst of it. I'd start by following another solver flag like --strong-flag and adding this --solver-log-format (WDYT? mimicking nix's flag name) next to it. If I am not mistaken, this gets parsed in Client.Setup and converted to ProjectConfigShared by convertLegacyAllPackageFlags. The same flag has to be added to ProjectConfigShared as well, again following what is done for the other ones.

I hope this helps 🤞

Copy link
Collaborator

@grayjay grayjay left a comment

Choose a reason for hiding this comment

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

I read the updated diff, and I'm not sure I completely understand the PR. It looks like it currently has two main parts, the refactoring to add an intermediate representation of the solver log and a --json flag.

--json flag

What is the reason for formatting the log as JSON? Is it related to making the log collapsible as in #9199? I'm still not sure I understand the use case for collapsing it. Do you want to collapse the error message that is shown by default when the solver fails to find a solution, or the full verbose (-v3) log? Do you have an example of a build where it would have helped? I'm also curious to see an example because if a subtree seems irrelevant, it's possible that the solver didn't need to explore it, and it could be optimized away.

Refactoring

I can see how the SolverTrace data structure is useful for implementing different types of formatting, such as JSON or bolded output, but I was wondering if you were also planning to use it for other parts of #8939. If so, how would SolverTrace be used to change the contents of error messages?

This is the way that I understand the current code for creating the solver log:

The solver traverses the search tree and converts it to a list of Message in the backjumpAndExplore function. The list of Message aims to preserve as much of the tree structure as possible, without using a large amount of memory, by describing going up and down the branches of the tree. Then the solver uses the remaining tree structure to convert the Messages into strings that are more helpful for the user in showMessages, for example, by combining similar rejected versions into a single line.

I would expect that it would be better to apply most processing of the contents of the log to the list of Messages, where more of the tree structure is available.

@yvan-sraka yvan-sraka marked this pull request as ready for review November 23, 2023 11:00
@yvan-sraka yvan-sraka changed the title [WIP] Refactor cabal-install solver config log output (and add a --json machine-readable option) Refactor cabal-install solver config log output Nov 23, 2023
@yvan-sraka
Copy link
Collaborator Author

yvan-sraka commented Nov 23, 2023

To answer @grayjay's concerns, I have decided to split this PR into two parts. This PR will now focus solely on the refactoring and is no longer in draft status (I have also updated the PR description accordingly).

The other PR, which is still a draft #9465, will introduce the --solver-log-format internal-json -v option and thus modify the cabal-install CLI behavior. I plan to continue the design discussions there.

Since I feel that part of the answers to your questions was in my comments: #9159 (comment) and #9199 (comment). I wrote up in the original RFC #8939 (comment) an up-to-date plan of action of what I’m trying to achieve. Hope the whole thing makes more sense :)

Copy link
Collaborator

@grayjay grayjay left a comment

Choose a reason for hiding this comment

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

@yvan-sraka Thanks for splitting the PR. I did a full review after our discussion in the meeting.

cabal-install-solver/src/Distribution/Solver/Modular.hs Outdated Show resolved Hide resolved
@@ -41,51 +42,130 @@ data Message =
| Success
| Failure ConflictSet FailReason

data Log
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could Log, SolverTrace, and related types go in cabal-install-solver/src/Distribution/Solver/Types/ with the other types that are part of the solver API?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant that the types related to SolverTrace/SummarizedMessage should go in the Types directory, because they are now part of the solver's API. Message should stay in Message.hs, in the Modular directory, because it is still only used internally.

createErrorMsg failure@(ExhaustiveSearch cs cm) =
handleFailure :: SolverFailure
-> RetryLog SolverTrace String (Assignment, RevDepMap)
handleFailure failure@(ExhaustiveSearch cs _cm) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why was cm renamed?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

| RejectS QSN Bool ConflictSet FailReason
| Skipping' (Set CS.Conflict)
| TryingF QFN Bool
| TryingP QPN POption (Maybe (GoalReason QPN))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it make sense to split the TryingP constructor into two, one for a new goal, and one for an existing goal? Then the Maybe could be removed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

@@ -41,51 +42,130 @@ data Message =
| Success
| Failure ConflictSet FailReason

data Log
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would help to have some comments describing how the new types and Message relate to each other.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

@yvan-sraka
Copy link
Collaborator Author

I moved the fix to #4251 into #9541 (so the name of this branch is no longer accurate, but anyway that's best to keep the conversation here focus on the refactoring)! Thanks again @grayjay for your full review, I've tried to address most of your comments :)

Copy link
Collaborator

@grayjay grayjay left a comment

Choose a reason for hiding this comment

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

@yvan-sraka Thanks! I still had some questions and comments from my last review, but they were collapsed due to the renaming changes, so I marked them with new comments to make them visible again. I also added a few comments on the new changes.

( maximumBy
)
import qualified Data.Map as Map
import qualified Data.Set as Set
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please undo the formatting of the imports or move it to a separate PR? It is difficult to see what has changed with the reordering.

createErrorMsg failure@(ExhaustiveSearch cs cm) =
handleFailure :: SolverFailure
-> RetryLog SolverTrace String (Assignment, RevDepMap)
handleFailure failure@(ExhaustiveSearch cs _cm) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

tryToMinimizeConflictSet runSolver sc cs cm =
foldl (\r v -> retryNoSolution r $ tryToRemoveOneVar v)
foldl (\r v -> retryMap mkErrorMsg $ retryNoSolution (retryMap show r) $ tryToRemoveOneVar v)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

Comment on lines +303 to +321
retryMap :: (t -> step) -> RetryLog t fail done -> RetryLog step fail done
retryMap f l = fromProgress $ (\p -> foldProgress (\x xs -> Step (f x) xs) Fail Done p) $ toProgress l
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

Comment on lines 165 to 166
-- 'Skip' should always be handled by 'goPSkip' in the case above.
go !l (Step (Skip conflicts) ms) = Step (SolverTrace $ AtLevel l $ (Skipping' conflicts)) (go l ms)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here

Comment on lines +74 to +86
data Entry
= LogPackageGoal QPN QGoalReason
| LogRejectF QFN Bool ConflictSet FailReason
| LogRejectS QSN Bool ConflictSet FailReason
| LogSkipping (Set CS.Conflict)
| LogTryingF QFN Bool
| LogTryingP QPN POption (Maybe (GoalReason QPN))
| LogTryingS QSN Bool
| LogRejectMany QPN [POption] ConflictSet FailReason
| LogSkipMany QPN [POption] (Set CS.Conflict)
| LogUnknownPackage QPN (GoalReason QPN)
| LogSuccessMsg
| LogFailureMsg ConflictSet FailReason
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the constructor names start with "Entry" now?

Comment on lines +85 to +86
| LogSuccessMsg
| LogFailureMsg ConflictSet FailReason
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that LogSuccessMsg and LogFailureMsg could be shortened to LogSuccess and LogFailure now.

| LogSuccessMsg
| LogFailureMsg ConflictSet FailReason

data EntryMsg = AtLevel Int Entry
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would probably be clearer to use the same name for the type and the constructor, such as EntryAtLevel.

displayMessage (LogSkipMany _ _ cs) = "skipping: " ++ showConflicts cs
displayMessage (LogRejectMany qpn is c fr) = "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr

-- | Transforms the structured message type to actual messages (SummarizedMsg s).
Copy link
Collaborator

Choose a reason for hiding this comment

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

The type name should be SummarizedMessage.


module Distribution.Solver.Modular.Message (
Message(..),
showMessages
SummarizedMessage(..),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that SummarizedMessage needs to be re-exported from this module.

@grayjay grayjay mentioned this pull request Jan 3, 2024
5 tasks
@grayjay grayjay self-requested a review January 4, 2024 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants