{-# LANGUAGE CPP #-}

-- | Temporary file-system management
module GHC.Utils.TmpFs
    ( TmpFs
    , initTmpFs
    , forkTmpFsFrom
    , mergeTmpFsInto
    , PathsToClean(..)
    , emptyPathsToClean
    , TempFileLifetime(..)
    , TempDir (..)
    , cleanTempDirs
    , cleanTempFiles
    , cleanCurrentModuleTempFiles
    , keepCurrentModuleTempFiles
    , addFilesToClean
    , changeTempFilesLifetime
    , newTempName
    , newTempLibName
    , newTempSubDir
    , withSystemTempDirectory
    , withTempDirectory
    )
where

import GHC.Prelude

import GHC.Utils.Error
import GHC.Utils.Outputable
import GHC.Utils.Logger
import GHC.Utils.Misc
import GHC.Utils.Exception as Exception
import GHC.Driver.Phases

import Data.List (partition)
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.Map as Map
import Data.Map (Map)
import Data.IORef
import System.Directory
import System.FilePath
import System.IO.Error

#if !defined(mingw32_HOST_OS)
import qualified System.Posix.Internals
#endif

-- | Temporary file-system
data TmpFs = TmpFs
  { TmpFs -> IORef (Map FilePath FilePath)
tmp_dirs_to_clean :: IORef (Map FilePath FilePath)
      -- ^ Maps system temporary directory (passed via settings or DynFlags) to
      -- an actual temporary directory for this process.
      --
      -- It's a Map probably to support changing the system temporary directory
      -- over time.
      --
      -- Shared with forked TmpFs.

  , TmpFs -> IORef Int
tmp_next_suffix :: IORef Int
      -- ^ The next available suffix to uniquely name a temp file, updated
      -- atomically.
      --
      -- Shared with forked TmpFs.

  , TmpFs -> IORef PathsToClean
tmp_files_to_clean :: IORef PathsToClean
      -- ^ Files to clean (per session or per module)
      --
      -- Not shared with forked TmpFs.
  , TmpFs -> IORef PathsToClean
tmp_subdirs_to_clean :: IORef PathsToClean
      -- ^ Subdirs to clean (per session or per module)
      --
      -- Not shared with forked TmpFs.
  }

-- | A collection of paths that must be deleted before ghc exits.
data PathsToClean = PathsToClean
    { PathsToClean -> Set FilePath
ptcGhcSession :: !(Set FilePath)
        -- ^ Paths that will be deleted at the end of runGhc(T)

    , PathsToClean -> Set FilePath
ptcCurrentModule :: !(Set FilePath)
        -- ^ Paths that will be deleted the next time
        -- 'cleanCurrentModuleTempFiles' is called, or otherwise at the end of
        -- the session.
    }

-- | Used when a temp file is created. This determines which component Set of
-- PathsToClean will get the temp file
data TempFileLifetime
  = TFL_CurrentModule
  -- ^ A file with lifetime TFL_CurrentModule will be cleaned up at the
  -- end of upweep_mod
  | TFL_GhcSession
  -- ^ A file with lifetime TFL_GhcSession will be cleaned up at the end of
  -- runGhc(T)
  deriving (Int -> TempFileLifetime -> ShowS
[TempFileLifetime] -> ShowS
TempFileLifetime -> FilePath
(Int -> TempFileLifetime -> ShowS)
-> (TempFileLifetime -> FilePath)
-> ([TempFileLifetime] -> ShowS)
-> Show TempFileLifetime
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TempFileLifetime -> ShowS
showsPrec :: Int -> TempFileLifetime -> ShowS
$cshow :: TempFileLifetime -> FilePath
show :: TempFileLifetime -> FilePath
$cshowList :: [TempFileLifetime] -> ShowS
showList :: [TempFileLifetime] -> ShowS
Show)

newtype TempDir = TempDir FilePath

-- | An empty PathsToClean
emptyPathsToClean :: PathsToClean
emptyPathsToClean :: PathsToClean
emptyPathsToClean = Set FilePath -> Set FilePath -> PathsToClean
PathsToClean Set FilePath
forall a. Set a
Set.empty Set FilePath
forall a. Set a
Set.empty

-- | Merge two PathsToClean
mergePathsToClean :: PathsToClean -> PathsToClean -> PathsToClean
mergePathsToClean :: PathsToClean -> PathsToClean -> PathsToClean
mergePathsToClean PathsToClean
x PathsToClean
y = PathsToClean
    { ptcGhcSession :: Set FilePath
ptcGhcSession    = Set FilePath -> Set FilePath -> Set FilePath
forall a. Ord a => Set a -> Set a -> Set a
Set.union (PathsToClean -> Set FilePath
ptcGhcSession PathsToClean
x) (PathsToClean -> Set FilePath
ptcGhcSession PathsToClean
y)
    , ptcCurrentModule :: Set FilePath
ptcCurrentModule = Set FilePath -> Set FilePath -> Set FilePath
forall a. Ord a => Set a -> Set a -> Set a
Set.union (PathsToClean -> Set FilePath
ptcCurrentModule PathsToClean
x) (PathsToClean -> Set FilePath
ptcCurrentModule PathsToClean
y)
    }

-- | Initialise an empty TmpFs
initTmpFs :: IO TmpFs
initTmpFs :: IO TmpFs
initTmpFs = do
    files   <- PathsToClean -> IO (IORef PathsToClean)
forall a. a -> IO (IORef a)
newIORef PathsToClean
emptyPathsToClean
    subdirs <- newIORef emptyPathsToClean
    dirs    <- newIORef Map.empty
    next    <- newIORef 0
    return $ TmpFs
        { tmp_files_to_clean   = files
        , tmp_subdirs_to_clean = subdirs
        , tmp_dirs_to_clean    = dirs
        , tmp_next_suffix      = next
        }

-- | Initialise an empty TmpFs sharing unique numbers and per-process temporary
-- directories with the given TmpFs
--
-- It's not safe to use the subdirs created by the original TmpFs with the
-- forked one. Use @newTempSubDir@ to create new subdirs instead.
forkTmpFsFrom :: TmpFs -> IO TmpFs
forkTmpFsFrom :: TmpFs -> IO TmpFs
forkTmpFsFrom TmpFs
old = do
    files <- PathsToClean -> IO (IORef PathsToClean)
forall a. a -> IO (IORef a)
newIORef PathsToClean
emptyPathsToClean
    subdirs <- newIORef emptyPathsToClean
    return $ TmpFs
        { tmp_files_to_clean   = files
        , tmp_subdirs_to_clean = subdirs
        , tmp_dirs_to_clean    = tmp_dirs_to_clean old
        , tmp_next_suffix      = tmp_next_suffix old
        }

-- | Merge the first TmpFs into the second.
--
-- The first TmpFs is returned emptied.
mergeTmpFsInto :: TmpFs -> TmpFs -> IO ()
mergeTmpFsInto :: TmpFs -> TmpFs -> IO ()
mergeTmpFsInto TmpFs
src TmpFs
dst = do
    src_files <- IORef PathsToClean
-> (PathsToClean -> (PathsToClean, PathsToClean))
-> IO PathsToClean
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' (TmpFs -> IORef PathsToClean
tmp_files_to_clean TmpFs
src) (\PathsToClean
s -> (PathsToClean
emptyPathsToClean, PathsToClean
s))
    src_subdirs <- atomicModifyIORef' (tmp_subdirs_to_clean src) (\PathsToClean
s -> (PathsToClean
emptyPathsToClean, PathsToClean
s))
    atomicModifyIORef' (tmp_files_to_clean dst) (\PathsToClean
s -> (PathsToClean -> PathsToClean -> PathsToClean
mergePathsToClean PathsToClean
src_files PathsToClean
s, ()))
    atomicModifyIORef' (tmp_subdirs_to_clean dst) (\PathsToClean
s -> (PathsToClean -> PathsToClean -> PathsToClean
mergePathsToClean PathsToClean
src_subdirs PathsToClean
s, ()))


cleanTempDirs :: Logger -> TmpFs -> IO ()
cleanTempDirs :: Logger -> TmpFs -> IO ()
cleanTempDirs Logger
logger TmpFs
tmpfs
   = IO () -> IO ()
forall a. IO a -> IO a
mask_
   (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do let ref :: IORef (Map FilePath FilePath)
ref = TmpFs -> IORef (Map FilePath FilePath)
tmp_dirs_to_clean TmpFs
tmpfs
        ds <- IORef (Map FilePath FilePath)
-> (Map FilePath FilePath
    -> (Map FilePath FilePath, Map FilePath FilePath))
-> IO (Map FilePath FilePath)
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef (Map FilePath FilePath)
ref ((Map FilePath FilePath
  -> (Map FilePath FilePath, Map FilePath FilePath))
 -> IO (Map FilePath FilePath))
-> (Map FilePath FilePath
    -> (Map FilePath FilePath, Map FilePath FilePath))
-> IO (Map FilePath FilePath)
forall a b. (a -> b) -> a -> b
$ \Map FilePath FilePath
ds -> (Map FilePath FilePath
forall k a. Map k a
Map.empty, Map FilePath FilePath
ds)
        removeTmpDirs logger (Map.elems ds)

-- | Delete all paths in @tmp_files_to_clean@ and @tmp_subdirs_to_clean@.
cleanTempFiles :: Logger -> TmpFs -> IO ()
cleanTempFiles :: Logger -> TmpFs -> IO ()
cleanTempFiles Logger
logger TmpFs
tmpfs
   = IO () -> IO ()
forall a. IO a -> IO a
mask_
   (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do ([FilePath] -> IO ()) -> IORef PathsToClean -> IO ()
forall {b}. ([FilePath] -> IO b) -> IORef PathsToClean -> IO b
removeWith (Logger -> [FilePath] -> IO ()
removeTmpFiles Logger
logger) (TmpFs -> IORef PathsToClean
tmp_files_to_clean TmpFs
tmpfs)
        ([FilePath] -> IO ()) -> IORef PathsToClean -> IO ()
forall {b}. ([FilePath] -> IO b) -> IORef PathsToClean -> IO b
removeWith (Logger -> [FilePath] -> IO ()
removeTmpSubdirs Logger
logger) (TmpFs -> IORef PathsToClean
tmp_subdirs_to_clean TmpFs
tmpfs)
  where
    removeWith :: ([FilePath] -> IO b) -> IORef PathsToClean -> IO b
removeWith [FilePath] -> IO b
remove IORef PathsToClean
ref = do
      to_delete <- IORef PathsToClean
-> (PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath]
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef PathsToClean
ref ((PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath])
-> (PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$
        \PathsToClean
            { ptcCurrentModule :: PathsToClean -> Set FilePath
ptcCurrentModule = Set FilePath
cm_paths
            , ptcGhcSession :: PathsToClean -> Set FilePath
ptcGhcSession = Set FilePath
gs_paths
            } -> ( PathsToClean
emptyPathsToClean
                  , Set FilePath -> [FilePath]
forall a. Set a -> [a]
Set.toList Set FilePath
cm_paths [FilePath] -> [FilePath] -> [FilePath]
forall a. [a] -> [a] -> [a]
++ Set FilePath -> [FilePath]
forall a. Set a -> [a]
Set.toList Set FilePath
gs_paths)
      remove to_delete

-- | Keep all the paths in @tmp_files_to_clean@ and @tmp_subdirs_to_clean@
-- that have lifetime TFL_CurrentModule. This function is used when `-keep-tmp-files` is
-- used in an OPTIONS_GHC pragma.
-- This function removes the temporary file from the TmpFs so we no longer remove
-- it at the env when cleanTempFiles is called.
keepCurrentModuleTempFiles :: HasCallStack => Logger -> TmpFs -> IO ()
keepCurrentModuleTempFiles :: HasCallStack => Logger -> TmpFs -> IO ()
keepCurrentModuleTempFiles Logger
logger TmpFs
tmpfs
   = IO () -> IO ()
forall a. IO a -> IO a
mask_
   (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do to_keep_files <- IORef PathsToClean -> IO [FilePath]
keep  (TmpFs -> IORef PathsToClean
tmp_files_to_clean TmpFs
tmpfs)
        to_keep_subdirs <- keep  (tmp_subdirs_to_clean tmpfs)
        -- Remove any folders which contain any files we want to keep from the
        -- directories we are tracking. A new temporary directory will be created
        -- the next time a temporary file is needed (by perhaps another module).
        keepDirs (to_keep_files ++ to_keep_subdirs) (tmp_dirs_to_clean tmpfs)
  where
    keepDirs :: [FilePath] -> IORef (Map k FilePath) -> IO ()
keepDirs [FilePath]
keeps IORef (Map k FilePath)
ref = do
      let keep_dirs :: Set FilePath
keep_dirs = [FilePath] -> Set FilePath
forall a. Ord a => [a] -> Set a
Set.fromList (ShowS -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map ShowS
takeDirectory [FilePath]
keeps)
      IORef (Map k FilePath)
-> (Map k FilePath -> (Map k FilePath, ())) -> IO ()
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef (Map k FilePath)
ref  ((Map k FilePath -> (Map k FilePath, ())) -> IO ())
-> (Map k FilePath -> (Map k FilePath, ())) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Map k FilePath
m -> ((FilePath -> Bool) -> Map k FilePath -> Map k FilePath
forall a k. (a -> Bool) -> Map k a -> Map k a
Map.filter (\FilePath
fp -> FilePath
fp FilePath -> Set FilePath -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.notMember` Set FilePath
keep_dirs) Map k FilePath
m, ())

    keep :: IORef PathsToClean -> IO [FilePath]
keep IORef PathsToClean
ref = do
        to_keep <- IORef PathsToClean
-> (PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath]
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef PathsToClean
ref ((PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath])
-> (PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$
            \ptc :: PathsToClean
ptc@PathsToClean{ptcCurrentModule :: PathsToClean -> Set FilePath
ptcCurrentModule = Set FilePath
cm_paths} ->
                (PathsToClean
ptc {ptcCurrentModule = Set.empty}, Set FilePath -> [FilePath]
forall a. Set a -> [a]
Set.toList Set FilePath
cm_paths)
        debugTraceMsg logger 2 (text "Keeping:" <+> hsep (map text to_keep))
        return to_keep

-- | Delete all paths in @tmp_files_to_clean@ and @tmp_subdirs_to_clean@
-- That have lifetime TFL_CurrentModule.
-- If a file must be cleaned eventually, but must survive a
-- cleanCurrentModuleTempFiles, ensure it has lifetime TFL_GhcSession.
cleanCurrentModuleTempFiles :: Logger -> TmpFs -> IO ()
cleanCurrentModuleTempFiles :: Logger -> TmpFs -> IO ()
cleanCurrentModuleTempFiles Logger
logger TmpFs
tmpfs
   = IO () -> IO ()
forall a. IO a -> IO a
mask_
   (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do ([FilePath] -> IO ()) -> IORef PathsToClean -> IO ()
forall {b}. ([FilePath] -> IO b) -> IORef PathsToClean -> IO b
removeWith (Logger -> [FilePath] -> IO ()
removeTmpFiles Logger
logger) (TmpFs -> IORef PathsToClean
tmp_files_to_clean TmpFs
tmpfs)
        ([FilePath] -> IO ()) -> IORef PathsToClean -> IO ()
forall {b}. ([FilePath] -> IO b) -> IORef PathsToClean -> IO b
removeWith (Logger -> [FilePath] -> IO ()
removeTmpSubdirs Logger
logger) (TmpFs -> IORef PathsToClean
tmp_subdirs_to_clean TmpFs
tmpfs)
  where
    removeWith :: ([FilePath] -> IO b) -> IORef PathsToClean -> IO b
removeWith [FilePath] -> IO b
remove IORef PathsToClean
ref = do
        to_delete <- IORef PathsToClean
-> (PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath]
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef PathsToClean
ref ((PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath])
-> (PathsToClean -> (PathsToClean, [FilePath])) -> IO [FilePath]
forall a b. (a -> b) -> a -> b
$
            \ptc :: PathsToClean
ptc@PathsToClean{ptcCurrentModule :: PathsToClean -> Set FilePath
ptcCurrentModule = Set FilePath
cm_paths} ->
                (PathsToClean
ptc {ptcCurrentModule = Set.empty}, Set FilePath -> [FilePath]
forall a. Set a -> [a]
Set.toList Set FilePath
cm_paths)
        remove to_delete

-- | Ensure that new_files are cleaned on the next call of
-- 'cleanTempFiles' or 'cleanCurrentModuleTempFiles', depending on lifetime.
-- If any of new_files are already tracked, they will have their lifetime
-- updated.
addFilesToClean :: TmpFs -> TempFileLifetime -> [FilePath] -> IO ()
addFilesToClean :: TmpFs -> TempFileLifetime -> [FilePath] -> IO ()
addFilesToClean TmpFs
tmpfs TempFileLifetime
lifetime [FilePath]
new_files =
  IORef PathsToClean -> TempFileLifetime -> [FilePath] -> IO ()
addToClean (TmpFs -> IORef PathsToClean
tmp_files_to_clean TmpFs
tmpfs) TempFileLifetime
lifetime [FilePath]
new_files

addSubdirsToClean :: TmpFs -> TempFileLifetime -> [FilePath] -> IO ()
addSubdirsToClean :: TmpFs -> TempFileLifetime -> [FilePath] -> IO ()
addSubdirsToClean TmpFs
tmpfs TempFileLifetime
lifetime [FilePath]
new_subdirs =
  IORef PathsToClean -> TempFileLifetime -> [FilePath] -> IO ()
addToClean (TmpFs -> IORef PathsToClean
tmp_subdirs_to_clean TmpFs
tmpfs) TempFileLifetime
lifetime [FilePath]
new_subdirs

addToClean :: IORef PathsToClean -> TempFileLifetime -> [FilePath] -> IO ()
addToClean :: IORef PathsToClean -> TempFileLifetime -> [FilePath] -> IO ()
addToClean IORef PathsToClean
ref TempFileLifetime
lifetime [FilePath]
new_filepaths = IORef PathsToClean -> (PathsToClean -> PathsToClean) -> IO ()
forall a. IORef a -> (a -> a) -> IO ()
modifyIORef' IORef PathsToClean
ref ((PathsToClean -> PathsToClean) -> IO ())
-> (PathsToClean -> PathsToClean) -> IO ()
forall a b. (a -> b) -> a -> b
$
  \PathsToClean
    { ptcCurrentModule :: PathsToClean -> Set FilePath
ptcCurrentModule = Set FilePath
cm_paths
    , ptcGhcSession :: PathsToClean -> Set FilePath
ptcGhcSession = Set FilePath
gs_paths
    } -> case TempFileLifetime
lifetime of
      TempFileLifetime
TFL_CurrentModule -> PathsToClean
        { ptcCurrentModule :: Set FilePath
ptcCurrentModule = Set FilePath
cm_paths Set FilePath -> Set FilePath -> Set FilePath
forall a. Ord a => Set a -> Set a -> Set a
`Set.union` Set FilePath
new_filepaths_set
        , ptcGhcSession :: Set FilePath
ptcGhcSession = Set FilePath
gs_paths Set FilePath -> Set FilePath -> Set FilePath
forall a. Ord a => Set a -> Set a -> Set a
`Set.difference` Set FilePath
new_filepaths_set
        }
      TempFileLifetime
TFL_GhcSession -> PathsToClean
        { ptcCurrentModule :: Set FilePath
ptcCurrentModule = Set FilePath
cm_paths Set FilePath -> Set FilePath -> Set FilePath
forall a. Ord a => Set a -> Set a -> Set a
`Set.difference` Set FilePath
new_filepaths_set
        , ptcGhcSession :: Set FilePath
ptcGhcSession = Set FilePath
gs_paths Set FilePath -> Set FilePath -> Set FilePath
forall a. Ord a => Set a -> Set a -> Set a
`Set.union` Set FilePath
new_filepaths_set
        }
  where
    new_filepaths_set :: Set FilePath
new_filepaths_set = [FilePath] -> Set FilePath
forall a. Ord a => [a] -> Set a
Set.fromList [FilePath]
new_filepaths

-- | Update the lifetime of files already being tracked. If any files are
-- not being tracked they will be discarded.
changeTempFilesLifetime :: TmpFs -> TempFileLifetime -> [FilePath] -> IO ()
changeTempFilesLifetime :: TmpFs -> TempFileLifetime -> [FilePath] -> IO ()
changeTempFilesLifetime TmpFs
tmpfs TempFileLifetime
lifetime [FilePath]
files = do
  PathsToClean
    { ptcCurrentModule = cm_paths
    , ptcGhcSession = gs_paths
    } <- IORef PathsToClean -> IO PathsToClean
forall a. IORef a -> IO a
readIORef (TmpFs -> IORef PathsToClean
tmp_files_to_clean TmpFs
tmpfs)
  let old_set = case TempFileLifetime
lifetime of
        TempFileLifetime
TFL_CurrentModule -> Set FilePath
gs_paths
        TempFileLifetime
TFL_GhcSession -> Set FilePath
cm_paths
      existing_files = [FilePath
f | FilePath
f <- [FilePath]
files, FilePath
f FilePath -> Set FilePath -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set FilePath
old_set]
  addFilesToClean tmpfs lifetime existing_files

-- Return a unique numeric temp file suffix
newTempSuffix :: TmpFs -> IO Int
newTempSuffix :: TmpFs -> IO Int
newTempSuffix TmpFs
tmpfs =
  IORef Int -> (Int -> (Int, Int)) -> IO Int
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' (TmpFs -> IORef Int
tmp_next_suffix TmpFs
tmpfs) ((Int -> (Int, Int)) -> IO Int) -> (Int -> (Int, Int)) -> IO Int
forall a b. (a -> b) -> a -> b
$ \Int
n -> (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1,Int
n)

-- Find a temporary name that doesn't already exist.
newTempName :: Logger -> TmpFs -> TempDir -> TempFileLifetime -> Suffix -> IO FilePath
newTempName :: Logger
-> TmpFs -> TempDir -> TempFileLifetime -> FilePath -> IO FilePath
newTempName Logger
logger TmpFs
tmpfs TempDir
tmp_dir TempFileLifetime
lifetime FilePath
extn
  = do d <- Logger -> TmpFs -> TempDir -> IO FilePath
getTempDir Logger
logger TmpFs
tmpfs TempDir
tmp_dir
       findTempName (d </> "ghc_") -- See Note [Deterministic base name]
  where
    findTempName :: FilePath -> IO FilePath
    findTempName :: FilePath -> IO FilePath
findTempName FilePath
prefix
      = do n <- TmpFs -> IO Int
newTempSuffix TmpFs
tmpfs
           let filename = FilePath
prefix FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> FilePath
forall a. Show a => a -> FilePath
show Int
n FilePath -> ShowS
<.> FilePath
extn
           b <- doesFileExist filename
           if b then findTempName prefix
                else do -- clean it up later
                        addFilesToClean tmpfs lifetime [filename]
                        return filename

-- | Create a new temporary subdirectory that doesn't already exist
-- The temporary subdirectory is automatically removed at the end of the
-- GHC session, but its contents aren't. Make sure to leave the directory
-- empty before the end of the session, either by removing content
-- directly or by using @addFilesToClean@.
--
-- If the created subdirectory is not empty, it will not be removed (along
-- with its parent temporary directory) and a warning message will be
-- printed at verbosity 2 and higher.
newTempSubDir :: Logger -> TmpFs -> TempDir -> IO FilePath
newTempSubDir :: Logger -> TmpFs -> TempDir -> IO FilePath
newTempSubDir Logger
logger TmpFs
tmpfs TempDir
tmp_dir
  = do d <- Logger -> TmpFs -> TempDir -> IO FilePath
getTempDir Logger
logger TmpFs
tmpfs TempDir
tmp_dir
       findTempDir (d </> "ghc_")
  where
    findTempDir :: FilePath -> IO FilePath
    findTempDir :: FilePath -> IO FilePath
findTempDir FilePath
prefix
      = do n <- TmpFs -> IO Int
newTempSuffix TmpFs
tmpfs
           let name = FilePath
prefix FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> FilePath
forall a. Show a => a -> FilePath
show Int
n
           b <- doesDirectoryExist name
           if b then findTempDir prefix
                else (do
                  createDirectory name
                  addSubdirsToClean tmpfs TFL_GhcSession [name]
                  return name)
            `Exception.catchIO` \IOException
e -> if IOException -> Bool
isAlreadyExistsError IOException
e
                  then FilePath -> IO FilePath
findTempDir FilePath
prefix else IOException -> IO FilePath
forall a. IOException -> IO a
ioError IOException
e

newTempLibName :: Logger -> TmpFs -> TempDir -> TempFileLifetime -> Suffix
  -> IO (FilePath, FilePath, String)
newTempLibName :: Logger
-> TmpFs
-> TempDir
-> TempFileLifetime
-> FilePath
-> IO (FilePath, FilePath, FilePath)
newTempLibName Logger
logger TmpFs
tmpfs TempDir
tmp_dir TempFileLifetime
lifetime FilePath
extn
  = do d <- Logger -> TmpFs -> TempDir -> IO FilePath
getTempDir Logger
logger TmpFs
tmpfs TempDir
tmp_dir
       findTempName d ("ghc_")
  where
    findTempName :: FilePath -> String -> IO (FilePath, FilePath, String)
    findTempName :: FilePath -> FilePath -> IO (FilePath, FilePath, FilePath)
findTempName FilePath
dir FilePath
prefix
      = do n <- TmpFs -> IO Int
newTempSuffix TmpFs
tmpfs -- See Note [Deterministic base name]
           let libname = FilePath
prefix FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> FilePath
forall a. Show a => a -> FilePath
show Int
n
               filename = FilePath
dir FilePath -> ShowS
</> FilePath
"lib" FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
libname FilePath -> ShowS
<.> FilePath
extn
           b <- doesFileExist filename
           if b then findTempName dir prefix
                else do -- clean it up later
                        addFilesToClean tmpfs lifetime [filename]
                        return (filename, dir, libname)


-- Return our temporary directory within tmp_dir, creating one if we
-- don't have one yet.
getTempDir :: Logger -> TmpFs -> TempDir -> IO FilePath
getTempDir :: Logger -> TmpFs -> TempDir -> IO FilePath
getTempDir Logger
logger TmpFs
tmpfs (TempDir FilePath
tmp_dir) = do
    mapping <- IORef (Map FilePath FilePath) -> IO (Map FilePath FilePath)
forall a. IORef a -> IO a
readIORef IORef (Map FilePath FilePath)
dir_ref
    case Map.lookup tmp_dir mapping of
        Maybe FilePath
Nothing -> do
            pid <- IO Int
getProcessID
            let prefix = FilePath
tmp_dir FilePath -> ShowS
</> FilePath
"ghc" FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> FilePath
forall a. Show a => a -> FilePath
show Int
pid FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
"_"
            mask_ $ mkTempDir prefix
        Just FilePath
dir -> FilePath -> IO FilePath
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
dir
  where
    dir_ref :: IORef (Map FilePath FilePath)
dir_ref = TmpFs -> IORef (Map FilePath FilePath)
tmp_dirs_to_clean TmpFs
tmpfs

    mkTempDir :: FilePath -> IO FilePath
    mkTempDir :: FilePath -> IO FilePath
mkTempDir FilePath
prefix = do
        n <- TmpFs -> IO Int
newTempSuffix TmpFs
tmpfs
        let our_dir = FilePath
prefix FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> FilePath
forall a. Show a => a -> FilePath
show Int
n

        -- 1. Speculatively create our new directory.
        createDirectory our_dir

        -- 2. Update the tmp_dirs_to_clean mapping unless an entry already exists
        -- (i.e. unless another thread beat us to it).
        their_dir <- atomicModifyIORef' dir_ref $ \Map FilePath FilePath
mapping ->
            case FilePath -> Map FilePath FilePath -> Maybe FilePath
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup FilePath
tmp_dir Map FilePath FilePath
mapping of
                Just FilePath
dir -> (Map FilePath FilePath
mapping, FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just FilePath
dir)
                Maybe FilePath
Nothing  -> (FilePath
-> FilePath -> Map FilePath FilePath -> Map FilePath FilePath
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert FilePath
tmp_dir FilePath
our_dir Map FilePath FilePath
mapping, Maybe FilePath
forall a. Maybe a
Nothing)

        -- 3. If there was an existing entry, return it and delete the
        -- directory we created.  Otherwise return the directory we created.
        case their_dir of
            Maybe FilePath
Nothing  -> do
                Logger -> Int -> SDoc -> IO ()
debugTraceMsg Logger
logger Int
2 (SDoc -> IO ()) -> SDoc -> IO ()
forall a b. (a -> b) -> a -> b
$
                    FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
"Created temporary directory:" SDoc -> SDoc -> SDoc
forall doc. IsLine doc => doc -> doc -> doc
<+> FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
our_dir
                FilePath -> IO FilePath
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
our_dir
            Just FilePath
dir -> do
                FilePath -> IO ()
removeDirectory FilePath
our_dir
                FilePath -> IO FilePath
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
dir
      IO FilePath -> (IOException -> IO FilePath) -> IO FilePath
forall a. IO a -> (IOException -> IO a) -> IO a
`Exception.catchIO` \IOException
e -> if IOException -> Bool
isAlreadyExistsError IOException
e
                      then FilePath -> IO FilePath
mkTempDir FilePath
prefix else IOException -> IO FilePath
forall a. IOException -> IO a
ioError IOException
e

{- Note [Deterministic base name]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The filename of temporary files, especially the basename of C files, can end
up in the output in some form, e.g. as part of linker debug information. In the
interest of bit-wise exactly reproducible compilation (#4012), the basename of
the temporary file no longer contains random information (it used to contain
the process id).

This is ok, as the temporary directory used contains the pid (see getTempDir).
-}
removeTmpDirs :: Logger -> [FilePath] -> IO ()
removeTmpDirs :: Logger -> [FilePath] -> IO ()
removeTmpDirs Logger
logger [FilePath]
ds
  = Logger -> FilePath -> FilePath -> IO () -> IO ()
forall a. Logger -> FilePath -> FilePath -> IO a -> IO a
traceCmd Logger
logger FilePath
"Deleting temp dirs"
             (FilePath
"Deleting: " FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ [FilePath] -> FilePath
unwords [FilePath]
ds)
             ((FilePath -> IO ()) -> [FilePath] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Logger -> (FilePath -> IO ()) -> FilePath -> IO ()
removeWith Logger
logger FilePath -> IO ()
removeDirectory) [FilePath]
ds)

removeTmpFiles :: Logger -> [FilePath] -> IO ()
removeTmpFiles :: Logger -> [FilePath] -> IO ()
removeTmpFiles Logger
logger [FilePath]
fs
  = IO () -> IO ()
warnNon (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
    Logger -> FilePath -> FilePath -> IO () -> IO ()
forall a. Logger -> FilePath -> FilePath -> IO a -> IO a
traceCmd Logger
logger FilePath
"Deleting temp files"
             (FilePath
"Deleting: " FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ [FilePath] -> FilePath
unwords [FilePath]
deletees)
             ((FilePath -> IO ()) -> [FilePath] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Logger -> (FilePath -> IO ()) -> FilePath -> IO ()
removeWith Logger
logger FilePath -> IO ()
removeFile) [FilePath]
deletees)
  where
     -- Flat out refuse to delete files that are likely to be source input
     -- files (is there a worse bug than having a compiler delete your source
     -- files?)
     --
     -- Deleting source files is a sign of a bug elsewhere, so prominently flag
     -- the condition.
    warnNon :: IO () -> IO ()
warnNon IO ()
act
     | [FilePath] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [FilePath]
non_deletees = IO ()
act
     | Bool
otherwise         = do
        Logger -> SDoc -> IO ()
putMsg Logger
logger (FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
"WARNING - NOT deleting source files:"
                   SDoc -> SDoc -> SDoc
forall doc. IsLine doc => doc -> doc -> doc
<+> [SDoc] -> SDoc
forall doc. IsLine doc => [doc] -> doc
hsep ((FilePath -> SDoc) -> [FilePath] -> [SDoc]
forall a b. (a -> b) -> [a] -> [b]
map FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text [FilePath]
non_deletees))
        IO ()
act

    ([FilePath]
non_deletees, [FilePath]
deletees) = (FilePath -> Bool) -> [FilePath] -> ([FilePath], [FilePath])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition FilePath -> Bool
isHaskellUserSrcFilename [FilePath]
fs

removeTmpSubdirs :: Logger -> [FilePath] -> IO ()
removeTmpSubdirs :: Logger -> [FilePath] -> IO ()
removeTmpSubdirs Logger
logger [FilePath]
fs
  = Logger -> FilePath -> FilePath -> IO () -> IO ()
forall a. Logger -> FilePath -> FilePath -> IO a -> IO a
traceCmd Logger
logger FilePath
"Deleting temp subdirs"
             (FilePath
"Deleting: " FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ [FilePath] -> FilePath
unwords [FilePath]
fs)
             ((FilePath -> IO ()) -> [FilePath] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Logger -> (FilePath -> IO ()) -> FilePath -> IO ()
removeWith Logger
logger FilePath -> IO ()
removeDirectory) [FilePath]
fs)

removeWith :: Logger -> (FilePath -> IO ()) -> FilePath -> IO ()
removeWith :: Logger -> (FilePath -> IO ()) -> FilePath -> IO ()
removeWith Logger
logger FilePath -> IO ()
remover FilePath
f = FilePath -> IO ()
remover FilePath
f IO () -> (IOException -> IO ()) -> IO ()
forall a. IO a -> (IOException -> IO a) -> IO a
`Exception.catchIO`
  (\IOException
e ->
   let msg :: SDoc
msg = if IOException -> Bool
isDoesNotExistError IOException
e
             then FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
"Warning: deleting non-existent" SDoc -> SDoc -> SDoc
forall doc. IsLine doc => doc -> doc -> doc
<+> FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
f
             else FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
"Warning: exception raised when deleting"
                                            SDoc -> SDoc -> SDoc
forall doc. IsLine doc => doc -> doc -> doc
<+> FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text FilePath
f SDoc -> SDoc -> SDoc
forall doc. IsLine doc => doc -> doc -> doc
<> SDoc
forall doc. IsLine doc => doc
colon
               SDoc -> SDoc -> SDoc
forall doc. IsDoc doc => doc -> doc -> doc
$$ FilePath -> SDoc
forall doc. IsLine doc => FilePath -> doc
text (IOException -> FilePath
forall a. Show a => a -> FilePath
show IOException
e)
   in Logger -> Int -> SDoc -> IO ()
debugTraceMsg Logger
logger Int
2 SDoc
msg
  )

#if defined(mingw32_HOST_OS)
-- relies on Int == Int32 on Windows
foreign import ccall unsafe "_getpid" getProcessID :: IO Int
#else
getProcessID :: IO Int
getProcessID :: IO Int
getProcessID = IO CPid
System.Posix.Internals.c_getpid IO CPid -> (CPid -> IO Int) -> IO Int
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> IO Int
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> IO Int) -> (CPid -> Int) -> CPid -> IO Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CPid -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral
#endif

-- The following three functions are from the `temporary` package.

-- | Create and use a temporary directory in the system standard temporary
-- directory.
--
-- Behaves exactly the same as 'withTempDirectory', except that the parent
-- temporary directory will be that returned by 'getTemporaryDirectory'.
withSystemTempDirectory :: String   -- ^ Directory name template. See 'openTempFile'.
                        -> (FilePath -> IO a) -- ^ Callback that can use the directory
                        -> IO a
withSystemTempDirectory :: forall a. FilePath -> (FilePath -> IO a) -> IO a
withSystemTempDirectory FilePath
template FilePath -> IO a
action =
  IO FilePath
getTemporaryDirectory IO FilePath -> (FilePath -> IO a) -> IO a
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \FilePath
tmpDir -> FilePath -> FilePath -> (FilePath -> IO a) -> IO a
forall a. FilePath -> FilePath -> (FilePath -> IO a) -> IO a
withTempDirectory FilePath
tmpDir FilePath
template FilePath -> IO a
action


-- | Create and use a temporary directory.
--
-- Creates a new temporary directory inside the given directory, making use
-- of the template. The temp directory is deleted after use. For example:
--
-- > withTempDirectory "src" "sdist." $ \tmpDir -> do ...
--
-- The @tmpDir@ will be a new subdirectory of the given directory, e.g.
-- @src/sdist.342@.
withTempDirectory :: FilePath -- ^ Temp directory to create the directory in
                  -> String   -- ^ Directory name template. See 'openTempFile'.
                  -> (FilePath -> IO a) -- ^ Callback that can use the directory
                  -> IO a
withTempDirectory :: forall a. FilePath -> FilePath -> (FilePath -> IO a) -> IO a
withTempDirectory FilePath
targetDir FilePath
template =
  IO FilePath -> (FilePath -> IO ()) -> (FilePath -> IO a) -> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
Exception.bracket
    (FilePath -> FilePath -> IO FilePath
createTempDirectory FilePath
targetDir FilePath
template)
    (IO () -> IO ()
ignoringIOErrors (IO () -> IO ()) -> (FilePath -> IO ()) -> FilePath -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> IO ()
removeDirectoryRecursive)

ignoringIOErrors :: IO () -> IO ()
ignoringIOErrors :: IO () -> IO ()
ignoringIOErrors IO ()
ioe = IO ()
ioe IO () -> (IOException -> IO ()) -> IO ()
forall a. IO a -> (IOException -> IO a) -> IO a
`Exception.catchIO` IO () -> IOException -> IO ()
forall a b. a -> b -> a
const (() -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ())


createTempDirectory :: FilePath -> String -> IO FilePath
createTempDirectory :: FilePath -> FilePath -> IO FilePath
createTempDirectory FilePath
dir FilePath
template = do
  pid <- IO Int
getProcessID
  findTempName pid
  where findTempName :: Int -> IO FilePath
findTempName Int
x = do
            let path :: FilePath
path = FilePath
dir FilePath -> ShowS
</> FilePath
template FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> FilePath
forall a. Show a => a -> FilePath
show Int
x
            FilePath -> IO ()
createDirectory FilePath
path
            FilePath -> IO FilePath
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
path
          IO FilePath -> (IOException -> IO FilePath) -> IO FilePath
forall a. IO a -> (IOException -> IO a) -> IO a
`Exception.catchIO` \IOException
e -> if IOException -> Bool
isAlreadyExistsError IOException
e
                          then Int -> IO FilePath
findTempName (Int
xInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) else IOException -> IO FilePath
forall a. IOException -> IO a
ioError IOException
e