dyre-0.9.2: Config/Dyre/Paths.hs
{- |
File paths of interest to Dyre, and related values.
-}
module Config.Dyre.Paths where
import Control.Monad ( filterM )
import Data.List ( isSuffixOf )
import System.Info (os, arch)
import System.FilePath
( (</>), (<.>), takeExtension, splitExtension )
import System.Directory
( doesDirectoryExist
, doesFileExist
, getCurrentDirectory
, getDirectoryContents
, getModificationTime
)
import System.Environment.XDG.BaseDir (getUserCacheDir, getUserConfigDir)
import System.Environment.Executable (getExecutablePath)
import Data.Time
import Config.Dyre.Params
import Config.Dyre.Options
-- | Data type to make it harder to confuse which path is which.
data PathsConfig = PathsConfig
{ runningExecutable :: FilePath
, customExecutable :: FilePath
, configFile :: FilePath
-- ^ Where Dyre looks for the custom configuration file.
, libsDirectory :: FilePath
-- ^ @<configDir>/libs@. This directory gets added to the GHC
-- include path during compilation, so use configurations can be
-- split up into modules. Changes to files under this directory
-- trigger recompilation.
, cacheDirectory :: FilePath
-- ^ Where the custom executable, object and interface files, errors
-- file and other metadata get stored.
}
-- | Determine a file name for the compiler to write to, based on
-- the 'customExecutable' path.
--
outputExecutable :: FilePath -> FilePath
outputExecutable path =
let (base, ext) = splitExtension path
in base <.> "tmp" <.> ext
-- | Return a 'PathsConfig', which records the current binary, the custom
-- binary, the config file, and the cache directory.
getPaths :: Params c r -> IO (FilePath, FilePath, FilePath, FilePath, FilePath)
getPaths params@Params{projectName = pName} = do
thisBinary <- getExecutablePath
debugMode <- getDebug
cwd <- getCurrentDirectory
cacheDir' <- case (debugMode, cacheDir params) of
(True, _ ) -> return $ cwd </> "cache"
(False, Nothing) -> getUserCacheDir pName
(False, Just cd) -> cd
confDir <- case (debugMode, configDir params) of
(True, _ ) -> return cwd
(False, Nothing) -> getUserConfigDir pName
(False, Just cd) -> cd
let
tempBinary =
cacheDir' </> pName ++ "-" ++ os ++ "-" ++ arch <.> takeExtension thisBinary
configFile' = confDir </> pName ++ ".hs"
libsDir = confDir </> "lib"
pure (thisBinary, tempBinary, configFile', cacheDir', libsDir)
getPathsConfig :: Params cfg a -> IO PathsConfig
getPathsConfig params = do
(cur, custom, conf, cache, libs) <- getPaths params
pure $ PathsConfig cur custom conf libs cache
-- | Check if a file exists. If it exists, return Just the modification
-- time. If it doesn't exist, return Nothing.
maybeModTime :: FilePath -> IO (Maybe UTCTime)
maybeModTime path = do
fileExists <- doesFileExist path
if fileExists
then Just <$> getModificationTime path
else return Nothing
checkFilesModified :: PathsConfig -> IO Bool
checkFilesModified paths = do
confTime <- maybeModTime (configFile paths)
libFiles <- findHaskellFiles (libsDirectory paths)
libTimes <- traverse maybeModTime libFiles
thisTime <- maybeModTime (runningExecutable paths)
tempTime <- maybeModTime (customExecutable paths)
pure $
tempTime < confTime -- config newer than custom bin
|| tempTime < thisTime -- main bin newer than custom bin
|| any (tempTime <) libTimes
-- | Recursively find Haskell files (@.hs@, @.lhs@) at the given
-- location.
findHaskellFiles :: FilePath -> IO [FilePath]
findHaskellFiles d = do
exists <- doesDirectoryExist d
if exists
then do
nodes <- getDirectoryContents d
let nodes' = map (d </>) . filter (`notElem` [".", ".."]) $ nodes
files <- filterM isHaskellFile nodes'
dirs <- filterM doesDirectoryExist nodes'
subfiles <- concat <$> traverse findHaskellFiles dirs
pure $ files ++ subfiles
else pure []
where
isHaskellFile f
| any (`isSuffixOf` f) [".hs", ".lhs"] = doesFileExist f
| otherwise = pure False