fbrnch-1.6: src/Package.hs
{-# LANGUAGE CPP #-}
module Package (
clonePkg,
CloneUser(..),
fedpkg,
fedpkg_,
checkForSpecFile,
getChangelog,
cleanChangelog,
changeLogPrompt,
releaseSystemBranch,
getPackageName,
getSummaryURL,
findSpecfile,
maybeFindSpecfile,
localBranchSpecFile,
putPkgHdr,
putPkgBrnchHdr,
putPkgAnyBrnchHdr,
withExistingDirectory,
initialPkgRepo,
withPackagesByBranches,
withPackagesBranch,
withPackagesMaybeBranch,
withPackagesMaybeBranchNoHeadergit,
HeaderShow(..),
boolHeader,
LimitBranches(..),
cleanGit,
cleanGitActive,
cleanGitFetch,
cleanGitFetchActive,
dirtyGit,
dirtyGitActive,
dirtyGitFetch,
dirtyGitHEAD,
stashGitFetch,
Package(..),
packageSpec,
pkgNameVerRel,
pkgNameVerRel',
pkgNameVerRelNodist,
pkgNameVerRelDist,
equivNVR,
editSpecField,
isAutoChangelog,
isAutoRelease,
autoReleaseBump,
sourceFieldFile,
isArchiveFile,
sourceDirCwdOpt
) where
import Data.RPM (NV(..), VerRel(..))
import Data.RPM.NVR (maybeNVR, NVR(..))
import Distribution.Fedora.Branch (branchDistTag)
import Safe (tailSafe)
import SimpleCmd.Rpm
import SimplePrompt (prompt, promptInitial)
import Branches
import Common
import Common.System
import Git
import Types (ChangeType(..))
fedpkg :: String -> [String] -> IO String
fedpkg c args =
cmd "fedpkg" (c:args)
fedpkg_ :: String -> [String] -> IO ()
fedpkg_ c args =
cmd_ "fedpkg" (c:args)
checkForSpecFile :: String -> IO ()
checkForSpecFile spec = do
have <- doesFileExist spec
unless have $ error' $ spec +-+ "not found"
-- FIXME allow editor to be used
changeLogPrompt :: ChangeType -> FilePath -> IO String
changeLogPrompt change spec = do
clog <- cleanChangelog (change /= ChangeReview) spec
putNewLn
putStrLn "```"
putStrLn clog
putStrLn "```"
-- FIXME is this actually useful?
tty <- isTty
if not tty
then return clog
else do
-- FIXME ask what to do: edit/skip/append?
userlog <- promptInitial
("Describe" +-+
case change of
ChangeBodhi -> "update"
ChangeCommit -> "commit"
ChangeReview -> "change"
+-+ "summary now"
++ (if change == ChangeBodhi then "; or 'no' to skip update" else "")
++ ":\n")
clog
return $ if null userlog then clog else userlog
getChangelog :: FilePath -> IO [String]
getChangelog spec = do
autochangelog <- isAutoChangelog spec
if autochangelog
then takeWhile (not . null) . drop 1 <$>
cmdLines "rpmautospec" ["generate-changelog", spec]
else cmdLines "rpmspec" ["-q", "--srpm", "--qf", "%{changelogtext}", spec]
cleanChangelog :: Bool -> FilePath -> IO String
cleanChangelog nobullet spec = do
ls <- getChangelog spec
return $
case filter ("- " `isPrefixOf`) ls of
[l] | nobullet -> removePrefix "- " l
_ -> intercalate "\n" ls
getSummaryURL :: FilePath -> IO String
getSummaryURL spec = do
notes <- cmd "rpmspec" ["-q", "--srpm", "--qf", "%{summary}\n\n- %{url}", spec]
putNewLn
putStrLn "```"
putStrLn notes
putStrLn "```"
tty <- isTty
if tty
then do
usernote <- prompt "Press Enter to use above or input notes"
return $ if null usernote then notes else usernote
else return notes
-- FIXME check spec filename/%name more carefully
getPackageName :: FilePath -> IO Package
getPackageName pkgdir =
if pkgdir == "."
then Package <$> getDirectoryName
else return $ Package $ takeFileName pkgdir
findSpecfile :: IO FilePath
findSpecfile = do
mspec <- maybeFindSpecfile
case mspec of
Just spec -> return spec
Nothing -> do
dir <- getDirectoryName
error' $ "No spec file found in:" +-+ dir
-- adapted from fileWithExtension
maybeFindSpecfile :: IO (Maybe FilePath)
maybeFindSpecfile = do
files <- filter (".spec" `isExtensionOf`) <$> listDirectory "."
case files of
[] -> return Nothing
[spec] -> return $ Just spec
_ -> error' $ "More than one .spec file:" +-+ unwords files
localBranchSpecFile :: Package -> AnyBranch -> IO FilePath
localBranchSpecFile pkg br = do
gitdir <- isPkgGitRepo
if gitdir
then do
gitSwitchBranch br
let spec = packageSpec pkg
exists <- doesFileExist spec
if exists
then return spec
else do
mspec <- maybeFindSpecfile
case mspec of
Just spc -> do
putStrLn $ "Warning: directory name differs from" +-+ spc ++ "\n"
return spc
Nothing -> error' $ "No spec file for:" +-+ unPackage pkg
else findSpecfile
withExistingDirectory :: FilePath -> IO a -> IO a
withExistingDirectory dir act = do
exists <- doesDirectoryExist dir
if exists
then withCurrentDirectory dir act
else error' $ "No such directory:" +-+ dir
-- newly created Fedora repos/branches have just one README commit
initialPkgRepo :: IO Bool
initialPkgRepo = do
commits <- length <$> gitShortLogN (Just 2) Nothing
return $ commits <= 1
newtype Package = Package {unPackage :: String}
deriving Eq
putPkgHdr :: Package -> IO ()
putPkgHdr pkg =
putStrLn $ "\n=" +-+ unPackage pkg +-+ "="
putPkgBrnchHdr :: Package -> Branch -> IO ()
putPkgBrnchHdr pkg br =
putStrLn $ "\n==" +-+ unPackage pkg +-+ showBranch br +-+ "=="
putPkgAnyBrnchHdr :: Package -> AnyBranch -> IO ()
putPkgAnyBrnchHdr pkg br =
putStrLn $ "\n==" +-+ unPackage pkg +-+ show br +-+ "=="
packageSpec :: Package -> FilePath
packageSpec pkg = unPackage pkg <.> "spec"
-- FIXME rebase
data GitOpts =
GitOpts
{ gitOptClean :: Bool
, gitOptFetch :: Bool
, gitOptActive :: Bool
, gitOptHEAD :: Bool -- allow detached head/rebase state
, gitOptStash :: Bool
}
cleanGit, cleanGitActive, cleanGitFetch, cleanGitFetchActive, dirtyGit, dirtyGitActive, dirtyGitFetch, dirtyGitHEAD, stashGitFetch :: Maybe GitOpts
-- clean fetch active HEAD stash
cleanGit = Just $ GitOpts True False False False False
cleanGitActive = Just $ GitOpts True False True False False
cleanGitFetch = Just $ GitOpts True True False False False
cleanGitFetchActive = Just $ GitOpts True True True False False
dirtyGit = Just $ GitOpts False False False False False
dirtyGitActive = Just $ GitOpts False False True False False
dirtyGitFetch = Just $ GitOpts False True False False False
dirtyGitHEAD = Just $ GitOpts False False False True False
stashGitFetch = Just $ GitOpts True True False False True
data LimitBranches = AnyNumber | Zero | ZeroOrOne | ExactlyOne
deriving Eq
data HeaderShow = HeaderNone | HeaderMay | HeaderMust
deriving Eq
boolHeader :: Bool -> HeaderShow
boolHeader b = if b then HeaderMust else HeaderMay
-- FIXME option to filter out dead.packages
-- FIXME option to pause between packages?
withPackagesByBranches :: HeaderShow
-> Bool
-> Maybe GitOpts
-> LimitBranches
-> (Package -> AnyBranch -> IO ())
-> (BranchesReq,[String])
-> IO ()
withPackagesByBranches header count mgitopts limitBranches action (breq,pkgs) =
if null pkgs
then withPackageDir (0, ".")
else do
let numpkgs = length pkgs
when (numpkgs > 1 && breq == Branches []) $
case limitBranches of
Zero -> return ()
ZeroOrOne -> warning "Better to specify an explicit branch for multiple packages"
_ -> error' "At least one branch must be specified when there are multiple packages"
mapM_ withPackageDir $ zip [numpkgs,(numpkgs-1)..1] pkgs
where
-- FIXME support arbitrary (module) branches
withPackageDir :: (Int,FilePath) -> IO ()
withPackageDir (n, path) = do
let dir =
if ".spec" `isExtensionOf` path
then takeDirectory path
else path
withExistingDirectory dir $ do
mspec <- if ".spec" `isExtensionOf` path
then return $ Just $ takeFileName path
else maybeFindSpecfile
pkg <- Package <$>
case mspec of
-- FIXME fails if spec file can't be parsed and also is *slow*
-- cmd "rpmspec" ["-q", "--srpm", "--qf", "%{name}", spec]
-- For now assume spec filename = package name
Just spec -> return $ takeBaseName spec
Nothing -> getDirectoryName
when (count && length pkgs >= 2) $ do
putNewLn
putStrLn $ plural n "package" +-+ "left"
unless (isNothing mspec || mspec == Just (unPackage pkg <.> "spec")) $
putStrLn $ "Warning: package name (" ++ unPackage pkg ++ ") differs from spec filename!"
haveGit <- isPkgGitRepo
when (isJust mgitopts && not haveGit) $
error' $ "Not a pkg git dir:" +-+ unPackage pkg
mcurrentbranch <-
if haveGit
then
if have gitOptHEAD
then do
cur <- gitCurrentBranch'
if cur == OtherBranch "HEAD"
then warning "detached HEAD" >> return Nothing
else return $ Just cur
else Just <$> gitCurrentBranch
else return Nothing
let fetch = have gitOptFetch
-- quiet to avoid output before header
when fetch $ gitFetchSilent True
brs <- listOfAnyBranches (haveGit && not (have gitOptHEAD)) (have gitOptActive) breq
case limitBranches of
ZeroOrOne | length brs > 1 ->
-- FIXME: could be handled better (testcase: run long list of packages in wrong directory)
error' $ "more than one branch given:" +-+ unwords (map show brs)
ExactlyOne | null brs ->
error' "please specify one branch"
ExactlyOne | length brs > 1 ->
error' "please only specify one branch"
_ -> return ()
when ((header /= HeaderNone || fetch) && dir /= ".") $
case brs of
[br] -> when (fetch || header == HeaderMust) $ putPkgAnyBrnchHdr pkg br
_ -> when (fetch || header /= HeaderNone) $ putPkgHdr pkg
when haveGit $
when (have gitOptClean) $ checkWorkingDirClean (have gitOptStash)
-- FIXME!! no branch restriction
when (breq `elem` map BranchOpt [AllBranches,AllFedora,AllEPEL]) $
putStrLn $ "Branches:" +-+ unwords (map show brs) ++ "\n"
-- FIXME add newline at end?
let action' p b = do
when (header /= HeaderNone && length brs > 1) $ putPkgAnyBrnchHdr p b
action p b
mapM_ (action' pkg) brs
when (length brs /= 1) $
whenJust mcurrentbranch gitSwitchBranch
have :: (GitOpts -> Bool) -> Bool
have opt = maybe False opt mgitopts
withPackagesBranch :: HeaderShow
-> Bool
-> Maybe GitOpts
-> (Package -> AnyBranch -> IO ())
-> (Branch,[String])
-> IO ()
withPackagesBranch header count mgitopts action (br, pkgs) =
withPackagesByBranches header count mgitopts ExactlyOne action (Branches [br],pkgs)
withPackagesMaybeBranch :: HeaderShow
-> Bool
-> Maybe GitOpts
-> (Package -> AnyBranch -> IO ())
-> (Maybe Branch,[String])
-> IO ()
withPackagesMaybeBranch header count mgitopts action (mbr, pkgs) =
withPackagesByBranches header count mgitopts ZeroOrOne action (Branches (maybeToList mbr),pkgs)
withPackagesMaybeBranchNoHeadergit :: (Package -> AnyBranch -> IO ())
-> (Maybe Branch,[String])
-> IO ()
withPackagesMaybeBranchNoHeadergit =
withPackagesMaybeBranch HeaderNone False Nothing
data CloneUser = AnonClone | UserClone
clonePkg :: Bool -> CloneUser -> Maybe Branch -> String -> IO ()
clonePkg quiet cloneuser mbr pkg = do
exists <- doesDirectoryExist pkg
if exists
then putStrLn $ pkg ++ "/ already exists"
else do
let mbranch = case mbr of
Nothing -> []
Just br -> ["--branch", showBranch br]
case cloneuser of
AnonClone -> do
msgout
git_ "clone" $ ["--quiet"] ++ mbranch ++ ["https://src.fedoraproject.org/rpms/" ++ pkg <.> "git"]
UserClone -> do
fedpkg_ "clone" $ mbranch ++ [pkg]
where
msgout =
putStrLn $ if quiet then "cloning..." else "Cloning:" +-+ pkg
isAutoRelease :: FilePath -> IO Bool
isAutoRelease spec = do
matches <- filter ("Release:" `isPrefixOf`) <$> grep "%autorelease" spec
return $ not (null matches)
autoReleaseBump :: FilePath -> IO Bool
autoReleaseBump spec = do
matches <- filter ("Release:" `isPrefixOf`) <$> grep "%autorelease" spec
return $
case matches of
[] -> False
[release] ->
case words release of
("Release:":"%autorelease":"-b":_) -> True
_ -> False
_ -> error' $ "multiple autorelease fields in" +-+ spec
isAutoChangelog :: FilePath -> IO Bool
isAutoChangelog = grep_ "^%autochangelog"
pkgNameVerRel :: Branch -> FilePath -> IO (Maybe NVR)
pkgNameVerRel = pkgNameVerRelDist True . Just
pkgNameVerRelDist :: Bool -> Maybe Branch -> FilePath -> IO (Maybe NVR)
pkgNameVerRelDist _strict Nothing spec = do
nvrs <- rpmspec ["--srpm", "--undefine=dist"] (Just "%{name}-%{version}-%{release}") spec
return $
case nvrs of
[] -> Nothing
[nvr] -> maybeNVR nvr
_ -> error' "could not determine unique nvr"
pkgNameVerRelDist strict (Just br) spec = do
disttag <- branchDistTag br
-- workaround dist with bootstrap
hostdist <- cmd "rpm" ["--eval", "%{dist}"]
-- FIXME more precise regexp with "Release:"
autorelease <- isAutoRelease spec
nvrs <- do
if autorelease
then do
pkggit <- isPkgGitRepo
if pkggit
then do
mfedpkg <- findExecutable "fedpkg"
when (isNothing mfedpkg) $
error' "requires fedpkg.."
cmdLines "fedpkg" ["verrel"]
else
if strict
then error' "cannot determine NVR accurately for autorelease outside dist-git"
else return []
else rpmspec ["--srpm"] (Just "%{name}-%{version}-%{release}") spec
seq disttag $
return $
case nvrs of
[] -> Nothing
[nvr] -> maybeNVR (replace hostdist disttag nvr)
_ -> error' "could not determine unique nvr"
pkgNameVerRel' :: Branch -> FilePath -> IO NVR
pkgNameVerRel' br spec = do
mnvr <- pkgNameVerRel br spec
case mnvr of
Nothing -> error' $ "rpmspec failed to parse" +-+ spec
Just nvr -> return nvr
pkgNameVerRelNodist :: FilePath -> IO NVR
pkgNameVerRelNodist spec = do
mnvr <- pkgNameVerRelDist True Nothing spec
case mnvr of
Nothing -> error' $ "rpmspec failed to parse" +-+ spec
Just nvr -> return nvr
releaseSystemBranch :: AnyBranch -> IO Branch
releaseSystemBranch (RelBranch br) = return br
releaseSystemBranch (OtherBranch _) = systemBranch
-- FIXME should be more strict about dist tag (eg .fcNN only)
equivNVR :: NVR -> Maybe NVR -> Bool
equivNVR _ Nothing = False
equivNVR nvr1 (Just nvr2) =
nvr1 == nvr2
||
let (nv1,r1) = splitRelease nvr1
(nv2,r2) = splitRelease nvr2
in
nv1 == nv2
&&
compareReleases r1 r2
where
compareReleases :: String -> String -> Bool
compareReleases r1 r2 =
r1 == r2
||
case ('.' `elem` r1, '.' `elem` r2) of
(True,True) ->
let (r1',drs1) = span (/= '.') r1
(r2',drs2) = span (/= '.') r2
in
case (take 2 r1', take 2 r2') of
("fc","fc") -> True
_ -> r1' == r2' && length drs1 == length drs2
&&
compareReleases (tailSafe drs1) (tailSafe drs2)
_ -> False
-- data BrPkg = IsBr AnyBranch | Unknown String | IsPkg String
-- deriving Show
-- splitBranchesPkgs :: Bool -> Maybe BranchOpts -> Bool -> [String]
-- -> IO ([AnyBranch], [String])
-- splitBranchesPkgs release mbrnchopts exists args = do
-- pkggit <- isPkgGitRepo
-- brPkgs <- mapM (toBrPkg pkggit) args
-- let (brs,pkgs) = brPkgsToBranchesPkgs brPkgs
-- return $ case mbrnchopts of
-- Just _ | brs /= [] -> error' "cannot specify branches with branch options"
-- _ -> (brs,pkgs)
-- where
-- toBrPkg :: Bool -> String -> IO BrPkg
-- toBrPkg gitdir str =
-- case anyBranch str of
-- rbr@(RelBranch _) -> return (IsBr rbr)
-- abr@(OtherBranch p) -> if release then return (IsPkg p)
-- else
-- ifM (isPath str)
-- (return $ IsPkg str) $
-- if gitdir
-- then return (IsBr abr)
-- else return $ if exists
-- then IsBr abr
-- else IsPkg str
-- where
-- isPath :: FilePath -> IO Bool
-- isPath fp =
-- if ".spec" `isExtensionOf` fp
-- then do
-- exists' <- doesFileExist fp
-- unless exists' $ error' $ fp +-+ "file not found"
-- return True
-- else do
-- exists' <- doesDirectoryExist fp
-- let ispath = '/' `elem` fp
-- when (not exists' && ispath) $
-- error' $ fp +-+ "directory not found"
-- return exists'
-- brPkgsToBranchesPkgs :: [BrPkg] -> ([AnyBranch], [String])
-- brPkgsToBranchesPkgs brpkgs =
-- let (pbrs,ppkgs) = span isBranch brpkgs
-- in (map toBranch pbrs, map toPackage ppkgs)
-- where
-- isBranch :: BrPkg -> Bool
-- isBranch (IsBr _) = True
-- isBranch (Unknown _) = True
-- isBranch (IsPkg _) = False
-- toBranch :: BrPkg -> AnyBranch
-- toBranch (IsBr br) = br
-- toBranch (Unknown br) = OtherBranch br
-- toBranch (IsPkg p) = error' $ "can't map package to branch:" +-+ p
-- toPackage :: BrPkg -> String
-- toPackage (IsPkg p) = p
-- toPackage (Unknown p) = p
-- toPackage (IsBr b) = error' $ "can't map branch to package:" +-+ show b
editSpecField :: String -> String -> FilePath -> IO ()
editSpecField field new spec =
cmd_ "sed" ["-i", "-e s/^\\(" ++ field ++ ":\\s\\+\\).*/\\1" ++ new ++ "/", spec]
#if !MIN_VERSION_rpm_nvr(0,1,3)
splitRelease :: NVR -> (NV,String)
splitRelease (NVR n (VerRel v r)) = (NV n v, r)
#endif
sourceFieldFile :: String -> FilePath
sourceFieldFile field =
if null field then
-- should be impossible
error "empty source field!"
else (takeFileName . last . words) field
isArchiveFile :: FilePath -> Bool
isArchiveFile f =
any ($ f) [(".tar." `isInfixOf`),
(".zip" `isSuffixOf`),
(".gpg" `isSuffixOf`),
(".tgz" `isSuffixOf`)]
sourceDirCwdOpt :: IO [String]
sourceDirCwdOpt = do
cwd <- getCurrentDirectory
return ["--define", "_sourcedir" +-+ cwd]