Skip to content

Commit 150432f

Browse files
committed
Merge branch 'fix-stack-ghci-ctrl-c-3821' of https://github.com/dbaynard/stack
2 parents 6da921b + 62ca009 commit 150432f

File tree

3 files changed

+67
-43
lines changed

3 files changed

+67
-43
lines changed

ChangeLog.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ Other enhancements:
7878

7979
Bug fixes:
8080

81+
* `stack ghci` now replaces the stack process with ghci. This improves
82+
signal handling behavior. In particular, handling of Ctrl-C. To make
83+
this possible, the generated files are now left behind after exit.
84+
The paths are based on hashing file contents, and it's stored in the
85+
system temporary directory, so this shouldn't result in too much
86+
garbage. See
87+
[#3821](https://github.com/commercialhaskell/stack/issues/3821).
8188

8289
## v1.6.5
8390

@@ -141,7 +148,6 @@ Bug fixes:
141148
[hackage-security #187](https://github.com/haskell/hackage-security/issues/187)
142149
and [#3073](https://github.com/commercialhaskell/stack/issues/3073).
143150

144-
145151
## v1.6.3.1
146152

147153
Hackage-only release with no user facing changes (updated to build with

src/Stack/Ghci.hs

Lines changed: 55 additions & 40 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
@@ -31,6 +32,7 @@ import Path
3132
import Path.Extra (toFilePathNoTrailingSep)
3233
import Path.IO hiding (withSystemTempDir)
3334
import qualified RIO
35+
import RIO.Process (HasProcessContext, exec, proc, readProcess_)
3436
import Stack.Build
3537
import Stack.Build.Installed
3638
import Stack.Build.Source
@@ -50,7 +52,7 @@ import Stack.Types.PackageIdentifier
5052
import Stack.Types.PackageName
5153
import Stack.Types.Runner
5254
import System.IO (putStrLn, putStr, getLine)
53-
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
@@ -366,7 +368,7 @@ runGhci GhciOpts{..} targets mainIsTargets pkgs extraFiles exposePackages = do
366368
mconcat (intersperse ", " (map (RIO.display . ghciPkgName) pkgs))
367369
let execGhci extras = do
368370
menv <- liftIO $ configProcessContextSettings config defaultEnvSettings
369-
withProcessContext menv $ execSpawn
371+
withProcessContext menv $ exec
370372
(fromMaybe (compilerExeName wc) ghciGhcCommand)
371373
(("--interactive" : ) $
372374
-- This initial "-i" resets the include directories to
@@ -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
@@ -809,23 +841,6 @@ getExtraLoadDeps loadAllDeps sourceMap targets =
809841
(_, Just PSIndex{}) -> return loadAllDeps
810842
(_, _) -> return False
811843

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

13091310
shaPath :: (IsPath Rel t, MonadThrow m) => Path Rel t -> m (Path Rel t)
1310-
shaPath
1311+
shaPath = shaPathForBytes . encodeUtf8 . T.pack . toFilePath
1312+
1313+
shaPathForBytes :: (IsPath Rel t, MonadThrow m) => ByteString -> m (Path Rel t)
1314+
shaPathForBytes
13111315
= parsePath . S8.unpack . S8.take 8
13121316
. Mem.convertToBase Mem.Base16 . hashWith SHA1
1313-
. encodeUtf8 . T.pack . toFilePath
13141317

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

0 commit comments

Comments
 (0)