Support GHC 9.12
[cabal.git] / Cabal / src / Distribution / Simple / SrcDist.hs
blob90250290fc1a204c4b1b4332265d5888e832724d
1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE RankNTypes #-}
4 -----------------------------------------------------------------------------
6 -- NOTE: FIX: we don't have a great way of testing this module, since
7 -- we can't easily look inside a tarball once its created.
9 -- |
10 -- Module : Distribution.Simple.SrcDist
11 -- Copyright : Simon Marlow 2004
12 -- License : BSD3
14 -- Maintainer : cabal-devel@haskell.org
15 -- Portability : portable
17 -- This handles the @sdist@ command. The module exports an 'sdist' action but
18 -- also some of the phases that make it up so that other tools can use just the
19 -- bits they need. In particular the preparation of the tree of files to go
20 -- into the source tarball is separated from actually building the source
21 -- tarball.
23 -- The 'createArchive' action uses the external @tar@ program and assumes that
24 -- it accepts the @-z@ flag. Neither of these assumptions are valid on Windows.
25 -- The 'sdist' action now also does some distribution QA checks.
26 module Distribution.Simple.SrcDist
27 ( -- * The top level action
28 sdist
30 -- ** Parts of 'sdist'
31 , printPackageProblems
32 , prepareTree
33 , createArchive
35 -- ** Snapshots
36 , prepareSnapshotTree
37 , snapshotPackage
38 , snapshotVersion
39 , dateToSnapshotNumber
41 -- * Extracting the source files
42 , listPackageSources
43 , listPackageSourcesWithDie
44 ) where
46 import Distribution.Compat.Prelude
47 import Prelude ()
49 import Distribution.ModuleName
50 import qualified Distribution.ModuleName as ModuleName
51 import Distribution.Package
52 import Distribution.PackageDescription
53 import Distribution.PackageDescription.Check hiding (doesFileExist)
54 import Distribution.Pretty
55 import Distribution.Simple.BuildPaths
56 import Distribution.Simple.Configure (findDistPrefOrDefault)
57 import Distribution.Simple.Flag
58 import Distribution.Simple.Glob (matchDirFileGlobWithDie)
59 import Distribution.Simple.PreProcess
60 import Distribution.Simple.Program
61 import Distribution.Simple.Setup.SDist
62 import Distribution.Simple.Utils
63 import Distribution.Utils.Path
64 import Distribution.Verbosity
65 import Distribution.Version
67 import qualified Data.Map as Map
68 import Data.Time (UTCTime, getCurrentTime, toGregorian, utctDay)
69 import Distribution.Simple.Errors
70 import System.Directory (doesFileExist)
71 import System.FilePath (dropExtension, isRelative, (<.>), (</>))
72 import System.IO (IOMode (WriteMode), hPutStrLn, withFile)
74 -- | Create a source distribution.
75 sdist
76 :: PackageDescription
77 -- ^ information from the tarball
78 -> SDistFlags
79 -- ^ verbosity & snapshot
80 -> (FilePath -> FilePath)
81 -- ^ build prefix (temp dir)
82 -> [PPSuffixHandler]
83 -- ^ extra preprocessors (includes suffixes)
84 -> IO ()
85 sdist pkg flags mkTmpDir pps = do
86 distPref <- findDistPrefOrDefault $ sDistDistPref flags
87 let targetPref = distPref
88 tmpTargetDir = mkTmpDir distPref
90 -- When given --list-sources, just output the list of sources to a file.
91 case sDistListSources flags of
92 Flag path -> withFile path WriteMode $ \outHandle -> do
93 ordinary <- listPackageSources verbosity "." pkg pps
94 traverse_ (hPutStrLn outHandle) ordinary
95 notice verbosity $ "List of package sources written to file '" ++ path ++ "'"
96 NoFlag -> do
97 -- do some QA
98 printPackageProblems verbosity pkg
100 date <- getCurrentTime
101 let pkg'
102 | snapshot = snapshotPackage date pkg
103 | otherwise = pkg
105 case flagToMaybe (sDistDirectory flags) of
106 Just targetDir -> do
107 generateSourceDir targetDir pkg'
108 info verbosity $ "Source directory created: " ++ targetDir
109 Nothing -> do
110 createDirectoryIfMissingVerbose verbosity True tmpTargetDir
111 withTempDirectory verbosity tmpTargetDir "sdist." $ \tmpDir -> do
112 let targetDir = tmpDir </> tarBallName pkg'
113 generateSourceDir targetDir pkg'
114 targzFile <- createArchive verbosity pkg' tmpDir targetPref
115 notice verbosity $ "Source tarball created: " ++ targzFile
116 where
117 generateSourceDir :: FilePath -> PackageDescription -> IO ()
118 generateSourceDir targetDir pkg' = do
119 setupMessage verbosity "Building source dist for" (packageId pkg')
120 prepareTree verbosity pkg' targetDir pps
121 when snapshot $
122 overwriteSnapshotPackageDesc verbosity pkg' targetDir
124 verbosity = fromFlag (sDistVerbosity flags)
125 snapshot = fromFlag (sDistSnapshot flags)
127 -- | List all source files of a package.
129 -- Since @Cabal-3.4@ returns a single list. There shouldn't be any
130 -- executable files, they are hardly portable.
131 listPackageSources
132 :: Verbosity
133 -- ^ verbosity
134 -> FilePath
135 -- ^ directory with cabal file
136 -> PackageDescription
137 -- ^ info from the cabal file
138 -> [PPSuffixHandler]
139 -- ^ extra preprocessors (include suffixes)
140 -> IO [FilePath]
141 -- ^ relative paths
142 listPackageSources verbosity cwd pkg_descr0 pps = do
143 -- Call helpers that actually do all work.
144 listPackageSources' verbosity dieWithException cwd pkg_descr pps
145 where
146 pkg_descr = filterAutogenModules pkg_descr0
148 -- | A variant of 'listPackageSources' with configurable 'die'.
150 -- /Note:/ may still 'die' directly. For example on missing include file.
152 -- Since @3.4.0.0
153 listPackageSourcesWithDie
154 :: Verbosity
155 -- ^ verbosity
156 -> (Verbosity -> CabalException -> IO [FilePath])
157 -- ^ 'die'' alternative.
158 -- Since 'die'' prefixes the error message with 'errorPrefix',
159 -- whatever is passed in here and wants to die should do the same.
160 -- See issue #7331.
161 -> FilePath
162 -- ^ directory with cabal file
163 -> PackageDescription
164 -- ^ info from the cabal file
165 -> [PPSuffixHandler]
166 -- ^ extra preprocessors (include suffixes)
167 -> IO [FilePath]
168 -- ^ relative paths
169 listPackageSourcesWithDie verbosity rip cwd pkg_descr0 pps = do
170 -- Call helpers that actually do all work.
171 listPackageSources' verbosity rip cwd pkg_descr pps
172 where
173 pkg_descr = filterAutogenModules pkg_descr0
175 listPackageSources'
176 :: Verbosity
177 -- ^ verbosity
178 -> (Verbosity -> CabalException -> IO [FilePath])
179 -- ^ 'die'' alternative.
180 -- Since 'die'' prefixes the error message with 'errorPrefix',
181 -- whatever is passed in here and wants to die should do the same.
182 -- See issue #7331.
183 -> FilePath
184 -- ^ directory with cabal file
185 -> PackageDescription
186 -- ^ info from the cabal file
187 -> [PPSuffixHandler]
188 -- ^ extra preprocessors (include suffixes)
189 -> IO [FilePath]
190 -- ^ relative paths
191 listPackageSources' verbosity rip cwd pkg_descr pps =
192 fmap concat . sequenceA $
193 [ -- Library sources.
194 fmap concat
195 . withAllLib
196 $ \Library
197 { exposedModules = modules
198 , signatures = sigs
199 , libBuildInfo = libBi
200 } ->
201 allSourcesBuildInfo verbosity rip cwd libBi pps (modules ++ sigs)
202 , -- Executables sources.
203 fmap concat
204 . withAllExe
205 $ \Executable{modulePath = mainPath, buildInfo = exeBi} -> do
206 biSrcs <- allSourcesBuildInfo verbosity rip cwd exeBi pps []
207 mainSrc <- findMainExeFile verbosity cwd exeBi pps mainPath
208 return (mainSrc : biSrcs)
209 , -- Foreign library sources
210 fmap concat
211 . withAllFLib
212 $ \flib@(ForeignLib{foreignLibBuildInfo = flibBi}) -> do
213 biSrcs <- allSourcesBuildInfo verbosity rip cwd flibBi pps []
214 defFiles <-
215 traverse
216 (findModDefFile verbosity cwd flibBi pps)
217 (foreignLibModDefFile flib)
218 return (defFiles ++ biSrcs)
219 , -- Test suites sources.
220 fmap concat
221 . withAllTest
222 $ \t -> do
223 let bi = testBuildInfo t
224 case testInterface t of
225 TestSuiteExeV10 _ mainPath -> do
226 biSrcs <- allSourcesBuildInfo verbosity rip cwd bi pps []
227 srcMainFile <- findMainExeFile verbosity cwd bi pps mainPath
228 return (srcMainFile : biSrcs)
229 TestSuiteLibV09 _ m ->
230 allSourcesBuildInfo verbosity rip cwd bi pps [m]
231 TestSuiteUnsupported tp ->
232 rip verbosity $ UnsupportedTestSuite (show tp)
233 , -- Benchmarks sources.
234 fmap concat
235 . withAllBenchmark
236 $ \bm -> do
237 let bi = benchmarkBuildInfo bm
238 case benchmarkInterface bm of
239 BenchmarkExeV10 _ mainPath -> do
240 biSrcs <- allSourcesBuildInfo verbosity rip cwd bi pps []
241 srcMainFile <- findMainExeFile verbosity cwd bi pps mainPath
242 return (srcMainFile : biSrcs)
243 BenchmarkUnsupported tp ->
244 rip verbosity $ UnsupportedBenchMark (show tp)
245 , -- Data files.
246 fmap concat
247 . for (dataFiles pkg_descr)
248 $ \filename ->
250 let srcDataDirRaw = dataDir pkg_descr
251 srcDataDir
252 | null srcDataDirRaw = "."
253 | otherwise = srcDataDirRaw
254 matchDirFileGlobWithDie verbosity rip (specVersion pkg_descr) cwd (srcDataDir </> filename)
255 , -- Extra source files.
256 fmap concat . for (extraSrcFiles pkg_descr) $ \fpath ->
257 matchDirFileGlobWithDie verbosity rip (specVersion pkg_descr) cwd fpath
258 , -- Extra doc files.
259 fmap concat
260 . for (extraDocFiles pkg_descr)
261 $ \filename ->
262 matchDirFileGlobWithDie verbosity rip (specVersion pkg_descr) cwd filename
263 , -- License file(s).
264 return (map getSymbolicPath $ licenseFiles pkg_descr)
265 , -- Install-include files, without autogen-include files
266 fmap concat
267 . withAllLib
268 $ \l -> do
269 let lbi = libBuildInfo l
270 incls = filter (`notElem` autogenIncludes lbi) (installIncludes lbi)
271 relincdirs = "." : filter isRelative (includeDirs lbi)
272 traverse (fmap snd . findIncludeFile verbosity cwd relincdirs) incls
273 , -- Setup script, if it exists.
274 fmap (maybe [] (\f -> [f])) $ findSetupFile cwd
275 , -- The .cabal file itself.
276 fmap (\d -> [d]) (tryFindPackageDescCwd verbosity cwd ".")
278 where
279 -- We have to deal with all libs and executables, so we have local
280 -- versions of these functions that ignore the 'buildable' attribute:
281 withAllLib action = traverse action (allLibraries pkg_descr)
282 withAllFLib action = traverse action (foreignLibs pkg_descr)
283 withAllExe action = traverse action (executables pkg_descr)
284 withAllTest action = traverse action (testSuites pkg_descr)
285 withAllBenchmark action = traverse action (benchmarks pkg_descr)
287 -- | Prepare a directory tree of source files.
288 prepareTree
289 :: Verbosity
290 -- ^ verbosity
291 -> PackageDescription
292 -- ^ info from the cabal file
293 -> FilePath
294 -- ^ source tree to populate
295 -> [PPSuffixHandler]
296 -- ^ extra preprocessors (includes suffixes)
297 -> IO ()
298 prepareTree verbosity pkg_descr0 targetDir pps = do
299 ordinary <- listPackageSources verbosity "." pkg_descr pps
300 installOrdinaryFiles verbosity targetDir (zip (repeat []) ordinary)
301 maybeCreateDefaultSetupScript targetDir
302 where
303 pkg_descr = filterAutogenModules pkg_descr0
305 -- | Find the setup script file, if it exists.
306 findSetupFile :: FilePath -> IO (Maybe FilePath)
307 findSetupFile targetDir = do
308 hsExists <- doesFileExist (targetDir </> setupHs)
309 lhsExists <- doesFileExist (targetDir </> setupLhs)
310 if hsExists
311 then return (Just setupHs)
312 else
313 if lhsExists
314 then return (Just setupLhs)
315 else return Nothing
316 where
317 setupHs = "Setup.hs"
318 setupLhs = "Setup.lhs"
320 -- | Create a default setup script in the target directory, if it doesn't exist.
321 maybeCreateDefaultSetupScript :: FilePath -> IO ()
322 maybeCreateDefaultSetupScript targetDir = do
323 mSetupFile <- findSetupFile targetDir
324 case mSetupFile of
325 Just _setupFile -> return ()
326 Nothing -> do
327 writeUTF8File (targetDir </> "Setup.hs") $
328 unlines
329 [ "import Distribution.Simple"
330 , "main = defaultMain"
333 -- | Find the main executable file.
334 findMainExeFile
335 :: Verbosity
336 -> FilePath
337 -- ^ cwd
338 -> BuildInfo
339 -> [PPSuffixHandler]
340 -> FilePath
341 -- ^ main-is
342 -> IO FilePath
343 findMainExeFile verbosity cwd exeBi pps mainPath = do
344 ppFile <-
345 findFileCwdWithExtension
347 (ppSuffixes pps)
348 (map getSymbolicPath (hsSourceDirs exeBi))
349 (dropExtension mainPath)
350 case ppFile of
351 Nothing -> findFileCwd verbosity cwd (map getSymbolicPath (hsSourceDirs exeBi)) mainPath
352 Just pp -> return pp
354 -- | Find a module definition file
356 -- TODO: I don't know if this is right
357 findModDefFile
358 :: Verbosity -> FilePath -> BuildInfo -> [PPSuffixHandler] -> FilePath -> IO FilePath
359 findModDefFile verbosity cwd flibBi _pps modDefPath =
360 findFileCwd verbosity cwd ("." : map getSymbolicPath (hsSourceDirs flibBi)) modDefPath
362 -- | Given a list of include paths, try to find the include file named
363 -- @f@. Return the name of the file and the full path, or exit with error if
364 -- there's no such file.
365 findIncludeFile :: Verbosity -> FilePath -> [FilePath] -> String -> IO (String, FilePath)
366 findIncludeFile verbosity _ [] f = dieWithException verbosity $ NoIncludeFileFound f
367 findIncludeFile verbosity cwd (d : ds) f = do
368 let path = (d </> f)
369 b <- doesFileExist (cwd </> path)
370 if b then return (f, path) else findIncludeFile verbosity cwd ds f
372 -- | Remove the auto-generated modules (like 'Paths_*') from 'exposed-modules'
373 -- and 'other-modules'.
374 filterAutogenModules :: PackageDescription -> PackageDescription
375 filterAutogenModules pkg_descr0 =
376 mapLib filterAutogenModuleLib $
377 mapAllBuildInfo filterAutogenModuleBI pkg_descr0
378 where
379 mapLib f pkg =
381 { library = fmap f (library pkg)
382 , subLibraries = map f (subLibraries pkg)
384 filterAutogenModuleLib lib =
386 { exposedModules = filter (filterFunction (libBuildInfo lib)) (exposedModules lib)
388 filterAutogenModuleBI bi =
390 { otherModules = filter (filterFunction bi) (otherModules bi)
392 pathsModule = autogenPathsModuleName pkg_descr0
393 packageInfoModule = autogenPackageInfoModuleName pkg_descr0
394 filterFunction bi = \mn ->
395 mn /= pathsModule
396 && mn /= packageInfoModule
397 && not (mn `elem` autogenModules bi)
399 -- | Prepare a directory tree of source files for a snapshot version.
400 -- It is expected that the appropriate snapshot version has already been set
401 -- in the package description, eg using 'snapshotPackage' or 'snapshotVersion'.
402 prepareSnapshotTree
403 :: Verbosity
404 -- ^ verbosity
405 -> PackageDescription
406 -- ^ info from the cabal file
407 -> FilePath
408 -- ^ source tree to populate
409 -> [PPSuffixHandler]
410 -- ^ extra preprocessors (includes suffixes)
411 -> IO ()
412 prepareSnapshotTree verbosity pkg targetDir pps = do
413 prepareTree verbosity pkg targetDir pps
414 overwriteSnapshotPackageDesc verbosity pkg targetDir
416 overwriteSnapshotPackageDesc
417 :: Verbosity
418 -- ^ verbosity
419 -> PackageDescription
420 -- ^ info from the cabal file
421 -> FilePath
422 -- ^ source tree
423 -> IO ()
424 overwriteSnapshotPackageDesc verbosity pkg targetDir = do
425 -- We could just writePackageDescription targetDescFile pkg_descr,
426 -- but that would lose comments and formatting.
427 descFile <- defaultPackageDesc verbosity
428 withUTF8FileContents descFile $
429 writeUTF8File (targetDir </> descFile)
430 . unlines
431 . map (replaceVersion (packageVersion pkg))
432 . lines
433 where
434 replaceVersion :: Version -> String -> String
435 replaceVersion version line
436 | "version:" `isPrefixOf` map toLower line =
437 "version: " ++ prettyShow version
438 | otherwise = line
440 -- | Modifies a 'PackageDescription' by appending a snapshot number
441 -- corresponding to the given date.
442 snapshotPackage :: UTCTime -> PackageDescription -> PackageDescription
443 snapshotPackage date pkg =
445 { package = pkgid{pkgVersion = snapshotVersion date (pkgVersion pkgid)}
447 where
448 pkgid = packageId pkg
450 -- | Modifies a 'Version' by appending a snapshot number corresponding
451 -- to the given date.
452 snapshotVersion :: UTCTime -> Version -> Version
453 snapshotVersion date = alterVersion (++ [dateToSnapshotNumber date])
455 -- | Given a date produce a corresponding integer representation.
456 -- For example given a date @18/03/2008@ produce the number @20080318@.
457 dateToSnapshotNumber :: UTCTime -> Int
458 dateToSnapshotNumber date = case toGregorian (utctDay date) of
459 (year, month, day) ->
460 fromIntegral year * 10000
461 + month * 100
462 + day
464 -- | Create an archive from a tree of source files, and clean up the tree.
465 createArchive
466 :: Verbosity
467 -- ^ verbosity
468 -> PackageDescription
469 -- ^ info from cabal file
470 -> FilePath
471 -- ^ source tree to archive
472 -> FilePath
473 -- ^ name of archive to create
474 -> IO FilePath
475 createArchive verbosity pkg_descr tmpDir targetPref = do
476 let tarBallFilePath = targetPref </> tarBallName pkg_descr <.> "tar.gz"
477 (tarProg, _) <- requireProgram verbosity tarProgram defaultProgramDb
478 let formatOptSupported =
479 maybe False (== "YES") $
480 Map.lookup
481 "Supports --format"
482 (programProperties tarProg)
483 runProgram verbosity tarProg $
484 -- Hmm: I could well be skating on thinner ice here by using the -C option
485 -- (=> seems to be supported at least by GNU and *BSD tar) [The
486 -- prev. solution used pipes and sub-command sequences to set up the paths
487 -- correctly, which is problematic in a Windows setting.]
488 ["-czf", tarBallFilePath, "-C", tmpDir]
489 ++ (if formatOptSupported then ["--format", "ustar"] else [])
490 ++ [tarBallName pkg_descr]
491 return tarBallFilePath
493 -- | Given a buildinfo, return the names of all source files.
494 allSourcesBuildInfo
495 :: Verbosity
496 -> (Verbosity -> CabalException -> IO [FilePath])
497 -- ^ 'die'' alternative.
498 -- Since 'die'' prefixes the error message with 'errorPrefix',
499 -- whatever is passed in here and wants to die should do the same.
500 -- See issue #7331.
501 -> FilePath
502 -- ^ cwd -- change me to 'BuildPath Absolute PackageDir'
503 -> BuildInfo
504 -> [PPSuffixHandler]
505 -- ^ Extra preprocessors
506 -> [ModuleName]
507 -- ^ Exposed modules
508 -> IO [FilePath]
509 allSourcesBuildInfo verbosity rip cwd bi pps modules = do
510 let searchDirs = map getSymbolicPath (hsSourceDirs bi)
511 sources <-
512 fmap concat $
513 sequenceA $
514 [ let file = ModuleName.toFilePath module_
515 in -- NB: *Not* findFileWithExtension, because the same source
516 -- file may show up in multiple paths due to a conditional;
517 -- we need to package all of them. See #367.
518 findAllFilesCwdWithExtension cwd suffixes searchDirs file
519 >>= nonEmpty' (notFound module_) return
520 | module_ <- modules ++ otherModules bi
522 bootFiles <-
523 sequenceA
524 [ let file = ModuleName.toFilePath module_
525 fileExts = builtinHaskellBootSuffixes
526 in findFileCwdWithExtension cwd fileExts (map getSymbolicPath (hsSourceDirs bi)) file
527 | module_ <- modules ++ otherModules bi
530 return $
531 sources
532 ++ catMaybes bootFiles
533 ++ cSources bi
534 ++ cxxSources bi
535 ++ cmmSources bi
536 ++ asmSources bi
537 ++ jsSources bi
538 where
539 nonEmpty' :: b -> ([a] -> b) -> [a] -> b
540 nonEmpty' x _ [] = x
541 nonEmpty' _ f xs = f xs
543 suffixes = ppSuffixes pps ++ builtinHaskellSuffixes
545 notFound :: ModuleName -> IO [FilePath]
546 notFound m =
547 rip verbosity $ NoModuleFound m suffixes
549 -- | Note: must be called with the CWD set to the directory containing
550 -- the '.cabal' file.
551 printPackageProblems :: Verbosity -> PackageDescription -> IO ()
552 printPackageProblems verbosity pkg_descr = do
553 ioChecks <- checkPackageFiles verbosity pkg_descr "."
554 let pureChecks = checkConfiguredPackage pkg_descr
555 (errors, warnings) = partition isHackageDistError (pureChecks ++ ioChecks)
556 unless (null errors) $
557 notice verbosity $
558 "Distribution quality errors:\n"
559 ++ unlines (map ppPackageCheck errors)
560 unless (null warnings) $
561 notice verbosity $
562 "Distribution quality warnings:\n"
563 ++ unlines (map ppPackageCheck warnings)
564 unless (null errors) $
565 notice
566 verbosity
567 "Note: the public hackage server would reject this package."
569 ------------------------------------------------------------
571 -- | The name of the tarball without extension
572 tarBallName :: PackageDescription -> String
573 tarBallName = prettyShow . packageId
575 mapAllBuildInfo
576 :: (BuildInfo -> BuildInfo)
577 -> (PackageDescription -> PackageDescription)
578 mapAllBuildInfo f pkg =
580 { library = fmap mapLibBi (library pkg)
581 , subLibraries = fmap mapLibBi (subLibraries pkg)
582 , foreignLibs = fmap mapFLibBi (foreignLibs pkg)
583 , executables = fmap mapExeBi (executables pkg)
584 , testSuites = fmap mapTestBi (testSuites pkg)
585 , benchmarks = fmap mapBenchBi (benchmarks pkg)
587 where
588 mapLibBi lib = lib{libBuildInfo = f (libBuildInfo lib)}
589 mapFLibBi flib = flib{foreignLibBuildInfo = f (foreignLibBuildInfo flib)}
590 mapExeBi exe = exe{buildInfo = f (buildInfo exe)}
591 mapTestBi tst = tst{testBuildInfo = f (testBuildInfo tst)}
592 mapBenchBi bm = bm{benchmarkBuildInfo = f (benchmarkBuildInfo bm)}