Skip to content

Commit 6fb48c7

Browse files
cabal check: Warn if readmes or changelogs are omitted from extra-doc-files (#8657)
* cabal check: Warn if expected files are omitted from extra-doc-files * Add test * Add missing files and their hackage’s reference * Move doc file check from checkPackageContent to checkPackageFilesPreDistribution * Check based on globs only Check only globs, not the filesystem. * Update test outputs * Make check more robust to relative paths * Fix tests failing with "Distribution quality warnings" * Fix comments and changelog [skip ci] * Test changelog only if present * Revert "Update test outputs" This reverts commit ce49487. * Revert "Fix tests failing with "Distribution quality warnings"" This reverts commit 705f6bd. * Fix changelog * Remove getGlobFiles * Adapt warnings to version 1.12 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 86b6077 commit 6fb48c7

File tree

23 files changed

+291
-24
lines changed

23 files changed

+291
-24
lines changed

Cabal/src/Distribution/PackageDescription/Check.hs

Lines changed: 163 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ module Distribution.PackageDescription.Check (
3636
checkPackageFileNames,
3737
) where
3838

39+
import Data.Foldable (foldrM)
3940
import Distribution.Compat.Prelude
4041
import Prelude ()
4142

42-
import Data.List (group)
43+
import Data.List (delete, group)
4344
import Distribution.CabalSpecVersion
4445
import Distribution.Compat.Lens
4546
import Distribution.Compiler
@@ -64,7 +65,8 @@ import Distribution.Version
6465
import Distribution.Utils.Path
6566
import Language.Haskell.Extension
6667
import System.FilePath
67-
(splitDirectories, splitExtension, splitPath, takeExtension, takeFileName, (<.>), (</>))
68+
( makeRelative, normalise, splitDirectories, splitExtension, splitPath
69+
, takeExtension, takeFileName, (<.>), (</>))
6870

6971
import qualified Data.ByteString.Lazy as BS
7072
import qualified Data.Map as Map
@@ -251,6 +253,8 @@ data CheckExplanation =
251253
| MissingConfigureScript
252254
| UnknownDirectory String FilePath
253255
| MissingSourceControl
256+
| MissingExpectedDocFiles Bool [FilePath]
257+
| WrongFieldForExpectedDocFiles Bool String [FilePath]
254258
deriving (Eq, Ord, Show)
255259

256260
-- | Wraps `ParseWarning` into `PackageCheck`.
@@ -786,6 +790,24 @@ ppExplanation MissingSourceControl =
786790
++ "control information in the .cabal file using one or more "
787791
++ "'source-repository' sections. See the Cabal user guide for "
788792
++ "details."
793+
ppExplanation (MissingExpectedDocFiles extraDocFileSupport paths) =
794+
"Please consider including the " ++ quotes paths
795+
++ " in the '" ++ targetField ++ "' section of the .cabal file "
796+
++ "if it contains useful information for users of the package."
797+
where quotes [p] = "file " ++ quote p
798+
quotes ps = "files " ++ intercalate ", " (map quote ps)
799+
targetField = if extraDocFileSupport
800+
then "extra-doc-files"
801+
else "extra-source-files"
802+
ppExplanation (WrongFieldForExpectedDocFiles extraDocFileSupport field paths) =
803+
"Please consider moving the " ++ quotes paths
804+
++ " from the '" ++ field ++ "' section of the .cabal file "
805+
++ "to the section '" ++ targetField ++ "'."
806+
where quotes [p] = "file " ++ quote p
807+
quotes ps = "files " ++ intercalate ", " (map quote ps)
808+
targetField = if extraDocFileSupport
809+
then "extra-doc-files"
810+
else "extra-source-files"
789811

790812

791813
-- | Results of some kind of failed package check.
@@ -2412,27 +2434,141 @@ checkGlobFiles :: Verbosity
24122434
-> PackageDescription
24132435
-> FilePath
24142436
-> IO [PackageCheck]
2415-
checkGlobFiles verbosity pkg root =
2416-
fmap concat $ for allGlobs $ \(field, dir, glob) ->
2417-
-- Note: we just skip over parse errors here; they're reported elsewhere.
2418-
case parseFileGlob (specVersion pkg) glob of
2419-
Left _ -> return []
2420-
Right parsedGlob -> do
2421-
results <- runDirFileGlob verbosity (root </> dir) parsedGlob
2422-
let individualWarnings = results >>= getWarning field glob
2423-
noMatchesWarning =
2424-
[ PackageDistSuspiciousWarn (GlobNoMatch field glob)
2425-
| all (not . suppressesNoMatchesWarning) results
2426-
]
2427-
return (noMatchesWarning ++ individualWarnings)
2437+
checkGlobFiles verbosity pkg root = do
2438+
-- Get the desirable doc files from package’s directory
2439+
rootContents <- System.Directory.getDirectoryContents root
2440+
docFiles0 <- filterM System.doesFileExist
2441+
[ file
2442+
| file <- rootContents
2443+
, isDesirableExtraDocFile desirableDocFiles file
2444+
]
2445+
-- Check the globs
2446+
(warnings, unlisted) <- foldrM checkGlob ([], docFiles0) allGlobs
2447+
2448+
return $ if null unlisted
2449+
-- No missing desirable file
2450+
then warnings
2451+
-- Some missing desirable files
2452+
else warnings ++
2453+
let unlisted' = (root </>) <$> unlisted
2454+
in [ PackageDistSuspiciousWarn
2455+
(MissingExpectedDocFiles extraDocFilesSupport unlisted')
2456+
]
24282457
where
2429-
adjustedDataDir = if null (dataDir pkg) then "." else dataDir pkg
2458+
-- `extra-doc-files` is supported only from version 1.18
2459+
extraDocFilesSupport = specVersion pkg >= CabalSpecV1_18
2460+
adjustedDataDir = if null (dataDir pkg) then root else root </> dataDir pkg
2461+
-- Cabal fields with globs
2462+
allGlobs :: [(String, Bool, FilePath, FilePath)]
24302463
allGlobs = concat
2431-
[ (,,) "extra-source-files" "." <$> extraSrcFiles pkg
2432-
, (,,) "extra-doc-files" "." <$> extraDocFiles pkg
2433-
, (,,) "data-files" adjustedDataDir <$> dataFiles pkg
2464+
[ (,,,) "extra-source-files" (not extraDocFilesSupport) root <$>
2465+
extraSrcFiles pkg
2466+
, (,,,) "extra-doc-files" True root <$> extraDocFiles pkg
2467+
, (,,,) "data-files" False adjustedDataDir <$> dataFiles pkg
24342468
]
24352469

2470+
-- For each field with globs (see allGlobs), look for:
2471+
-- • errors (missing directory, no match)
2472+
-- • omitted documentation files (changelog)
2473+
checkGlob :: (String, Bool, FilePath, FilePath)
2474+
-> ([PackageCheck], [FilePath])
2475+
-> IO ([PackageCheck], [FilePath])
2476+
checkGlob (field, isDocField, dir, glob) acc@(warnings, docFiles1) =
2477+
-- Note: we just skip over parse errors here; they're reported elsewhere.
2478+
case parseFileGlob (specVersion pkg) glob of
2479+
Left _ -> return acc
2480+
Right parsedGlob -> do
2481+
results <- runDirFileGlob verbosity (root </> dir) parsedGlob
2482+
let acc0 = (warnings, True, docFiles1, [])
2483+
return $ case foldr checkGlobResult acc0 results of
2484+
(individualWarn, noMatchesWarn, docFiles1', wrongPaths) ->
2485+
let wrongFieldWarnings = [ PackageDistSuspiciousWarn
2486+
(WrongFieldForExpectedDocFiles
2487+
extraDocFilesSupport
2488+
field wrongPaths)
2489+
| not (null wrongPaths) ]
2490+
in
2491+
( if noMatchesWarn
2492+
then [PackageDistSuspiciousWarn (GlobNoMatch field glob)] ++
2493+
individualWarn ++
2494+
wrongFieldWarnings
2495+
else individualWarn ++ wrongFieldWarnings
2496+
, docFiles1'
2497+
)
2498+
where
2499+
checkGlobResult :: GlobResult FilePath
2500+
-> ([PackageCheck], Bool, [FilePath], [FilePath])
2501+
-> ([PackageCheck], Bool, [FilePath], [FilePath])
2502+
checkGlobResult result (ws, noMatchesWarn, docFiles2, wrongPaths) =
2503+
let noMatchesWarn' = noMatchesWarn &&
2504+
not (suppressesNoMatchesWarning result)
2505+
in case getWarning field glob result of
2506+
-- No match: add warning and do no further check
2507+
Left w ->
2508+
( w : ws
2509+
, noMatchesWarn'
2510+
, docFiles2
2511+
, wrongPaths
2512+
)
2513+
-- Match: check doc files
2514+
Right path ->
2515+
let path' = makeRelative root (normalise path)
2516+
(docFiles2', wrongPaths') = checkDoc isDocField
2517+
path'
2518+
docFiles2
2519+
wrongPaths
2520+
in
2521+
( ws
2522+
, noMatchesWarn'
2523+
, docFiles2'
2524+
, wrongPaths'
2525+
)
2526+
2527+
-- Check whether a path is a desirable doc: if so, check if it is in the
2528+
-- field "extra-doc-files".
2529+
checkDoc :: Bool -- Is it "extra-doc-files" ?
2530+
-> FilePath -- Path to test
2531+
-> [FilePath] -- Pending doc files to check
2532+
-> [FilePath] -- Previous wrong paths
2533+
-> ([FilePath], [FilePath]) -- Updated paths
2534+
checkDoc isDocField path docFiles wrongFieldPaths =
2535+
if path `elem` docFiles
2536+
-- Found desirable doc file
2537+
then
2538+
( delete path docFiles
2539+
, if isDocField then wrongFieldPaths else path : wrongFieldPaths
2540+
)
2541+
-- Not a desirable doc file
2542+
else
2543+
( docFiles
2544+
, wrongFieldPaths
2545+
)
2546+
2547+
-- Predicate for desirable documentation file on Hackage server
2548+
isDesirableExtraDocFile :: [FilePath] -> FilePath -> Bool
2549+
isDesirableExtraDocFile paths path = map toLower basename `elem` paths
2550+
where
2551+
(basename, _ext) = splitExtension path
2552+
2553+
-- Changelog patterns
2554+
-- Source: hackage-server/src/Distribution/Server/Packages/ChangeLog.hs
2555+
desirableChangeLog =
2556+
[ "news"
2557+
, "changelog"
2558+
, "change_log"
2559+
, "changes"
2560+
]
2561+
-- [TODO] Check readme. Observations:
2562+
-- • Readme is not necessary if package description is good.
2563+
-- • Some readmes exists only for repository browsing.
2564+
-- • There is currently no reliable way to check what a good
2565+
-- description is; there will be complains if the criterion is
2566+
-- based on the length or number of words (can of worms).
2567+
-- -- Readme patterns
2568+
-- -- Source: hackage-server/src/Distribution/Server/Packages/Readme.hs
2569+
-- desirableReadme = ["readme"]
2570+
desirableDocFiles = desirableChangeLog
2571+
24362572
-- If there's a missing directory in play, since our globs don't
24372573
-- (currently) support disjunction, that will always mean there are no
24382574
-- matches. The no matches error in this case is strictly less informative
@@ -2441,17 +2577,20 @@ checkGlobFiles verbosity pkg root =
24412577
suppressesNoMatchesWarning (GlobWarnMultiDot _) = False
24422578
suppressesNoMatchesWarning (GlobMissingDirectory _) = True
24432579

2444-
getWarning :: String -> FilePath -> GlobResult FilePath -> [PackageCheck]
2445-
getWarning _ _ (GlobMatch _) =
2446-
[]
2580+
getWarning :: String
2581+
-> FilePath
2582+
-> GlobResult FilePath
2583+
-> Either PackageCheck FilePath
2584+
getWarning _ _ (GlobMatch path) =
2585+
Right path
24472586
-- Before Cabal 2.4, the extensions of globs had to match the file
24482587
-- exactly. This has been relaxed in 2.4 to allow matching only the
24492588
-- suffix. This warning detects when pre-2.4 package descriptions are
24502589
-- omitting files purely because of the stricter check.
24512590
getWarning field glob (GlobWarnMultiDot file) =
2452-
[ PackageDistSuspiciousWarn (GlobExactMatch field glob file) ]
2591+
Left (PackageDistSuspiciousWarn (GlobExactMatch field glob file))
24532592
getWarning field glob (GlobMissingDirectory dir) =
2454-
[ PackageDistSuspiciousWarn (GlobNoDir field glob dir) ]
2593+
Left (PackageDistSuspiciousWarn (GlobNoDir field glob dir))
24552594

24562595
-- | Check that setup dependencies, have proper bounds.
24572596
-- In particular, @base@ and @Cabal@ upper bounds are mandatory.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# cabal check
2+
No errors or warnings could be found in the package.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Test.Cabal.Prelude
2+
3+
import System.Directory (createDirectoryIfMissing)
4+
5+
-- Omitting ChangeLog.md but not README in extra-doc-files
6+
main = cabalTest $ do
7+
cabal "check" []
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
cabal-version: 3.0
2+
name: pkg
3+
synopsis: synopsis
4+
description: description
5+
version: 0
6+
category: example
7+
maintainer: [email protected]
8+
license: GPL-3.0-or-later
9+
10+
library
11+
exposed-modules: Foo
12+
default-language: Haskell2010

cabal-testsuite/PackageTests/Check/PackageFiles/MissingExpectedDocFiles/ChangeLog/NotIncluded/V1.12/ChangeLog.md

Whitespace-only changes.

cabal-testsuite/PackageTests/Check/PackageFiles/MissingExpectedDocFiles/ChangeLog/NotIncluded/V1.12/LICENSE

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# cabal check
2+
Warning: These warnings may cause trouble when distributing the package:
3+
Warning: Please consider including the file './ChangeLog.md' in the 'extra-source-files' section of the .cabal file if it contains useful information for users of the package.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Test.Cabal.Prelude
2+
3+
import System.Directory (createDirectoryIfMissing)
4+
5+
-- Omitting ChangeLog.md but not README in extra-doc-files
6+
main = cabalTest $ do
7+
cabal "check" []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cabal-version: 1.12
2+
build-type: Simple
3+
name: pkg
4+
synopsis: synopsis
5+
description: description
6+
version: 0
7+
category: example
8+
maintainer: [email protected]
9+
license: GPL-3
10+
license-file: LICENSE
11+
12+
library
13+
exposed-modules: Foo
14+
default-language: Haskell2010

cabal-testsuite/PackageTests/Check/PackageFiles/MissingExpectedDocFiles/ChangeLog/NotIncluded/V3.0/ChangeLog.md

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# cabal check
2+
Warning: These warnings may cause trouble when distributing the package:
3+
Warning: Please consider including the file './ChangeLog.md' in the 'extra-doc-files' section of the .cabal file if it contains useful information for users of the package.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Test.Cabal.Prelude
2+
3+
import System.Directory (createDirectoryIfMissing)
4+
5+
-- Omitting ChangeLog.md but not README in extra-doc-files
6+
main = cabalTest $ do
7+
cabal "check" []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
cabal-version: 3.0
2+
name: pkg
3+
synopsis: synopsis
4+
description: description
5+
version: 0
6+
category: example
7+
maintainer: [email protected]
8+
license: GPL-3.0-or-later
9+
10+
library
11+
exposed-modules: Foo
12+
default-language: Haskell2010

cabal-testsuite/PackageTests/Check/PackageFiles/MissingExpectedDocFiles/ChangeLog/WrongField/V1.12/ChangeLog.md

Whitespace-only changes.

cabal-testsuite/PackageTests/Check/PackageFiles/MissingExpectedDocFiles/ChangeLog/WrongField/V1.12/LICENSE

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# cabal check
2+
Warning: These warnings may cause trouble when distributing the package:
3+
Warning: Please consider moving the file 'ChangeLog.md' from the 'data-files' section of the .cabal file to the section 'extra-source-files'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Test.Cabal.Prelude
2+
3+
import System.Directory (createDirectoryIfMissing)
4+
5+
-- Included ChangeLog.md but not in extra-doc-files
6+
main = cabalTest $ do
7+
cabal "check" []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
cabal-version: 1.12
2+
build-type: Simple
3+
name: pkg
4+
synopsis: synopsis
5+
description: description
6+
version: 0
7+
category: example
8+
maintainer: [email protected]
9+
license: GPL-3
10+
license-file: LICENSE
11+
data-files: ChangeLog.md
12+
13+
library
14+
exposed-modules: Foo
15+
default-language: Haskell2010

cabal-testsuite/PackageTests/Check/PackageFiles/MissingExpectedDocFiles/ChangeLog/WrongField/V3.0/ChangeLog.md

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# cabal check
2+
Warning: These warnings may cause trouble when distributing the package:
3+
Warning: Please consider moving the file 'ChangeLog.md' from the 'extra-source-files' section of the .cabal file to the section 'extra-doc-files'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Test.Cabal.Prelude
2+
3+
import System.Directory (createDirectoryIfMissing)
4+
5+
-- Included ChangeLog.md but not in extra-doc-files
6+
main = cabalTest $ do
7+
cabal "check" []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
cabal-version: 3.0
2+
name: pkg
3+
synopsis: synopsis
4+
description: description
5+
version: 0
6+
category: example
7+
maintainer: [email protected]
8+
license: GPL-3.0-or-later
9+
extra-source-files: ChangeLog.md
10+
11+
library
12+
exposed-modules: Foo
13+
default-language: Haskell2010

changelog.d/pr-8657

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
synopsis: Warn if expected files are omitted from extra-doc-files
2+
packages: Cabal
3+
prs: #8657
4+
issues: #3964
5+
6+
description: {
7+
8+
- Emit a warning if there exist a “changelog” file at the root of the
9+
package which is not included in any field.
10+
- Emit a warning if a “changelog” file at the root of the package is included
11+
in a field different from “extra-doc-files” (Cabal spec >= 1.18) or
12+
“extra-source-files” (spec < 1.18).
13+
}

0 commit comments

Comments
 (0)