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

Deduplicate "using config from" message #10546

Merged
merged 2 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ViewPatterns #-}

module Distribution.Solver.Types.ProjectConfigPath
(
Expand All @@ -7,10 +8,11 @@ module Distribution.Solver.Types.ProjectConfigPath
, projectConfigPathRoot
, nullProjectConfigPath
, consProjectConfigPath
, unconsProjectConfigPath

-- * Messages
, docProjectConfigPath
, docProjectConfigPaths
, docProjectConfigFiles
, cyclicalImportMsg
, docProjectConfigPathFailReason

Expand All @@ -21,17 +23,19 @@ module Distribution.Solver.Types.ProjectConfigPath
) where

import Distribution.Solver.Compat.Prelude hiding (toList, (<>))
import qualified Distribution.Solver.Compat.Prelude as P ((<>))
import Prelude (sequence)

import Data.Coerce (coerce)
import Data.List.NonEmpty ((<|))
import Network.URI (parseURI)
import Network.URI (parseURI, parseAbsoluteURI)
import System.Directory
import System.FilePath
import qualified Data.List.NonEmpty as NE
import Distribution.Solver.Modular.Version (VR)
import Distribution.Pretty (prettyShow)
import Text.PrettyPrint
import Distribution.Simple.Utils (ordNub)

-- | Path to a configuration file, either a singleton project root, or a longer
-- list representing a path to an import. The path is a non-empty list that we
Expand All @@ -45,7 +49,41 @@ import Text.PrettyPrint
-- List elements are relative to each other but once canonicalized, elements are
-- relative to the directory of the project root.
newtype ProjectConfigPath = ProjectConfigPath (NonEmpty FilePath)
deriving (Eq, Ord, Show, Generic)
deriving (Eq, Show, Generic)

-- | Sorts URIs after local file paths and longer file paths after shorter ones
-- as measured by the number of path segments. If still equal, then sorting is
-- lexical.
--
-- The project itself, a single element root path, compared to any of the
-- configuration paths it imports, should always sort first. Comparing one
-- project root path against another is done lexically.
instance Ord ProjectConfigPath where
compare pa@(ProjectConfigPath (NE.toList -> as)) pb@(ProjectConfigPath (NE.toList -> bs)) =
case (as, bs) of
-- There should only ever be one root project path, only one path
-- with length 1. Comparing it to itself should be EQ. Don't assume
-- this though, do a comparison anyway when both sides have length
-- 1. The root path, the project itself, should always be the first
-- path in a sorted listing.
([a], [b]) -> compare a b
([_], _) -> LT
(_, [_]) -> GT

(a:_, b:_) -> case (parseAbsoluteURI a, parseAbsoluteURI b) of
(Just ua, Just ub) -> compare ua ub P.<> compare aImporters bImporters
(Just _, Nothing) -> GT
(Nothing, Just _) -> LT
(Nothing, Nothing) -> compare (splitPath a) (splitPath b) P.<> compare aImporters bImporters
_ ->
compare (length as) (length bs)
P.<> compare (length aPaths) (length bPaths)
P.<> compare aPaths bPaths
where
aPaths = splitPath <$> as
bPaths = splitPath <$> bs
aImporters = snd $ unconsProjectConfigPath pa
bImporters = snd $ unconsProjectConfigPath pb

instance Binary ProjectConfigPath
instance Structured ProjectConfigPath
Expand Down Expand Up @@ -95,15 +133,16 @@ docProjectConfigPath (ProjectConfigPath (p :| ps)) = vcat $
-- , ProjectConfigPath ("project-cabal/pkgs/integration-tests.config" :| ["project-cabal/pkgs.config","cabal.project"])
-- , ProjectConfigPath ("project-cabal/pkgs/tests.config" :| ["project-cabal/pkgs.config","cabal.project"])
-- ]
-- return . render $ docProjectConfigPaths ps
-- return . render $ docProjectConfigFiles ps
-- :}
-- "- cabal.project\n- project-cabal/constraints.config\n- project-cabal/ghc-latest.config\n- project-cabal/ghc-options.config\n- project-cabal/pkgs.config\n- project-cabal/pkgs/benchmarks.config\n- project-cabal/pkgs/buildinfo.config\n- project-cabal/pkgs/cabal.config\n- project-cabal/pkgs/install.config\n- project-cabal/pkgs/integration-tests.config\n- project-cabal/pkgs/tests.config"
docProjectConfigPaths :: [ProjectConfigPath] -> Doc
docProjectConfigPaths ps = vcat
[ text "-" <+> text p | ProjectConfigPath (p :| _) <- ps ]
docProjectConfigFiles :: [ProjectConfigPath] -> Doc
docProjectConfigFiles ps = vcat
[ text "-" <+> text p
| p <- ordNub [ p | ProjectConfigPath (p :| _) <- ps ]
]

-- | A message for a cyclical import, assuming the head of the path is the
-- duplicate.
-- | A message for a cyclical import, a "cyclical import of".
cyclicalImportMsg :: ProjectConfigPath -> Doc
cyclicalImportMsg path@(ProjectConfigPath (duplicate :| _)) =
vcat
Expand Down Expand Up @@ -148,6 +187,10 @@ isTopLevelConfigPath (ProjectConfigPath p) = NE.length p == 1
consProjectConfigPath :: FilePath -> ProjectConfigPath -> ProjectConfigPath
consProjectConfigPath p ps = ProjectConfigPath (p <| coerce ps)

-- | Split the path into the importee and the importer path.
unconsProjectConfigPath :: ProjectConfigPath -> (FilePath, Maybe ProjectConfigPath)
unconsProjectConfigPath ps = fmap ProjectConfigPath <$> NE.uncons (coerce ps)

-- | Make paths relative to the directory of the root of the project, not
-- relative to the file they were imported from.
makeRelativeConfigPath :: FilePath -> ProjectConfigPath -> ProjectConfigPath
Expand Down
2 changes: 1 addition & 1 deletion cabal-install/src/Distribution/Client/ProjectConfig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ renderBadPackageLocations (BadPackageLocations provenance bpls)

renderExplicit =
"When using configuration from:\n"
++ render (nest 2 . docProjectConfigPaths $ mapMaybe getExplicit (Set.toList provenance))
++ render (nest 2 . docProjectConfigFiles $ mapMaybe getExplicit (Set.toList provenance))
++ "\nThe following errors occurred:\n"
++ render (nest 2 $ vcat ((text "-" <+>) . text <$> map renderBadPackageLocation bpls))

Expand Down
4 changes: 2 additions & 2 deletions cabal-testsuite/PackageTests/ConditionalAndImport/cabal.out
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ cyclical import of cyclical-2-out-out-self-b.config;
# checking that cyclical check doesn't false-positive on same file names in different folders; hoping within a folder and then into a subfolder
# cabal v2-build
Configuration is affected by the following files:
- noncyclical-same-filename-a.project
- noncyclical-same-filename-a.config
imported by: noncyclical-same-filename-a.project
- noncyclical-same-filename-a.project
- same-filename/noncyclical-same-filename-a.config
imported by: noncyclical-same-filename-a.config
imported by: noncyclical-same-filename-a.project
Expand All @@ -83,10 +83,10 @@ Building library for my-0.1...
# checking that cyclical check doesn't false-positive on same file names in different folders; hoping into a subfolder and then back out again
# cabal v2-build
Configuration is affected by the following files:
- noncyclical-same-filename-b.project
- noncyclical-same-filename-b.config
imported by: same-filename/noncyclical-same-filename-b.config
imported by: noncyclical-same-filename-b.project
- noncyclical-same-filename-b.project
- same-filename/noncyclical-same-filename-b.config
imported by: noncyclical-same-filename-b.project
Up to date
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- WARNING: Override the `with-compiler: ghc-x.y.z` of the stackage import, of
-- https://www.stackage.org/nightly-yyyy-mm-dd/cabal.config. Otherwise tests
-- will fail with:
-- -Error: [Cabal-5490]
-- -Cannot find the program 'ghc'. User-specified path 'ghc-x.y.z' does not
-- refer to an executable and the program is not on the system path.
with-compiler: ghc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import: cfg/1.config
import: cfg/3.config
import: cfg/5.config
import: cfg/7.config
import: cfg/9.config

import: with-ghc.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: cfg/3.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: cfg/5.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: cfg/7.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: cfg/9.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# checking "using config from message" with URI imports
# cabal v2-build
# checking that package directories and locations are reported in order
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Test.Cabal.Prelude

main = cabalTest . recordMode RecordMarked $ do
let log = recordHeader . pure

log "checking \"using config from message\" with URI imports"
out <- fails $ cabal' "v2-build" [ "all", "--dry-run", "--project-file=no-pkgs.project" ]

-- Use assertRegex when the output is tainted by the temp directory, like
-- this:
--
-- When using configuration from:
-- - /tmp/cabal-testsuite-282695/cabal.project
-- - /tmp/cabal-testsuite-282695/2.config etc
assertRegex
"Project configuration with URI imports is listed in full"
"When using configuration from:(\n|\r\n) \
\ .*no-pkgs\\.project(\n|\r\n) \
\ .*0\\.config(\n|\r\n) \
\ .*2\\.config(\n|\r\n) \
\ .*4\\.config(\n|\r\n) \
\ .*6\\.config(\n|\r\n) \
\ .*8\\.config(\n|\r\n) \
\ .*1\\.config(\n|\r\n) \
\ .*3\\.config(\n|\r\n) \
\ .*5\\.config(\n|\r\n) \
\ .*7\\.config(\n|\r\n) \
\ .*9\\.config(\n|\r\n) \
\ .*with-ghc\\.config(\n|\r\n) \
\ .*https://www.stackage.org/lts-21.25/cabal.config(\n|\r\n)"
out

log "checking that package directories and locations are reported in order"
assertOutputContains
"The following errors occurred: \
\ - The package directory 'no-pkg-1' does not contain any .cabal file. \
\ - The package location 'no-pkg-2-dir' does not exist. \
\ - The package directory 'no-pkg-3' does not contain any .cabal file. \
\ - The package location 'no-pkg-4-dir' does not exist."
out

return ()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: ../2.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: ../4.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: ../6.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: ../8.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- No imports here
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
There's intentionally no package here but the directory for the package exists
so that the project can find it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
There's intentionally no package here but the directory for the package exists
so that the project can find it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
packages:
no-pkg-1
no-pkg-2-dir
no-pkg-3
no-pkg-4-dir

import: 0.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- WARNING: Override the `with-compiler: ghc-x.y.z` of the stackage import, of
-- https://www.stackage.org/nightly-yyyy-mm-dd/cabal.config. Otherwise tests
-- will fail with:
-- -Error: [Cabal-5490]
-- -Cannot find the program 'ghc'. User-specified path 'ghc-x.y.z' does not
-- refer to an executable and the program is not on the system path.
with-compiler: ghc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: https://www.stackage.org/lts-21.25/cabal.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import: https://www.stackage.org/lts-21.25/cabal.config
import: https://www.stackage.org/lts-21.25/cabal.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# cabal v2-build
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
packages: no-pkg-dir
import: z-empty.config
import: an-extra.config
import: an-extra.config
import: a-very-extra.config
import: a-very-extra.config
import: https://www.stackage.org/lts-21.25/cabal.config
import: https://www.stackage.org/lts-21.25/cabal.config
import: with-ghc.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Test.Cabal.Prelude

main = cabalTest . recordMode RecordMarked $ do
let log = recordHeader . pure

out <- fails $ cabal' "v2-build" [ "all", "--dry-run" ]

-- Use assertRegex when the output is tainted by the temp directory, like
-- this:
--
-- When using configuration from:
-- - /tmp/cabal-testsuite-282695/cabal.project
assertRegex
"Project configuration is listed in full and deduplicated"
"When using configuration from:(\n|\r\n) \
\ .*cabal\\.project(\n|\r\n) \
\ .*a-very-extra\\.config(\n|\r\n) \
\ .*an-extra\\.config(\n|\r\n) \
\ .*with-ghc\\.config(\n|\r\n) \
\ .*z-empty\\.config(\n|\r\n) \
\ .*https://www.stackage.org/lts-21.25/cabal.config(\n|\r\n)"
out

return ()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- WARNING: Override the `with-compiler: ghc-x.y.z` of the stackage import, of
-- https://www.stackage.org/nightly-yyyy-mm-dd/cabal.config. Otherwise tests
-- will fail with:
-- -Error: [Cabal-5490]
-- -Cannot find the program 'ghc'. User-specified path 'ghc-x.y.z' does not
-- refer to an executable and the program is not on the system path.
with-compiler: ghc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- This file is intentionally empty, just this comment.
82 changes: 82 additions & 0 deletions changelog.d/pr-10546
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
synopsis: Deduplicate "using configuration from" message
packages: [cabal-install-solver]
prs: 10546
---

## Using Configuration From Message Changes

Deduplicates and sorts the list of configuration files and URIs printed with the
"using configuration from" message. This message is shown when there's a build
failure. We can trigger that message by using a non-existant package in the
project, "no-pkg-dir".

If an import is repeated in a `.project` or `.config` file it only imported once
but if the same import is made from an imported file then it was being repeated
in the message. Additional problems were not showing the project first and
mixing configuration files and URIs together.

* The test set up:

```
$ cat cabal.project
cat cabal.project
packages: no-pkg-dir
import: z-empty.config
import: an-extra.config
import: an-extra.config
import: a-very-extra.config
import: a-very-extra.config
import: https://www.stackage.org/lts-21.25/cabal.config
import: https://www.stackage.org/lts-21.25/cabal.config

$ cat an-extra.config
import: https://www.stackage.org/lts-21.25/cabal.config
import: https://www.stackage.org/lts-21.25/cabal.config

$ cat a-very-extra.config
import: https://www.stackage.org/lts-21.25/cabal.config
import: https://www.stackage.org/lts-21.25/cabal.config

$ cat z-empty.config
- This file is intentionally empty, just this comment.
```

* Before the fix:

```
$ ~/.ghcup/bin/cabal-3.12.1.0 build all --dry-run
When using configuration from:
- a-very-extra.config
- an-extra.config
- cabal.project
- https://www.stackage.org/lts-21.25/cabal.config
- https://www.stackage.org/lts-21.25/cabal.config
- https://www.stackage.org/lts-21.25/cabal.config
- z-empty.config
The following errors occurred:
- The package location 'no-pkg-dir' does not exist.
```

* After the fix:

```
$ cabal build all --dry-run
When using configuration from:
- cabal.project
- a-very-extra.config
- an-extra.config
- z-empty.config
- https://www.stackage.org/lts-21.25/cabal.config
The following errors occurred:
- The package location 'no-pkg-dir' does not exist.
```

## Ord ProjectConfigPath Instance Changes

Adds a custom `Ord` instance for `ProjectConfigPath` that sorts URIs after local
file paths and longer file paths after shorter ones as measured by the number of
path segments. If still equal, then sorting is lexical. The project itself, a
single element root path, compared to any of the configuration paths it imports,
should always sort first. Comparing one project root path against another is
done lexically.
Loading