{-| Copyright : (c) 2015 Javran Cheng License : MIT Maintainer : Javran.C@gmail.com Stability : unstable Portability : non-portable (requires X11) Compiling-related functions -} module XMonad.Util.EntryHelper.Compile ( defaultCompile , defaultPostCompile , compileUsingShell , withFileLock , withLock ) where import Control.Applicative import System.IO import System.Posix.Process import System.Process import Control.Exception.Extensible import System.Exit import System.FilePath import System.Directory import Data.List import System.Posix.User import XMonad.Util.EntryHelper.File import XMonad.Util.EntryHelper.Util -- | the default compiling action. -- checks whether any of the sources files under @"~\/.xmonad\/"@ -- is newer than the binary and recompiles XMonad if so. defaultCompile :: Bool -> IO ExitCode defaultCompile force = do b <- isSourceNewer if force || b then do bin <- binPath <$> getXMonadPaths let cmd = "ghc --make xmonad.hs -i -ilib -fforce-recomp -o " ++ bin compileUsingShell cmd else return ExitSuccess -- | the default post-compiling action. -- prints out error log to stderr and pops up a message -- when the last compilation has failed. defaultPostCompile :: ExitCode -> IO () defaultPostCompile ExitSuccess = return () defaultPostCompile st@(ExitFailure _) = do ghcErr <- readFile =<< getXMonadLog src <- getXMonadSrc let msg = unlines $ [ "Error detected while loading xmonad configuration file: " ++ src] ++ lines (if null ghcErr then show st else ghcErr) ++ ["","Please check the file for errors."] hPutStrLn stderr msg _ <- forkProcess $ executeFile "xmessage" True ["-default", "okay", msg] Nothing return () -- | @compileUsingShell cmd@ spawns a new process to run a shell command -- (shell expansion is applied). -- The working directory of the shell command is @"~\/.xmonad\/"@, and -- the process' stdout and stdout are redirected to @"~\/.xmonad\/xmonad.errors"@ compileUsingShell :: String -> IO ExitCode compileUsingShell cmd = do -- please make sure "installSignalHandlers" hasn't been executed -- or has been undone by "uninstallSignalHandlers" -- see also: https://ghc.haskell.org/trac/ghc/ticket/5212 dir <- getXMonadDir compileLogPath <- getXMonadLog hNullInput <- openFile "/dev/null" ReadMode hCompileLog <- openFile compileLogPath WriteMode hSetBuffering hCompileLog NoBuffering let cp = (shell cmd) { cwd = Just dir , std_in = UseHandle hNullInput , std_out = UseHandle hCompileLog , std_err = UseHandle hCompileLog } -- std_out and std_err are closed automatically -- so we don't need to take care of them. (_,_,_,ph) <- createProcess cp waitForProcess ph -- | @withLock def action@ is the same as @withFileLock fpath def action@ with -- @fpath@ being @"xmonad.${USERNAME}.lock"@ under your temporary directory. -- Wrapping an action with more than one @withLock@ will not work. -- -- See also: `withFileLock`, 'getTemporaryDirectory', 'getEffectiveUserName' withLock :: a -> IO a -> IO a withLock def action = do tmpDir <- getTemporaryDirectory -- https://ghc.haskell.org/trac/ghc/ticket/1487 -- avoid using "getLoginName" here usr <- getEffectiveUserName let lockFile = tmpDir intercalate "." ["xmonad",usr,"lock"] withFileLock lockFile def action -- | prevents an IO action from parallel execution by using a lock file. -- @withFileLock fpath def action@ checks whether the file indicated by @fpath@ -- exists. And: -- -- * returns @def@ if the file exists. -- * creates @fpath@, executes the action, and deletes @fpath@ when the action -- has completed. If @action@ has failed, @def@ will be returned instead. -- -- Note that: -- -- * the action will be protected by 'safeIO', meaning the lock file will be deleted -- regardless of any error. -- * No check on @fpath@ will be done by this function. Please make sure the lock file -- does not exist. -- * please prevent wrapping the action with same file lock multiple times, -- in which case the action will never be executed. withFileLock :: FilePath -> a -> IO a -> IO a withFileLock fPath def action = do lock <- doesFileExist fPath if lock then skipCompile else doCompile where skipCompile = do putStrLn $ "Lock file " ++ fPath ++ " found, aborting ..." putStrLn "Delete lock file to continue." return def doCompile = bracket_ (writeFile fPath "") (removeFile fPath) (safeIO def action)