Skip to content

Commit 1082c0b

Browse files
authored
Merge pull request #10647 from MercuryTechnologies/improve-tar-errors
Improve "Cannot read .cabal file inside ..." errors
2 parents 62073c9 + f86d2d9 commit 1082c0b

File tree

20 files changed

+190
-33
lines changed

20 files changed

+190
-33
lines changed

cabal-install/src/Distribution/Client/Errors.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ data CabalInstallException
7070
| ReportTargetProblems String
7171
| ListBinTargetException String
7272
| ResolveWithoutDependency String
73-
| CannotReadCabalFile FilePath
73+
| CannotReadCabalFile FilePath FilePath
7474
| ErrorUpdatingIndex FilePath IOException
7575
| InternalError FilePath
7676
| ReadIndexCache FilePath
@@ -390,7 +390,11 @@ exceptionMessageCabalInstall e = case e of
390390
ReportTargetProblems problemsMsg -> problemsMsg
391391
ListBinTargetException errorStr -> errorStr
392392
ResolveWithoutDependency errorStr -> errorStr
393-
CannotReadCabalFile file -> "Cannot read .cabal file inside " ++ file
393+
CannotReadCabalFile expect file ->
394+
"Failed to read "
395+
++ expect
396+
++ " from archive "
397+
++ file
394398
ErrorUpdatingIndex name ioe -> "Error while updating index for " ++ name ++ " repository " ++ show ioe
395399
InternalError msg ->
396400
"internal error when reading package index: "

cabal-install/src/Distribution/Client/IndexUtils.hs

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ import Distribution.Client.Types
6666
import Distribution.Parsec (simpleParsecBS)
6767
import Distribution.Verbosity
6868

69+
import Distribution.Client.ProjectConfig
70+
( CabalFileParseError
71+
, readSourcePackageCabalFile'
72+
)
6973
import Distribution.Client.Setup
7074
( RepoContext (..)
7175
)
@@ -97,6 +101,7 @@ import Distribution.Simple.Utils
97101
, fromUTF8LBS
98102
, info
99103
, warn
104+
, warnError
100105
)
101106
import Distribution.Types.Dependency
102107
import Distribution.Types.PackageName (PackageName)
@@ -880,14 +885,22 @@ withIndexEntries verbosity (RepoIndex _repoCtxt (RepoLocalNoIndex (LocalRepo nam
880885
where
881886
cabalPath = prettyShow pkgid ++ ".cabal"
882887
Just pkgId -> do
888+
let tarFile = localDir </> file
883889
-- check for the right named .cabal file in the compressed tarball
884-
tarGz <- BS.readFile (localDir </> file)
890+
tarGz <- BS.readFile tarFile
885891
let tar = GZip.decompress tarGz
886892
entries = Tar.read tar
893+
expectFilename = prettyShow pkgId FilePath.</> prettyShow (packageName pkgId) ++ ".cabal"
887894

888-
case Tar.foldEntries (readCabalEntry pkgId) Nothing (const Nothing) entries of
895+
tarballPackageDescription <-
896+
Tar.foldEntries
897+
(readCabalEntry tarFile expectFilename)
898+
(pure Nothing)
899+
(handleTarFormatError tarFile)
900+
entries
901+
case tarballPackageDescription of
889902
Just ce -> return (Just ce)
890-
Nothing -> dieWithException verbosity $ CannotReadCabalFile file
903+
Nothing -> dieWithException verbosity $ CannotReadCabalFile expectFilename tarFile
891904

892905
let (prefs, gpds) =
893906
partitionEithers $
@@ -918,16 +931,55 @@ withIndexEntries verbosity (RepoIndex _repoCtxt (RepoLocalNoIndex (LocalRepo nam
918931

919932
stripSuffix sfx str = fmap reverse (stripPrefix (reverse sfx) (reverse str))
920933

921-
-- look for <pkgid>/<pkgname>.cabal inside the tarball
922-
readCabalEntry :: PackageIdentifier -> Tar.Entry -> Maybe NoIndexCacheEntry -> Maybe NoIndexCacheEntry
923-
readCabalEntry pkgId entry Nothing
924-
| filename == Tar.entryPath entry
925-
, Tar.NormalFile contents _ <- Tar.entryContent entry =
926-
let bs = BS.toStrict contents
927-
in ((`CacheGPD` bs) <$> parseGenericPackageDescriptionMaybe bs)
928-
where
929-
filename = prettyShow pkgId FilePath.</> prettyShow (packageName pkgId) ++ ".cabal"
930-
readCabalEntry _ _ x = x
934+
handleTarFormatError :: FilePath -> Tar.FormatError -> IO (Maybe NoIndexCacheEntry)
935+
handleTarFormatError tarFile formatError = do
936+
-- This is printed at warning-level because malformed `.tar.gz`s in
937+
-- indexes are unexpected and we want users to tell us about them.
938+
warnError verbosity $
939+
"Failed to parse "
940+
<> tarFile
941+
<> ": "
942+
<> displayException formatError
943+
pure Nothing
944+
945+
-- look for `expectFilename` inside the tarball
946+
readCabalEntry
947+
:: FilePath
948+
-> FilePath
949+
-> Tar.Entry
950+
-> IO (Maybe NoIndexCacheEntry)
951+
-> IO (Maybe NoIndexCacheEntry)
952+
readCabalEntry tarFile expectFilename entry previous' = do
953+
previous <- previous'
954+
case previous of
955+
Just _entry -> pure previous
956+
Nothing -> do
957+
if expectFilename /= Tar.entryPath entry
958+
then pure Nothing
959+
else case Tar.entryContent entry of
960+
Tar.NormalFile contents _fileSize -> do
961+
let bytes = BS.toStrict contents
962+
maybePackageDescription
963+
:: Either CabalFileParseError GenericPackageDescription <-
964+
-- Warnings while parsing `.cabal` files are only shown in
965+
-- verbose mode because people are allowed to upload packages
966+
-- with warnings to Hackage (and we may _add_ warnings by
967+
-- shipping new versions of Cabal). You probably don't care
968+
-- if there's a warning in some random package in your
969+
-- transitive dependency tree, as long as it's not causing
970+
-- issues. If it is causing issues, you can add `-v` to see
971+
-- the warnings!
972+
try $ readSourcePackageCabalFile' (info verbosity) expectFilename bytes
973+
case maybePackageDescription of
974+
Left exception -> do
975+
-- Here we show the _failure_ to parse the `.cabal` file as
976+
-- a warning. This will impact which versions/packages are
977+
-- available in your index, so users should know!
978+
warn verbosity $ "In " <> tarFile <> ": " <> displayException exception
979+
pure Nothing
980+
Right genericPackageDescription ->
981+
pure $ Just $ CacheGPD genericPackageDescription bytes
982+
_ -> pure Nothing
931983
withIndexEntries verbosity index callback _ = do
932984
-- non-secure repositories
933985
withFile (indexFile index) ReadMode $ \h -> do

cabal-install/src/Distribution/Client/ProjectConfig.hs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ module Distribution.Client.ProjectConfig
3737
, writeProjectConfigFile
3838
, commandLineFlagsToProjectConfig
3939
, onlyTopLevelProvenance
40+
, readSourcePackageCabalFile
41+
, readSourcePackageCabalFile'
42+
, CabalFileParseError (..)
4043

4144
-- * Packages within projects
4245
, ProjectPackageLocation (..)
@@ -173,7 +176,6 @@ import Distribution.Simple.Setup
173176
import Distribution.Simple.Utils
174177
( createDirectoryIfMissingVerbose
175178
, dieWithException
176-
, info
177179
, maybeExit
178180
, notice
179181
, rawSystemIOWithEnv
@@ -1610,10 +1612,22 @@ readSourcePackageCabalFile
16101612
-> BS.ByteString
16111613
-> IO GenericPackageDescription
16121614
readSourcePackageCabalFile verbosity pkgfilename content =
1615+
readSourcePackageCabalFile' (warn verbosity) pkgfilename content
1616+
1617+
-- | Like `readSourcePackageCabalFile`, but the `warn` function is an argument.
1618+
--
1619+
-- This is used when reading @.cabal@ files in indexes, where warnings should
1620+
-- generally be ignored.
1621+
readSourcePackageCabalFile'
1622+
:: (String -> IO ())
1623+
-> FilePath
1624+
-> BS.ByteString
1625+
-> IO GenericPackageDescription
1626+
readSourcePackageCabalFile' logWarnings pkgfilename content =
16131627
case runParseResult (parseGenericPackageDescription content) of
16141628
(warnings, Right pkg) -> do
16151629
unless (null warnings) $
1616-
info verbosity (formatWarnings warnings)
1630+
logWarnings (formatWarnings warnings)
16171631
return pkg
16181632
(warnings, Left (mspecVersion, errors)) ->
16191633
throwIO $ CabalFileParseError pkgfilename content errors mspecVersion warnings

cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/pkg/pkg.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: pkg
22
version: 1.0
33
build-type: Custom
4-
cabal-version: >= 1.20
4+
cabal-version: 1.20
55

66
custom-setup
77
setup-depends: base, Cabal, setup-dep == 2.*

cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/repo/setup-dep-1.0/setup-dep.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: setup-dep
22
version: 1.0
33
build-type: Simple
4-
cabal-version: >= 1.20
4+
cabal-version: 1.20
55

66
library
77
build-depends: base

cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/setup-dep/setup-dep.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: setup-dep
22
version: 2.0
33
build-type: Simple
4-
cabal-version: >= 1.20
4+
cabal-version: 1.20
55

66
library
77
build-depends: base
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# cabal v2-update
2+
Downloading the latest package list from test-local-repo
3+
Warning: In <ROOT>/cabal.dist/repo/my-lib-1.0.tar.gz: Errors encountered when parsing cabal file my-lib-1.0/my-lib.cabal:
4+
5+
my-lib-1.0/my-lib.cabal:4:22: error:
6+
unexpected Unknown SPDX license identifier: 'puppy'
7+
8+
3 | version: 1.0
9+
4 | license: puppy license :)
10+
| ^
11+
12+
my-lib-1.0/my-lib.cabal:5:1: warning:
13+
Unknown field: "puppy"
14+
15+
4 | license: puppy license :)
16+
5 | puppy: teehee!
17+
| ^
18+
Error: [Cabal-7046]
19+
Failed to read my-lib-1.0/my-lib.cabal from archive <ROOT>/cabal.dist/repo/my-lib-1.0.tar.gz
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Test.Cabal.Prelude
2+
3+
main = cabalTest $ withRepoNoUpdate "repo" $ do
4+
fails $ cabal "v2-update" []
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cabal-version: 3.0
2+
name: my-lib
3+
version: 1.0
4+
license: puppy license :)
5+
puppy: teehee!

cabal-testsuite/PackageTests/NewBuild/CmdRun/Script/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# cabal v2-run
2+
Warning: The package description file ./script.cabal has warnings: script.cabal:0:0: A package using 'cabal-version: >=1.10' must use section syntax. See the Cabal user guide for details.
23
Configuration is affected by the following files:
34
- cabal.project
45
Resolving dependencies...

cabal-testsuite/PackageTests/NewBuild/CmdRun/ScriptBad/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# cabal v2-run
2+
Warning: The package description file ./script.cabal has warnings: script.cabal:0:0: A package using 'cabal-version: >=1.10' must use section syntax. See the Cabal user guide for details.
23
Configuration is affected by the following files:
34
- cabal.project
45
Error: [Cabal-7121]

cabal-testsuite/PackageTests/NewBuild/CmdRun/ScriptLiterate/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# cabal v2-run
2+
Warning: The package description file ./script.cabal has warnings: script.cabal:0:0: A package using 'cabal-version: >=1.10' must use section syntax. See the Cabal user guide for details.
23
Configuration is affected by the following files:
34
- cabal.project
45
Resolving dependencies...

cabal-testsuite/PackageTests/NewConfigure/ConfigFile/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# cabal v2-configure
2+
Warning: The package description file ./ConfigFile.cabal has warnings: ConfigFile.cabal:0:0: A package using 'cabal-version: >=1.10' must use section syntax. See the Cabal user guide for details.
23
Configuration is affected by the following files:
34
- cabal.project
45
Config file not written due to flag(s).

cabal-testsuite/PackageTests/NewFreeze/BuildTools/new_freeze.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# cabal v2-update
22
Downloading the latest package list from test-local-repo
33
# cabal v2-build
4+
Warning: The package description file ./my-local-package.cabal has warnings: my-local-package.cabal:3:23: Packages with 'cabal-version: 1.12' or later should specify a specific version of the Cabal spec of the form 'cabal-version: x.y'. Use 'cabal-version: 1.20'.
45
Configuration is affected by the following files:
56
- cabal.project
67
Resolving dependencies...
@@ -16,6 +17,7 @@ Configuration is affected by the following files:
1617
Resolving dependencies...
1718
Wrote freeze file: <ROOT>/cabal.project.freeze
1819
# cabal v2-build
20+
Warning: The package description file ./my-local-package.cabal has warnings: my-local-package.cabal:3:23: Packages with 'cabal-version: 1.12' or later should specify a specific version of the Cabal spec of the form 'cabal-version: x.y'. Use 'cabal-version: 1.20'.
1921
Configuration is affected by the following files:
2022
- cabal.project
2123
- cabal.project.freeze

cabal-testsuite/PackageTests/Regression/T7234/Fail/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# cabal v2-build
2+
Warning: The package description file ./issue7234.cabal has warnings: issue7234.cabal:13:3: The field "other-extensions" is available only since the Cabal specification version 1.10.
23
Configuration is affected by the following files:
34
- cabal.project
45
Resolving dependencies...

cabal-testsuite/PackageTests/Regression/T7234/Success/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# cabal v2-build
2+
Warning: The package description file ./issue7234.cabal has warnings: issue7234.cabal:14:3: The field "other-extensions" is available only since the Cabal specification version 1.10.
23
Configuration is affected by the following files:
34
- cabal.project
45
Resolving dependencies...

cabal-testsuite/PackageTests/Regression/T9640/cabal.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# cabal v2-update
22
Downloading the latest package list from test-local-repo
33
# cabal build
4+
Warning: The package description file ./depend-on-custom-with-exe.cabal has warnings: depend-on-custom-with-exe.cabal:16:1: Ignoring trailing fields after sections: "ghc-options"
45
Configuration is affected by the following files:
56
- cabal.project
67
Resolving dependencies...

cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ normalizeOutput :: NormalizerEnv -> String -> String
2121
normalizeOutput nenv =
2222
-- Normalize backslashes to forward slashes to normalize
2323
-- file paths
24-
map (\c -> if c == '\\' then '/' else c)
24+
backslashToSlash
2525
-- Install path frequently has architecture specific elements, so
2626
-- nub it out
2727
. resub "Installing (.+) in .+" "Installing \\1 in <PATH>"
@@ -46,6 +46,24 @@ normalizeOutput nenv =
4646
. resub (posixRegexEscape "tmp/src-" ++ "[0-9]+") "<TMPDIR>"
4747
. resub (posixRegexEscape (normalizerTmpDir nenv) ++ sameDir) "<ROOT>/"
4848
. resub (posixRegexEscape (normalizerCanonicalTmpDir nenv) ++ sameDir) "<ROOT>/"
49+
. (if buildOS == Windows
50+
then
51+
-- OK. Here's the deal. In `./Prelude.hs`, `withRepoNoUpdate` sets
52+
-- `repoUri` to the tmpdir but with backslashes replaced with
53+
-- slashes. This is because Windows treats backslashes and forward
54+
-- slashes largely the same in paths, and backslashes aren't allowed
55+
-- in a URL like `file+noindex://...`.
56+
--
57+
-- But that breaks the regexes above, which expect the paths to have
58+
-- backslashes.
59+
--
60+
-- Honestly this whole `normalizeOutput` thing is super janky and
61+
-- worth rewriting from the ground up. To you, poor soul in the
62+
-- future, here is one more hack upon a great pile. Hey, at least all
63+
-- the `PackageTests` function as a test suite for this thing...
64+
resub (posixRegexEscape (backslashToSlash $ normalizerTmpDir nenv) ++ sameDir) "<ROOT>/"
65+
. resub (posixRegexEscape (backslashToSlash $ normalizerCanonicalTmpDir nenv) ++ sameDir) "<ROOT>/"
66+
else id)
4967
-- Munge away C: prefix on filenames (Windows). We convert C:\\ to \\.
5068
. (if buildOS == Windows then resub "([A-Z]):\\\\" "\\\\" else id)
5169
. appEndo (F.fold (map (Endo . packageIdRegex) (normalizerKnownPackages nenv)))
@@ -154,6 +172,9 @@ posixSpecialChars = ".^$*+?()[{\\|"
154172
posixRegexEscape :: String -> String
155173
posixRegexEscape = concatMap (\c -> if c `elem` posixSpecialChars then ['\\', c] else [c])
156174

175+
backslashToSlash :: String -> String
176+
backslashToSlash = map (\c -> if c == '\\' then '/' else c)
177+
157178
-- From regex-compat-tdfa by Christopher Kuklewicz and shelarcy, BSD-3-Clause
158179
-------------------------
159180

0 commit comments

Comments
 (0)