@@ -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, 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
@@ -76,7 +78,7 @@ import Obelisk.CliApp (
76 withSpinner,
78 withSpinner,
78import Obelisk.Command.Nix
80import Obelisk.Command.Nix
79−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets)
81+import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
80import Obelisk.Command.Thunk (attrCacheFileName)
82import Obelisk.Command.Thunk (attrCacheFileName)
81import Obelisk.Command.Utils (findExePath, ghcidExePath)
83import Obelisk.Command.Utils (findExePath, ghcidExePath)
@@ -109,9 +111,6 @@ data PathTree a = PathTree_Node
109 (Map FilePath (PathTree a)) -- Branches to deeper leaves
111 (Map FilePath (PathTree a)) -- Branches to deeper leaves
110 deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
112 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.
114-- | 2D ASCII drawing of a 'PathTree'. Adapted from Data.Tree.draw.
116drawPathTree :: (a -> Text) -> PathTree a -> Text
115drawPathTree :: (a -> Text) -> PathTree a -> Text
117drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
116drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
@@ -164,12 +163,12 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
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 True
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 ++ ")"
@@ -181,15 +180,15 @@ runRepl :: 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 "." True
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 True
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
194exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
193exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
195exportGhciConfig useRelativePaths root interpretPaths = do
194exportGhciConfig useRelativePaths root interpretPaths = do
@@ -396,13 +395,29 @@ parsePackagesOrFail dirs' = do
396packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
395packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
397packageInfoToNamePathMap = Map.fromList . map (_cabalPackageInfo_packageName &&& _cabalPackageInfo_packageRoot) . toList
396packageInfoToNamePathMap = Map.fromList . map (_cabalPackageInfo_packageName &&& _cabalPackageInfo_packageRoot) . toList
398+-- Like 'withGhciScript' but provides the precise ghci arguments to add to a ghci session
400+ :: (MonadObelisk m, Foldable f)
401+ => f CabalPackageInfo -- ^ List of packages to load into ghci
402+ -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
404+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
406+ -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
407+ -- is passed to the ghci session.
408+ loadPreludeManually =
409+ [ ":add Prelude" -- @:add@ is used because it's less noisy when there is no custom Prelude
410+ , ":set -XImplicitPrelude" -- Turn the default setting on
399-- | Create ghci configuration to load the given packages
413-- | Create ghci configuration to load the given packages
400withGhciScript
414withGhciScript
401 :: (MonadObelisk m, Foldable f)
415 :: (MonadObelisk m, Foldable f)
416+ => [String] -- ^ Commands to prefix to file
402− => f CabalPackageInfo -- ^ List of packages to load into ghci
417+ -> f CabalPackageInfo -- ^ List of packages to load into ghci
403 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
418 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
405−withGhciScript (toList -> packageInfos) f =
420+withGhciScript preCommands (toList -> packageInfos) f =
406 withSystemTempDirectory "ob-ghci" $ \fp -> do
421 withSystemTempDirectory "ob-ghci" $ \fp -> do
407 let dotGhciPath = fp </> ".ghci"
422 let dotGhciPath = fp </> ".ghci"
408 liftIO $ writeFile dotGhciPath dotGhci
423 liftIO $ writeFile dotGhciPath dotGhci
@@ -414,7 +429,8 @@ withGhciScript (toList -> packageInfos) f =
414 , [ "Backend" | "backend" `Set.member` packageNames ]
429 , [ "Backend" | "backend" `Set.member` packageNames ]
415 , [ "Frontend" | "frontend" `Set.member` packageNames ]
430 , [ "Frontend" | "frontend" `Set.member` packageNames ]
417− dotGhci = unlines
432+ dotGhci = unlines $
418 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
434 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
419 , "import qualified Obelisk.Run"
435 , "import qualified Obelisk.Run"
420 , "import qualified Frontend"
436 , "import qualified Frontend"
@@ -467,7 +483,7 @@ runGhciRepl root (toList -> packages) ghciArgs =
467 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
483 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
468 -- ghc that is provided by the shell in the user's project.
484 -- ghc that is provided by the shell in the user's project.
469 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
485 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
470− Just $ unwords $ "ghci" : ghciArgs -- TODO: Shell escape
486+ Just $ unwords $ fmap bashEscape $ "ghci" : ghciArgs
472-- | Run ghcid
488-- | Run ghcid
@@ -480,32 +496,28 @@ runGhcid
481runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
497runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
482 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
498 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
483− Just $ unwords $ ghcidExePath : opts -- TODO: Shell escape
499+ Just $ unwords $ fmap bashEscape $ ghcidExePath : opts
485− opts =
501+ opts = concat
487− , "--outputfile=ghcid-output.txt"
503+ , ["--outputfile=ghcid-output.txt"]
488− ] <> map (\x -> "--reload='" <> x <> "'") reloadFiles
504+ , map (\x -> "--reload=" <> x) reloadFiles
489− <> map (\x -> "--restart='" <> x <> "'") restartFiles
505+ , map (\x -> "--restart=" <> x) restartFiles
490− <> testCmd
506+ , maybe [] (\cmd -> ["--test=" <> cmd]) mcmd
491− <> ["--command='" <> unwords ("ghci" : ghciArgs) <> "'"] -- TODO: Shell escape
507+ , ["--command=" <> unwords ("ghci" : ghciArgs)]
492− testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
508+ ]
494 adjustRoot x = if chdirToRoot then makeRelative root x else x
509 adjustRoot x = if chdirToRoot then makeRelative root x else x
495 reloadFiles = map adjustRoot [root </> "config"]
510 reloadFiles = map adjustRoot [root </> "config"]
496 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
511 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
498−mkGhciScriptArg :: FilePath -> [String]
499−mkGhciScriptArg dotGhci = ["-ghci-script", dotGhci]
501−getFreePort :: MonadIO m => m PortNumber
513+getFreePort :: MonadIO m => m Socket.PortNumber
502−getFreePort = liftIO $ withSocketsDo $ do
514+getFreePort = liftIO $ Socket.withSocketsDo $ do
503− addr:_ <- getAddrInfo (Just defaultHints) (Just "127.0.0.1") (Just "0")
515+ addr:_ <- Socket.getAddrInfo (Just Socket.defaultHints) (Just "127.0.0.1") (Just "0")
504− bracket (open addr) close socketPort
516+ bracket (open addr) Socket.close Socket.socketPort
506 open addr = do
518 open addr = do
507− sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
519+ sock <- Socket.socket (Socket.addrFamily addr) (Socket.addrSocketType addr) (Socket.addrProtocol addr)
508− bind sock (addrAddress addr)
520+ Socket.bind sock (Socket.addrAddress addr)
509 return sock
521 return sock