@@ -7,16 +7,17 @@
7module Obelisk.Command.Run where
7module Obelisk.Command.Run where
9import Control.Exception (Exception, bracket)
9import Control.Exception (Exception, bracket)
10−import Control.Monad (filterM, unless, when)
10+import Control.Monad (filterM, unless)
11import Control.Monad.Except (runExceptT, throwError)
11import Control.Monad.Except (runExceptT, throwError)
12import Control.Monad.IO.Class (liftIO)
12import Control.Monad.IO.Class (liftIO)
13import Control.Monad.Reader (MonadIO)
13import Control.Monad.Reader (MonadIO)
14import Data.Coerce (coerce)
14import Data.Coerce (coerce)
15import Data.Either
15import Data.Either
16−import Data.Foldable (for_)
16+import Data.Foldable (for_, toList)
17import Data.List.Extra (dropPrefix)
17import Data.List.Extra (dropPrefix)
18import qualified Data.List.NonEmpty as NE
18import qualified Data.List.NonEmpty as NE
19import Data.Maybe
19import Data.Maybe
20+import qualified Data.Set as Set
20import qualified Data.Text as T
21import qualified Data.Text as T
21import Data.Traversable (for)
22import Data.Traversable (for)
22import Debug.Trace (trace)
23import Debug.Trace (trace)
@@ -43,7 +44,7 @@ import System.IO.Temp (withSystemTempDirectory)
44import Obelisk.App (MonadObelisk)
45import Obelisk.App (MonadObelisk)
45import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr)
46import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr)
46−import Obelisk.Command.Project (inProjectShell, obeliskDirName, toObeliskDir, withProjectRoot)
47+import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, toNixPath)
47import Obelisk.Command.Utils (findExePath, ghcidExePath, nixBuildExePath, nixExePath)
48import Obelisk.Command.Utils (findExePath, ghcidExePath, nixBuildExePath, nixExePath)
49data CabalPackageInfo = CabalPackageInfo
50data CabalPackageInfo = CabalPackageInfo
@@ -66,7 +67,7 @@ preprocessorIdentifier = "__preprocessor-apply-packages"
67run :: MonadObelisk m => m ()
68run :: MonadObelisk m => m ()
68run = withProjectRoot "." $ \root -> do
69run = withProjectRoot "." $ \root -> do
69− pkgs <- getLocalPkgs root
70+ pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
70 withGhciScript pkgs $ \dotGhciPath -> do
71 withGhciScript pkgs $ \dotGhciPath -> do
71 freePort <- getFreePort
72 freePort <- getFreePort
72 assets <- do
73 assets <- do
@@ -91,7 +92,7 @@ run = withProjectRoot "." $ \root -> do
91 else readProcessAndLogStderr Debug $
92 else readProcessAndLogStderr Debug $
92 proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
93 proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
93 putLog Debug $ "Assets impurely loaded from: " <> assets
94 putLog Debug $ "Assets impurely loaded from: " <> assets
94− runGhcid dotGhciPath $ Just $ unwords
95+ runGhcid dotGhciPath pkgs $ Just $ unwords
95 [ "Obelisk.Run.run"
96 [ "Obelisk.Run.run"
96 , show freePort
97 , show freePort
97 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
98 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
@@ -99,30 +100,23 @@ run = withProjectRoot "." $ \root -> do
99 , "Frontend.frontend"
100 , "Frontend.frontend"
102−-- | Nix syntax requires relative paths to be prefixed by @./@ or
103−-- @../@. This will make a 'FilePath' that can be embedded in a Nix
105−toNixPath :: FilePath -> FilePath
106−toNixPath root | "/" `isInfixOf` root = root
107− | otherwise = "./" <> root
109runRepl :: MonadObelisk m => m ()
103runRepl :: MonadObelisk m => m ()
110runRepl = do
104runRepl = do
111− pkgs <- withProjectRoot "." getLocalPkgs
105+ pkgs <- fmap toList . parsePackagesOrFail =<< withProjectRoot "." getLocalPkgs
112 withGhciScript pkgs $ \dotGhciPath -> do
106 withGhciScript pkgs $ \dotGhciPath -> do
113− runGhciRepl dotGhciPath
107+ runGhciRepl pkgs dotGhciPath
115runWatch :: MonadObelisk m => m ()
109runWatch :: MonadObelisk m => m ()
116runWatch = do
110runWatch = do
117− pkgs <- withProjectRoot "." getLocalPkgs
111+ pkgs <- fmap toList . parsePackagesOrFail =<< withProjectRoot "." getLocalPkgs
118− withGhciScript pkgs $ \dotGhciPath -> runGhcid dotGhciPath Nothing
112+ withGhciScript pkgs $ \dotGhciPath -> runGhcid dotGhciPath pkgs Nothing
120-- | Relative paths to local packages of an obelisk project.
114-- | Relative paths to local packages of an obelisk project.
122-- These are a combination of the obelisk predefined local packages,
116-- These are a combination of the obelisk predefined local packages,
123-- and any packages that the user has set with the @packages@ argument
117-- and any packages that the user has set with the @packages@ argument
124-- to the Nix @project@ function.
118-- to the Nix @project@ function.
125−getLocalPkgs :: (MonadObelisk m, MonadIO m) => FilePath -> m [FilePath]
119+getLocalPkgs :: MonadObelisk m => FilePath -> m [FilePath]
126getLocalPkgs root = do
120getLocalPkgs root = do
127 (_exitCode, out, err) <- readCreateProcessWithExitCode $
121 (_exitCode, out, err) <- readCreateProcessWithExitCode $
128 proc findExePath ["-L", root, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
122 proc findExePath ["-L", root, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
@@ -233,33 +227,42 @@ parseCabalPackage' pkg = runExceptT $ do
233 Left (_, errors) ->
227 Left (_, errors) ->
234 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
228 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
236−-- | Create ghci configuration to load the given packages
230+parsePackagesOrFail :: MonadObelisk m => [FilePath] -> m (NE.NonEmpty CabalPackageInfo)
237−withGhciScript
231+parsePackagesOrFail dirs = do
239− => [FilePath] -- ^ List of packages to load into ghci
240− -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
242−withGhciScript dirs f = do
243− (pkgDirErrs, packageInfos) <- fmap partitionEithers $ for dirs $ \dir -> do
232+ (pkgDirErrs, packageInfos') <- fmap partitionEithers $ for dirs $ \dir -> do
244 flip fmap (parseCabalPackage dir) $ \case
233 flip fmap (parseCabalPackage dir) $ \case
245 Nothing -> Left dir
234 Nothing -> Left dir
246 Just packageInfo -> Right packageInfo
235 Just packageInfo -> Right packageInfo
248− when (null packageInfos) $
237+ packageInfos <- case NE.nonEmpty packageInfos' of
249− failWith $ T.pack $ "No valid packages found in " <> intercalate ", " dirs
238+ Nothing -> failWith $ T.pack $ "No valid packages found in " <> intercalate ", " dirs
250 unless (null pkgDirErrs) $
241 unless (null pkgDirErrs) $
251 putLog Warning $ T.pack $ "Failed to find packages in " <> intercalate ", " pkgDirErrs
242 putLog Warning $ T.pack $ "Failed to find packages in " <> intercalate ", " pkgDirErrs
246+-- | Create ghci configuration to load the given packages
249+ => [CabalPackageInfo] -- ^ List of packages to load into ghci
250+ -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
252+withGhciScript packageInfos f = do
253 selfExe <- liftIO getExecutablePath
253 selfExe <- liftIO getExecutablePath
255− packageNames = map _cabalPackageInfo_packageName packageInfos
255+ packageNames = Set.fromList $ map _cabalPackageInfo_packageName packageInfos
256+ modulesToLoad = mconcat
257+ [ [ "Obelisk.Run" | "obelisk-run" `Set.member` packageNames ]
258+ , [ "Backend" | "backend" `Set.member` packageNames ]
259+ , [ "Frontend" | "frontend" `Set.member` packageNames ]
256 dotGhci = unlines
261 dotGhci = unlines
257 -- TODO: Shell escape
262 -- TODO: Shell escape
258 [ ":set -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . _cabalPackageInfo_packageRoot) packageInfos)
263 [ ":set -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . _cabalPackageInfo_packageRoot) packageInfos)
259 , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
264 , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
260− , ":load Backend Frontend"
265+ , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
261− -- We need to also load 'Obelisk.Run' if it's part of our ghci session.
262− <> if "obelisk-run" `elem` packageNames then " Obelisk.Run" else ""
263 , "import qualified Obelisk.Run"
266 , "import qualified Obelisk.Run"
264 , "import qualified Frontend"
267 , "import qualified Frontend"
265 , "import qualified Backend"
268 , "import qualified Backend"
@@ -276,21 +279,27 @@ withGhciScript dirs f = do
276-- | Run ghci repl
279-- | Run ghci repl
277runGhciRepl
280runGhciRepl
278 :: MonadObelisk m
281 :: MonadObelisk m
282+ => [CabalPackageInfo]
279− => FilePath -- ^ Path to .ghci
283+ -> FilePath -- ^ Path to .ghci
281−runGhciRepl dotGhci =
285+runGhciRepl packages dotGhci = withProjectRoot "." $ \root ->
282 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
286 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
283 -- ghc that is provided by the shell in the user's project.
287 -- ghc that is provided by the shell in the user's project.
284− inProjectShell "ghc" $ "ghci " <> makeBaseGhciOptions dotGhci
288+ nixShellWithPkgs root True packageNames $ Just $ "ghci " <> makeBaseGhciOptions dotGhci -- TODO: Shell escape
290+ packageNames = map (T.unpack . _cabalPackageInfo_packageName) packages
286-- | Run ghcid
292-- | Run ghcid
288 :: MonadObelisk m
294 :: MonadObelisk m
289 => FilePath -- ^ Path to .ghci
295 => FilePath -- ^ Path to .ghci
296+ -> [CabalPackageInfo]
290 -> Maybe String -- ^ Optional command to run at every reload
297 -> Maybe String -- ^ Optional command to run at every reload
299+runGhcid dotGhci packages mcmd = withProjectRoot "." $ \root ->
292−runGhcid dotGhci mcmd = inProjectShell "ghc" $ unwords $ ghcidExePath : opts -- TODO: Shell escape
300+ nixShellWithPkgs root True packageNames (Just $ unwords $ ghcidExePath : opts) -- TODO: Shell escape
302+ packageNames = map (T.unpack . _cabalPackageInfo_packageName) packages
296 --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user
305 --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user