module Main (main) where import Autoproc.Configuration (defaultVariables) import Autoproc.Run (autoprocMain) import Autoproc.Rules.Dagit (dagitRules) import Prelude hiding (catch) import System.IO (openFile, IOMode(WriteMode), hClose) import System.Posix.Process (executeFile, forkProcess, createSession, getProcessStatus) import System.Process (runProcess, waitForProcess) import System.Directory (getAppUserDataDirectory, getModificationTime) import Control.Monad.Trans (liftIO, MonadIO) import Control.Exception (bracket, catch) import Control.Monad (when) import System.Exit (ExitCode(..), exitWith) import Control.Applicative ((<$>)) -- | The entry point into autoproc. Attempts to compile @~/.autoproc/autoproc.hs@ -- for autoproc, and if it doesn't find one, just compiles the default. -- This code and method is totally stolen from XMonad. Thanks guys! main :: IO () main = catch (buildLaunch) (\_ -> autoprocMain defaultVariables dagitRules) -- | Build "~/.autoproc/autoproc.hs" with GHC, then execute it. If there are no -- errors, this function does not return. An exception is raised in any of -- these cases: -- * ghc missing -- * ~/.autoproc/autoproc.hs missing -- * autoproc.hs fails to compile -- ** wrong ghc in path (fails to compile) -- ** type error, syntax error, .. -- * Missing autoproc/AutoprocContrib modules due to ghc upgrade -- buildLaunch :: IO () buildLaunch = do recompile True dir <- getAutoprocDir executeFile (dir ++ "/autoproc") False [] Nothing return () -- | Return the path to @~\/.autoproc@. getAutoprocDir :: MonadIO m => m String getAutoprocDir = liftIO $ getAppUserDataDirectory "autoproc" -- | 'recompile force', recompile @~\/.autoproc\/autoproc.hs@ when any of the -- following apply: -- * force is True -- * the autoproc executable does not exist -- * the autoproc executable is older than autoproc.hs -- -- The -i flag is used to restrict recompilation to the autoproc.hs file only. -- -- Compilation errors (if any) are logged to ~\/.autoproc\/autoproc.errors. If -- GHC indicates failure with a non-zero exit code, an xmessage displaying -- that file is spawned. -- -- False is returned if there are compilation errors. recompile :: MonadIO m => Bool -> m Bool recompile force = liftIO $ do dir <- getAutoprocDir let binn = "autoproc" bin = dir ++ "/" ++ binn base = dir ++ "/" ++ "autoproc" err = base ++ ".errors" src = base ++ ".hs" srcT <- getModTime src binT <- getModTime bin if (force || srcT > binT) then do status <- bracket (openFile err WriteMode) hClose $ \h -> do waitForProcess =<< runProcess "ghc" ["--make", "autoproc.hs", "-i", "-no-recomp", "-v0", "-o",binn] (Just dir) Nothing Nothing Nothing (Just h) -- now, if it fails, run xmessage to let the user know: when (status /= ExitSuccess) $ do ghcErr <- readFile err let msg = unlines $ ["Error detected while loading autoproc configuration file: " ++ src] ++ lines ghcErr ++ ["","Please check the file for errors."] doubleFork $ executeFile "xmessage" True ["-default", "okay", msg] Nothing return (status == ExitSuccess) else return True where getModTime f = catch (Just <$> getModificationTime f) (const $ return Nothing) -- | Double fork and execute an IO action (usually one of the exec family of -- functions) doubleFork :: MonadIO m => IO () -> m () doubleFork m = liftIO $ do pid <- forkProcess $ do forkProcess (createSession >> m) exitWith ExitSuccess getProcessStatus True False pid return ()