@@ -117,7 +117,7 @@ execStmtCaptureResult ::
117117execStmtCaptureResult recorder stmt opts = do
118118 (result, (output, execResultE)) <-
119119 withCaptureResult recorder $
120- withCaptureStdHandles opts $
120+ withCaptureStdHandles recorder opts $
121121 gStrictTry (execStmt stmt opts)
122122 case execResultE of
123123 Left exc ->
@@ -146,16 +146,19 @@ execStmtCaptureResult recorder stmt opts = do
146146 where
147147 trimmed = dropWhileEnd (== ' \n ' ) output
148148
149- -- 'System.IO.Extra.withTempFile' is specialized to 'IO'.
149+ -- | Provide a fresh temporary file, removed once @k@ returns.
150+ -- ('System.IO.Extra.withTempFile' is specialized to 'IO'.)
151+ withTempFile_ :: (MonadIO m , MonadMask m ) => (FilePath -> m b ) -> m b
152+ withTempFile_ k =
153+ bracket (liftIO newTempFile) (liftIO . snd ) (k . fst )
154+
155+ -- | Like 'withTempFile_', but also read the file's contents back once @k@
156+ -- | returns.
150157withTempFile :: (MonadIO m , MonadMask m ) => (FilePath -> m b ) -> m (String , b )
151- withTempFile k = do
152- bracket
153- (liftIO newTempFile)
154- (\ (_, purgeTempFile) -> liftIO purgeTempFile)
155- (\ (tempFile, _) -> do
156- r <- k tempFile
157- o <- liftIO $ readFile' tempFile
158- pure (o, r))
158+ withTempFile k = withTempFile_ $ \ tempFile -> do
159+ r <- k tempFile
160+ o <- liftIO $ readFile' tempFile
161+ pure (o, r)
159162
160163-- | Capture the value the statement evaluates to (printed by GHCi via the
161164-- interactive print function) by writing it to a temporary file.
@@ -187,31 +190,74 @@ withCaptureResult recorder action = withTempFile $ \resultTemp -> do
187190-- may leak. base provides no per-thread standard handles, so this is
188191-- unavoidable with this approach.
189192withCaptureStdHandles ::
190- ExecOptions
193+ Recorder (WithPriority Log )
194+ -> ExecOptions
191195 -> Ghc a
192196 -> Ghc (String , a )
193- withCaptureStdHandles opts action = withTempFile $ \ outputTemp -> do
197+ withCaptureStdHandles recorder opts action =
198+ -- @outputTemp@ collects the captured @stdout@/@stderr@; its contents are the
199+ -- returned 'String'. @inputTemp@ is a separate, empty file standing in for
200+ -- @stdin@: redirecting from it (rather than closing @stdin@) means reads in
201+ -- the evaluated code hit EOF immediately -- as if the program were run with
202+ -- @< /dev/null@ -- instead of raising "handle is closed". This is portable
203+ -- (no @/dev/null@/@NUL@ path) and keeps @stdin@ open so 'captureTeardown'
204+ -- can restore it cleanly.
205+ withTempFile $ \ outputTemp ->
206+ withTempFile_ $ \ inputTemp ->
207+ withRedirectedStdHandles recorder opts outputTemp inputTemp action
208+
209+ -- | Redirect the interpreted standard handles ('captureSetup') around @action@,
210+ -- restoring them ('captureTeardown') no matter how it terminates.
211+ withRedirectedStdHandles ::
212+ Recorder (WithPriority Log )
213+ -> ExecOptions
214+ -> FilePath -- ^ File the interpreted @stdout@/@stderr@ are written to.
215+ -> FilePath -- ^ File the interpreted @stdin@ is read from.
216+ -> Ghc a
217+ -> Ghc a
218+ withRedirectedStdHandles recorder opts outputTemp inputTemp action =
194219 bracket
195- (execStmt (captureSetup outputTemp) opts)
196- -- Restore the handles no matter how the statement terminated.
197- (\ _ -> execStmt captureTeardown opts)
220+ (execStmtCheck recorder " capture setup" (captureSetup outputTemp inputTemp) opts)
221+ (\ _ -> execStmtCheck recorder " capture teardown" captureTeardown opts)
198222 (\ _ -> action)
199223
200- -- Open a temporary file and redirect the interpreted @stdout@/@stderr@ to
201- -- it, saving the original handles in interactive bindings so 'captureTeardown'
202- -- can restore them. Bound to a tuple (rather than evaluated as a bare
203- -- expression) so GHCi does not pass it to the interactive print function.
204- captureSetup :: FilePath -> String
224+ -- | Run an internal handle-redirection statement (capture setup/teardown) and
225+ -- log on failure.
226+ execStmtCheck ::
227+ Recorder (WithPriority Log )
228+ -> String
229+ -> String
230+ -> ExecOptions
231+ -> Ghc ()
232+ execStmtCheck recorder phase stmt opts = do
233+ result <- execStmt stmt opts
234+ case result of
235+ ExecComplete (Left err) _ ->
236+ logWith recorder Log. Warning $ LogEvalCaptureStdHandles phase (show err)
237+ _ -> pure ()
238+
239+ -- | Capture setup
240+ --
241+ -- Redirect the interpreted @stdout@/@stderr@ to a temporary file.
242+ --
243+ -- Redirect @stdin@ from an (empty) @inputTemp@ file.
244+ --
245+ -- The original handles are saved in interactive bindings so 'captureTeardown'
246+ -- can restore them.
247+ captureSetup :: FilePath -> FilePath -> String
205248-- Squeeze into one line (executed by GHCi).
206- captureSetup outputTemp = unwords
207- [ " (__hls_captureHandle, __hls_savedStdout, __hls_savedStderr) <- do {"
208- , " __hls_h <- System.IO.openFile" , show outputTemp, " System.IO.WriteMode;"
209- , " System.IO.hSetBuffering __hls_h System.IO.LineBuffering;"
210- , " __hls_o <- GHC.IO.Handle.hDuplicate System.IO.stdout;"
211- , " __hls_e <- GHC.IO.Handle.hDuplicate System.IO.stderr;"
212- , " GHC.IO.Handle.hDuplicateTo __hls_h System.IO.stdout;"
213- , " GHC.IO.Handle.hDuplicateTo __hls_h System.IO.stderr;"
214- , " P.return (__hls_h, __hls_o, __hls_e);"
249+ captureSetup outputTemp inputTemp = unwords
250+ [ " (__hls_captureOut, __hls_captureIn, __hls_stdout, __hls_stderr, __hls_stdin) <- do {"
251+ , " __hls_co <- System.IO.openFile" , show outputTemp, " System.IO.WriteMode;"
252+ , " System.IO.hSetBuffering __hls_co System.IO.LineBuffering;"
253+ , " __hls_ci <- System.IO.openFile" , show inputTemp, " System.IO.ReadMode;"
254+ , " __hls_so <- GHC.IO.Handle.hDuplicate System.IO.stdout;"
255+ , " __hls_se <- GHC.IO.Handle.hDuplicate System.IO.stderr;"
256+ , " __hls_si <- GHC.IO.Handle.hDuplicate System.IO.stdin;"
257+ , " GHC.IO.Handle.hDuplicateTo __hls_co System.IO.stdout;"
258+ , " GHC.IO.Handle.hDuplicateTo __hls_co System.IO.stderr;"
259+ , " GHC.IO.Handle.hDuplicateTo __hls_ci System.IO.stdin;"
260+ , " P.return (__hls_co, __hls_ci, __hls_so, __hls_se, __hls_si);"
215261 , " }"
216262 ]
217263
@@ -223,11 +269,15 @@ captureTeardown = unwords
223269 [ " __hls_restored <- do {"
224270 , " System.IO.hFlush System.IO.stdout;"
225271 , " System.IO.hFlush System.IO.stderr;"
226- , " GHC.IO.Handle.hDuplicateTo __hls_savedStdout System.IO.stdout;"
227- , " GHC.IO.Handle.hDuplicateTo __hls_savedStderr System.IO.stderr;"
228- , " System.IO.hClose __hls_savedStdout;"
229- , " System.IO.hClose __hls_savedStderr;"
230- , " System.IO.hClose __hls_captureHandle;"
272+ -- stdin is an input handle, so it has nothing to flush.
273+ , " GHC.IO.Handle.hDuplicateTo __hls_stdout System.IO.stdout;"
274+ , " GHC.IO.Handle.hDuplicateTo __hls_stderr System.IO.stderr;"
275+ , " GHC.IO.Handle.hDuplicateTo __hls_stdin System.IO.stdin;"
276+ , " System.IO.hClose __hls_stdout;"
277+ , " System.IO.hClose __hls_stderr;"
278+ , " System.IO.hClose __hls_stdin;"
279+ , " System.IO.hClose __hls_captureOut;"
280+ , " System.IO.hClose __hls_captureIn;"
231281 , " }"
232282 ]
233283
0 commit comments