cabal icon indicating copy to clipboard operation
cabal copied to clipboard

Show import tree provenance

Open philderbeast opened this issue 1 year ago • 51 comments

Fixes #9562. This adds the path in the project import tree with the constraint conflict just below the solver's rejecting message (sample taken from the change log entry).

$ cabal build all --dry-run
...
[__1] next goal: hashable
[__1] rejecting: hashable-1.4.3.0
      (<ROOT>/1-web-import-constraints.project requires ==1.4.2.0)
[__1] rejecting: hashable-1.4.2.0
      (https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0)
        imported by: <ROOT>/1-web-import-constraints.project

[!IMPORTANT] The way imports worked changed between cabal-install-3.8 and cabal-install-3.10. With the earlier version imports were anchored to the project root (relative to a fixed location) but this was changed to make them relative to their importer. I had to pay close attention to the check for seen imports, something I added tests for. These tests merged already with #9665 and #9680. I've also added verbose logging at the info level so we can see the import we're processing, the seen imports and the import we're fetching.

Because relative imports may contain .. and the only way to resolve this is to use canonicalizePath I had to take an extra dependency on directory in cabal-install-solver. I also needed network-uri to check for URIs and avoid messing with them. I assume that once we import a URI config then imports stop there. Put another way, something imported via https://... doesn't itself import another config. Those extra dependencies we should be able to remove when the suggested change of @andreabedini lands, #9642.

  • [x] Patches conform to the coding conventions.
  • [x] Any changes that could be relevant to users have been recorded in the changelog.
  • [ ] ~The documentation has been updated, if necessary.~ (Can't find "rejecting" in the docs to change).
  • [x] Manual QA notes have been included.
  • [x] Tests have been added. (I took stripped down tests that I hope to reuse and expand for #9510).

If testing manually, I suggest trying cabal build --dry-run with a simple cabal.project, a project with imports and a bare package with no project. To see the change, a solver rejection will need to be triggered by introducing a version constraint.

[!NOTE] I'd tried to use a tree-like layout but settled on a list instead after review.

I used +-- ASCII chars to mark tree nodes (and not the same ones as tree does). Can we use the nicer unicode chars?

$ tree -P 'cabal.project*' --prune -L 1 --charset ascii
.
|-- cabal.project
|-- cabal.project.buildinfo
|-- cabal.project.coverage
|-- cabal.project.doctest
|-- cabal.project.latest-ghc
|-- cabal.project.libonly
|-- cabal.project.meta
|-- cabal.project.release
|-- cabal.project.validate
|-- cabal.project.validate.libonly
`-- cabal.project.weeder

$ tree -P 'cabal.project*' --prune -L 1 --charset utf8
.
├── cabal.project
├── cabal.project.buildinfo
├── cabal.project.coverage
├── cabal.project.doctest
├── cabal.project.latest-ghc
├── cabal.project.libonly
├── cabal.project.meta
├── cabal.project.release
├── cabal.project.validate
├── cabal.project.validate.libonly
└── cabal.project.weeder

philderbeast avatar Dec 31 '23 14:12 philderbeast

@andreabedini project configuration is already in the solver. I've replaced a FilePath with a simple data type that distinguishes between the root and imported FilePath.


data ConstraintSource =
...
-  | ConstraintSourceProjectConfig FilePath
+  | ConstraintSourceProjectConfig ProjectConfigPath

I had a quick go at making ConstraintSource polymorphic but so many other things depend on it that this would lead to a lot of cascading changes.

What I can do is move some pretty printing functions from cabal-install-solver to cabal-install with https://github.com/cabalism/cabal/commit/fc87c2919317d634e989d83debae6783c33baf9f, functions showConstraintSource and showProjectConfigPath.

Does that help?

@grayjay would you want me to move those printing functions out of cabal-install-solver?

philderbeast avatar Jan 17 '24 21:01 philderbeast

@philderbeast I know it's already used but we could try to improve the separation between those parts (provided you agree with my assessment, of course).

I spent some time looking at this today and ConstraintSource is barely used in cabal-install-solver. It's only part of FailReason. Adding a type variable for it was indeed a bit invasive since it ends up propagating to Tree, which is used everywhere. But by using existential qualification it seems to be possible to confine the changes to small part of the code. Done that I was able to move ConstraintSource back into cabal-install. I haven't quite finished yet. I'll let you know how it goes.

andreabedini avatar Jan 18 '24 10:01 andreabedini

@andreabedini your changes sound promising. Do you have a WIP branch I could look at?

Do you want to raise a separate issue for improving separation between the solver, ConstraintSource and printing failures?

philderbeast avatar Jan 18 '24 11:01 philderbeast

@philderbeast I opened https://github.com/haskell/cabal/pull/9642 to not block you here.

andreabedini avatar Jan 22 '24 05:01 andreabedini

I've fixed the remaining CI problem (@gbaz using project overrides as they currently work ;-)).

philderbeast avatar Jan 23 '24 17:01 philderbeast

@grayjay could you please take a look at this? @andreabedini has #9642 that will tease ConstraintSource out of cabal-install-solver separately.

philderbeast avatar Jan 23 '24 17:01 philderbeast

This change that shows more context on a constraint conflict (the path to the conflict in the import tree) is ready for review.

philderbeast avatar Feb 06 '24 11:02 philderbeast

While working on this, I uncovered and fixed some bugs with imports. These fixes can be seen in the changes to cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs.

philderbeast avatar Feb 06 '24 13:02 philderbeast

@Mikolaj thanks for expediting #9665 and #9680 ahead of this change. Do you know anyone that could be interested in being second reviewer? I'm keen to do more work in this area after this merges.

@gbaz thanks for proposing project overrides for summer of code 2024.

philderbeast avatar Feb 07 '24 14:02 philderbeast

This pull request has been sitting idle since 1-Feb waiting on a second reviewer. Is anyone available to help?

philderbeast avatar Feb 09 '24 17:02 philderbeast

Ideally @grayjay and/or @gbaz would be so kind as to review this, since it relates to the dark arts of Solving.

Mikolaj avatar Feb 09 '24 18:02 Mikolaj

Meta

I'm a little worried about how this fits into a bigger picture of improving solver's output. See e.g. #8475. Especially I'm always nervous when we add more information to the default output. In this case, the solver is already infamous for being mouthful. I'd feel much easier if this new output was hidden behind a flag. But I understand that people may have reasons to want it on by default. Such tradeoffs are always hard to settle.

Feedback

It'd be good to show an example output right in the PR description. I'm going with what I see in the changelog file...

  [__1] next goal: hashable
  [__1] rejecting: hashable-1.4.3.0 (constraint from project requires ==1.4.2.0)
       +-- cabal.project requires ==1.4.2.0
  [__1] rejecting: hashable-1.4.2.0 (constraint from project requires ==1.4.3.0)
       +-- cabal.project
        +-- https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0

I have the following issues.

First, it'd be nice to show "before and after", not just "after", otherwise it's hard to judge presentation, and presentation is important here.

Second, the nested part doesn't seem aligned right to me. I'm taking as an example a variant of tree (exa -T):

❯ exa -T
.
├── Cabal-syntax.cabal
├── ChangeLog.md
├── LICENSE
├── README.md
├── Setup.hs
└── src
   ├── Distribution
   │  ├── Backpack.hs
   │  ├── CabalSpecVersion.hs

By analogy, the relevant portion of output should be something like:

       +-- cabal.project
          +-- https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0

Note the two extra spaces in the second line.

Third, the relative alignment between [__1] and +-- looks strange to me but I guess it more or less follows the logic of tree I discuss above. So, I'm not sure how to improve it. Perhaps, it's fine.

Unicode

I'm all for it. It'd make things much more readable. And we can create a precedent that others will be happy to follow. I think we shouldn't kick the can down the road once again and should try to settle this question in this PR. This will unlock #8475 and perhaps others.

Could you invest a bit of your time into researching: what exactly may go wrong with Unicode? One thing I'd try is to run cabal with TERM=dumb and see how your output looks. But that's a cheap guess. Perhaps the Internet knows more about what kind of arrangements can be made so that we don't blow up someone's terminal with our emoji and stuff... Maybe there's a nice Haskell package that abstracts some logic to choose between unicode v. ascii based on the environment? Cabal-install(-solver) could use more packages no problem, I think.

ulysses4ever avatar Feb 09 '24 19:02 ulysses4ever

https://downloads.haskell.org/ghc/9.2.5/docs/html/libraries/base-4.16.4.0/GHC-IO-Encoding-Iconv.html with https://downloads.haskell.org/ghc/9.2.5/docs/html/libraries/base-4.16.4.0/GHC-IO-Encoding-Failure.html#t:CodingFailureMode may be of interest here.

geekosaur avatar Feb 09 '24 19:02 geekosaur

Third, the relative alignment between [__1] and +-- looks strange to me but I guess it more or less follows the logic of tree I discuss above. So, I'm not sure how to improve it. Perhaps, it's fine.

This was the first available column that didn't visually seem to encroach on the [__n] step counter. I tried further left alignment and it looked odd.

philderbeast avatar Feb 09 '24 19:02 philderbeast

I only used as much indentation as needed to show nesting but am happy to indent more. It is a quick fix. Some imports are quite long and I wanted to save columns.

       ... cabal.project
-        +-- https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0
+          +-- https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0

philderbeast avatar Feb 09 '24 19:02 philderbeast

It'd be good to show an example output right in the PR description.

Thanks for the review @ulysses4ever and thanks for the suggestion. I've done this.

philderbeast avatar Feb 09 '24 19:02 philderbeast

@ulysses4ever I'd really rather not tackle unicode output on this pull request but would be happy to investigate it on a separate issue.

philderbeast avatar Feb 09 '24 19:02 philderbeast

Especially I'm always nervous when we add more information to the default output.

That's a good point @ulysses4ever especially in the case where there's only one cabal.project with no further imports.

philderbeast avatar Feb 09 '24 19:02 philderbeast

I'd really rather not tackle unicode output on this pull request but would be happy to investigate it on a separate issue.

Fair enough. Let's wait up to one more week to see if Mikolaj's pings come through and we get the attention of the solver gurus.

ulysses4ever avatar Feb 09 '24 19:02 ulysses4ever

First, it'd be nice to show "before and after", not just "after", otherwise it's hard to judge presentation, and presentation is important here.

Do the tests' .out files show enough detail of before and after?

philderbeast avatar Feb 09 '24 19:02 philderbeast

especially in the case where there's only one cabal.project with no further imports.

Indeed. I'd potentially look into special-casing one project file and see if the output can be compacted in that case. It may look a little ugly in code, but may look much nicer to the user, and the latter is what I care more about.

That's, again, more like food for thought than an actual suggestion.

ulysses4ever avatar Feb 09 '24 19:02 ulysses4ever

Do the tests' .out files show enough detail of before and after?

Well, diffs are not always pleasant to read. Also, it'd be interesting to see deeper nesting than what we have in tests. Finally, as a rule of thumb, I suggest to always put before/after for UI/X-related PR into PRs description: it just gives a better chance for getting people to discuss it, in my experience.

ulysses4ever avatar Feb 09 '24 19:02 ulysses4ever

I only used as much indentation as needed to show nesting but am happy to indent more. It is a quick fix. Some imports are quite long and I wanted to save columns.

I was wondering if the nested indentation was necessary. Could all of the paths be indented by the same amount, since they form a list, not a tree? I also think that the +-- should be indented at least as far as "rejecting" in the message above.

sebright avatar Feb 10 '24 07:02 sebright

Sorry for the delay.

No worries and thanks for the thorough review.

philderbeast avatar Feb 11 '24 22:02 philderbeast

From Haskell with UTF-8, I was able to print the unicode chars I need for the tree with just these changes;

git diff cabal-install/src/Distribution/Client/Main.hs
diff --git a/cabal-install/src/Distribution/Client/Main.hs b/cabal-install/src/Distribution/Client/Main.hs
index 6ef0a6737..b6408bf52 100644
--- a/cabal-install/src/Distribution/Client/Main.hs
+++ b/cabal-install/src/Distribution/Client/Main.hs
@@ -96,6 +96,8 @@ import Distribution.Simple.Setup
   , toFlag
   )
 
+import Main.Utf8 (withUtf8)
 import Distribution.Client.Compat.Prelude hiding (get)
 import Prelude ()
 
@@ -299,7 +301,7 @@ import System.Process (createProcess, env, proc)
 -- producing 'CommandParse' data, which mainWorker pattern-matches
 -- into IO actions for execution.
 main :: [String] -> IO ()
-main args = do
+main args = withUtf8 $ do
   installTerminationHandler
   -- Enable line buffering so that we can get fast feedback even when piped.
   -- This is especially important for CI and build systems.

git diff cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs
diff --git a/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs b/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs
index 3143276d0..198b2929b 100644
--- a/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs
+++ b/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs
@@ -41,7 +41,7 @@ instance Structured ProjectConfigPath
 showProjectConfigPath :: ProjectConfigPath -> String
 showProjectConfigPath (ProjectConfigPath xs) =
     unlines
-        [ (nTimes i (showChar ' ') . showString "+-- " . showString x) ""
+        [ (nTimes i (showChar ' ') . showString "└─ " . showString x) ""
         | x <- reverse $ toList xs
         | i <- [0..]
         ]

Is it acceptable for cabal-install to take a dependency on with-utf8?

philderbeast avatar Feb 12 '24 19:02 philderbeast

I double checked

From Haskell with UTF-8, I was able to print the unicode chars I need for the tree with just these changes;

Scratch that optimism :cry: . I think that I must have always been able to print those unicode chars to my shell (fish on ubuntu). It must have been cabal-testsuite:cabal-tests that was not printing the unicode chars correctly. It prints ?? instead of └─ to cabal-testsuite/PackageTests/ConditionalAndImport/cabal.out when I run that test.

philderbeast avatar Feb 12 '24 19:02 philderbeast

I also think that the +-- should be indented at least as far as "rejecting" in the message above.

I've done this @grayjay.

philderbeast avatar Feb 13 '24 13:02 philderbeast

I was wondering if the nested indentation was necessary. Could all of the paths be indented by the same amount, since they form a list, not a tree?

@grayjay I tried the list and it doesn't work for me. I immediately wonder, do all items in the list import the .config with the conflict?

[__1] rejecting: hashable-1.4.3.0
      (<ROOT>/3-web-import-constraints.project requires ==1.4.2.0)
[__1] rejecting: hashable-1.4.2.0
      (https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0)
      imported by: 
      - stackage-web.config
      - hop-web.config
      - <ROOT>/3-web-import-constraints.project

Here are some keyed list alternatives that may be better than a plain list but they feel clunky;

[__1] rejecting: hashable-1.4.2.0
      (#4. https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0)
       #4. imported by #3. stackage-web.config
       #3. imported by #2. hop-web.config
       #2. imported by #1. <ROOT>/3-web-import-constraints.project
[__1] rejecting: hashable-1.4.2.0
      (#4. https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0)
       #3. stackage-web.config
       #2. hop-web.config
       #1. <ROOT>/3-web-import-constraints.project
      (#1 imports (#2 imports (#3 imports #4)))

Showing the .config with the conflict first seems better, not so far to scan with the eyes from the "rejecting" to the "requires".

Of the list-like displays I've tried so far, this is the one I like the most and the one I've commited;

[__1] rejecting: hashable-1.4.3.0
      (<ROOT>/3-web-import-constraints.project requires ==1.4.2.0)
[__1] rejecting: hashable-1.4.2.0
      (https://www.stackage.org/nightly-2023-12-07/cabal.config requires ==1.4.3.0)
      imported by: stackage-web.config
      imported by: hop-web.config
      imported by: <ROOT>/3-web-import-constraints.project

philderbeast avatar Feb 13 '24 14:02 philderbeast

I'd potentially look into special-casing one project file and see if the output can be compacted in that case.

I've done this @grayjay, showProjectConfigPathFailReason prints a single line for one project with no imports or where the failure is in the project root.

philderbeast avatar Feb 14 '24 00:02 philderbeast

@grayjay I've made suggested changes, responded to questions and added another test case that now catches Y-shaped duplicate imports. This pull request is now ready for a follow up review.

philderbeast avatar Feb 14 '24 01:02 philderbeast