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.
10 -- Module : Distribution.Simple.SrcDist
11 -- Copyright : Simon Marlow 2004
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
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
30 -- ** Parts of 'sdist'
31 , printPackageProblems
39 , dateToSnapshotNumber
41 -- * Extracting the source files
43 , listPackageSourcesWithDie
46 import Distribution
.Compat
.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.
77 -- ^ information from the tarball
79 -- ^ verbosity & snapshot
80 -> (FilePath -> FilePath)
81 -- ^ build prefix (temp dir)
83 -- ^ extra preprocessors (includes suffixes)
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
++ "'"
98 printPackageProblems verbosity pkg
100 date
<- getCurrentTime
102 | snapshot
= snapshotPackage date pkg
105 case flagToMaybe
(sDistDirectory flags
) of
107 generateSourceDir targetDir pkg
'
108 info verbosity
$ "Source directory created: " ++ targetDir
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
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
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.
135 -- ^ directory with cabal file
136 -> PackageDescription
137 -- ^ info from the cabal file
139 -- ^ extra preprocessors (include suffixes)
142 listPackageSources verbosity cwd pkg_descr0 pps
= do
143 -- Call helpers that actually do all work.
144 listPackageSources
' verbosity dieWithException cwd pkg_descr pps
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.
153 listPackageSourcesWithDie
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.
162 -- ^ directory with cabal file
163 -> PackageDescription
164 -- ^ info from the cabal file
166 -- ^ extra preprocessors (include suffixes)
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
173 pkg_descr
= filterAutogenModules pkg_descr0
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.
184 -- ^ directory with cabal file
185 -> PackageDescription
186 -- ^ info from the cabal file
188 -- ^ extra preprocessors (include suffixes)
191 listPackageSources
' verbosity rip cwd pkg_descr pps
=
192 fmap concat . sequenceA
$
193 [ -- Library sources.
197 { exposedModules
= modules
199 , libBuildInfo
= libBi
201 allSourcesBuildInfo verbosity rip cwd libBi pps
(modules
++ sigs
)
202 , -- Executables sources.
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
212 $ \flib
@(ForeignLib
{foreignLibBuildInfo
= flibBi
}) -> do
213 biSrcs
<- allSourcesBuildInfo verbosity rip cwd flibBi pps
[]
216 (findModDefFile verbosity cwd flibBi pps
)
217 (foreignLibModDefFile flib
)
218 return (defFiles
++ biSrcs
)
219 , -- Test suites sources.
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.
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
)
247 . for
(dataFiles pkg_descr
)
250 let srcDataDirRaw
= dataDir pkg_descr
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.
260 . for
(extraDocFiles pkg_descr
)
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
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
".")
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.
291 -> PackageDescription
292 -- ^ info from the cabal file
294 -- ^ source tree to populate
296 -- ^ extra preprocessors (includes suffixes)
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
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
)
311 then return (Just setupHs
)
314 then return (Just setupLhs
)
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
325 Just _setupFile
-> return ()
327 writeUTF8File
(targetDir
</> "Setup.hs") $
329 [ "import Distribution.Simple"
330 , "main = defaultMain"
333 -- | Find the main executable file.
343 findMainExeFile verbosity cwd exeBi pps mainPath
= do
345 findFileCwdWithExtension
348 (map getSymbolicPath
(hsSourceDirs exeBi
))
349 (dropExtension mainPath
)
351 Nothing
-> findFileCwd verbosity cwd
(map getSymbolicPath
(hsSourceDirs exeBi
)) mainPath
354 -- | Find a module definition file
356 -- TODO: I don't know if this is right
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
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
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
->
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'.
405 -> PackageDescription
406 -- ^ info from the cabal file
408 -- ^ source tree to populate
410 -- ^ extra preprocessors (includes suffixes)
412 prepareSnapshotTree verbosity pkg targetDir pps
= do
413 prepareTree verbosity pkg targetDir pps
414 overwriteSnapshotPackageDesc verbosity pkg targetDir
416 overwriteSnapshotPackageDesc
419 -> PackageDescription
420 -- ^ info from the cabal file
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
)
431 . map (replaceVersion
(packageVersion pkg
))
434 replaceVersion
:: Version
-> String -> String
435 replaceVersion version line
436 |
"version:" `
isPrefixOf`
map toLower line
=
437 "version: " ++ prettyShow version
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
)}
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
464 -- | Create an archive from a tree of source files, and clean up the tree.
468 -> PackageDescription
469 -- ^ info from cabal file
471 -- ^ source tree to archive
473 -- ^ name of archive to create
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") $
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.
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.
502 -- ^ cwd -- change me to 'BuildPath Absolute PackageDir'
505 -- ^ Extra preprocessors
509 allSourcesBuildInfo verbosity rip cwd bi pps modules
= do
510 let searchDirs
= map getSymbolicPath
(hsSourceDirs bi
)
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
524 [ let file
= ModuleName
.toFilePath module_
525 fileExts
= builtinHaskellBootSuffixes
526 in findFileCwdWithExtension cwd fileExts
(map getSymbolicPath
(hsSourceDirs bi
)) file
527 | module_
<- modules
++ otherModules bi
532 ++ catMaybes bootFiles
539 nonEmpty
' :: b
-> ([a
] -> b
) -> [a
] -> b
541 nonEmpty
' _ f xs
= f xs
543 suffixes
= ppSuffixes pps
++ builtinHaskellSuffixes
545 notFound
:: ModuleName
-> IO [FilePath]
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
) $
558 "Distribution quality errors:\n"
559 ++ unlines (map ppPackageCheck errors
)
560 unless (null warnings
) $
562 "Distribution quality warnings:\n"
563 ++ unlines (map ppPackageCheck warnings
)
564 unless (null errors
) $
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
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
)
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
)}