Skip to content

Commit 35917bf

Browse files
committed
Improve "Cannot read .cabal file inside ..." errors
This error message was very confusing -- it doesn't tell you what `.cabal` file it's looking for or why it cannot read the `.cabal` file. (Did it fail to parse the `.tar` archive itself? Did parsing the `.cabal` file fail? If so, why?) This patch improves the error message to include the name of the `.cabal` file being searched for. Additionally, parse errors and warnings are printed, as are format failures in the tarball itself. I ran into this error while I was writing tests for Cabal and it confused the hell out of me; this is an expanded version of the changes I made to help debug that failure.
1 parent 3727226 commit 35917bf

File tree

7 files changed

+120
-29
lines changed

7 files changed

+120
-29
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: 52 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,42 @@ 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+
warnError verbosity $
937+
"Failed to parse "
938+
<> tarFile
939+
<> ": "
940+
<> displayException formatError
941+
pure Nothing
942+
943+
-- look for `expectFilename` inside the tarball
944+
readCabalEntry
945+
:: FilePath
946+
-> FilePath
947+
-> Tar.Entry
948+
-> IO (Maybe NoIndexCacheEntry)
949+
-> IO (Maybe NoIndexCacheEntry)
950+
readCabalEntry tarFile expectFilename entry previous' = do
951+
previous <- previous'
952+
case previous of
953+
Just _entry -> pure previous
954+
Nothing -> do
955+
if expectFilename /= Tar.entryPath entry
956+
then pure Nothing
957+
else case Tar.entryContent entry of
958+
Tar.NormalFile contents _fileSize -> do
959+
let bytes = BS.toStrict contents
960+
maybePackageDescription
961+
:: Either CabalFileParseError GenericPackageDescription <-
962+
try $ readSourcePackageCabalFile verbosity expectFilename bytes
963+
case maybePackageDescription of
964+
Left exception -> do
965+
warnError verbosity $ "In " <> tarFile <> ": " <> displayException exception
966+
pure Nothing
967+
Right genericPackageDescription ->
968+
pure $ Just $ CacheGPD genericPackageDescription bytes
969+
_ -> pure Nothing
931970
withIndexEntries verbosity index callback _ = do
932971
-- non-secure repositories
933972
withFile (indexFile index) ReadMode $ \h -> do

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ module Distribution.Client.ProjectConfig
3838
, writeProjectConfigFile
3939
, commandLineFlagsToProjectConfig
4040
, onlyTopLevelProvenance
41+
, readSourcePackageCabalFile
42+
, CabalFileParseError (..)
4143

4244
-- * Packages within projects
4345
, ProjectPackageLocation (..)
@@ -174,7 +176,6 @@ import Distribution.Simple.Setup
174176
import Distribution.Simple.Utils
175177
( createDirectoryIfMissingVerbose
176178
, dieWithException
177-
, info
178179
, maybeExit
179180
, notice
180181
, rawSystemIOWithEnv
@@ -1615,7 +1616,7 @@ readSourcePackageCabalFile verbosity pkgfilename content =
16151616
case runParseResult (parseGenericPackageDescription content) of
16161617
(warnings, Right pkg) -> do
16171618
unless (null warnings) $
1618-
info verbosity (formatWarnings warnings)
1619+
warn verbosity (formatWarnings warnings)
16191620
return pkg
16201621
(warnings, Left (mspecVersion, errors)) ->
16211622
throwIO $ CabalFileParseError pkgfilename content errors mspecVersion warnings
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+
Error: 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/src/Test/Cabal/Prelude.hs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,9 @@ src `archiveTo` dst = do
564564

565565
infixr 4 `archiveTo`
566566

567-
-- | Given a directory (relative to the 'testCurrentDir') containing
568-
-- a series of directories representing packages, generate an
569-
-- external repository corresponding to all of these packages
570-
withRepo :: FilePath -> TestM a -> TestM a
571-
withRepo repo_dir m = do
567+
-- | Like 'withRepo', but doesn't run @cabal update@.
568+
withRepoNoUpdate :: FilePath -> TestM a -> TestM a
569+
withRepoNoUpdate repo_dir m = do
572570
env <- getTestEnv
573571

574572
-- 1. Initialize repo directory
@@ -606,11 +604,7 @@ withRepo repo_dir m = do
606604
liftIO $ print $ testUserCabalConfigFile env
607605
liftIO $ print =<< readFile (testUserCabalConfigFile env)
608606

609-
-- 4. Update our local index
610-
-- Note: this doesn't do anything for file+noindex repositories.
611-
cabal "v2-update" ["-z"]
612-
613-
-- 5. Profit
607+
-- 4. Profit
614608
withReaderT (\env' -> env' { testHaveRepo = True }) m
615609
-- TODO: Arguably should undo everything when we're done...
616610
where
@@ -620,6 +614,17 @@ withRepo repo_dir m = do
620614
_ -> x)
621615
else id) (testRepoDir env)
622616

617+
-- | Given a directory (relative to the 'testCurrentDir') containing
618+
-- a series of directories representing packages, generate an
619+
-- external repository corresponding to all of these packages
620+
withRepo :: FilePath -> TestM a -> TestM a
621+
withRepo repo_dir m = do
622+
withRepoNoUpdate repo_dir $ do
623+
-- Update our local index
624+
-- Note: this doesn't do anything for file+noindex repositories.
625+
cabal "v2-update" ["-z"]
626+
m
627+
623628
-- | Given a directory (relative to the 'testCurrentDir') containing
624629
-- a series of directories representing packages, generate an
625630
-- remote repository corresponding to all of these packages
@@ -798,17 +803,31 @@ recordMode mode = withReaderT (\env -> env {
798803
assertOutputContains :: MonadIO m => WithCallStack (String -> Result -> m ())
799804
assertOutputContains needle result =
800805
withFrozenCallStack $
801-
unless (needle `isInfixOf` (concatOutput output)) $
806+
unless (needle `isInfixOf` concatOutput output) $
802807
assertFailure $ " expected: " ++ needle
803808
where output = resultOutput result
804809

805810
assertOutputDoesNotContain :: MonadIO m => WithCallStack (String -> Result -> m ())
806811
assertOutputDoesNotContain needle result =
807812
withFrozenCallStack $
808-
when (needle `isInfixOf` (concatOutput output)) $
813+
when (needle `isInfixOf` concatOutput output) $
809814
assertFailure $ "unexpected: " ++ needle
810815
where output = resultOutput result
811816

817+
assertOutputMatches :: MonadIO m => WithCallStack (String -> Result -> m ())
818+
assertOutputMatches regex result =
819+
withFrozenCallStack $
820+
unless (concatOutput output =~ regex) $
821+
assertFailure $ "expected regex match: " ++ regex
822+
where output = resultOutput result
823+
824+
assertOutputDoesNotMatch :: MonadIO m => WithCallStack (String -> Result -> m ())
825+
assertOutputDoesNotMatch regex result =
826+
withFrozenCallStack $
827+
when (concatOutput output =~ regex) $
828+
assertFailure $ "unexpected regex match: " ++ regex
829+
where output = resultOutput result
830+
812831
assertFindInFile :: MonadIO m => WithCallStack (String -> FilePath -> m ())
813832
assertFindInFile needle path =
814833
withFrozenCallStack $

0 commit comments

Comments
 (0)