Make “sublibrary” standard terminology in docs
[cabal.git] / Cabal / src / Distribution / Simple / SrcDist.hs
blob706d3b51e35033cb0dbafd9a723015db9a209766
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 -> do
249 let srcDataDirRaw = dataDir pkg_descr
250 srcDataDir
251 | null srcDataDirRaw = "."
252 | otherwise = srcDataDirRaw
253 matchDirFileGlobWithDie verbosity rip (specVersion pkg_descr) cwd (srcDataDir </> filename)
254 , -- Extra source files.
255 fmap concat . for (extraSrcFiles pkg_descr) $ \fpath ->
256 matchDirFileGlobWithDie verbosity rip (specVersion pkg_descr) cwd fpath
257 , -- Extra doc files.
258 fmap concat
259 . for (extraDocFiles pkg_descr)
260 $ \filename ->
261 matchDirFileGlobWithDie verbosity rip (specVersion pkg_descr) cwd filename
262 , -- License file(s).
263 return (map getSymbolicPath $ licenseFiles pkg_descr)
264 , -- Install-include files, without autogen-include files
265 fmap concat
266 . withAllLib
267 $ \l -> do
268 let lbi = libBuildInfo l
269 incls = filter (`notElem` autogenIncludes lbi) (installIncludes lbi)
270 relincdirs = "." : filter isRelative (includeDirs lbi)
271 traverse (fmap snd . findIncludeFile verbosity cwd relincdirs) incls
272 , -- Setup script, if it exists.
273 fmap (maybe [] (\f -> [f])) $ findSetupFile cwd
274 , -- The .cabal file itself.
275 fmap (\d -> [d]) (tryFindPackageDescCwd verbosity cwd ".")
277 where
278 -- We have to deal with all libs and executables, so we have local
279 -- versions of these functions that ignore the 'buildable' attribute:
280 withAllLib action = traverse action (allLibraries pkg_descr)
281 withAllFLib action = traverse action (foreignLibs pkg_descr)
282 withAllExe action = traverse action (executables pkg_descr)
283 withAllTest action = traverse action (testSuites pkg_descr)
284 withAllBenchmark action = traverse action (benchmarks pkg_descr)
286 -- | Prepare a directory tree of source files.
287 prepareTree
288 :: Verbosity
289 -- ^ verbosity
290 -> PackageDescription
291 -- ^ info from the cabal file
292 -> FilePath
293 -- ^ source tree to populate
294 -> [PPSuffixHandler]
295 -- ^ extra preprocessors (includes suffixes)
296 -> IO ()
297 prepareTree verbosity pkg_descr0 targetDir pps = do
298 ordinary <- listPackageSources verbosity "." pkg_descr pps
299 installOrdinaryFiles verbosity targetDir (zip (repeat []) ordinary)
300 maybeCreateDefaultSetupScript targetDir
301 where
302 pkg_descr = filterAutogenModules pkg_descr0
304 -- | Find the setup script file, if it exists.
305 findSetupFile :: FilePath -> IO (Maybe FilePath)
306 findSetupFile targetDir = do
307 hsExists <- doesFileExist (targetDir </> setupHs)
308 lhsExists <- doesFileExist (targetDir </> setupLhs)
309 if hsExists
310 then return (Just setupHs)
311 else
312 if lhsExists
313 then return (Just setupLhs)
314 else return Nothing
315 where
316 setupHs = "Setup.hs"
317 setupLhs = "Setup.lhs"
319 -- | Create a default setup script in the target directory, if it doesn't exist.
320 maybeCreateDefaultSetupScript :: FilePath -> IO ()
321 maybeCreateDefaultSetupScript targetDir = do
322 mSetupFile <- findSetupFile targetDir
323 case mSetupFile of
324 Just _setupFile -> return ()
325 Nothing -> do
326 writeUTF8File (targetDir </> "Setup.hs") $
327 unlines
328 [ "import Distribution.Simple"
329 , "main = defaultMain"
332 -- | Find the main executable file.
333 findMainExeFile
334 :: Verbosity
335 -> FilePath
336 -- ^ cwd
337 -> BuildInfo
338 -> [PPSuffixHandler]
339 -> FilePath
340 -- ^ main-is
341 -> IO FilePath
342 findMainExeFile verbosity cwd exeBi pps mainPath = do
343 ppFile <-
344 findFileCwdWithExtension
346 (ppSuffixes pps)
347 (map getSymbolicPath (hsSourceDirs exeBi))
348 (dropExtension mainPath)
349 case ppFile of
350 Nothing -> findFileCwd verbosity cwd (map getSymbolicPath (hsSourceDirs exeBi)) mainPath
351 Just pp -> return pp
353 -- | Find a module definition file
355 -- TODO: I don't know if this is right
356 findModDefFile
357 :: Verbosity -> FilePath -> BuildInfo -> [PPSuffixHandler] -> FilePath -> IO FilePath
358 findModDefFile verbosity cwd flibBi _pps modDefPath =
359 findFileCwd verbosity cwd ("." : map getSymbolicPath (hsSourceDirs flibBi)) modDefPath
361 -- | Given a list of include paths, try to find the include file named
362 -- @f@. Return the name of the file and the full path, or exit with error if
363 -- there's no such file.
364 findIncludeFile :: Verbosity -> FilePath -> [FilePath] -> String -> IO (String, FilePath)
365 findIncludeFile verbosity _ [] f = dieWithException verbosity $ NoIncludeFileFound f
366 findIncludeFile verbosity cwd (d : ds) f = do
367 let path = (d </> f)
368 b <- doesFileExist (cwd </> path)
369 if b then return (f, path) else findIncludeFile verbosity cwd ds f
371 -- | Remove the auto-generated modules (like 'Paths_*') from 'exposed-modules'
372 -- and 'other-modules'.
373 filterAutogenModules :: PackageDescription -> PackageDescription
374 filterAutogenModules pkg_descr0 =
375 mapLib filterAutogenModuleLib $
376 mapAllBuildInfo filterAutogenModuleBI pkg_descr0
377 where
378 mapLib f pkg =
380 { library = fmap f (library pkg)
381 , subLibraries = map f (subLibraries pkg)
383 filterAutogenModuleLib lib =
385 { exposedModules = filter (filterFunction (libBuildInfo lib)) (exposedModules lib)
387 filterAutogenModuleBI bi =
389 { otherModules = filter (filterFunction bi) (otherModules bi)
391 pathsModule = autogenPathsModuleName pkg_descr0
392 packageInfoModule = autogenPackageInfoModuleName pkg_descr0
393 filterFunction bi = \mn ->
394 mn /= pathsModule
395 && mn /= packageInfoModule
396 && not (mn `elem` autogenModules bi)
398 -- | Prepare a directory tree of source files for a snapshot version.
399 -- It is expected that the appropriate snapshot version has already been set
400 -- in the package description, eg using 'snapshotPackage' or 'snapshotVersion'.
401 prepareSnapshotTree
402 :: Verbosity
403 -- ^ verbosity
404 -> PackageDescription
405 -- ^ info from the cabal file
406 -> FilePath
407 -- ^ source tree to populate
408 -> [PPSuffixHandler]
409 -- ^ extra preprocessors (includes suffixes)
410 -> IO ()
411 prepareSnapshotTree verbosity pkg targetDir pps = do
412 prepareTree verbosity pkg targetDir pps
413 overwriteSnapshotPackageDesc verbosity pkg targetDir
415 overwriteSnapshotPackageDesc
416 :: Verbosity
417 -- ^ verbosity
418 -> PackageDescription
419 -- ^ info from the cabal file
420 -> FilePath
421 -- ^ source tree
422 -> IO ()
423 overwriteSnapshotPackageDesc verbosity pkg targetDir = do
424 -- We could just writePackageDescription targetDescFile pkg_descr,
425 -- but that would lose comments and formatting.
426 descFile <- defaultPackageDesc verbosity
427 withUTF8FileContents descFile $
428 writeUTF8File (targetDir </> descFile)
429 . unlines
430 . map (replaceVersion (packageVersion pkg))
431 . lines
432 where
433 replaceVersion :: Version -> String -> String
434 replaceVersion version line
435 | "version:" `isPrefixOf` map toLower line =
436 "version: " ++ prettyShow version
437 | otherwise = line
439 -- | Modifies a 'PackageDescription' by appending a snapshot number
440 -- corresponding to the given date.
441 snapshotPackage :: UTCTime -> PackageDescription -> PackageDescription
442 snapshotPackage date pkg =
444 { package = pkgid{pkgVersion = snapshotVersion date (pkgVersion pkgid)}
446 where
447 pkgid = packageId pkg
449 -- | Modifies a 'Version' by appending a snapshot number corresponding
450 -- to the given date.
451 snapshotVersion :: UTCTime -> Version -> Version
452 snapshotVersion date = alterVersion (++ [dateToSnapshotNumber date])
454 -- | Given a date produce a corresponding integer representation.
455 -- For example given a date @18/03/2008@ produce the number @20080318@.
456 dateToSnapshotNumber :: UTCTime -> Int
457 dateToSnapshotNumber date = case toGregorian (utctDay date) of
458 (year, month, day) ->
459 fromIntegral year * 10000
460 + month * 100
461 + day
463 -- | Create an archive from a tree of source files, and clean up the tree.
464 createArchive
465 :: Verbosity
466 -- ^ verbosity
467 -> PackageDescription
468 -- ^ info from cabal file
469 -> FilePath
470 -- ^ source tree to archive
471 -> FilePath
472 -- ^ name of archive to create
473 -> IO FilePath
474 createArchive verbosity pkg_descr tmpDir targetPref = do
475 let tarBallFilePath = targetPref </> tarBallName pkg_descr <.> "tar.gz"
476 (tarProg, _) <- requireProgram verbosity tarProgram defaultProgramDb
477 let formatOptSupported =
478 maybe False (== "YES") $
479 Map.lookup
480 "Supports --format"
481 (programProperties tarProg)
482 runProgram verbosity tarProg $
483 -- Hmm: I could well be skating on thinner ice here by using the -C option
484 -- (=> seems to be supported at least by GNU and *BSD tar) [The
485 -- prev. solution used pipes and sub-command sequences to set up the paths
486 -- correctly, which is problematic in a Windows setting.]
487 ["-czf", tarBallFilePath, "-C", tmpDir]
488 ++ (if formatOptSupported then ["--format", "ustar"] else [])
489 ++ [tarBallName pkg_descr]
490 return tarBallFilePath
492 -- | Given a buildinfo, return the names of all source files.
493 allSourcesBuildInfo
494 :: Verbosity
495 -> (Verbosity -> CabalException -> IO [FilePath])
496 -- ^ 'die'' alternative.
497 -- Since 'die'' prefixes the error message with 'errorPrefix',
498 -- whatever is passed in here and wants to die should do the same.
499 -- See issue #7331.
500 -> FilePath
501 -- ^ cwd -- change me to 'BuildPath Absolute PackageDir'
502 -> BuildInfo
503 -> [PPSuffixHandler]
504 -- ^ Extra preprocessors
505 -> [ModuleName]
506 -- ^ Exposed modules
507 -> IO [FilePath]
508 allSourcesBuildInfo verbosity rip cwd bi pps modules = do
509 let searchDirs = map getSymbolicPath (hsSourceDirs bi)
510 sources <-
511 fmap concat $
512 sequenceA $
513 [ let file = ModuleName.toFilePath module_
514 in -- NB: *Not* findFileWithExtension, because the same source
515 -- file may show up in multiple paths due to a conditional;
516 -- we need to package all of them. See #367.
517 findAllFilesCwdWithExtension cwd suffixes searchDirs file
518 >>= nonEmpty' (notFound module_) return
519 | module_ <- modules ++ otherModules bi
521 bootFiles <-
522 sequenceA
523 [ let file = ModuleName.toFilePath module_
524 fileExts = ["hs-boot", "lhs-boot"]
525 in findFileCwdWithExtension cwd fileExts (map getSymbolicPath (hsSourceDirs bi)) file
526 | module_ <- modules ++ otherModules bi
529 return $
530 sources
531 ++ catMaybes bootFiles
532 ++ cSources bi
533 ++ cxxSources bi
534 ++ cmmSources bi
535 ++ asmSources bi
536 ++ jsSources bi
537 where
538 nonEmpty' :: b -> ([a] -> b) -> [a] -> b
539 nonEmpty' x _ [] = x
540 nonEmpty' _ f xs = f xs
542 suffixes = ppSuffixes pps ++ ["hs", "lhs", "hsig", "lhsig"]
544 notFound :: ModuleName -> IO [FilePath]
545 notFound m =
546 rip verbosity $ NoModuleFound m suffixes
548 -- | Note: must be called with the CWD set to the directory containing
549 -- the '.cabal' file.
550 printPackageProblems :: Verbosity -> PackageDescription -> IO ()
551 printPackageProblems verbosity pkg_descr = do
552 ioChecks <- checkPackageFiles verbosity pkg_descr "."
553 let pureChecks = checkConfiguredPackage pkg_descr
554 (errors, warnings) = partition isHackageDistError (pureChecks ++ ioChecks)
555 unless (null errors) $
556 notice verbosity $
557 "Distribution quality errors:\n"
558 ++ unlines (map ppPackageCheck errors)
559 unless (null warnings) $
560 notice verbosity $
561 "Distribution quality warnings:\n"
562 ++ unlines (map ppPackageCheck warnings)
563 unless (null errors) $
564 notice
565 verbosity
566 "Note: the public hackage server would reject this package."
568 ------------------------------------------------------------
570 -- | The name of the tarball without extension
571 tarBallName :: PackageDescription -> String
572 tarBallName = prettyShow . packageId
574 mapAllBuildInfo
575 :: (BuildInfo -> BuildInfo)
576 -> (PackageDescription -> PackageDescription)
577 mapAllBuildInfo f pkg =
579 { library = fmap mapLibBi (library pkg)
580 , subLibraries = fmap mapLibBi (subLibraries pkg)
581 , foreignLibs = fmap mapFLibBi (foreignLibs pkg)
582 , executables = fmap mapExeBi (executables pkg)
583 , testSuites = fmap mapTestBi (testSuites pkg)
584 , benchmarks = fmap mapBenchBi (benchmarks pkg)
586 where
587 mapLibBi lib = lib{libBuildInfo = f (libBuildInfo lib)}
588 mapFLibBi flib = flib{foreignLibBuildInfo = f (foreignLibBuildInfo flib)}
589 mapExeBi exe = exe{buildInfo = f (buildInfo exe)}
590 mapTestBi tst = tst{testBuildInfo = f (testBuildInfo tst)}
591 mapBenchBi bm = bm{benchmarkBuildInfo = f (benchmarkBuildInfo bm)}