packages feed

fbrnch-1.1.2: src/Cmd/Copr.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}

module Cmd.Copr (
  BuildBy(..),
  coprCmd
  )
where

import Branches
import Common
import Common.System
import qualified Common.Text as T
import Package
import Types (Archs(..))

import Data.Ini.Config
import Network.HTTP.Query (lookupKey, lookupKey')
import System.Environment.XDG.BaseDir (getUserConfigDir)
import System.Time.Extra (sleep)
import Web.Fedora.Copr (coprChroots, fedoraCopr)
import Web.Fedora.Copr.API (coprGetBuild)

data BuildBy = SingleBuild | ValidateByRelease | ValidateByArch | BuildByRelease
  deriving (Eq)

coprServer :: String
coprServer = "copr.fedorainfracloud.org"

-- FIXME handle fedora-eln
-- FIXME make project optional (if same as pkg??) or configurable ?
-- FIXME repo config with a setup command?
-- FIXME interact with copr dist-git
-- FIXME parallel copr builds
-- FIXME --exclude-arch
-- FIXME skip existing builds
-- FIXME distless srpm
-- FIXME time builds?
coprCmd :: Bool -> Bool -> BuildBy -> Maybe Archs -> String
        -> (BranchesReq,[String]) -> IO ()
coprCmd dryrun listchroots buildBy marchs project (breq, pkgs) = do
  chroots <- coprGetChroots
  if listchroots
    then mapM_ putStrLn chroots
    else
    if null pkgs then
      getPackageName "." >>= coprBuildPkg chroots
    else
      mapM_ (\ p -> withExistingDirectory p $ coprBuildPkg chroots (Package p)) pkgs
  where
    coprGetChroots = do
      username <- getUsername
      chroots <- map T.unpack <$> coprChroots coprServer username project
      when (null chroots) $
        error' $ "No chroots found for " ++ username ++ "/" ++ project
      branches <-
        case breq of
          Branches brs ->
            if null brs
            then return $ (map (releaseBranch . T.pack) . nub . map removeArch) chroots
            else listOfBranches False False breq
          BranchOpt AllFedora -> filter isFedoraBranch <$> getFedoraBranches
          BranchOpt AllEPEL -> filter isEPELBranch <$> getFedoraBranched
          _ -> listOfBranches False False breq
      let buildroots =
            reverseSort $
            case marchs of
              Nothing ->
                [chroot | chroot <- chroots, removeArch chroot `elem` map branchRelease branches]
              Just (Archs archs) ->
                [chroot | arch <- archs, br <- branches, let chroot = branchRelease br ++ "-" ++ arch, chroot `elem` chroots]
              Just (ExcludedArchs exarchs) ->
                [chroot | chroot <- chroots, takeArch chroot `notElem` exarchs, removeArch chroot `elem` map branchRelease branches]
      if null buildroots
        then error' "No valid chroots"
        else return buildroots

    coprBuildPkg buildroots pkg = do
      -- FIXME check is pkg.spec
      spec <- localBranchSpecFile pkg (RelBranch Rawhide)
      -- pkg <- takeFileName <$> getCurrentDirectory
      -- hack to avoid generating srpm for dryrun
      srpm <- if not dryrun
              then generateSrpm Nothing spec -- FIXME: let distopt = ["--undefine", "dist"]
              else return spec
      case buildBy of
        SingleBuild -> coprBuild dryrun project srpm buildroots
        -- FIXME or default to secondary parallel to previous primary
        ValidateByRelease -> do
          let initialChroots =
                let primaryArch = releaseArch $ head buildroots
                in map pure $ filter (isArch primaryArch) buildroots
              remainingChroots = buildroots \\ concat initialChroots
          staggerBuilds srpm initialChroots remainingChroots
        ValidateByArch -> do
          let initialChroots =
                let newestRelease = removeArch $ head buildroots
                in map pure $ filter (newestRelease `isPrefixOf`) buildroots
              remainingChroots = buildroots \\ concat initialChroots
          staggerBuilds srpm initialChroots remainingChroots
        BuildByRelease -> do
          let initialChroots = groupBy sameRelease buildroots
              remainingChroots = buildroots \\ concat initialChroots
          staggerBuilds srpm initialChroots remainingChroots

    removeArch relarch = init $ dropWhileEnd (/= '-') relarch
    takeArch = takeWhileEnd (/= '-')

    staggerBuilds srpm initialChroots remainingChroots = do
      mapM_ (coprBuild dryrun project srpm) initialChroots
      unless (null remainingChroots) $
        coprBuild dryrun project srpm remainingChroots

    releaseArch = takeWhileEnd (/= '-')

    isArch arch release = releaseArch release == arch

    sameRelease r1 r2 = removeArch r1 == removeArch r2

    reverseSort = reverse . sort

branchRelease :: Branch -> String
branchRelease Rawhide = "fedora-rawhide"
branchRelease (Fedora n) = "fedora-" ++ show n
branchRelease (EPEL n) = "epel-" ++ show n
branchRelease (EPELNext n) = error' $ show (EPELNext n) ++ " is unsupported"

--data Chroot = Chroot Release Arch

getUsername :: IO String
getUsername = do
  rc <- getUserConfigDir "copr"
  readIniConfig rc rcParser id
  where
    rcParser :: IniParser String
    rcParser =
      section "copr-cli" $
      fieldOf "username" string

-- changed from Bugzilla.hs
readIniConfig :: FilePath -> IniParser a -> (a -> b) -> IO b
readIniConfig inifile iniparser record = do
  havefile <- doesFileExist inifile
  if not havefile
    then error' $ inifile ++ " not found: try https://" ++ coprServer ++ "/api"
    else do
    ini <- T.readFile inifile
    let config = parseIniFile ini iniparser
    return $ either error' record config

coprBuild :: Bool -> String -> FilePath -> [String] -> IO ()
coprBuild _ _ _ [] = error' "No chroots chosen"
coprBuild dryrun project srpm buildroots = do
  let chrootargs = mconcat [["-r", bldrt] | bldrt <- buildroots]
      buildargs = ["build", "--nowait"] ++ chrootargs ++ [project, srpm]
  putStrLn ""
  putStrLn $ unwords buildroots
  unless dryrun $ do
    output <- cmd "copr" buildargs
    putStrLn output
    let bid = read $ last $ words $ last $ lines output
    ok <- timeIO $ coprWatchBuild bid Nothing
    unless ok $
      error' $ "Failed: copr " ++ unwords buildargs

-- FIXME idea: Maybe Seconds to increment sleep
coprWatchBuild :: Int -> Maybe String -> IO Bool
coprWatchBuild bid mstate = do
  res <- coprGetBuild fedoraCopr bid
  case lookupKey "state" res of
    Just state ->
      if mstate == Just state
      then sleep 20 >> coprWatchBuild bid mstate
      else do
        logMsg $ "Build " ++ show bid ++ " " ++ state
        case state of
          "succeeded" -> return True
          "skipped" -> return True
          "canceled" -> return False
          "failed" -> return False
          _ -> sleep 1 >> coprWatchBuild bid (Just state)
    Nothing -> do
      let err = lookupKey' "error" res
      logMsg $ "Error: " ++ err
      return False

-- #if !MIN_VERSION_simple_cmd(0,1,4)
-- error' :: String -> a
-- #if MIN_VERSION_base(4,9,0)
-- error' = errorWithoutStackTrace
-- #else
-- error' = error
-- #endif
-- #endif