module Releaser.Primitives (
CabalInfo(..)
, cabalRead
, cabalWriteVersion
, cabalBumpVersion
, cabalSdist
, cabalUpload
, gitCheckout
, gitGetTags
, gitTag
, gitCommit
, gitPush
, gitPushTags
, gitAssertEmptyStaging
, prompt
, abort
, logStep
, changelogPrepare
) where
import System.IO
import System.Process
import System.Console.Pretty (Color(..), color)
import System.Environment (lookupEnv)
import System.Exit (ExitCode(..), exitFailure)
import Text.Regex.PCRE
import Data.Functor (void)
import Data.List (intercalate)
import Text.ParserCombinators.ReadP (ReadP, readP_to_S)
import Data.Version (parseVersion)
import Distribution.PackageDescription.Parsec
import Distribution.Verbosity (silent)
import Distribution.Types.PackageId (pkgVersion, pkgName)
import Distribution.Types.PackageDescription (package)
import Distribution.Types.GenericPackageDescription (packageDescription)
import Distribution.PackageDescription.PrettyPrint (writeGenericPackageDescription)
import Distribution.Types.Version (versionNumbers, mkVersion')
import Distribution.Simple.Utils (tryFindPackageDesc)
import Distribution.Types.PackageName (unPackageName)
logStep :: String -> IO ()
logStep str =
putStrLn $ color Green ">> " <> str
prompt :: String -> IO String
prompt str = do
putStr $ color Blue ">> " <> str
hFlush stdout
getLine
abort :: String -> IO a
abort str = do
putStrLnErr $ color Red ">> " <> str
exitFailure
data CabalInfo = CabalInfo
{ name :: String
, version :: String
}
cabalRead :: FilePath -> IO CabalInfo
cabalRead dir = do
logStep $ "Looking for a cabal file in " <> dir
cabalFile <- tryFindPackageDesc dir
genericPackageDescription <- readGenericPackageDescription silent cabalFile
let pkgversion = pkgVersion $ package $ packageDescription genericPackageDescription
pkgname = pkgName $ package $ packageDescription genericPackageDescription
cabalinfo = CabalInfo
{ version = intercalate "." $ show <$> versionNumbers pkgversion
, name = unPackageName pkgname
}
logStep $ "Found " <> name cabalinfo <> "-" <> version cabalinfo
return cabalinfo
cabalWriteVersion :: FilePath -> String -> IO ()
cabalWriteVersion dir versionStr = do
cabalFile <- tryFindPackageDesc dir
genericPackageDescription <- readGenericPackageDescription silent cabalFile
version <- case parseMaybe parseVersion versionStr of
Nothing -> abort "parsing the cabal version failed"
Just ver -> return ver
let
pd = packageDescription genericPackageDescription
p = package $ packageDescription genericPackageDescription
gpd = genericPackageDescription { packageDescription = pd { package = p { pkgVersion = mkVersion' version } } }
writeGenericPackageDescription cabalFile gpd
logStep $ "Bumped " <> unPackageName (pkgName p) <> " to " <> versionStr
where
parseMaybe :: ReadP a -> String -> Maybe a
parseMaybe parser input =
case readP_to_S parser input of
[] -> Nothing
xs -> Just $ fst (last xs)
validCabalVersion :: String -> Bool
validCabalVersion version =
version =~ "^[0-9]+([.][0-9]+)*$"
putStrLnErr :: String -> IO ()
putStrLnErr = hPutStrLn stderr
cabalBumpVersion :: FilePath -> IO String
cabalBumpVersion dir = do
cabalinfo <- cabalRead dir
version <- prompt $ "Bump cabal version from " <> version cabalinfo <> " to: "
if validCabalVersion version
then do
cabalWriteVersion dir version
return version
else do
putStrLnErr "Cabal version does not match /^[0-9]+([.][0-9]+)*$/. Try again."
cabalBumpVersion dir
cabalSdist :: FilePath -> IO FilePath
cabalSdist dir = do
logStep "Running $ cabal dist"
cabalinfo <- cabalRead dir
void $ readProcess "cabal" ["sdist"] mempty
let sdistTarball = "dist/" <> name cabalinfo <> "-" <> version cabalinfo <> ".tar.gz"
logStep $ "Created " <> sdistTarball
return sdistTarball
cabalUpload :: FilePath -> IO ()
cabalUpload sdistTarball = do
logStep "Running $ cabal upload"
interactiveProcess (proc "cabal" ["upload", "--publish", sdistTarball]) (return ()) $ \_ -> do
cabalUpload sdistTarball
gitGetTags :: IO [String]
gitGetTags = do
lines <$> readProcess "git" ["tag"] mempty
gitCheckout :: String -> IO ()
gitCheckout tag = do
logStep $ "Running $ git checkout -b " <> tag
void $ readProcess "git" ["checkout", "-b", tag] mempty
gitTag :: String -> IO ()
gitTag tag = do
logStep $ "Running $ git tag --annotate --sign " <> tag
tags <- gitGetTags
if elem tag tags
then abort "git tag already exists, please delete it and start over"
else interactiveProcess (proc "git" ["tag", "--annotate", "--sign", tag]) (return ()) $ \i-> do
gitTag tag
gitCommit :: String -> IO ()
gitCommit message = do
logStep $ "Running $ git commit "
void $ readProcess "git" ["commit", "-a", "-m", message] mempty
gitPush :: String -> IO ()
gitPush remote = do
logStep $ "Pushing git to " <> remote
void $ readProcess "git" ["push", remote, "HEAD"] mempty
gitPushTags :: String -> IO ()
gitPushTags remote = do
logStep $ "Pushing git to " <> remote
void $ readProcess "git" ["push", remote, "--tags"] mempty
gitAssertEmptyStaging :: IO ()
gitAssertEmptyStaging = do
logStep "Assserting there are no uncommitted files"
output <- readProcess "git" ["status", "--untracked-files=no", "--porcelain"] mempty
if output == ""
then return ()
else abort "git status is not clean"
changelogPrepare :: IO ()
changelogPrepare = do
logStep "Assserting there are no uncommitted files"
editorEnv <- lookupEnv "EDITOR"
case editorEnv of
Nothing -> abort "please make sure $EDITOR is set"
Just editor -> do
interactiveProcess (proc editor ["CHANGELOG.md"]) (return ()) $ \i -> do
logStep $ editor <> " failed with " <> show i <> ", retrying"
changelogPrepare
interactiveProcess :: CreateProcess -> IO b -> (Int -> IO b) -> IO b
interactiveProcess cmd good bad = do
(_, _, _, ph) <- createProcess cmd
exitcode <- waitForProcess ph
case exitcode of
ExitSuccess -> good
ExitFailure i -> bad i