1
- {-# LANGUAGE TemplateHaskell #-}
2
1
{-# LANGUAGE TupleSections #-}
2
+ {-# LANGUAGE BangPatterns #-}
3
3
module HIE.Bios.Cradle (
4
4
findCradle
5
5
, loadCradle
@@ -15,18 +15,26 @@ import System.Directory hiding (findFile)
15
15
import Control.Monad.Trans.Maybe
16
16
import System.FilePath
17
17
import Control.Monad
18
- import Control.Monad.IO.Class
19
18
import System.Info.Extra
19
+ import Control.Monad.IO.Class
20
+ import System.Environment
20
21
import Control.Applicative ((<|>) )
21
- import Data.FileEmbed
22
22
import System.IO.Temp
23
23
import Data.List
24
24
import Data.Ord (Down (.. ))
25
25
26
26
import System.PosixCompat.Files
27
+ import HIE.Bios.Wrappers
28
+ import System.IO
29
+ import Control.DeepSeq
27
30
28
31
import Data.Version (showVersion )
29
32
import Paths_hie_bios
33
+ import Data.Conduit.Process
34
+ import qualified Data.Conduit.Combinators as C
35
+ import qualified Data.Conduit as C
36
+ import qualified Data.Conduit.Text as C
37
+ import Data.Text (unpack )
30
38
----------------------------------------------------------------
31
39
32
40
-- | Given root\/foo\/bar.hs, return root\/hie.yaml, or wherever the yaml file was found.
@@ -81,9 +89,9 @@ addCradleDeps deps c =
81
89
where
82
90
addActionDeps :: CradleAction -> CradleAction
83
91
addActionDeps ca =
84
- ca { runCradle = \ fp ->
92
+ ca { runCradle = \ l fp ->
85
93
(fmap (\ (ComponentOptions os' ds) -> ComponentOptions os' (ds `union` deps)))
86
- <$> runCradle ca fp }
94
+ <$> runCradle ca l fp }
87
95
88
96
implicitConfig :: FilePath -> MaybeT IO (CradleConfig , FilePath )
89
97
implicitConfig fp = do
@@ -125,7 +133,7 @@ defaultCradle cur_dir =
125
133
{ cradleRootDir = cur_dir
126
134
, cradleOptsProg = CradleAction
127
135
{ actionName = " default"
128
- , runCradle = const $ return (CradleSuccess (ComponentOptions [] [] ))
136
+ , runCradle = \ _ _ -> return (CradleSuccess (ComponentOptions [] [] ))
129
137
}
130
138
}
131
139
@@ -138,7 +146,7 @@ noneCradle cur_dir =
138
146
{ cradleRootDir = cur_dir
139
147
, cradleOptsProg = CradleAction
140
148
{ actionName = " none"
141
- , runCradle = const $ return CradleNone
149
+ , runCradle = \ _ _ -> return CradleNone
142
150
}
143
151
}
144
152
@@ -151,18 +159,19 @@ multiCradle cur_dir cs =
151
159
{ cradleRootDir = cur_dir
152
160
, cradleOptsProg = CradleAction
153
161
{ actionName = " multi"
154
- , runCradle = canonicalizePath >=> multiAction cur_dir cs
162
+ , runCradle = \ l fp -> canonicalizePath fp >>= multiAction cur_dir cs l
155
163
}
156
164
}
157
165
158
166
multiAction :: FilePath
159
167
-> [(FilePath , CradleConfig )]
168
+ -> LoggingFunction
160
169
-> FilePath
161
170
-> IO (CradleLoadResult ComponentOptions )
162
- multiAction cur_dir cs cur_fp = selectCradle =<< canonicalizeCradles
171
+ multiAction cur_dir cs l cur_fp = selectCradle =<< canonicalizeCradles
163
172
164
173
where
165
- err_msg = unlines $ [" Multi Cradle: No prefixes matched"
174
+ err_msg = [" Multi Cradle: No prefixes matched"
166
175
, " pwd: " ++ cur_dir
167
176
, " filepath" ++ cur_fp
168
177
, " prefixes:"
@@ -180,7 +189,7 @@ multiAction cur_dir cs cur_fp = selectCradle =<< canonicalizeCradles
180
189
return (CradleFail (CradleError ExitSuccess err_msg))
181
190
selectCradle ((p, c): css) =
182
191
if p `isPrefixOf` cur_fp
183
- then runCradle (cradleOptsProg (getCradle (c, cur_dir))) cur_fp
192
+ then runCradle (cradleOptsProg (getCradle (c, cur_dir))) l cur_fp
184
193
else selectCradle css
185
194
186
195
@@ -192,7 +201,7 @@ directCradle wdir args =
192
201
{ cradleRootDir = wdir
193
202
, cradleOptsProg = CradleAction
194
203
{ actionName = " direct"
195
- , runCradle = const $ return (CradleSuccess (ComponentOptions args [] ))
204
+ , runCradle = \ _ _ -> return (CradleSuccess (ComponentOptions args [] ))
196
205
}
197
206
}
198
207
@@ -214,28 +223,30 @@ biosCradle wdir biosProg biosDepsProg =
214
223
biosWorkDir :: FilePath -> MaybeT IO FilePath
215
224
biosWorkDir = findFileUpwards (" .hie-bios" == )
216
225
217
- biosDepsAction :: Maybe FilePath -> IO [FilePath ]
218
- biosDepsAction (Just biosDepsProg) = do
226
+ biosDepsAction :: LoggingFunction -> FilePath -> Maybe FilePath -> IO [FilePath ]
227
+ biosDepsAction l wdir (Just biosDepsProg) = do
219
228
biosDeps' <- canonicalizePath biosDepsProg
220
- (ex, sout, serr) <- readProcessWithExitCode biosDeps' [] []
229
+ (ex, sout, serr, args ) <- readProcessWithOutputFile l wdir biosDeps' []
221
230
case ex of
222
231
ExitFailure _ -> error $ show (ex, sout, serr)
223
- ExitSuccess -> return ( lines sout)
224
- biosDepsAction Nothing = return []
232
+ ExitSuccess -> return args
233
+ biosDepsAction _ _ Nothing = return []
225
234
226
235
biosAction :: FilePath
227
236
-> FilePath
228
237
-> Maybe FilePath
238
+ -> LoggingFunction
229
239
-> FilePath
230
240
-> IO (CradleLoadResult ComponentOptions )
231
- biosAction _wdir bios bios_deps fp = do
241
+ biosAction wdir bios bios_deps l fp = do
232
242
bios' <- canonicalizePath bios
233
- (ex, res, std) <- readProcessWithExitCode bios' [fp] []
234
- deps <- biosDepsAction bios_deps
235
- -- Output from the program should be delimited by newlines.
243
+ (ex, _stdo, std, res) <- readProcessWithOutputFile l wdir bios' [fp]
244
+ deps <- biosDepsAction l wdir bios_deps
245
+ -- Output from the program should be written to the output file and
246
+ -- delimited by newlines.
236
247
-- Execute the bios action and add dependencies of the cradle.
237
248
-- Removes all duplicates.
238
- return $ makeCradleResult (ex, std, lines res) deps
249
+ return $ makeCradleResult (ex, std, res) deps
239
250
240
251
------------------------------------------------------------------------
241
252
-- Cabal Cradle
@@ -262,31 +273,18 @@ findCabalFiles wdir = do
262
273
dirContent <- listDirectory wdir
263
274
return $ filter ((== " .cabal" ) . takeExtension) dirContent
264
275
265
- cabalWrapper :: String
266
- cabalWrapper = $ (embedStringFile " wrappers/cabal" )
267
276
268
- cabalWrapperHs :: String
269
- cabalWrapperHs = $ (embedStringFile " wrappers/cabal.hs" )
270
-
271
- processCabalWrapperArgs :: String -> Maybe [String ]
277
+ processCabalWrapperArgs :: [String ] -> Maybe [String ]
272
278
processCabalWrapperArgs args =
273
- case lines args of
274
- [ dir, ghc_args] ->
279
+ case args of
280
+ ( dir: ghc_args) ->
275
281
let final_args =
276
282
removeVerbosityOpts
277
283
$ removeInteractive
278
284
$ map (fixImportDirs dir)
279
- $ limited ghc_args
285
+ $ ghc_args
280
286
in Just final_args
281
287
_ -> Nothing
282
- where
283
- limited :: String -> [String ]
284
- limited = unfoldr $ \ argstr ->
285
- if null argstr
286
- then Nothing
287
- else
288
- let (arg, argstr') = break (== ' \NUL ' ) argstr
289
- in Just (arg, drop 1 argstr')
290
288
291
289
-- generate a fake GHC that can be passed to cabal
292
290
-- when run with --interactive, it will print out its
@@ -315,20 +313,20 @@ getCabalWrapperTool = do
315
313
_check <- readFile wrapper_fp
316
314
return wrapper_fp
317
315
318
- cabalAction :: FilePath -> Maybe String -> FilePath -> IO (CradleLoadResult ComponentOptions )
319
- cabalAction work_dir mc _fp = do
316
+ cabalAction :: FilePath -> Maybe String -> LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions )
317
+ cabalAction work_dir mc l _fp = do
320
318
wrapper_fp <- getCabalWrapperTool
321
- let cab_args = [" v2-repl" , " -v0 " , " --disable-documentation " , " - -with-compiler" , wrapper_fp]
319
+ let cab_args = [" v2-repl" , " --with-compiler" , wrapper_fp]
322
320
++ [component_name | Just component_name <- [mc]]
323
- (ex, args, stde) <-
324
- readProcessWithExitCodeInDirectory work_dir " cabal" cab_args []
325
-
321
+ (ex, output, stde, args) <-
322
+ readProcessWithOutputFile l work_dir " cabal" cab_args
326
323
deps <- cabalCradleDependencies work_dir
327
324
case processCabalWrapperArgs args of
328
325
Nothing -> pure $ CradleFail (CradleError ex
329
- (unlines [" Failed to parse result of calling cabal"
330
- , stde
331
- , args]))
326
+ [" Failed to parse result of calling cabal"
327
+ , unlines output
328
+ , unlines stde
329
+ , unlines args])
332
330
Just final_args -> pure $ makeCradleResult (ex, stde, final_args) deps
333
331
334
332
removeInteractive :: [String ] -> [String ]
@@ -367,25 +365,25 @@ stackCradle wdir =
367
365
368
366
stackCradleDependencies :: FilePath -> IO [FilePath ]
369
367
stackCradleDependencies wdir = do
370
- cabalFiles <- findCabalFiles wdir
371
- return $ cabalFiles ++ [" package.yaml" , " stack.yaml" ]
368
+ cabalFiles <- findCabalFiles wdir
369
+ return $ cabalFiles ++ [" package.yaml" , " stack.yaml" ]
372
370
373
- stackAction :: FilePath -> FilePath -> IO (CradleLoadResult ComponentOptions )
374
- stackAction work_dir fp = do
371
+ stackAction :: FilePath -> LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions )
372
+ stackAction work_dir l fp = do
375
373
-- Same wrapper works as with cabal
376
374
wrapper_fp <- getCabalWrapperTool
377
- (ex1, args , stde) <-
378
- readProcessWithExitCodeInDirectory work_dir " stack" [" repl" , " --silent " , " --no-load" , " --with-ghc" , wrapper_fp, fp ] [ ]
379
- (ex2, pkg_args, stdr) <-
380
- readProcessWithExitCodeInDirectory work_dir " stack" [" path" , " --ghc-package-path" ] [ ]
381
- let split_pkgs = splitSearchPath ( init pkg_args)
375
+ (ex1, _stdo , stde, args ) <-
376
+ readProcessWithOutputFile l work_dir " stack" [" repl" , " --no-nix-pure " , " --no-load" , " --with-ghc" , wrapper_fp, fp ]
377
+ (ex2, pkg_args, stdr, _ ) <-
378
+ readProcessWithOutputFile l work_dir " stack" [" path" , " --ghc-package-path" ]
379
+ let split_pkgs = concatMap splitSearchPath pkg_args
382
380
pkg_ghc_args = concatMap (\ p -> [" -package-db" , p] ) split_pkgs
383
381
deps <- stackCradleDependencies work_dir
384
382
return $ case processCabalWrapperArgs args of
385
- Nothing -> CradleFail (CradleError ex1
386
- (unlines [ " Failed to parse result of calling cabal"
387
- , stde
388
- , args]) )
383
+ Nothing -> CradleFail (CradleError ex1 $
384
+ (" Failed to parse result of calling cabal" :
385
+ stde)
386
+ ++ args)
389
387
390
388
Just ghc_args -> makeCradleResult (combineExitCodes [ex1, ex2], stde ++ stdr, ghc_args ++ pkg_ghc_args) deps
391
389
@@ -435,7 +433,7 @@ rulesHaskellAction work_dir fp = do
435
433
setFileMode wrapper_fp accessModes
436
434
let rel_path = makeRelative work_dir fp
437
435
(ex, args, stde) <-
438
- readProcessWithExitCodeInDirectory work_dir wrapper_fp [rel_path] []
436
+ readProcessWithOutputFile work_dir wrapper_fp [rel_path] []
439
437
let args' = filter (/= '\'') args
440
438
let args'' = filter (/= "\"$GHCI_LOCATION\"") (words args')
441
439
deps <- rulesHaskellCradleDependencies work_dir
@@ -471,7 +469,7 @@ obeliskCradle wdir =
471
469
obeliskAction :: FilePath -> FilePath -> IO (CradleLoadResult ComponentOptions)
472
470
obeliskAction work_dir _fp = do
473
471
(ex, args, stde) <-
474
- readProcessWithExitCodeInDirectory work_dir "ob" ["ide-args"] []
472
+ readProcessWithOutputFile work_dir "ob" ["ide-args"] []
475
473
476
474
o_deps <- obeliskCradleDependencies work_dir
477
475
return (makeCradleResult (ex, stde, words args) o_deps )
@@ -502,15 +500,34 @@ findFile p dir = do
502
500
getFiles = filter p <$> getDirectoryContents dir
503
501
doesPredFileExist file = doesFileExist $ dir </> file
504
502
505
- -- | Call a process with the given arguments and the given stdin
506
- -- in the given working directory.
507
- readProcessWithExitCodeInDirectory
508
- :: FilePath -> FilePath -> [String ] -> String -> IO (ExitCode , String , String )
509
- readProcessWithExitCodeInDirectory work_dir fp args stdin =
510
- let process = (proc fp args) { cwd = Just work_dir }
511
- in readCreateProcessWithExitCode process stdin
512
-
513
- makeCradleResult :: (ExitCode , String , [String ]) -> [FilePath ] -> CradleLoadResult ComponentOptions
503
+ -- | Call a process with the given arguments
504
+ -- * A special file is created for the process to write to, the process can discover the name of
505
+ -- the file by reading the @HIE_BIOS_OUTPUT@ environment variable. The contents of this file is
506
+ -- returned by the function.
507
+ -- * The logging function is called every time the process emits anything to stdout or stderr.
508
+ -- it can be used to report progress of the process to a user.
509
+ -- * The process is executed in the given directory
510
+ readProcessWithOutputFile
511
+ :: LoggingFunction
512
+ -> FilePath
513
+ -> FilePath
514
+ -> [String ]
515
+ -> IO (ExitCode , [String ], [String ], [String ])
516
+ readProcessWithOutputFile l work_dir fp args = withSystemTempFile " bios-output" $ \ output_file h -> do
517
+ hSetBuffering h LineBuffering
518
+ old_env <- getEnvironment
519
+ -- Pipe stdout directly into the logger
520
+ let process = (proc fp args) { cwd = Just work_dir
521
+ , env = Just ((" HIE_BIOS_OUTPUT" , output_file) : old_env)
522
+ }
523
+ -- Windows line endings are not converted so you have to filter out `'r` characters
524
+ loggingConduit = (C. decodeUtf8 C. .| C. lines C. .| C. filterE (/= ' \r ' ) C. .| C. map unpack C. .| C. iterM l C. .| C. sinkList)
525
+ (ex, stdo, stde) <- sourceProcessWithStreams process mempty loggingConduit loggingConduit
526
+ ! res <- force <$> hGetContents h
527
+ return (ex, stdo, stde, lines (filter (/= ' \r ' ) res))
528
+
529
+
530
+ makeCradleResult :: (ExitCode , [String ], [String ]) -> [FilePath ] -> CradleLoadResult ComponentOptions
514
531
makeCradleResult (ex, err, gopts) deps =
515
532
case ex of
516
533
ExitFailure _ -> CradleFail (CradleError ex err)
0 commit comments