PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/interpreter/ghc/libraries/Cabal/Distribution/Simple/SrcDist.hs

https://github.com/khskrede/mehh
Haskell | 384 lines | 253 code | 41 blank | 90 comment | 8 complexity | 6c8ec8e1a1391dcbd83c8c67a12fac76 MD5 | raw file
  1. -----------------------------------------------------------------------------
  2. -- |
  3. -- Module : Distribution.Simple.SrcDist
  4. -- Copyright : Simon Marlow 2004
  5. --
  6. -- Maintainer : cabal-devel@haskell.org
  7. -- Portability : portable
  8. --
  9. -- This handles the @sdist@ command. The module exports an 'sdist' action but
  10. -- also some of the phases that make it up so that other tools can use just the
  11. -- bits they need. In particular the preparation of the tree of files to go
  12. -- into the source tarball is separated from actually building the source
  13. -- tarball.
  14. --
  15. -- The 'createArchive' action uses the external @tar@ program and assumes that
  16. -- it accepts the @-z@ flag. Neither of these assumptions are valid on Windows.
  17. -- The 'sdist' action now also does some distribution QA checks.
  18. {- Copyright (c) 2003-2004, Simon Marlow
  19. All rights reserved.
  20. Redistribution and use in source and binary forms, with or without
  21. modification, are permitted provided that the following conditions are
  22. met:
  23. * Redistributions of source code must retain the above copyright
  24. notice, this list of conditions and the following disclaimer.
  25. * Redistributions in binary form must reproduce the above
  26. copyright notice, this list of conditions and the following
  27. disclaimer in the documentation and/or other materials provided
  28. with the distribution.
  29. * Neither the name of Isaac Jones nor the names of other
  30. contributors may be used to endorse or promote products derived
  31. from this software without specific prior written permission.
  32. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  33. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  34. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  35. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  36. OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  38. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  39. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  40. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  41. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  42. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -}
  43. -- NOTE: FIX: we don't have a great way of testing this module, since
  44. -- we can't easily look inside a tarball once its created.
  45. module Distribution.Simple.SrcDist (
  46. -- * The top level action
  47. sdist,
  48. -- ** Parts of 'sdist'
  49. printPackageProblems,
  50. prepareTree,
  51. createArchive,
  52. -- ** Snaphots
  53. prepareSnapshotTree,
  54. snapshotPackage,
  55. snapshotVersion,
  56. dateToSnapshotNumber,
  57. ) where
  58. import Distribution.PackageDescription
  59. ( PackageDescription(..), BuildInfo(..), Executable(..), Library(..) )
  60. import Distribution.PackageDescription.Check
  61. ( PackageCheck(..), checkConfiguredPackage, checkPackageFiles )
  62. import Distribution.Package
  63. ( PackageIdentifier(pkgVersion), Package(..), packageVersion )
  64. import Distribution.ModuleName (ModuleName)
  65. import qualified Distribution.ModuleName as ModuleName
  66. import Distribution.Version
  67. ( Version(versionBranch) )
  68. import Distribution.Simple.Utils
  69. ( createDirectoryIfMissingVerbose, withUTF8FileContents, writeUTF8File
  70. , installOrdinaryFile, installOrdinaryFiles
  71. , findFile, findFileWithExtension, matchFileGlob
  72. , withTempDirectory, defaultPackageDesc
  73. , die, warn, notice, setupMessage )
  74. import Distribution.Simple.Setup (SDistFlags(..), fromFlag)
  75. import Distribution.Simple.PreProcess (PPSuffixHandler, ppSuffixes, preprocessSources)
  76. import Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..) )
  77. import Distribution.Simple.BuildPaths ( autogenModuleName )
  78. import Distribution.Simple.Program ( defaultProgramConfiguration, requireProgram,
  79. rawSystemProgram, tarProgram )
  80. import Distribution.Text
  81. ( display )
  82. import Control.Monad(when, unless)
  83. import Data.Char (toLower)
  84. import Data.List (partition, isPrefixOf)
  85. import Data.Maybe (isNothing, catMaybes)
  86. import System.Time (getClockTime, toCalendarTime, CalendarTime(..))
  87. import System.Directory
  88. ( doesFileExist, Permissions(executable), getPermissions )
  89. import Distribution.Compat.CopyFile (setFileExecutable)
  90. import Distribution.Verbosity (Verbosity)
  91. import System.FilePath
  92. ( (</>), (<.>), takeDirectory, dropExtension, isAbsolute )
  93. -- |Create a source distribution.
  94. sdist :: PackageDescription -- ^information from the tarball
  95. -> Maybe LocalBuildInfo -- ^Information from configure
  96. -> SDistFlags -- ^verbosity & snapshot
  97. -> (FilePath -> FilePath) -- ^build prefix (temp dir)
  98. -> [PPSuffixHandler] -- ^ extra preprocessors (includes suffixes)
  99. -> IO ()
  100. sdist pkg mb_lbi flags mkTmpDir pps = do
  101. let distPref = fromFlag $ sDistDistPref flags
  102. targetPref = distPref
  103. tmpTargetDir = mkTmpDir distPref
  104. -- do some QA
  105. printPackageProblems verbosity pkg
  106. when (isNothing mb_lbi) $
  107. warn verbosity "Cannot run preprocessors. Run 'configure' command first."
  108. createDirectoryIfMissingVerbose verbosity True tmpTargetDir
  109. withTempDirectory verbosity tmpTargetDir "sdist." $ \tmpDir -> do
  110. date <- toCalendarTime =<< getClockTime
  111. let pkg' | snapshot = snapshotPackage date pkg
  112. | otherwise = pkg
  113. setupMessage verbosity "Building source dist for" (packageId pkg')
  114. -- FIXME This looks a bit suspicious. Should createArchive be passed
  115. -- the result of prepareSnapshotTree/prepareTree?
  116. _ <- if snapshot
  117. then prepareSnapshotTree verbosity pkg' mb_lbi distPref tmpDir pps
  118. else prepareTree verbosity pkg' mb_lbi distPref tmpDir pps
  119. targzFile <- createArchive verbosity pkg' mb_lbi tmpDir targetPref
  120. notice verbosity $ "Source tarball created: " ++ targzFile
  121. where
  122. verbosity = fromFlag (sDistVerbosity flags)
  123. snapshot = fromFlag (sDistSnapshot flags)
  124. -- |Prepare a directory tree of source files.
  125. prepareTree :: Verbosity -- ^verbosity
  126. -> PackageDescription -- ^info from the cabal file
  127. -> Maybe LocalBuildInfo
  128. -> FilePath -- ^dist dir
  129. -> FilePath -- ^source tree to populate
  130. -> [PPSuffixHandler] -- ^extra preprocessors (includes suffixes)
  131. -> IO FilePath -- ^the name of the dir created and populated
  132. prepareTree verbosity pkg_descr0 mb_lbi distPref tmpDir pps = do
  133. let targetDir = tmpDir </> tarBallName pkg_descr
  134. createDirectoryIfMissingVerbose verbosity True targetDir
  135. -- maybe move the library files into place
  136. withLib $ \Library { exposedModules = modules, libBuildInfo = libBi } ->
  137. prepareDir verbosity pkg_descr distPref targetDir pps modules libBi
  138. -- move the executables into place
  139. withExe $ \Executable { modulePath = mainPath, buildInfo = exeBi } -> do
  140. prepareDir verbosity pkg_descr distPref targetDir pps [] exeBi
  141. srcMainFile <- do
  142. ppFile <- findFileWithExtension (ppSuffixes pps) (hsSourceDirs exeBi) (dropExtension mainPath)
  143. case ppFile of
  144. Nothing -> findFile (hsSourceDirs exeBi) mainPath
  145. Just pp -> return pp
  146. copyFileTo verbosity targetDir srcMainFile
  147. flip mapM_ (dataFiles pkg_descr) $ \ filename -> do
  148. files <- matchFileGlob (dataDir pkg_descr </> filename)
  149. let dir = takeDirectory (dataDir pkg_descr </> filename)
  150. createDirectoryIfMissingVerbose verbosity True (targetDir </> dir)
  151. sequence_ [ installOrdinaryFile verbosity file (targetDir </> file)
  152. | file <- files ]
  153. when (not (null (licenseFile pkg_descr))) $
  154. copyFileTo verbosity targetDir (licenseFile pkg_descr)
  155. flip mapM_ (extraSrcFiles pkg_descr) $ \ fpath -> do
  156. files <- matchFileGlob fpath
  157. sequence_
  158. [ do copyFileTo verbosity targetDir file
  159. -- preserve executable bit on extra-src-files like ./configure
  160. perms <- getPermissions file
  161. when (executable perms) --only checks user x bit
  162. (setFileExecutable (targetDir </> file))
  163. | file <- files ]
  164. -- copy the install-include files
  165. withLib $ \ l -> do
  166. let lbi = libBuildInfo l
  167. relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi)
  168. incs <- mapM (findInc relincdirs) (installIncludes lbi)
  169. flip mapM_ incs $ \(_,fpath) ->
  170. copyFileTo verbosity targetDir fpath
  171. -- if the package was configured then we can run platform independent
  172. -- pre-processors and include those generated files
  173. case mb_lbi of
  174. Just lbi | not (null pps)
  175. -> preprocessSources pkg_descr (lbi { buildDir = targetDir </> buildDir lbi })
  176. True verbosity pps
  177. _ -> return ()
  178. -- setup isn't listed in the description file.
  179. hsExists <- doesFileExist "Setup.hs"
  180. lhsExists <- doesFileExist "Setup.lhs"
  181. if hsExists then copyFileTo verbosity targetDir "Setup.hs"
  182. else if lhsExists then copyFileTo verbosity targetDir "Setup.lhs"
  183. else writeUTF8File (targetDir </> "Setup.hs") $ unlines [
  184. "import Distribution.Simple",
  185. "main = defaultMain"]
  186. -- the description file itself
  187. descFile <- defaultPackageDesc verbosity
  188. installOrdinaryFile verbosity descFile (targetDir </> descFile)
  189. return targetDir
  190. where
  191. pkg_descr = mapAllBuildInfo filterAutogenModule pkg_descr0
  192. filterAutogenModule bi = bi {
  193. otherModules = filter (/=autogenModule) (otherModules bi)
  194. }
  195. autogenModule = autogenModuleName pkg_descr0
  196. findInc [] f = die ("can't find include file " ++ f)
  197. findInc (d:ds) f = do
  198. let path = (d </> f)
  199. b <- doesFileExist path
  200. if b then return (f,path) else findInc ds f
  201. -- We have to deal with all libs and executables, so we have local
  202. -- versions of these functions that ignore the 'buildable' attribute:
  203. withLib action = maybe (return ()) action (library pkg_descr)
  204. withExe action = mapM_ action (executables pkg_descr)
  205. -- | Prepare a directory tree of source files for a snapshot version.
  206. -- It is expected that the appropriate snapshot version has already been set
  207. -- in the package description, eg using 'snapshotPackage' or 'snapshotVersion'.
  208. --
  209. prepareSnapshotTree :: Verbosity -- ^verbosity
  210. -> PackageDescription -- ^info from the cabal file
  211. -> Maybe LocalBuildInfo
  212. -> FilePath -- ^dist dir
  213. -> FilePath -- ^source tree to populate
  214. -> [PPSuffixHandler] -- ^extra preprocessors (includes suffixes)
  215. -> IO FilePath -- ^the resulting temp dir
  216. prepareSnapshotTree verbosity pkg mb_lbi distPref tmpDir pps = do
  217. targetDir <- prepareTree verbosity pkg mb_lbi distPref tmpDir pps
  218. overwriteSnapshotPackageDesc (packageVersion pkg) targetDir
  219. return targetDir
  220. where
  221. overwriteSnapshotPackageDesc version targetDir = do
  222. -- We could just writePackageDescription targetDescFile pkg_descr,
  223. -- but that would lose comments and formatting.
  224. descFile <- defaultPackageDesc verbosity
  225. withUTF8FileContents descFile $
  226. writeUTF8File (targetDir </> descFile)
  227. . unlines . map (replaceVersion version) . lines
  228. replaceVersion :: Version -> String -> String
  229. replaceVersion version line
  230. | "version:" `isPrefixOf` map toLower line
  231. = "version: " ++ display version
  232. | otherwise = line
  233. -- | Modifies a 'PackageDescription' by appending a snapshot number
  234. -- corresponding to the given date.
  235. --
  236. snapshotPackage :: CalendarTime -> PackageDescription -> PackageDescription
  237. snapshotPackage date pkg =
  238. pkg {
  239. package = pkgid { pkgVersion = snapshotVersion date (pkgVersion pkgid) }
  240. }
  241. where pkgid = packageId pkg
  242. -- | Modifies a 'Version' by appending a snapshot number corresponding
  243. -- to the given date.
  244. --
  245. snapshotVersion :: CalendarTime -> Version -> Version
  246. snapshotVersion date version = version {
  247. versionBranch = versionBranch version
  248. ++ [dateToSnapshotNumber date]
  249. }
  250. -- | Given a date produce a corresponding integer representation.
  251. -- For example given a date @18/03/2008@ produce the number @20080318@.
  252. --
  253. dateToSnapshotNumber :: CalendarTime -> Int
  254. dateToSnapshotNumber date = year * 10000
  255. + month * 100
  256. + day
  257. where
  258. year = ctYear date
  259. month = fromEnum (ctMonth date) + 1
  260. day = ctDay date
  261. -- |Create an archive from a tree of source files, and clean up the tree.
  262. createArchive :: Verbosity -- ^verbosity
  263. -> PackageDescription -- ^info from cabal file
  264. -> Maybe LocalBuildInfo -- ^info from configure
  265. -> FilePath -- ^source tree to archive
  266. -> FilePath -- ^name of archive to create
  267. -> IO FilePath
  268. createArchive verbosity pkg_descr mb_lbi tmpDir targetPref = do
  269. let tarBallFilePath = targetPref </> tarBallName pkg_descr <.> "tar.gz"
  270. (tarProg, _) <- requireProgram verbosity tarProgram
  271. (maybe defaultProgramConfiguration withPrograms mb_lbi)
  272. -- Hmm: I could well be skating on thinner ice here by using the -C option (=> GNU tar-specific?)
  273. -- [The prev. solution used pipes and sub-command sequences to set up the paths correctly,
  274. -- which is problematic in a Windows setting.]
  275. rawSystemProgram verbosity tarProg
  276. ["-C", tmpDir, "-czf", tarBallFilePath, tarBallName pkg_descr]
  277. return tarBallFilePath
  278. -- |Move the sources into place based on buildInfo
  279. prepareDir :: Verbosity -- ^verbosity
  280. -> PackageDescription -- ^info from the cabal file
  281. -> FilePath -- ^dist dir
  282. -> FilePath -- ^TargetPrefix
  283. -> [PPSuffixHandler] -- ^ extra preprocessors (includes suffixes)
  284. -> [ModuleName] -- ^Exposed modules
  285. -> BuildInfo
  286. -> IO ()
  287. prepareDir verbosity _pkg _distPref inPref pps modules bi
  288. = do let searchDirs = hsSourceDirs bi
  289. sources <- sequence
  290. [ let file = ModuleName.toFilePath module_
  291. in findFileWithExtension suffixes searchDirs file
  292. >>= maybe (notFound module_) return
  293. | module_ <- modules ++ otherModules bi ]
  294. bootFiles <- sequence
  295. [ let file = ModuleName.toFilePath module_
  296. fileExts = ["hs-boot", "lhs-boot"]
  297. in findFileWithExtension fileExts (hsSourceDirs bi) file
  298. | module_ <- modules ++ otherModules bi ]
  299. let allSources = sources ++ catMaybes bootFiles ++ cSources bi
  300. installOrdinaryFiles verbosity inPref (zip (repeat []) allSources)
  301. where suffixes = ppSuffixes pps ++ ["hs", "lhs"]
  302. notFound m = die $ "Error: Could not find module: " ++ display m
  303. ++ " with any suffix: " ++ show suffixes
  304. copyFileTo :: Verbosity -> FilePath -> FilePath -> IO ()
  305. copyFileTo verbosity dir file = do
  306. let targetFile = dir </> file
  307. createDirectoryIfMissingVerbose verbosity True (takeDirectory targetFile)
  308. installOrdinaryFile verbosity file targetFile
  309. printPackageProblems :: Verbosity -> PackageDescription -> IO ()
  310. printPackageProblems verbosity pkg_descr = do
  311. ioChecks <- checkPackageFiles pkg_descr "."
  312. let pureChecks = checkConfiguredPackage pkg_descr
  313. isDistError (PackageDistSuspicious _) = False
  314. isDistError _ = True
  315. (errors, warnings) = partition isDistError (pureChecks ++ ioChecks)
  316. unless (null errors) $
  317. notice verbosity $ "Distribution quality errors:\n"
  318. ++ unlines (map explanation errors)
  319. unless (null warnings) $
  320. notice verbosity $ "Distribution quality warnings:\n"
  321. ++ unlines (map explanation warnings)
  322. unless (null errors) $
  323. notice verbosity
  324. "Note: the public hackage server would reject this package."
  325. ------------------------------------------------------------
  326. -- | The name of the tarball without extension
  327. --
  328. tarBallName :: PackageDescription -> String
  329. tarBallName = display . packageId
  330. mapAllBuildInfo :: (BuildInfo -> BuildInfo)
  331. -> (PackageDescription -> PackageDescription)
  332. mapAllBuildInfo f pkg = pkg {
  333. library = fmap mapLibBi (library pkg),
  334. executables = fmap mapExeBi (executables pkg)
  335. }
  336. where
  337. mapLibBi lib = lib { libBuildInfo = f (libBuildInfo lib) }
  338. mapExeBi exe = exe { buildInfo = f (buildInfo exe) }