Skip to content

Commit 39c0386

Browse files
committed
Add lots of new package checks
Check that Setup.(l)hs exists. Check for ./configure file if build-type: Configure Check for duplicate modules in library and executables. Check ghc-options and cc-options for -I -l -L flags that should be elsewhere Check for lots of insane ghc-options that people have used in real packages. Check for old ghc -f flags that correspond to extensions
1 parent f1a833b commit 39c0386

File tree

1 file changed

+168
-12
lines changed

1 file changed

+168
-12
lines changed

Distribution/PackageDescription/Check.hs

Lines changed: 168 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module Distribution.PackageDescription.Check (
4747
) where
4848

4949
import Data.Maybe (isNothing, catMaybes)
50+
import Data.List (intersperse, sort, group, isPrefixOf)
5051
import System.Directory (doesFileExist)
5152

5253
import Distribution.PackageDescription
@@ -56,6 +57,7 @@ import Distribution.Simple.Utils (cabalVersion)
5657

5758
import Distribution.Version (Version(..), withinRange, showVersionRange)
5859
import Distribution.Package (PackageIdentifier(..))
60+
import Language.Haskell.Extension (Extension(..))
5961
import System.FilePath (takeExtension, (</>))
6062

6163
-- | Results of some kind of failed package check.
@@ -100,8 +102,6 @@ check True pc = Just pc
100102
-- * Standard checks
101103
-- ------------------------------------------------------------
102104

103-
-- TODO: give hints about old extentions. see Simple.GHC, reverse mapping
104-
-- TODO: and allmost ghc -X flags should be extensions
105105
-- TODO: Once we implement striping (ticket #88) we should also reject
106106
-- ghc-options: -optl-Wl,-s.
107107

@@ -117,6 +117,7 @@ checkPackage pkg =
117117
++ checkFields pkg
118118
++ checkLicense pkg
119119
++ checkGhcOptions pkg
120+
++ checkCCOptions pkg
120121

121122

122123
-- ------------------------------------------------------------
@@ -160,8 +161,17 @@ checkLibrary lib =
160161
check (buildable (libBuildInfo lib) && null (exposedModules lib)) $
161162
PackageBuildImpossible
162163
"A library was specified, but no 'exposed-modules' list has been given."
164+
165+
, check (not (null moduleDuplicates)) $
166+
PackageBuildWarning $
167+
"Dulicate modules in library: " ++ commaSep moduleDuplicates
163168
]
164169

170+
where moduleDuplicates = [ module_
171+
| let modules = exposedModules lib
172+
++ otherModules (libBuildInfo lib)
173+
, (module_:_:_) <- group (sort modules) ]
174+
165175
checkExecutable :: Executable -> [PackageCheck]
166176
checkExecutable exe =
167177
catMaybes [
@@ -175,8 +185,16 @@ checkExecutable exe =
175185
PackageBuildImpossible $
176186
"The 'Main-Is' field must specify a '.hs' or '.lhs' file\n"
177187
++ " (even if it is generated by a preprocessor)."
188+
189+
, check (not (null moduleDuplicates)) $
190+
PackageBuildWarning $
191+
"Dulicate modules in executable '" ++ exeName exe ++ "': "
192+
++ commaSep moduleDuplicates
178193
]
179194

195+
where moduleDuplicates = [ module_
196+
| let modules = otherModules (buildInfo exe)
197+
, (module_:_:_) <- group (sort modules) ]
180198

181199
-- ------------------------------------------------------------
182200
-- * Additional pure checks
@@ -233,24 +251,80 @@ checkGhcOptions pkg =
233251
"'ghc-options: -Werror' makes the package easy to "
234252
++ "break with future GHC versions."
235253

236-
, checkFlag "-fasm" $
254+
, checkFlags ["-fasm"] $
237255
PackageDistInexcusable $
238256
"'ghc-options: -fasm' is unnecessary and breaks on all "
239257
++ "arches except for x86, x86-64 and ppc."
240258

241-
, checkFlag "-O" $
259+
, checkFlags ["-fvia-C"] $
260+
PackageDistSuspicious $
261+
"'ghc-options: -fvia-C' is usually unnecessary."
262+
263+
, checkFlags ["-fhpc"] $
264+
PackageDistInexcusable $
265+
"'ghc-options: -fhpc' is not appropriate for a distributed package."
266+
267+
, check (any ("-d" `isPrefixOf`) all_ghc_options) $
268+
PackageDistInexcusable $
269+
"'ghc-options: -d*' debug flags are not appropriate for a distributed package."
270+
271+
, checkFlags ["-prof"] $
272+
PackageDistInexcusable $
273+
"'ghc-options: -prof' is not needed. Use the --enable-library-profiling configure flag."
274+
275+
, checkFlags ["-o"] $
276+
PackageDistInexcusable $
277+
"'ghc-options: -o' is not allowed. The output files are named automatically."
278+
279+
, checkFlags ["-hide-package"] $
280+
PackageDistInexcusable $
281+
"'ghc-options: -hide-package' is never needed. Cabal hides all packages\n"
282+
283+
, checkFlags ["-main-is"] $
284+
PackageDistSuspicious $
285+
"'ghc-options: -main-is' is not portable."
286+
287+
, checkFlags ["-O0", "-Onot"] $
288+
PackageDistInexcusable $
289+
"'ghc-options: -O0' is not needed. Use the --disable-optimization configure flag."
290+
291+
, checkFlags [ "-O", "-O1"] $
242292
PackageDistInexcusable $
243293
"'ghc-options: -O' is not needed. Cabal automatically adds the '-O' flag.\n"
244294
++ " Setting it yourself interferes with the --disable-optimization flag."
245295

246-
, checkFlag "-O2" $
296+
, checkFlags ["-O2"] $
247297
PackageDistSuspicious $
248298
"'ghc-options: -O2' is rarely needed. Check that it is giving a real benefit\n"
249299
++ " and not just imposing longer compile times on your users."
250300

251-
, check (any (`elem` all_ghc_options) ["-ffi", "-fffi"]) $
301+
, checkFlags ["-split-objs"] $
252302
PackageDistInexcusable $
253-
"Instead of using -ffi or -fffi, use 'extensions: ForeignFunctionInterface'"
303+
"'ghc-options: -split-objs' is not needed. Use the --enable-split-objs configure flag."
304+
305+
, checkFlags ["-fglasgow-exts"] $
306+
PackageDistSuspicious $
307+
"Instead of 'ghc-options: -fglasgow-exts' it is preferable to use the 'extensions' field."
308+
309+
, checkAlternatives "ghc-options" "extensions"
310+
[ (flag, show extension) | flag <- all_ghc_options
311+
, Just extension <- [ghcExtension flag] ]
312+
313+
, checkAlternatives "ghc-options" "extensions"
314+
[ (flag, extension) | flag@('-':'X':extension) <- all_ghc_options ]
315+
316+
, checkAlternatives "ghc-options" "cpp-options" $
317+
[ (flag, flag) | flag@('-':'D':_) <- all_ghc_options ]
318+
++ [ (flag, flag) | flag@('-':'U':_) <- all_ghc_options ]
319+
320+
, checkAlternatives "ghc-options" "include-dirs"
321+
[ (flag, dir) | flag@('-':'I':dir) <- all_ghc_options ]
322+
323+
, checkAlternatives "ghc-options" "extra-libraries"
324+
[ (flag, lib) | flag@('-':'l':lib) <- all_ghc_options ]
325+
326+
, checkAlternatives "ghc-options" "extra-lib-dirs"
327+
[ (flag, dir) | flag@('-':'L':dir) <- all_ghc_options ]
254328
]
255329

256330
where
@@ -263,8 +337,63 @@ checkGhcOptions pkg =
263337
, (GHC, strs) <- options bi ]
264338
all_ghc_options = concat ghc_options
265339

266-
checkFlag :: String -> PackageCheck -> Maybe PackageCheck
267-
checkFlag flag = check (flag `elem` all_ghc_options)
340+
checkFlags :: [String] -> PackageCheck -> Maybe PackageCheck
341+
checkFlags flags = check (any (`elem` flags) all_ghc_options)
342+
343+
ghcExtension ('-':'f':name) = case name of
344+
"allow-overlapping-instances" -> Just OverlappingInstances
345+
"th" -> Just TemplateHaskell
346+
"ffi" -> Just ForeignFunctionInterface
347+
"fi" -> Just ForeignFunctionInterface
348+
"no-monomorphism-restriction" -> Just NoMonomorphismRestriction
349+
"no-mono-pat-binds" -> Just NoMonoPatBinds
350+
"allow-undecidable-instances" -> Just UndecidableInstances
351+
"allow-incoherent-instances" -> Just IncoherentInstances
352+
"arrows" -> Just Arrows
353+
"generics" -> Just Generics
354+
"no-implicit-prelude" -> Just NoImplicitPrelude
355+
"implicit-params" -> Just ImplicitParams
356+
"bang-patterns" -> Just BangPatterns
357+
"scoped-type-variables" -> Just ScopedTypeVariables
358+
"extended-default-rules" -> Just ExtendedDefaultRules
359+
_ -> Nothing
360+
ghcExtension ('-':'c':"pp") = Just CPP
361+
ghcExtension _ = Nothing
362+
363+
364+
checkCCOptions :: PackageDescription -> [PackageCheck]
365+
checkCCOptions pkg =
366+
catMaybes [
367+
368+
checkAlternatives "cc-options" "include-dirs"
369+
[ (flag, dir) | flag@('-':'I':dir) <- all_ccOptions ]
370+
371+
, checkAlternatives "cc-options" "extra-libraries"
372+
[ (flag, lib) | flag@('-':'l':lib) <- all_ccOptions ]
373+
374+
, checkAlternatives "cc-options" "extra-lib-dirs"
375+
[ (flag, dir) | flag@('-':'L':dir) <- all_ccOptions ]
376+
377+
, checkAlternatives "ld-options" "extra-libraries"
378+
[ (flag, lib) | flag@('-':'l':lib) <- all_ldOptions ]
379+
380+
, checkAlternatives "ld-options" "extra-lib-dirs"
381+
[ (flag, dir) | flag@('-':'L':dir) <- all_ldOptions ]
382+
]
383+
384+
where all_ccOptions = [ opts | bi <- allBuildInfo pkg
385+
, opts <- ccOptions bi ]
386+
all_ldOptions = [ opts | bi <- allBuildInfo pkg
387+
, opts <- ldOptions bi ]
388+
389+
checkAlternatives :: String -> String -> [(String, String)] -> Maybe PackageCheck
390+
checkAlternatives badField goodField flags =
391+
check (not (null badFlags)) $
392+
PackageBuildWarning $
393+
"Instead of " ++ quote (badField ++ ": " ++ unwords badFlags)
394+
++ " use " ++ quote (goodField ++ ": " ++ unwords goodFlags)
395+
396+
where (badFlags, goodFlags) = unzip flags
268397

269398
-- ------------------------------------------------------------
270399
-- * Checks in IO
@@ -275,9 +404,11 @@ checkGhcOptions pkg =
275404
--
276405
checkPackageFiles :: PackageDescription -> FilePath -> IO [PackageCheck]
277406
checkPackageFiles pkg root = do
278-
licenseError <- checkLicenseExists pkg root
407+
licenseError <- checkLicenseExists pkg root
408+
setupError <- checkSetupExists pkg root
409+
configureError <- checkConfigureExists pkg root
279410

280-
return (catMaybes [licenseError])
411+
return (catMaybes [licenseError, setupError, configureError])
281412

282413
checkLicenseExists :: PackageDescription -> FilePath -> IO (Maybe PackageCheck)
283414
checkLicenseExists pkg root
@@ -291,4 +422,29 @@ checkLicenseExists pkg root
291422

292423
where
293424
file = licenseFile pkg
294-
quote s = ['"'] ++ s ++ ['"']
425+
426+
checkSetupExists :: PackageDescription -> FilePath -> IO (Maybe PackageCheck)
427+
checkSetupExists _ root = do
428+
hsexists <- doesFileExist (root </> "Setup.hs")
429+
lhsexists <- doesFileExist (root </> "Setup.lhs")
430+
return $ check (not hsexists && not lhsexists) $
431+
PackageDistInexcusable $
432+
"The package is missing a Setup.hs or Setup.lhs script."
433+
434+
checkConfigureExists :: PackageDescription -> FilePath -> IO (Maybe PackageCheck)
435+
checkConfigureExists PackageDescription { buildType = Just Configure } root = do
436+
exists <- doesFileExist (root </> "configure")
437+
return $ check (not exists) $
438+
PackageBuildWarning $
439+
"The 'build-type' is 'Configure' but there is no 'configure' script."
440+
checkConfigureExists _ _ = return Nothing
441+
442+
-- ------------------------------------------------------------
443+
-- * Utils
444+
-- ------------------------------------------------------------
445+
446+
quote :: String -> String
447+
quote s = "'" ++ s ++ "'"
448+
449+
commaSep :: [String] -> String
450+
commaSep = concat . intersperse ","

0 commit comments

Comments
 (0)