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
)
249 let srcDataDirRaw
= dataDir pkg_descr
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.
259 . for
(extraDocFiles pkg_descr
)
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
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
".")
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.
290 -> PackageDescription
291 -- ^ info from the cabal file
293 -- ^ source tree to populate
295 -- ^ extra preprocessors (includes suffixes)
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
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
)
310 then return (Just setupHs
)
313 then return (Just setupLhs
)
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
324 Just _setupFile
-> return ()
326 writeUTF8File
(targetDir
</> "Setup.hs") $
328 [ "import Distribution.Simple"
329 , "main = defaultMain"
332 -- | Find the main executable file.
342 findMainExeFile verbosity cwd exeBi pps mainPath
= do
344 findFileCwdWithExtension
347 (map getSymbolicPath
(hsSourceDirs exeBi
))
348 (dropExtension mainPath
)
350 Nothing
-> findFileCwd verbosity cwd
(map getSymbolicPath
(hsSourceDirs exeBi
)) mainPath
353 -- | Find a module definition file
355 -- TODO: I don't know if this is right
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
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
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
->
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'.
404 -> PackageDescription
405 -- ^ info from the cabal file
407 -- ^ source tree to populate
409 -- ^ extra preprocessors (includes suffixes)
411 prepareSnapshotTree verbosity pkg targetDir pps
= do
412 prepareTree verbosity pkg targetDir pps
413 overwriteSnapshotPackageDesc verbosity pkg targetDir
415 overwriteSnapshotPackageDesc
418 -> PackageDescription
419 -- ^ info from the cabal file
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
)
430 . map (replaceVersion
(packageVersion pkg
))
433 replaceVersion
:: Version
-> String -> String
434 replaceVersion version line
435 |
"version:" `
isPrefixOf`
map toLower line
=
436 "version: " ++ prettyShow version
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
)}
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
463 -- | Create an archive from a tree of source files, and clean up the tree.
467 -> PackageDescription
468 -- ^ info from cabal file
470 -- ^ source tree to archive
472 -- ^ name of archive to create
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") $
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.
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.
501 -- ^ cwd -- change me to 'BuildPath Absolute PackageDir'
504 -- ^ Extra preprocessors
508 allSourcesBuildInfo verbosity rip cwd bi pps modules
= do
509 let searchDirs
= map getSymbolicPath
(hsSourceDirs bi
)
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
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
531 ++ catMaybes bootFiles
538 nonEmpty
' :: b
-> ([a
] -> b
) -> [a
] -> b
540 nonEmpty
' _ f xs
= f xs
542 suffixes
= ppSuffixes pps
++ ["hs", "lhs", "hsig", "lhsig"]
544 notFound
:: ModuleName
-> IO [FilePath]
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
) $
557 "Distribution quality errors:\n"
558 ++ unlines (map ppPackageCheck errors
)
559 unless (null warnings
) $
561 "Distribution quality warnings:\n"
562 ++ unlines (map ppPackageCheck warnings
)
563 unless (null errors
) $
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
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
)
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
)}