@@ -14,7 +14,7 @@ module Obelisk.Command.Run where
14import Control.Arrow ((&&&))
14import Control.Arrow ((&&&))
15import Control.Exception (Exception, bracket)
15import Control.Exception (Exception, bracket)
16import Control.Lens (ifor, (.~), (&))
16import Control.Lens (ifor, (.~), (&))
17−import Control.Monad (filterM, unless, void)
17+import Control.Monad (filterM)
18import Control.Monad.Except (runExceptT, throwError)
18import Control.Monad.Except (runExceptT, throwError)
19import Control.Monad.IO.Class (liftIO)
19import Control.Monad.IO.Class (liftIO)
20import Control.Monad.Reader (MonadIO)
20import Control.Monad.Reader (MonadIO)
@@ -25,12 +25,14 @@ import Data.Default (def)
25import Data.Either (partitionEithers)
25import Data.Either (partitionEithers)
26import Data.Foldable (fold, for_, toList)
26import Data.Foldable (fold, for_, toList)
27import Data.Functor.Identity (runIdentity)
27import Data.Functor.Identity (runIdentity)
28+import Data.List (intercalate)
28import Data.List.NonEmpty (NonEmpty)
29import Data.List.NonEmpty (NonEmpty)
29import qualified Data.List.NonEmpty as NE
30import qualified Data.List.NonEmpty as NE
30import Data.Map (Map)
31import Data.Map (Map)
31import qualified Data.Map as Map
32import qualified Data.Map as Map
32import qualified Data.Map.Monoidal as MMap
33import qualified Data.Map.Monoidal as MMap
33−import Data.Maybe
34+import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
35+import Data.Ord (comparing)
34import Data.Set (Set)
36import Data.Set (Set)
35import qualified Data.Set as Set
37import qualified Data.Set as Set
36import Data.String.Here.Interpolated (i)
38import Data.String.Here.Interpolated (i)
@@ -44,17 +46,17 @@ import Distribution.Compiler (CompilerFlavor(..))
44import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
46import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
45import Distribution.Parsec.ParseResult (runParseResult)
47import Distribution.Parsec.ParseResult (runParseResult)
46import qualified Distribution.System as Dist
48import qualified Distribution.System as Dist
47−import Distribution.Types.BuildInfo
49+import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options)
48−import Distribution.Types.CondTree
50+import Distribution.Types.CondTree (simplifyCondTree)
49−import Distribution.Types.GenericPackageDescription
51+import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
50−import Distribution.Types.Library
52+import Distribution.Types.Library (libBuildInfo)
51−import Distribution.Utils.Generic
53+import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
52import qualified Distribution.Parsec.Common as Dist
54import qualified Distribution.Parsec.Common as Dist
53import qualified Hpack.Config as Hpack
55import qualified Hpack.Config as Hpack
54import qualified Hpack.Render as Hpack
56import qualified Hpack.Render as Hpack
55import qualified Hpack.Yaml as Hpack
57import qualified Hpack.Yaml as Hpack
56−import Language.Haskell.Extension
58+import Language.Haskell.Extension (Extension, Language)
57−import Network.Socket hiding (Debug)
59+import qualified Network.Socket as Socket
58import System.Directory
60import System.Directory
59import System.Environment (getExecutablePath)
61import System.Environment (getExecutablePath)
60import System.FilePath
62import System.FilePath
@@ -64,19 +66,18 @@ import System.IO.Temp (withSystemTempDirectory)
64import Obelisk.App (MonadObelisk)
66import Obelisk.App (MonadObelisk)
65import Obelisk.CliApp (
67import Obelisk.CliApp (
66 Severity (..),
68 Severity (..),
71 readCreateProcessWithExitCode,
72 readCreateProcessWithExitCode,
72 readProcessAndLogStderr,
73 readProcessAndLogStderr,
74 setDelegateCtlc,
76 setDelegateCtlc,
76 withSpinner,
77 withSpinner,
78import Obelisk.Command.Nix
79import Obelisk.Command.Nix
79−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets)
80+import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
80import Obelisk.Command.Thunk (attrCacheFileName)
81import Obelisk.Command.Thunk (attrCacheFileName)
81import Obelisk.Command.Utils (findExePath, ghcidExePath)
82import Obelisk.Command.Utils (findExePath, ghcidExePath)
@@ -93,9 +94,11 @@ data CabalPackageInfo = CabalPackageInfo
93 -- ^ List of globally set languages of the library component
94 -- ^ List of globally set languages of the library component
94 , _cabalPackageInfo_compilerOptions :: [(CompilerFlavor, [String])]
95 , _cabalPackageInfo_compilerOptions :: [(CompilerFlavor, [String])]
95 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
96 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
97+ , _cabalPackageInfo_cppOptions :: [String]
98+ -- ^ List of CPP (C Preprocessor) options (e.g. the "cpp-options" field of the cabal file)
98−-- | 'Bool' with a better name for it's purpose.
101+-- | 'Bool' with a better name for its purpose.
99data Interpret = Interpret_Interpret | Interpret_NoInterpret deriving (Eq, Ord, Show)
102data Interpret = Interpret_Interpret | Interpret_NoInterpret deriving (Eq, Ord, Show)
101textInterpret :: Interpret -> Text
104textInterpret :: Interpret -> Text
@@ -109,9 +112,6 @@ data PathTree a = PathTree_Node
109 (Map FilePath (PathTree a)) -- Branches to deeper leaves
112 (Map FilePath (PathTree a)) -- Branches to deeper leaves
110 deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
113 deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
112−emptyPathTree :: PathTree a
113−emptyPathTree = PathTree_Node Nothing mempty
115-- | 2D ASCII drawing of a 'PathTree'. Adapted from Data.Tree.draw.
115-- | 2D ASCII drawing of a 'PathTree'. Adapted from Data.Tree.draw.
116drawPathTree :: (a -> Text) -> PathTree a -> Text
116drawPathTree :: (a -> Text) -> PathTree a -> Text
117drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
117drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
@@ -151,7 +151,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
151 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
151 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
152 putLog Debug $ "Storing profiled data under base name of " <> T.pack (root </> profileBaseName)
152 putLog Debug $ "Storing profiled data under base name of " <> T.pack (root </> profileBaseName)
153 freePort <- getFreePort
153 freePort <- getFreePort
154− (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath </> "bin" </> "ob-run") $
154+ runProcess_ $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath </> "bin" </> "ob-run") $
155 [ show freePort
155 [ show freePort
156 , T.unpack assets
156 , T.unpack assets
157 , profileBaseName
157 , profileBaseName
@@ -159,17 +159,16 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
159 , "-po" <> profileBaseName
159 , "-po" <> profileBaseName
160 ] <> rtsFlags
160 ] <> rtsFlags
161 <> [ "-RTS" ]
161 <> [ "-RTS" ]
162− void $ waitForProcess ph
164run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
163run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
165run root interpretPaths = do
164run root interpretPaths = do
166 pkgs <- getParsedLocalPkgs root interpretPaths
165 pkgs <- getParsedLocalPkgs root interpretPaths
167− withGhciScript pkgs $ \dotGhciPath -> do
168− assets <- findProjectAssets root
166+ assets <- findProjectAssets root
169− putLog Debug $ "Assets impurely loaded from: " <> assets
167+ putLog Debug $ "Assets impurely loaded from: " <> assets
170− ghciArgs <- getGhciSessionSettings pkgs root
168+ ghciArgs <- getGhciSessionSettings pkgs root True
171− freePort <- getFreePort
169+ freePort <- getFreePort
170+ withGhciScriptArgs pkgs $ \dotGhciArgs -> do
172− runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs $ Just $ unwords
171+ runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
173 [ "Obelisk.Run.run"
172 [ "Obelisk.Run.run"
174 , show freePort
173 , show freePort
175 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
174 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
@@ -180,21 +179,21 @@ run root interpretPaths = do
180runRepl :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
179runRepl :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
181runRepl root interpretPaths = do
180runRepl root interpretPaths = do
182 pkgs <- getParsedLocalPkgs root interpretPaths
181 pkgs <- getParsedLocalPkgs root interpretPaths
183− ghciArgs <- getGhciSessionSettings pkgs "."
182+ ghciArgs <- getGhciSessionSettings pkgs "." True
184− withGhciScript pkgs $ \dotGhciPath ->
183+ withGhciScriptArgs pkgs $ \dotGhciArgs ->
185− runGhciRepl root pkgs (ghciArgs <> mkGhciScriptArg dotGhciPath)
184+ runGhciRepl root pkgs (ghciArgs <> dotGhciArgs)
187runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
186runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
188runWatch root interpretPaths = do
187runWatch root interpretPaths = do
189 pkgs <- getParsedLocalPkgs root interpretPaths
188 pkgs <- getParsedLocalPkgs root interpretPaths
190− ghciArgs <- getGhciSessionSettings pkgs root
189+ ghciArgs <- getGhciSessionSettings pkgs root True
191− withGhciScript pkgs $ \dotGhciPath ->
190+ withGhciScriptArgs pkgs $ \dotGhciArgs ->
192− runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs Nothing
191+ runGhcid root True (ghciArgs <> dotGhciArgs) pkgs Nothing
194−exportGhciConfig :: MonadObelisk m => FilePath -> PathTree Interpret -> m [String]
193+exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
195−exportGhciConfig root interpretPaths = do
194+exportGhciConfig useRelativePaths root interpretPaths = do
196 pkgs <- getParsedLocalPkgs root interpretPaths
195 pkgs <- getParsedLocalPkgs root interpretPaths
197− getGhciSessionSettings pkgs "."
196+ getGhciSessionSettings pkgs "." useRelativePaths
199nixShellForInterpretPaths :: MonadObelisk m => Bool -> String -> FilePath -> PathTree Interpret -> Maybe String -> m ()
198nixShellForInterpretPaths :: MonadObelisk m => Bool -> String -> FilePath -> PathTree Interpret -> Maybe String -> m ()
200nixShellForInterpretPaths isPure shell root interpretPaths cmd = do
199nixShellForInterpretPaths isPure shell root interpretPaths cmd = do
@@ -305,15 +304,16 @@ parseCabalPackage
305 -> m (Maybe CabalPackageInfo)
304 -> m (Maybe CabalPackageInfo)
306parseCabalPackage dir = parseCabalPackage' dir >>= \case
305parseCabalPackage dir = parseCabalPackage' dir >>= \case
307 Left err -> Nothing <$ putLog Error err
306 Left err -> Nothing <$ putLog Error err
308− Right (warnings, pkgInfo) -> do
307+ Right (Just (warnings, pkgInfo)) -> do
309 for_ warnings $ putLog Warning . T.pack . show
308 for_ warnings $ putLog Warning . T.pack . show
310 pure $ Just pkgInfo
309 pure $ Just pkgInfo
310+ Right Nothing -> pure Nothing
312-- | Like 'parseCabalPackage' but returns errors and warnings directly so as to avoid 'MonadObelisk'.
312-- | Like 'parseCabalPackage' but returns errors and warnings directly so as to avoid 'MonadObelisk'.
313parseCabalPackage'
313parseCabalPackage'
314 :: (MonadIO m)
314 :: (MonadIO m)
315 => FilePath -- ^ Package directory
315 => FilePath -- ^ Package directory
316− -> m (Either T.Text ([Dist.PWarning], CabalPackageInfo))
316+ -> m (Either T.Text (Maybe ([Dist.PWarning], CabalPackageInfo)))
317parseCabalPackage' pkg = runExceptT $ do
317parseCabalPackage' pkg = runExceptT $ do
318 (cabalContents, packageFile, packageName) <- guessCabalPackageFile pkg >>= \case
318 (cabalContents, packageFile, packageName) <- guessCabalPackageFile pkg >>= \case
319 Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack pkg
319 Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack pkg
@@ -341,7 +341,7 @@ parseCabalPackage' pkg = runExceptT $ do
341 case condLibrary <$> result of
341 case condLibrary <$> result of
342 Right (Just condLib) -> do
342 Right (Just condLib) -> do
343 let (_, lib) = simplifyCondTree evalConfVar condLib
343 let (_, lib) = simplifyCondTree evalConfVar condLib
344− pure $ (warnings,) $ CabalPackageInfo
344+ pure $ Just $ (warnings,) $ CabalPackageInfo
345 { _cabalPackageInfo_packageName = T.pack packageName
345 { _cabalPackageInfo_packageName = T.pack packageName
346 , _cabalPackageInfo_packageFile = packageFile
346 , _cabalPackageInfo_packageFile = packageFile
347 , _cabalPackageInfo_packageRoot = takeDirectory packageFile
347 , _cabalPackageInfo_packageRoot = takeDirectory packageFile
@@ -353,18 +353,19 @@ parseCabalPackage' pkg = runExceptT $ do
353 , _cabalPackageInfo_defaultLanguage =
353 , _cabalPackageInfo_defaultLanguage =
354 defaultLanguage $ libBuildInfo lib
354 defaultLanguage $ libBuildInfo lib
355 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
355 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
356+ , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
357− Right Nothing -> throwError "Haskell package has no library component"
358+ Right Nothing -> pure Nothing
358 Left (_, errors) ->
359 Left (_, errors) ->
359 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
360 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
361parsePackagesOrFail :: (MonadObelisk m, Foldable f) => f FilePath -> m (NE.NonEmpty CabalPackageInfo)
362parsePackagesOrFail :: (MonadObelisk m, Foldable f) => f FilePath -> m (NE.NonEmpty CabalPackageInfo)
362parsePackagesOrFail dirs' = do
363parsePackagesOrFail dirs' = do
363− (pkgDirErrs, packageInfos') <- fmap partitionEithers $ for dirs $ \dir -> do
364+ packageInfos' <- fmap catMaybes $ for dirs $ \dir -> do
364 flip fmap (parseCabalPackage dir) $ \case
365 flip fmap (parseCabalPackage dir) $ \case
365 Just packageInfo
366 Just packageInfo
366− | _cabalPackageInfo_buildable packageInfo -> Right packageInfo
367+ | _cabalPackageInfo_buildable packageInfo -> Just packageInfo
367− _ -> Left dir
368+ _ -> Nothing
369 -- Sort duplicate packages such that we prefer shorter paths, but fall back to alphabetical ordering.
370 -- Sort duplicate packages such that we prefer shorter paths, but fall back to alphabetical ordering.
370 let packagesByName = Map.map (NE.sortBy $ comparing $ \p -> let n = _cabalPackageInfo_packageFile p in (length n, n))
371 let packagesByName = Map.map (NE.sortBy $ comparing $ \p -> let n = _cabalPackageInfo_packageFile p in (length n, n))
@@ -386,9 +387,6 @@ parsePackagesOrFail dirs' = do
386 "No valid, buildable packages found" <> (if null dirs then "" else " in " <> intercalate ", " dirs)
387 "No valid, buildable packages found" <> (if null dirs then "" else " in " <> intercalate ", " dirs)
387 Just xs -> pure xs
388 Just xs -> pure xs
389− unless (null pkgDirErrs) $
390− putLog Warning $ T.pack $ "Failed to find buildable packages in " <> intercalate ", " pkgDirErrs
392 pure packageInfos
390 pure packageInfos
394 dirs = toList dirs'
392 dirs = toList dirs'
@@ -396,13 +394,29 @@ parsePackagesOrFail dirs' = do
396packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
394packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
397packageInfoToNamePathMap = Map.fromList . map (_cabalPackageInfo_packageName &&& _cabalPackageInfo_packageRoot) . toList
395packageInfoToNamePathMap = Map.fromList . map (_cabalPackageInfo_packageName &&& _cabalPackageInfo_packageRoot) . toList
397+-- Like 'withGhciScript' but provides the precise ghci arguments to add to a ghci session
399+ :: (MonadObelisk m, Foldable f)
400+ => f CabalPackageInfo -- ^ List of packages to load into ghci
401+ -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
403+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
405+ -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
406+ -- is passed to the ghci session.
407+ loadPreludeManually =
408+ [ ":add Prelude" -- @:add@ is used because it's less noisy when there is no custom Prelude
409+ , ":set -XImplicitPrelude" -- Turn the default setting on
399-- | Create ghci configuration to load the given packages
412-- | Create ghci configuration to load the given packages
400withGhciScript
413withGhciScript
401 :: (MonadObelisk m, Foldable f)
414 :: (MonadObelisk m, Foldable f)
415+ => [String] -- ^ Commands to prefix to file
402− => f CabalPackageInfo -- ^ List of packages to load into ghci
416+ -> f CabalPackageInfo -- ^ List of packages to load into ghci
403 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
417 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
405−withGhciScript (toList -> packageInfos) f =
419+withGhciScript preCommands (toList -> packageInfos) f =
406 withSystemTempDirectory "ob-ghci" $ \fp -> do
420 withSystemTempDirectory "ob-ghci" $ \fp -> do
407 let dotGhciPath = fp </> ".ghci"
421 let dotGhciPath = fp </> ".ghci"
408 liftIO $ writeFile dotGhciPath dotGhci
422 liftIO $ writeFile dotGhciPath dotGhci
@@ -414,7 +428,8 @@ withGhciScript (toList -> packageInfos) f =
414 , [ "Backend" | "backend" `Set.member` packageNames ]
428 , [ "Backend" | "backend" `Set.member` packageNames ]
415 , [ "Frontend" | "frontend" `Set.member` packageNames ]
429 , [ "Frontend" | "frontend" `Set.member` packageNames ]
417− dotGhci = unlines
431+ dotGhci = unlines $
418 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
433 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
419 , "import qualified Obelisk.Run"
434 , "import qualified Obelisk.Run"
420 , "import qualified Frontend"
435 , "import qualified Frontend"
@@ -427,8 +442,9 @@ getGhciSessionSettings
427 :: (MonadObelisk m, Foldable f)
442 :: (MonadObelisk m, Foldable f)
428 => f CabalPackageInfo -- ^ List of packages to load into ghci
443 => f CabalPackageInfo -- ^ List of packages to load into ghci
429 -> FilePath -- ^ All paths will be relative to this path
444 -> FilePath -- ^ All paths will be relative to this path
445+ -> Bool -- ^ Use relative paths
430 -> m [String]
446 -> m [String]
431−getGhciSessionSettings (toList -> packageInfos) pathBase = do
447+getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
432 -- N.B. ghci settings do NOT support escaping in any way. To minimize the likelihood that
448 -- N.B. ghci settings do NOT support escaping in any way. To minimize the likelihood that
433 -- paths-with-spaces ruin our day, we first canonicalize everything, and then relativize
449 -- paths-with-spaces ruin our day, we first canonicalize everything, and then relativize
434 -- all paths to 'pathBase'.
450 -- all paths to 'pathBase'.
@@ -438,13 +454,15 @@ getGhciSessionSettings (toList -> packageInfos) pathBase = do
438 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
454 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
439 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
455 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
440 canonicalPkgFile <- canonicalizePath $ _cabalPackageInfo_packageFile pkg
456 canonicalPkgFile <- canonicalizePath $ _cabalPackageInfo_packageFile pkg
441− pure (canonicalPkgFile `relativeTo` canonicalPathBase, (`relativeTo` canonicalPathBase) <$> canonicalSrcDirs)
457+ pure (canonicalPkgFile `relativeTo'` canonicalPathBase, (`relativeTo'` canonicalPathBase) <$> canonicalSrcDirs)
444 $ baseGhciOptions
460 $ baseGhciOptions
445 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
461 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
446 <> concatMap (\p -> ["-optF", p]) pkgFiles
462 <> concatMap (\p -> ["-optF", p]) pkgFiles
447 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
463 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
465+ relativeTo' = if useRelativePaths then relativeTo else const
449baseGhciOptions :: [String]
467baseGhciOptions :: [String]
450baseGhciOptions =
468baseGhciOptions =
@@ -464,7 +482,7 @@ runGhciRepl root (toList -> packages) ghciArgs =
464 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
482 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
465 -- ghc that is provided by the shell in the user's project.
483 -- ghc that is provided by the shell in the user's project.
466 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
484 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
467− Just $ unwords $ "ghci" : ghciArgs -- TODO: Shell escape
485+ Just $ unwords $ fmap bashEscape $ "ghci" : ghciArgs
469-- | Run ghcid
487-- | Run ghcid
@@ -477,32 +495,28 @@ runGhcid
478runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
496runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
479 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
497 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
480− Just $ unwords $ ghcidExePath : opts -- TODO: Shell escape
498+ Just $ unwords $ fmap bashEscape $ ghcidExePath : opts
482− opts =
500+ opts = concat
484− , "--outputfile=ghcid-output.txt"
502+ , ["--outputfile=ghcid-output.txt"]
485− ] <> map (\x -> "--reload='" <> x <> "'") reloadFiles
503+ , map (\x -> "--reload=" <> x) reloadFiles
486− <> map (\x -> "--restart='" <> x <> "'") restartFiles
504+ , map (\x -> "--restart=" <> x) restartFiles
487− <> testCmd
505+ , maybe [] (\cmd -> ["--test=" <> cmd]) mcmd
488− <> ["--command='" <> unwords ("ghci" : ghciArgs) <> "'"] -- TODO: Shell escape
506+ , ["--command=" <> unwords ("ghci" : ghciArgs)]
489− testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
507+ ]
491 adjustRoot x = if chdirToRoot then makeRelative root x else x
508 adjustRoot x = if chdirToRoot then makeRelative root x else x
492 reloadFiles = map adjustRoot [root </> "config"]
509 reloadFiles = map adjustRoot [root </> "config"]
493 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
510 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
495−mkGhciScriptArg :: FilePath -> [String]
496−mkGhciScriptArg dotGhci = ["-ghci-script", dotGhci]
498−getFreePort :: MonadIO m => m PortNumber
512+getFreePort :: MonadIO m => m Socket.PortNumber
499−getFreePort = liftIO $ withSocketsDo $ do
513+getFreePort = liftIO $ Socket.withSocketsDo $ do
500− addr:_ <- getAddrInfo (Just defaultHints) (Just "127.0.0.1") (Just "0")
514+ addr:_ <- Socket.getAddrInfo (Just Socket.defaultHints) (Just "127.0.0.1") (Just "0")
501− bracket (open addr) close socketPort
515+ bracket (open addr) Socket.close Socket.socketPort
503 open addr = do
517 open addr = do
504− sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
518+ sock <- Socket.socket (Socket.addrFamily addr) (Socket.addrSocketType addr) (Socket.addrProtocol addr)
505− bind sock (addrAddress addr)
519+ Socket.bind sock (Socket.addrAddress addr)
506 return sock
520 return sock
@@ -537,4 +551,4 @@ relativeTo dir base
537 $ zipDefaultWith Nothing Nothing (,)
551 $ zipDefaultWith Nothing Nothing (,)
538 (map Just $ splitDirectories base)
552 (map Just $ splitDirectories base)
539 (map Just $ splitDirectories dir)
553 (map Just $ splitDirectories dir)
540− where collapse = foldr (</>) ""
554+ where collapse = foldr (</>) ""