ymonad-0.1.0.0: src/YMonad/Launcher.hs
module YMonad.Launcher (
launcherMain,
) where
import Data.Time.Clock (UTCTime)
import System.Directory
import System.Environment (getEnvironment, getExecutablePath)
import System.Exit (ExitCode (..))
import System.FilePath (takeDirectory, (</>))
import System.Process
import YMonad
launcherMain :: IO ()
launcherMain = do
configPath <- getConfigPath
configExists <- doesFileExist configPath
if not configExists
then ymonad def
else do
cacheDir <- getCacheDirectory
createDirectoryIfMissing True cacheDir
let buildDir = cacheDir </> "build"
binaryPath = cacheDir </> "ymonad-config"
errorPath = cacheDir </> "ymonad.errors"
forceRebuild <- fmap isJust (lookupEnv "YMONAD_FORCE_REBUILD")
shouldBuild <- if forceRebuild then pure True else shouldRebuildConfig configPath binaryPath
when shouldBuild $ recompileConfig configPath buildDir binaryPath errorPath
launchCompiledConfig binaryPath
getConfigPath :: IO FilePath
getConfigPath = do
explicit <- lookupEnv "YMONAD_CONFIG"
case explicit of
Just path -> pure path
Nothing -> do
configHome <- lookupEnv "XDG_CONFIG_HOME"
case configHome of
Just dir -> pure (dir </> "ymonad" </> "ymonad.hs")
Nothing -> do
homeDir <- getHomeDirectory
pure (homeDir </> ".config" </> "ymonad" </> "ymonad.hs")
getCacheDirectory :: IO FilePath
getCacheDirectory = do
cacheHome <- lookupEnv "XDG_CACHE_HOME"
case cacheHome of
Just dir -> pure (dir </> "ymonad")
Nothing -> do
homeDir <- getHomeDirectory
pure (homeDir </> ".cache" </> "ymonad")
shouldRebuildConfig :: FilePath -> FilePath -> IO Bool
shouldRebuildConfig configPath binaryPath = do
binaryExists <- doesFileExist binaryPath
if not binaryExists
then pure True
else do
configTime <- getModificationTime configPath
binaryTime <- getModificationTime binaryPath
libTime <- newestLibraryTimestamp (takeDirectory configPath </> "lib")
pure (configTime > binaryTime || maybe False (> binaryTime) libTime)
newestLibraryTimestamp :: FilePath -> IO (Maybe UTCTime)
newestLibraryTimestamp root = do
exists <- doesDirectoryExist root
if not exists
then pure Nothing
else do
files <- listFilesRecursive root
case files of
[] -> pure Nothing
firstFile : otherFiles -> do
firstTime <- getModificationTime firstFile
otherTimes <- mapM getModificationTime otherFiles
pure (Just (foldl' max firstTime otherTimes))
listFilesRecursive :: FilePath -> IO [FilePath]
listFilesRecursive root = do
names <- listDirectory root
fmap concat . forM names $ \name -> do
let path = root </> name
isDir <- doesDirectoryExist path
if isDir
then listFilesRecursive path
else pure [path]
recompileConfig :: FilePath -> FilePath -> FilePath -> FilePath -> IO ()
recompileConfig configPath buildDir binaryPath errorPath = do
createDirectoryIfMissing True buildDir
ghcPath <- getCompilerPath
let configDir = takeDirectory configPath
libDir = configDir </> "lib"
ghcArgs =
[ "--make"
, configPath
, "-outputdir"
, buildDir
, "-o"
, binaryPath
, "-i" <> configDir
, "-i" <> libDir
, "-package"
, "YMonad"
]
(exitCode, stdoutText, stderrText) <- readProcessWithExitCode ghcPath ghcArgs ""
let buildLog = stdoutText <> stderrText
writeFile errorPath buildLog
case exitCode of
ExitSuccess -> pass
ExitFailure exitCodeValue -> die ("Failed to compile " <> configPath <> " (exit code " <> show exitCodeValue <> "). See " <> errorPath)
getCompilerPath :: IO FilePath
getCompilerPath = do
envPath <- lookupEnv "YMONAD_GHC"
case envPath of
Just path -> pure path
Nothing -> do
found <- findExecutable "ghc"
case found of
Just path -> pure path
Nothing -> die "ghc was not found in PATH; set YMONAD_GHC or install ghc"
launchCompiledConfig :: FilePath -> IO ()
launchCompiledConfig binaryPath = do
launcherPath <- getExecutablePath
env0 <- getEnvironment
let helperPath = takeDirectory launcherPath </> "ymonadctl"
env1 =
("YMONAD_CTL_PATH", helperPath)
: ("YMONAD_LAUNCHER_PATH", launcherPath)
: filter (\(name, _value) -> name /= "YMONAD_CTL_PATH" && name /= "YMONAD_LAUNCHER_PATH") env0
(_, _, _, ph) <- createProcess ((proc binaryPath []) {env = Just env1})
waitForProcess ph >>= exitWith