{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
module Agda.Setup
( getAgdaAppDir
, getDataDir
, getDataFileName
, setup
)
where
import Control.Exception ( IOException, try )
import Control.Monad ( forM, forM_, unless, void, when )
import Data.ByteString ( ByteString )
import qualified Data.ByteString as BS
import Data.Functor ( (<&>) )
import Data.List ( intercalate )
import Language.Haskell.TH.Syntax ( qAddDependentFile, runIO )
#if !MIN_VERSION_bytestring(0,11,2)
import Instances.TH.Lift ()
#endif
import System.Directory
( XdgDirectory (..)
, canonicalizePath, createDirectoryIfMissing, doesDirectoryExist
, getAppUserDataDirectory, getXdgDirectory, removeFile
)
import System.Environment ( lookupEnv )
import System.FileLock ( pattern Exclusive, withFileLock )
import System.FilePath ( (</>), joinPath, splitFileName, takeFileName )
import System.IO ( hPutStrLn, stderr )
import Agda.Setup.DataFiles ( dataFiles, dataPath )
import Agda.VersionCommit ( versionWithCommitInfo )
dataDirNameParts :: [FilePath]
dataDirNameParts :: [String]
dataDirNameParts = [ String
"share", String
versionWithCommitInfo ]
mkDataDir :: FilePath -> FilePath
mkDataDir :: String -> String
mkDataDir String
base = [String] -> String
joinPath ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ String
base String -> [String] -> [String]
forall a. a -> [a] -> [a]
: [String]
dataDirNameParts
[] <$ mapM_ (qAddDependentFile . dataPath) dataFiles
embeddedDataDir :: [(FilePath, ByteString)]
embeddedDataDir :: [(String, ByteString)]
embeddedDataDir = $(do
contents <- runIO $ mapM (BS.readFile . dataPath) dataFiles
[| zip dataFiles contents |]
)
getAgdaAppDir :: IO FilePath
getAgdaAppDir :: IO String
getAgdaAppDir = do
String -> IO (Maybe String)
lookupEnv String
"AGDA_DIR" IO (Maybe String) -> (Maybe String -> IO String) -> IO String
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Maybe String
Nothing -> IO String
agdaDir
Just String
dir -> String -> IO Bool
doesDirectoryExist String
dir IO Bool -> (Bool -> IO String) -> IO String
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Bool
True -> String -> IO String
canonicalizePath String
dir
Bool
False -> do
d <- IO String
agdaDir
inform $ "Warning: Environment variable AGDA_DIR points to non-existing directory " ++ show dir ++ ", using " ++ show d ++ " instead."
return d
where
agdaDir :: IO String
agdaDir = do
legacyAgdaDir <- String -> IO String
getAppUserDataDirectory String
"agda"
doesDirectoryExist legacyAgdaDir >>= \case
Bool
True -> String -> IO String
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return String
legacyAgdaDir
Bool
False -> XdgDirectory -> String -> IO String
getXdgDirectory XdgDirectory
XdgConfig String
"agda"
getDataDir :: IO FilePath
getDataDir :: IO String
getDataDir = String -> String
mkDataDir (String -> String) -> IO String -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO String
getAgdaAppDir
getDataFileName :: FilePath -> IO FilePath
getDataFileName :: String -> IO String
getDataFileName String
f = IO String
getDataDir IO String -> (String -> String) -> IO String
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> (String -> String -> String
</> String
f)
setup :: Bool -> IO ()
setup :: Bool -> IO ()
setup Bool
force = do
dir <- IO String
getAgdaAppDir
let doSetup = Bool -> String -> IO ()
dumpDataDir Bool
force String
dir
if force then doSetup else do
ex <- doesDirectoryExist $ mkDataDir dir
unless ex doSetup
dumpDataDir :: Bool -> FilePath -> IO ()
dumpDataDir :: Bool -> String -> IO ()
dumpDataDir Bool
verbose String
agdaDir = do
let dataDir :: String
dataDir = String -> String
mkDataDir String
agdaDir
Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
dataDir
let lock :: String
lock = String
agdaDir String -> String -> String
</> String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"-" (String
".lock" String -> [String] -> [String]
forall a. a -> [a] -> [a]
: [String]
dataDirNameParts)
String -> SharedExclusive -> (FileLock -> IO ()) -> IO ()
forall a. String -> SharedExclusive -> (FileLock -> IO a) -> IO a
withFileLock String
lock SharedExclusive
Exclusive \ FileLock
_lock -> do
[(String, ByteString)] -> ((String, ByteString) -> IO ()) -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [(String, ByteString)]
embeddedDataDir \ (String
relativePath, ByteString
content) -> do
let (String
relativeDir, String
file) = String -> (String, String)
splitFileName String
relativePath
let dir :: String
dir = String
dataDir String -> String -> String
</> String
relativeDir
Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
dir
let path :: String
path = String
dir String -> String -> String
</> String
file
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
verbose (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
inform (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"Writing " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
path
String -> ByteString -> IO ()
BS.writeFile String
path ByteString
content
IO (Either IOException ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Either IOException ()) -> IO ())
-> IO (Either IOException ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ forall e a. Exception e => IO a -> IO (Either e a)
try @IOException (IO () -> IO (Either IOException ()))
-> IO () -> IO (Either IOException ())
forall a b. (a -> b) -> a -> b
$ String -> IO ()
removeFile String
lock
inform :: String -> IO ()
inform :: String -> IO ()
inform = Handle -> String -> IO ()
hPutStrLn Handle
stderr