packages feed

ddc-war-0.2.1.1: DDC/War/Task/Nightly.hs

{-# OPTIONS -fno-warn-unused-matches #-}
-- | Run the nightly DDC build.
module DDC.War.Task.Nightly
        ( Spec   (..)
        , Result (..)
        , build)
where
import BuildBox.Command.Mail
import BuildBox.Command.System
import BuildBox.Command.File
import BuildBox.Command.Environment
import BuildBox.Control.Cron
import BuildBox.Pretty
import BuildBox.Build
import BuildBox.Time
import BuildBox                 as BuildBox
import Control.Monad
import Data.Char

import System.FilePath.Posix    as Remote
import System.FilePath          as Local


-- Spec -----------------------------------------------------------------------
data Spec
        = Spec
        {  -- | Use this scratch directory to perform the build.
          specLocalBuildDir     :: Maybe FilePath 

          -- | URL of DDC snapshot.tgz
        , specRemoteSnapshotURL :: String

          -- | URL of DDC repository, used to update the snapshot.
        , specRemoteRepoURL     :: String 

          -- | Number of threads to use when building.
        , specBuildThreads      :: Int

          -- | Cleanup after build
        , specCleanup           :: Bool

          -- | If set build continuously
        , specContinuous        :: Maybe When

          -- | If we're doing a continuous build, also build once on startup
        , specNow               :: Bool


          -- | Mailer to use to send build results,
          --   or Nothing if to not send mail.
        , specMailer            :: Maybe Mailer

          -- | Send mail from this address.
        , specMailFrom          :: Maybe String 

          -- | Send mail to this address.
        , specMailTo            :: Maybe String 


          -- | User and host name to copy logs to eg 'overlord@deluge.ouroborus.net'
        , specLogUserHost       :: Maybe String

          -- | Copy logs into this directory on the server
        , specLogRemoteDir      :: Maybe String

          -- | HTTP address of where the above logs appear
        , specLogRemoteURL      :: Maybe String }


        deriving Show


-- Result ---------------------------------------------------------------------
data Result
        = ResultSuccess
        | ResultFailure


instance Pretty Result where
 ppr result
  = case result of
        ResultSuccess   -> text "success"
        ResultFailure   -> text "failure"


-- Build ----------------------------------------------------------------------
-- | Run the nightly DDC build.
build :: Spec -> Build Result
build spec
 = case specContinuous spec of
        -- Do a one-shot build.
        Nothing         
         -> buildCatch spec

        -- Continuous build
        Just whence 
         -> do  outLn $ "* Starting cron loop"
                cronLoop 
                 $  makeSchedule 
                        [ ("nightly"
                          , whence
                          , if specNow spec 
                                then Just Immediate 
                                else Nothing
                          , buildCatch spec >> return ()) ]

                -- The cron loop will quit if the build died and we couldn't
                -- send a failure message. If that happens then give up completely.
                return ResultFailure


buildCatch :: Spec -> Build Result
buildCatch spec
 = BuildBox.catch 
        (buildProject spec) 
        (\err -> do
                postFailure spec err
                return  ResultFailure)


buildProject :: Spec -> Build Result
buildProject spec
 = do   
        strTime         <- io $ getStampyTime
        let Just localBuildDir = specLocalBuildDir spec 
        let buildDir           = localBuildDir Local.</> strTime

        let urlSnapshot  = specRemoteSnapshotURL spec
        let urlRepo      = specRemoteRepoURL     spec
        let buildThreads = specBuildThreads      spec

        ensureDir buildDir
        inDir     buildDir
         $ do 
                outLn "* Creating log directory"
                ensureDir "log"
                
                outLn "* Downloading snapshot"
                (getOut, getErr) <- ssystem $ "wget --progress=bar " ++ urlSnapshot
                io $ writeFile "log/01-wget.stdout" getOut
                io $ writeFile "log/01-wget.stderr" getErr

                needs (takeFileName urlSnapshot)
                outLn "* Unpacking snapshot"
                ssystem $ "tar zxf " ++ takeFileName urlSnapshot

                inDir "ddc-head"
                 $ do
                        outLn "* Updating shapshot"
                        (darcsOut, darcsErr) <- ssystem $ "darcs pull -av " ++ urlRepo
                        io $ writeFile "../log/02-darcs.stdout" darcsOut
                        io $ writeFile "../log/02-darcs.stderr" darcsErr

                        outLn "* Writing build config"
                        needs "make"
                        io $ writeFile "make/config-override.mk" 
                           $ unlines ["THREADS = " ++ show buildThreads]

                        outLn "* Building project"
                        needs "Makefile"
                        (makeOut, makeErr) <- ssystem $ "make nightly"
                        io $ writeFile "../log/03-make.stdout" makeOut
                        io $ writeFile "../log/03-make.stderr" makeErr

                        outLn "* Copying results"
                        needs "war.results"
                        needs "war.failed"
                        ssystem "cp war.results ../log"
                        ssystem "cp war.failed  ../log"

                        return ()

                -- Copy logs to remote host.
                copyLogs spec strTime

                -- Send mail reporting build success including failed tests.
                strFailed       <- io $ readFile "log/war.failed"
                postSuccess spec strTime strFailed

        when (specCleanup spec)
         $ do   outLn "* Cleaning up"
                clobberDir buildDir

        return ResultSuccess


-- | Copy logs to the remote host
copyLogs :: Spec -> String -> Build ()
copyLogs spec strTime
 | Just userHost <- specLogUserHost  spec
 , Just logDir   <- specLogRemoteDir spec
 = do   
        outLn $ "* Copying logs to " ++ userHost

        let dir = logDir Remote.</> strTime

        -- Make the destination directory.
        sesystemq $ "ssh " ++ userHost ++ " mkdir -p " ++ dir

        -- Copy the files.
        sesystemq $ "scp -r log/* " ++ userHost ++ ":" ++ dir

        return ()

 | otherwise    = return ()


-- | Send mail reporting build success.
postSuccess :: Spec -> String -> String -> Build ()
postSuccess spec strTime strFailed
 | Just mailer          <- specMailer   spec
 , Just addrFrom        <- specMailFrom spec
 , Just addrTo          <- specMailTo   spec
 = do   
        outLn "* Reporting build success"

        -- The overall build succeeded, but some tests might have failed.
        let nFailed     =  length $ lines strFailed

        -- Create message subject.
        hostId          <- getHostId
        let subject     
                | nFailed == 0  
                = "DDC Build Success (" ++ hostId ++ ")"

                | otherwise     
                = "DDC Build Success (" ++ hostId ++ ") with " ++ show nFailed ++ " failed tests"

        -- Create message body.
        let strLog      
                = case specLogRemoteURL spec of
                   Nothing  -> ""
                   Just url -> "Logs are at: " ++ url ++ "/" ++ strTime ++ "\n\n"

        let body        = strLog ++ strFailed

        -- Send the mail
        mail    <- createMailWithCurrentTime addrFrom addrTo subject body

        sendMailWithMailer mail mailer

-- don't send mail.
postSuccess _ _ _       = return ()


-- | Send mail reporting build failure.
postFailure :: Spec -> BuildError -> Build ()
postFailure spec err
 | Just mailer          <- specMailer   spec
 , Just addrFrom        <- specMailFrom spec
 , Just addrTo          <- specMailTo   spec
 = do   
        outLn "* Reporting build failure"

        -- Create message subject.
        hostId          <- getHostId
        let subject     =  "DDC Build FAILURE (" ++ hostId ++ ")"

        -- Create message body.
        let body        = render $ ppr err

        -- Send the mail
        mail    <- createMailWithCurrentTime addrFrom addrTo subject body

        sendMailWithMailer mail mailer

-- don't send mail
postFailure spec strFailed = return ()


-- | Build a string identifying this host
getHostId :: Build String
getHostId
 = do
        hostName        <- liftM (takeWhile (not . (==) '.')) $ getHostName
        machName        <- liftM (takeWhile (not . isSpace))  $ sesystemq "uname -m"
        osName          <- liftM (takeWhile (not . isSpace))  $ sesystemq "uname"
        return $ hostName ++ "." ++ machName ++ "." ++ osName