@@ -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)
@@ -161,12 +163,12 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
161run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
163run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
162run root interpretPaths = do
164run root interpretPaths = do
163 pkgs <- getParsedLocalPkgs root interpretPaths
165 pkgs <- getParsedLocalPkgs root interpretPaths
164− withGhciScript pkgs $ \dotGhciPath -> do
165− assets <- findProjectAssets root
166+ assets <- findProjectAssets root
166− putLog Debug $ "Assets impurely loaded from: " <> assets
167+ putLog Debug $ "Assets impurely loaded from: " <> assets
167− ghciArgs <- getGhciSessionSettings pkgs root True
168+ ghciArgs <- getGhciSessionSettings pkgs root True
168− freePort <- getFreePort
169+ freePort <- getFreePort
170+ withGhciScriptArgs pkgs $ \dotGhciArgs -> do
169− runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs $ Just $ unwords
171+ runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
170 [ "Obelisk.Run.run"
172 [ "Obelisk.Run.run"
171 , show freePort
173 , show freePort
172 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
174 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
@@ -178,15 +180,15 @@ runRepl :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
178runRepl root interpretPaths = do
180runRepl root interpretPaths = do
179 pkgs <- getParsedLocalPkgs root interpretPaths
181 pkgs <- getParsedLocalPkgs root interpretPaths
180 ghciArgs <- getGhciSessionSettings pkgs "." True
182 ghciArgs <- getGhciSessionSettings pkgs "." True
181− withGhciScript pkgs $ \dotGhciPath ->
183+ withGhciScriptArgs pkgs $ \dotGhciArgs ->
182− runGhciRepl root pkgs (ghciArgs <> mkGhciScriptArg dotGhciPath)
184+ runGhciRepl root pkgs (ghciArgs <> dotGhciArgs)
184runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
186runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
185runWatch root interpretPaths = do
187runWatch root interpretPaths = do
186 pkgs <- getParsedLocalPkgs root interpretPaths
188 pkgs <- getParsedLocalPkgs root interpretPaths
187 ghciArgs <- getGhciSessionSettings pkgs root True
189 ghciArgs <- getGhciSessionSettings pkgs root True
188− withGhciScript pkgs $ \dotGhciPath ->
190+ withGhciScriptArgs pkgs $ \dotGhciArgs ->
189− runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs Nothing
191+ runGhcid root True (ghciArgs <> dotGhciArgs) pkgs Nothing
191exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
193exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
192exportGhciConfig useRelativePaths root interpretPaths = do
194exportGhciConfig useRelativePaths root interpretPaths = do
@@ -393,13 +395,29 @@ parsePackagesOrFail dirs' = do
393packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
395packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
394packageInfoToNamePathMap = 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
396-- | Create ghci configuration to load the given packages
413-- | Create ghci configuration to load the given packages
397withGhciScript
414withGhciScript
398 :: (MonadObelisk m, Foldable f)
415 :: (MonadObelisk m, Foldable f)
416+ => [String] -- ^ Commands to prefix to file
399− => f CabalPackageInfo -- ^ List of packages to load into ghci
417+ -> f CabalPackageInfo -- ^ List of packages to load into ghci
400 -> (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
402−withGhciScript (toList -> packageInfos) f =
420+withGhciScript preCommands (toList -> packageInfos) f =
403 withSystemTempDirectory "ob-ghci" $ \fp -> do
421 withSystemTempDirectory "ob-ghci" $ \fp -> do
404 let dotGhciPath = fp </> ".ghci"
422 let dotGhciPath = fp </> ".ghci"
405 liftIO $ writeFile dotGhciPath dotGhci
423 liftIO $ writeFile dotGhciPath dotGhci
@@ -411,7 +429,8 @@ withGhciScript (toList -> packageInfos) f =
411 , [ "Backend" | "backend" `Set.member` packageNames ]
429 , [ "Backend" | "backend" `Set.member` packageNames ]
412 , [ "Frontend" | "frontend" `Set.member` packageNames ]
430 , [ "Frontend" | "frontend" `Set.member` packageNames ]
414− dotGhci = unlines
432+ dotGhci = unlines $
415 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
434 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
416 , "import qualified Obelisk.Run"
435 , "import qualified Obelisk.Run"
417 , "import qualified Frontend"
436 , "import qualified Frontend"
@@ -464,7 +483,7 @@ runGhciRepl root (toList -> packages) ghciArgs =
464 -- 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
465 -- 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.
466 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
485 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
467− Just $ unwords $ "ghci" : ghciArgs -- TODO: Shell escape
486+ Just $ unwords $ fmap bashEscape $ "ghci" : ghciArgs
469-- | Run ghcid
488-- | Run ghcid
@@ -477,32 +496,28 @@ runGhcid
478runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
497runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
479 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
498 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
480− Just $ unwords $ ghcidExePath : opts -- TODO: Shell escape
499+ Just $ unwords $ fmap bashEscape $ ghcidExePath : opts
482− opts =
501+ opts = concat
484− , "--outputfile=ghcid-output.txt"
503+ , ["--outputfile=ghcid-output.txt"]
485− ] <> map (\x -> "--reload='" <> x <> "'") reloadFiles
504+ , map (\x -> "--reload=" <> x) reloadFiles
486− <> map (\x -> "--restart='" <> x <> "'") restartFiles
505+ , map (\x -> "--restart=" <> x) restartFiles
487− <> testCmd
506+ , maybe [] (\cmd -> ["--test=" <> cmd]) mcmd
488− <> ["--command='" <> unwords ("ghci" : ghciArgs) <> "'"] -- TODO: Shell escape
507+ , ["--command=" <> unwords ("ghci" : ghciArgs)]
489− testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
508+ ]
491 adjustRoot x = if chdirToRoot then makeRelative root x else x
509 adjustRoot x = if chdirToRoot then makeRelative root x else x
492 reloadFiles = map adjustRoot [root </> "config"]
510 reloadFiles = map adjustRoot [root </> "config"]
493 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
511 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
495−mkGhciScriptArg :: FilePath -> [String]
496−mkGhciScriptArg dotGhci = ["-ghci-script", dotGhci]
498−getFreePort :: MonadIO m => m PortNumber
513+getFreePort :: MonadIO m => m Socket.PortNumber
499−getFreePort = liftIO $ withSocketsDo $ do
514+getFreePort = liftIO $ Socket.withSocketsDo $ do
500− 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")
501− bracket (open addr) close socketPort
516+ bracket (open addr) Socket.close Socket.socketPort
503 open addr = do
518 open addr = do
504− sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
519+ sock <- Socket.socket (Socket.addrFamily addr) (Socket.addrSocketType addr) (Socket.addrProtocol addr)
505− bind sock (addrAddress addr)
520+ Socket.bind sock (Socket.addrAddress addr)
506 return sock
521 return sock