/release.hs

https://github.com/GunioRobot/control-monad-exception · Haskell · 150 lines · 117 code · 27 blank · 6 comment · 19 complexity · e73ef57a91750099b1f338685467bd08 MD5 · raw file

  1. #!/usr/bin/env runHaskell
  2. {-# LANGUAGE ScopedTypeVariables #-}
  3. import Control.Applicative
  4. import Control.Exception as CE
  5. import Control.Monad
  6. import qualified Data.ByteString as BS
  7. import Data.Version
  8. import Distribution.Package
  9. import Distribution.PackageDescription
  10. import Distribution.PackageDescription.Parse
  11. import Distribution.Verbosity as Verbosity
  12. import System.Cmd
  13. import System.Directory
  14. import System.Environment
  15. import System.Exit
  16. import System.FilePath
  17. descriptors = ["control-monad-exception.cabal"
  18. ,"control-monad-exception-mtl.cabal.pp"
  19. ,"control-monad-exception-monadsfd.cabal.pp"
  20. ,"control-monad-exception-monadstf.cabal.pp"
  21. ]
  22. releaseDir = "release"
  23. data Action = Release | Test | Script deriving Eq
  24. isRelease Release = True
  25. isRelease Script = True
  26. isRelease Test = False
  27. isScript Script = True
  28. isScript _ = False
  29. main = do
  30. args <- getArgs
  31. action <- case args of
  32. [] -> return Script
  33. ["test"] -> return Test
  34. ["really", "release"] -> return Release
  35. _ -> do
  36. putStrLn "USAGE: release [test|really release]"
  37. exitWith ExitSuccess
  38. let real = not . isScript $ action
  39. -- Auxiliary functions
  40. let cmd = case action of
  41. Release-> \cmd -> do {putStrLn cmd; system cmd}
  42. Test -> \cmd -> do {putStrLn cmd; system cmd}
  43. Script -> \cmd -> do {putStrLn cmd; return ExitSuccess}
  44. mv f f' = do
  45. putStrLn ("mv " ++ f ++ " " ++ f')
  46. when real $ renameFile f f'
  47. rm f = do
  48. putStrLn ("rm " ++ f)
  49. when real $ removeFile f
  50. rmDir f = do
  51. putStrLn ("rmDir " ++ f)
  52. when real $ removeDirectory f
  53. copy f f' = do
  54. putStrLn ("cp " ++ f ++ " " ++ f')
  55. when real $ copyFile f f'
  56. createDir d = do
  57. putStrLn ("mkDir " ++ d)
  58. when real $ createDirectoryIfMissing True d
  59. stripExtension f = mv f (dropExtension f)
  60. cpTemp f = go [] where
  61. go tag = do
  62. let f' = f <.> "tmp" ++ if null tag then "" else show (head tag)
  63. exists <- doesFileExist f'
  64. if not exists then copy f f' *> pure f'
  65. else go (inc tag)
  66. inc [] = [1]
  67. inc [x] = [x+1]
  68. saveCurrentCabalFiles = do
  69. currentDescriptors <- filter (\x -> takeExtension x ==".cabal")
  70. <$> (getDirectoryContents =<< getCurrentDirectory)
  71. currentDescriptorsTmp <- mapM cpTemp currentDescriptors
  72. return (mapM stripExtension currentDescriptorsTmp)
  73. -- | build and test
  74. cabal_test d = do
  75. _ <- cmd "cabal clean -v0"
  76. guardOk (d ++ " failed to build correctly") =<< cmd "cabal install -v0"
  77. guardOk (d ++ " failed to test correctly") =<< cmd "cabal test"
  78. -- | package and store for release.
  79. cabal_dist d = when (isRelease action) $ do
  80. guardOk (d ++ " failed to package correctly") =<< cmd ("cabal sdist --builddir=" ++ releaseDir)
  81. -- | Returns an action to upload a package already stored in the release dir to Hackage
  82. cabal_upload d = do
  83. version <- showVersion . pkgVersion . package . packageDescription
  84. <$> readPackageDescription Verbosity.normal d
  85. let packagedFilePath = (dropExtension.dropExtension) d ++ "-" ++ version <.> "tar.gz"
  86. return $
  87. if isRelease action
  88. then toOk (packagedFilePath ++ " failed to upload correctly to Hackage") <$>
  89. cmd ("cabal upload " ++ releaseDir </> packagedFilePath)
  90. <* rm (releaseDir </> packagedFilePath)
  91. else return Ok
  92. -- THE ACTUAL SCRIPT
  93. restore <- saveCurrentCabalFiles
  94. createDir releaseDir
  95. (`finally` restore) . (`CE.catch` \e@SomeException{} -> print e >> putStrLn "aborting") $ do
  96. uploadActions <- forM descriptors $ \d -> do
  97. exists <- doesFileExist d
  98. if exists || isScript action
  99. then case takeExtension d of
  100. ".cabal" -> cabal_upload d <* cabal_test d <* cabal_dist d <* rm d
  101. ".pp" -> let d' = dropExtension d in (`finally` rm d') $
  102. cabal_upload d <* copy d d' <* cabal_test d' <* cabal_dist d'
  103. else pure (pure (Fail d))
  104. unless (isScript action) $ putStrLn "Packages tested succesfully"
  105. when (isRelease action) $ do
  106. done <- sequence uploadActions
  107. unless (isScript action) $ do
  108. putStrLn ("Release of " ++ show (length $ filter isOk done) ++ " packages completed.")
  109. when (not $ all isOk done) $ do
  110. putStrLn $ "Warning: there were " ++ show(length$ filter (not.isOk) done)
  111. ++ " packages which failed to release"
  112. forM_ (filter (not.isOk) done) $ \(Fail msg) -> putStrLn msg
  113. rmDir releaseDir
  114. ignore _ = return ()
  115. guardOk msg (ExitFailure _) = do
  116. putStrLn msg
  117. exitWith (ExitFailure 1)
  118. guardOk _ ExitSuccess = return ()
  119. data Ok = Ok | Fail {msg::String} deriving (Eq,Ord,Show)
  120. toOk _ ExitSuccess = Ok
  121. toOk msg _ = Fail msg
  122. isOk Ok = True
  123. isOk _ = False