Skip to content

Commit db321de

Browse files
mgsloandbaynard
authored andcommitted
"stack ghci" no longer deletes generated tmp files on exit #3821
1 parent 979e613 commit db321de

File tree

2 files changed

+58
-40
lines changed

2 files changed

+58
-40
lines changed

src/Stack/Ghci.hs

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Stack.Ghci
2020
import Stack.Prelude hiding (Display (..))
2121
import Control.Monad.State.Strict (State, execState, get, modify)
2222
import qualified Data.ByteString.Char8 as S8
23+
import qualified Data.ByteString.Lazy as LBS
2324
import Data.List
2425
import qualified Data.Map.Strict as M
2526
import qualified Data.Set as S
@@ -51,6 +52,7 @@ import Stack.Types.PackageName
5152
import Stack.Types.Runner
5253
import System.IO (putStrLn, putStr, getLine)
5354
import RIO.Process (HasProcessContext, execSpawn, proc, readProcess_)
55+
import System.IO.Temp (getCanonicalTemporaryDirectory)
5456

5557
#ifndef WINDOWS
5658
import qualified System.Posix.Files as Posix
@@ -387,32 +389,62 @@ runGhci GhciOpts{..} targets mainIsTargets pkgs extraFiles exposePackages = do
387389
$ runGrabFirstLine (fromMaybe (compilerExeName wc) ghciGhcCommand) ["--version"]
388390
return $ "Intero" `isPrefixOf` output
389391
_ -> return False
390-
withSystemTempDir "ghci" $ \tmpDirectory -> do
391-
macrosOptions <- writeMacrosFile tmpDirectory pkgs
392-
if ghciNoLoadModules
393-
then execGhci macrosOptions
394-
else do
395-
checkForDuplicateModules pkgs
396-
isIntero <- checkIsIntero
397-
bopts <- view buildOptsL
398-
mainFile <- figureOutMainFile bopts mainIsTargets targets pkgs
399-
scriptPath <- writeGhciScript tmpDirectory (renderScript isIntero pkgs mainFile ghciOnlyMain extraFiles)
400-
execGhci (macrosOptions ++ ["-ghci-script=" <> toFilePath scriptPath])
392+
-- Since usage of 'exec' does not return, we cannot do any cleanup
393+
-- on ghci exit. So, instead leave the generated files. To make this
394+
-- more efficient and avoid gratuitous generation of garbage, the
395+
-- file names are determined by hashing. This also has the nice side
396+
-- effect of making it possible to copy the ghci invocation out of
397+
-- the log and have it still work.
398+
tmpDirectory <-
399+
(</> $(mkRelDir "haskell-stack-ghci")) <$>
400+
(parseAbsDir =<< liftIO getCanonicalTemporaryDirectory)
401+
ensureDir tmpDirectory
402+
macrosOptions <- writeMacrosFile tmpDirectory pkgs
403+
if ghciNoLoadModules
404+
then execGhci macrosOptions
405+
else do
406+
checkForDuplicateModules pkgs
407+
isIntero <- checkIsIntero
408+
bopts <- view buildOptsL
409+
mainFile <- figureOutMainFile bopts mainIsTargets targets pkgs
410+
scriptOptions <- writeGhciScript tmpDirectory (renderScript isIntero pkgs mainFile ghciOnlyMain extraFiles)
411+
execGhci (macrosOptions ++ scriptOptions)
401412

402413
writeMacrosFile :: HasRunner env => Path Abs Dir -> [GhciPkgInfo] -> RIO env [String]
403-
writeMacrosFile tmpDirectory packages = do
404-
preprocessCabalMacros packages macrosFile
405-
where
406-
macrosFile = tmpDirectory </> $(mkRelFile "cabal_macros.h")
414+
writeMacrosFile tmpDirectory pkgs = do
415+
fps <- fmap (nubOrd . catMaybes . concat) $
416+
forM pkgs $ \pkg -> forM (ghciPkgOpts pkg) $ \(_, bio) -> do
417+
let cabalMacros = bioCabalMacros bio
418+
exists <- liftIO $ doesFileExist cabalMacros
419+
if exists
420+
then return $ Just cabalMacros
421+
else do
422+
prettyWarnL ["Didn't find expected autogen file:", display cabalMacros]
423+
return Nothing
424+
files <- liftIO $ mapM (S8.readFile . toFilePath) fps
425+
if null files then return [] else do
426+
out <- liftIO $ writeHashedFile tmpDirectory $(mkRelFile "cabal_macros.h") $
427+
S8.concat $ map (<> "\n#undef CURRENT_PACKAGE_KEY\n#undef CURRENT_COMPONENT_ID\n") files
428+
return ["-optP-include", "-optP" <> toFilePath out]
407429

408-
writeGhciScript :: (MonadIO m) => Path Abs Dir -> GhciScript -> m (Path Abs File)
430+
writeGhciScript :: (MonadIO m) => Path Abs Dir -> GhciScript -> m [String]
409431
writeGhciScript tmpDirectory script = do
410-
liftIO $ scriptToFile scriptPath script
432+
scriptPath <- liftIO $ writeHashedFile tmpDirectory $(mkRelFile "ghci-script") $
433+
LBS.toStrict $ scriptToLazyByteString script
434+
let scriptFilePath = toFilePath scriptPath
411435
setScriptPerms scriptFilePath
412-
return scriptPath
413-
where
414-
scriptPath = tmpDirectory </> $(mkRelFile "ghci-script")
415-
scriptFilePath = toFilePath scriptPath
436+
return ["-ghci-script=" <> scriptFilePath]
437+
438+
writeHashedFile :: Path Abs Dir -> Path Rel File -> ByteString -> IO (Path Abs File)
439+
writeHashedFile tmpDirectory relFile contents = do
440+
relSha <- shaPathForBytes contents
441+
let outDir = tmpDirectory </> relSha
442+
outFile = outDir </> relFile
443+
alreadyExists <- doesFileExist outFile
444+
unless alreadyExists $ do
445+
ensureDir outDir
446+
S8.writeFile (toFilePath outFile) contents
447+
return outFile
416448

417449
renderScript :: Bool -> [GhciPkgInfo] -> Maybe (Path Abs File) -> Bool -> [Path Abs File] -> GhciScript
418450
renderScript isIntero pkgs mainFile onlyMain extraFiles = do
@@ -808,23 +840,6 @@ getExtraLoadDeps loadAllDeps sourceMap targets =
808840
(_, Just PSIndex{}) -> return loadAllDeps
809841
(_, _) -> return False
810842

811-
preprocessCabalMacros :: HasRunner env => [GhciPkgInfo] -> Path Abs File -> RIO env [String]
812-
preprocessCabalMacros pkgs out = do
813-
fps <- fmap (nubOrd . catMaybes . concat) $
814-
forM pkgs $ \pkg -> forM (ghciPkgOpts pkg) $ \(_, bio) -> do
815-
let cabalMacros = bioCabalMacros bio
816-
exists <- liftIO $ doesFileExist cabalMacros
817-
if exists
818-
then return $ Just cabalMacros
819-
else do
820-
prettyWarnL ["Didn't find expected autogen file:", display cabalMacros]
821-
return Nothing
822-
files <- liftIO $ mapM (S8.readFile . toFilePath) fps
823-
if null files then return [] else do
824-
liftIO $ S8.writeFile (toFilePath out) $ S8.concat $
825-
map (<> "\n#undef CURRENT_PACKAGE_KEY\n#undef CURRENT_COMPONENT_ID\n") files
826-
return ["-optP-include", "-optP" <> toFilePath out]
827-
828843
setScriptPerms :: MonadIO m => FilePath -> m ()
829844
#ifdef WINDOWS
830845
setScriptPerms _ = do

src/Stack/Types/Config.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ module Stack.Types.Config
125125
,platformGhcVerOnlyRelDir
126126
,useShaPathOnWindows
127127
,shaPath
128+
,shaPathForBytes
128129
,workDirL
129130
-- * Command-specific types
130131
-- ** Eval
@@ -1301,10 +1302,12 @@ useShaPathOnWindows =
13011302
#endif
13021303

13031304
shaPath :: (IsPath Rel t, MonadThrow m) => Path Rel t -> m (Path Rel t)
1304-
shaPath
1305+
shaPath = shaPathForBytes . encodeUtf8 . T.pack . toFilePath
1306+
1307+
shaPathForBytes :: (IsPath Rel t, MonadThrow m) => ByteString -> m (Path Rel t)
1308+
shaPathForBytes
13051309
= parsePath . S8.unpack . S8.take 8
13061310
. Mem.convertToBase Mem.Base16 . hashWith SHA1
1307-
. encodeUtf8 . T.pack . toFilePath
13081311

13091312
-- TODO: Move something like this into the path package. Consider
13101313
-- subsuming path-io's 'AnyPath'?

0 commit comments

Comments
 (0)