@@ -20,6 +20,7 @@ module Stack.Ghci
2020import Stack.Prelude hiding (Display (.. ))
2121import Control.Monad.State.Strict (State , execState , get , modify )
2222import qualified Data.ByteString.Char8 as S8
23+ import qualified Data.ByteString.Lazy as LBS
2324import Data.List
2425import qualified Data.Map.Strict as M
2526import qualified Data.Set as S
@@ -51,6 +52,7 @@ import Stack.Types.PackageName
5152import Stack.Types.Runner
5253import System.IO (putStrLn , putStr , getLine )
5354import RIO.Process (HasProcessContext , execSpawn , proc , readProcess_ )
55+ import System.IO.Temp (getCanonicalTemporaryDirectory )
5456
5557#ifndef WINDOWS
5658import 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
402413writeMacrosFile :: 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 ]
409431writeGhciScript 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
417449renderScript :: Bool -> [GhciPkgInfo ] -> Maybe (Path Abs File ) -> Bool -> [Path Abs File ] -> GhciScript
418450renderScript 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-
828843setScriptPerms :: MonadIO m => FilePath -> m ()
829844#ifdef WINDOWS
830845setScriptPerms _ = do
0 commit comments