------------------------------------------------------------------------------- -- | -- Module : Happstack.Util.AutoBuild -- Copyright : Happstack.com 2009 -- License : BSD3 -- -- Maintainer : Matthew Elder -- Stability : provisional -- Portability : linux/windows -- ------------------------------------------------------------------------------- module Happstack.Util.AutoBuild ( autoBuild ) where import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar) import Control.Exception (bracket) import System.Directory (getModificationTime) import System.Exit (ExitCode(..), exitFailure) import System.Process import System.Time (ClockTime) import System.IO -- | Functionality for the autoBuild tool. -- Inspired by searchpath. autoBuild :: String -- ^ Build command -> String -- ^ Path to binary -> [String] -- ^ Arguments to use when running binary -> IO () autoBuild buildCmd binPath binArgs = do putStrLn "Performing the initial build. . ." buildSuccess <- buildBin buildCmd -- initial build if buildSuccess then do mph <- newEmptyMVar newMod <- getModificationTime binPath forkIO (builder mph buildCmd binPath newMod) runner mph binPath binArgs else do putStrLn "Initial build failed, see 'build.out.log' and 'build.err.log'." exitFailure -- builds the program builder :: MVar ProcessHandle -> String -> FilePath -> ClockTime -> IO () builder mph buildCmd binPath lastMod = do -- add a delay between build attempts (5 seconds) threadDelay 5000000 buildSuccess <- buildBin buildCmd newMod <- getModificationTime binPath -- if the build yielded a new binary, terminate the process if buildSuccess && (newMod /= lastMod) then do putStrLn "A new binary has been built, killing the existing one. . ." terminateProcess =<< takeMVar mph else return () -- continue loop builder mph buildCmd binPath newMod -- runs the program runner :: MVar ProcessHandle -> FilePath -> [String] -> IO () runner mph binPath binArgs = do bracket (runBin binPath binArgs) (terminateProcess) (\ph -> putMVar mph ph >> waitForProcess ph) -- continue loop runner mph binPath binArgs -- does not block, returns ph runBin :: String -> [String] -> IO ProcessHandle runBin binPath binArgs = do putStrLn $ "Running binary: " ++ (showCmd binPath binArgs) ph <- runProcess binPath binArgs Nothing Nothing Nothing Nothing Nothing return ph where showCmd bp [] = bp showCmd bp ba = bp ++ " " ++ unwords ba -- blocks until built, returns True if build was a success buildBin :: String -> IO Bool buildBin buildCmd = do (_inp,out,err,ph) <- runInteractiveCommand buildCmd appendFile "build.out.log" =<< hGetContents out appendFile "build.err.log" =<< hGetContents err waitForProcess ph exitCode <- getProcessExitCode ph return (exitCode == Just ExitSuccess)