@@ -3,47 +3,60 @@
33{-# LANGUAGE MultiParamTypeClasses #-}
44{-# LANGUAGE OverloadedStrings #-}
55{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE TemplateHaskell #-}
76{-# LANGUAGE TupleSections #-}
87module Obelisk.Command.Run where
98
10−import Control.Applicative (liftA2)
9+import Control.Arrow ((&&&))
1110import Control.Exception (Exception, bracket)
12−import Control.Monad
11+import Control.Monad (filterM, unless)
1312import Control.Monad.Except (runExceptT, throwError)
1413import Control.Monad.IO.Class (liftIO)
1514import Control.Monad.Reader (MonadIO)
15+import Data.Coerce (coerce)
1616import Data.Either
17−import Data.Foldable (for_)
18−import qualified Data.List as L
17+import Data.Foldable (for_, toList)
18+import Data.List.Extra (dropPrefix)
1919import qualified Data.List.NonEmpty as NE
20+import Data.Map (Map)
21+import qualified Data.Map as Map
2022import Data.Maybe
23+import qualified Data.Set as Set
24+import Data.Text (Text)
2125import qualified Data.Text as T
26+import Data.Traversable (for)
27+import Debug.Trace (trace)
2228import Distribution.Compiler (CompilerFlavor(..))
2329import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
2430import Distribution.Parsec.ParseResult (runParseResult)
25−import Distribution.Pretty
31+import qualified Distribution.System as Dist
2632import Distribution.Types.BuildInfo
2733import Distribution.Types.CondTree
2834import Distribution.Types.GenericPackageDescription
2935import Distribution.Types.Library
3036import Distribution.Utils.Generic
37+import qualified Distribution.Parsec.Common as Dist
3138import qualified Hpack.Config as Hpack
3239import qualified Hpack.Render as Hpack
3340import qualified Hpack.Yaml as Hpack
3441import Language.Haskell.Extension
3542import Network.Socket hiding (Debug)
3643import System.Directory
44+import System.Environment (getExecutablePath)
3745import System.FilePath
46+import qualified System.Info
3847import System.IO.Temp (withSystemTempDirectory)
39−import System.Which (staticWhich)
4048
41−import Obelisk.App (MonadObelisk, ObeliskT)
42−import Obelisk.CliApp (Severity (..) , callCommand, failWith, putLog, proc, readProcessAndLogStderr)
43−import Obelisk.Command.Project (inProjectShell, withProjectRoot)
49+import Obelisk.App (MonadObelisk)
50+import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr)
51+import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, toNixPath)
52+import Obelisk.Command.Thunk (attrCacheFileName)
53+import Obelisk.Command.Utils (findExePath, ghcidExePath, nixBuildExePath, nixExePath)
4454
4555data CabalPackageInfo = CabalPackageInfo
46− { _cabalPackageInfo_packageRoot :: FilePath
56+ { _cabalPackageInfo_packageFile :: FilePath
57+ , _cabalPackageInfo_packageName :: T.Text
58+ , _cabalPackageInfo_packageRoot :: FilePath
59+ , _cabalPackageInfo_buildable :: Bool
4760 , _cabalPackageInfo_sourceDirs :: NE.NonEmpty FilePath
4861 -- ^ List of hs src dirs of the library component
4962 , _cabalPackageInfo_defaultExtensions :: [Extension]
@@ -54,20 +67,19 @@ data CabalPackageInfo = CabalPackageInfo
5467 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
5568 }
5669
57−-- NOTE: `run` is not polymorphic like the rest because we use StaticPtr to invoke it.
58−run :: ObeliskT IO ()
59−run = do
60− let nixPath = $(staticWhich "nix")
61− nixBuildPath = $(staticWhich "nix-build")
62− pkgs <- getLocalPkgs
63− withGhciScript pkgs $ \dotGhciPath -> do
70+-- | Used to signal to obelisk that it's being invoked as a preprocessor
71+preprocessorIdentifier :: String
72+preprocessorIdentifier = "__preprocessor-apply-packages"
73+
74+run :: MonadObelisk m => m ()
75+run = withProjectRoot "." $ \root -> do
76+ pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
77+ withGhciScript pkgs root $ \dotGhciPath -> do
6478 freePort <- getFreePort
65− assets <- withProjectRoot "." $ \root -> do
66− let importableRoot = if "/" `isInfixOf` root
67− then root
68− else "./" <> root
79+ assets <- do
80+ let importableRoot = toNixPath root
6981 isDerivation <- readProcessAndLogStderr Debug $
70− proc nixPath
82+ proc nixExePath
7183 [ "eval"
7284 , "-f"
7385 , root
@@ -79,83 +91,143 @@ run = do
7991 -- Check whether the impure static files are a derivation (and so must be built)
8092 if isDerivation == "1"
8193 then fmap T.strip $ readProcessAndLogStderr Debug $ -- Strip whitespace here because nix-build has no --raw option
82− proc nixBuildPath
94+ proc nixBuildExePath
8395 [ "--no-out-link"
8496 , "-E", "(import " <> importableRoot <> "{}).passthru.staticFilesImpure"
8597 ]
8698 else readProcessAndLogStderr Debug $
87− proc nixPath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
99+ proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
88100 putLog Debug $ "Assets impurely loaded from: " <> assets
89− runGhcid dotGhciPath $ Just $ unwords
101+ runGhcid root True dotGhciPath pkgs $ Just $ unwords
90102 [ "Obelisk.Run.run"
91103 , show freePort
92− , "(runServeAsset " ++ show assets ++ ")"
104+ , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
93105 , "Backend.backend"
94106 , "Frontend.frontend"
95107 ]
96108
97109runRepl :: MonadObelisk m => m ()
98−runRepl = do
99− pkgs <- getLocalPkgs
100− withGhciScript pkgs $ \dotGhciPath -> do
101− runGhciRepl dotGhciPath
110+runRepl = withProjectRoot "." $ \root -> do
111+ pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
112+ withGhciScript pkgs "." $ \dotGhciPath ->
113+ runGhciRepl root pkgs dotGhciPath
102114
103115runWatch :: MonadObelisk m => m ()
104−runWatch = do
105− pkgs <- getLocalPkgs
106− withGhciScript pkgs $ \dotGhciPath -> runGhcid dotGhciPath Nothing
116+runWatch = withProjectRoot "." $ \root -> do
117+ pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
118+ withGhciScript pkgs root $ \dotGhciPath ->
119+ runGhcid root True dotGhciPath pkgs Nothing
120+
121+-- | Relative paths to local packages of an obelisk project.
122+--
123+-- These are a combination of the obelisk predefined local packages,
124+-- and any packages that the user has set with the @packages@ argument
125+-- to the Nix @project@ function.
126+getLocalPkgs :: MonadObelisk m => FilePath -> m [FilePath]
127+getLocalPkgs root = do
128+ (_exitCode, out, err) <- readCreateProcessWithExitCode $
129+ proc findExePath ["-L", root, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
130+ putLog Debug $ T.strip $ T.pack err
131+
132+ let
133+ -- We ignore any path that has ".obelisk" in it, but keep the root ".obelisk" paths
134+ packagePaths = filter (not . isIgnored) $ map T.unpack $ T.lines $ T.strip $ T.pack out
135+ isIgnored path =
136+ obeliskDirName `elem` dropPrefix (splitPath $ toObeliskDir root) pathSegments
137+ && attrCacheFileName `notElem` pathSegments
138+ where
139+ pathSegments = splitPath path
140+
141+ pure packagePaths
107142
108143data GuessPackageFileError = GuessPackageFileError_Ambiguous [FilePath] | GuessPackageFileError_NotFound
109144 deriving (Eq, Ord, Show)
110145instance Exception GuessPackageFileError
111146
147+newtype HPackFilePath = HPackFilePath { unHPackFilePath :: FilePath } deriving (Eq, Ord, Show)
148+newtype CabalFilePath = CabalFilePath { unCabalFilePath :: FilePath } deriving (Eq, Ord, Show)
149+
112150-- | Given a directory, try to guess what the appropriate @.cabal@ or @package.yaml@ file is for the package.
113151guessCabalPackageFile
114− :: MonadIO m
115− => FilePath -- ^ Directory to search for cabal package
116− -> m (Either GuessPackageFileError (Either FilePath FilePath))
117− -- ^ 'Right' 'Left' for hpack package, 'Right' 'Right' for cabal package; 'FilePath' is relative to given directory.
118−guessCabalPackageFile dir = do
119− candidates <- liftIO $
120− filterM (doesFileExist . (dir </>))
121− =<< filter (liftA2 (||) (== Hpack.packageConfig) (".cabal" `L.isSuffixOf`))
122− <$> listDirectory dir
123− pure $ case L.partition (== Hpack.packageConfig) candidates of
124− ([hpack], _) -> Right $ Left hpack
125− ([], [cabal]) -> Right $ Right cabal
126− ([], []) -> Left GuessPackageFileError_NotFound
127− (hpacks, cabals) -> Left $ GuessPackageFileError_Ambiguous $ hpacks <> cabals
152+ :: (MonadIO m)
153+ => FilePath -- ^ Directory or path to search for cabal package
154+ -> m (Either GuessPackageFileError (Either CabalFilePath HPackFilePath))
155+guessCabalPackageFile pkg = do
156+ liftIO (doesDirectoryExist pkg) >>= \case
157+ False -> case cabalOrHpackFile pkg of
158+ (Just hpack@(Right _)) -> pure $ Right hpack
159+ (Just cabal@(Left (CabalFilePath cabalFilePath))) -> do
160+ -- If the cabal file has a sibling hpack file, we use that instead
161+ -- since running hpack often generates a sibling cabal file
162+ let possibleHpackSibling = takeDirectory cabalFilePath </> Hpack.packageConfig
163+ hasHpackSibling <- liftIO $ doesFileExist possibleHpackSibling
164+ pure $ Right $ if hasHpackSibling then Right (HPackFilePath possibleHpackSibling) else cabal
165+ Nothing -> pure $ Left GuessPackageFileError_NotFound
166+ True -> do
167+ candidates <- liftIO $
168+ filterM (doesFileExist . either unCabalFilePath unHPackFilePath)
169+ =<< mapMaybe (cabalOrHpackFile . (pkg </>)) <$> listDirectory pkg
170+ pure $ case partitionEithers candidates of
171+ ([hpack], _) -> Right $ Left hpack
172+ ([], [cabal]) -> Right $ Right cabal
173+ ([], []) -> Left GuessPackageFileError_NotFound
174+ (hpacks, cabals) -> Left $ GuessPackageFileError_Ambiguous $ coerce hpacks <> coerce cabals
128175
129−-- | Relative paths to local packages of an obelisk project
130−-- TODO a way to query this
131−getLocalPkgs :: Applicative f => f [FilePath]
132−getLocalPkgs = pure ["backend", "common", "frontend"]
176+cabalOrHpackFile :: FilePath -> Maybe (Either CabalFilePath HPackFilePath)
177+cabalOrHpackFile = \case
178+ x | takeExtension x == ".cabal" -> Just (Left $ CabalFilePath x)
179+ | takeFileName x == Hpack.packageConfig -> Just (Right $ HPackFilePath x)
180+ | otherwise -> Nothing
133181
182+-- | Parses the cabal package in a given directory.
183+-- This automatically figures out which .cabal file or package.yaml (hpack) file to use in the given directory.
134184parseCabalPackage
135− :: (MonadObelisk m)
136− => FilePath -- ^ package directory
185+ :: MonadObelisk m
186+ => FilePath -- ^ Package directory
137187 -> m (Maybe CabalPackageInfo)
138−parseCabalPackage dir = either ((Nothing <$) . putLog Error) (pure . Just) <=< runExceptT $ do
139− (cabalContents, packageFile) <- guessCabalPackageFile dir >>= \case
140− Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack dir
141− Left (GuessPackageFileError_Ambiguous _) -> throwError $ "Unable to determine which .cabal file to use in " <> T.pack dir
142− Right (Right cabalFileName) -> let file = dir </> cabalFileName in (, file) <$> liftIO (readUTF8File file)
143− Right (Left hpackFileName) -> do
188+parseCabalPackage dir = parseCabalPackage' dir >>= \case
189+ Left err -> Nothing <$ putLog Error err
190+ Right (warnings, pkgInfo) -> do
191+ for_ warnings $ putLog Warning . T.pack . show
192+ pure $ Just pkgInfo
193+
194+-- | Like 'parseCabalPackage' but returns errors and warnings directly so as to avoid 'MonadObelisk'.
195+parseCabalPackage'
196+ :: (MonadIO m)
197+ => FilePath -- ^ Package directory
198+ -> m (Either T.Text ([Dist.PWarning], CabalPackageInfo))
199+parseCabalPackage' pkg = runExceptT $ do
200+ (cabalContents, packageFile, packageName) <- guessCabalPackageFile pkg >>= \case
201+ Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack pkg
202+ Left (GuessPackageFileError_Ambiguous _) -> throwError $ "Unable to determine which .cabal file to use in " <> T.pack pkg
203+ Right (Left (CabalFilePath file)) -> (, file, takeBaseName file) <$> liftIO (readUTF8File file)
204+ Right (Right (HPackFilePath file)) -> do
144205 let
145− file = dir </> hpackFileName
146206 decodeOptions = Hpack.DecodeOptions (Hpack.ProgramName "ob") file Nothing Hpack.decodeYaml
147207 liftIO (Hpack.readPackageConfig decodeOptions) >>= \case
148208 Left err -> throwError $ T.pack $ "Failed to parse " <> file <> ": " <> err
149− Right (Hpack.DecodeResult hpackPackage _ _ _) -> pure (Hpack.renderPackage [] hpackPackage, file)
209+ Right (Hpack.DecodeResult hpackPackage _ _ _) -> pure (Hpack.renderPackage [] hpackPackage, file, Hpack.packageName hpackPackage)
150210
151− let (warnings, result) = runParseResult $ parseGenericPackageDescription $
152− toUTF8BS cabalContents
153− for_ warnings $ putLog Warning . T.pack . show
211+ let
212+ (warnings, result) = runParseResult $ parseGenericPackageDescription $ toUTF8BS cabalContents
213+ osConfVar = case System.Info.os of
214+ "linux" -> Just Dist.Linux
215+ "darwin" -> Just Dist.OSX
216+ _ -> trace "Unrecgonized System.Info.os" Nothing
217+ archConfVar = Just Dist.X86_64 -- TODO: Actually infer this
218+ evalConfVar v = Right $ case v of
219+ OS osVar -> Just osVar == osConfVar
220+ Arch archVar -> Just archVar == archConfVar
221+ Impl GHC _ -> True -- TODO: Actually check version range
222+ _ -> False
154223 case condLibrary <$> result of
155224 Right (Just condLib) -> do
156− let (_, lib) = simplifyCondTree (const $ pure True) condLib
157− pure $ CabalPackageInfo
158− { _cabalPackageInfo_packageRoot = dir
225+ let (_, lib) = simplifyCondTree evalConfVar condLib
226+ pure $ (warnings,) $ CabalPackageInfo
227+ { _cabalPackageInfo_packageName = T.pack packageName
228+ , _cabalPackageInfo_packageFile = packageFile
229+ , _cabalPackageInfo_packageRoot = takeDirectory packageFile
230+ , _cabalPackageInfo_buildable = buildable $ libBuildInfo lib
159231 , _cabalPackageInfo_sourceDirs =
160232 fromMaybe (pure ".") $ NE.nonEmpty $ hsSourceDirs $ libBuildInfo lib
161233 , _cabalPackageInfo_defaultExtensions =
@@ -168,45 +240,51 @@ parseCabalPackage dir = either ((Nothing <$) . putLog Error) (pure . Just) <=< r
168240 Left (_, errors) ->
169241 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
170242
243+parsePackagesOrFail :: MonadObelisk m => [FilePath] -> m (NE.NonEmpty CabalPackageInfo)
244+parsePackagesOrFail dirs = do
245+ (pkgDirErrs, packageInfos') <- fmap partitionEithers $ for dirs $ \dir -> do
246+ flip fmap (parseCabalPackage dir) $ \case
247+ Just packageInfo
248+ | _cabalPackageInfo_buildable packageInfo -> Right packageInfo
249+ _ -> Left dir
250+
251+ packageInfos <- case NE.nonEmpty packageInfos' of
252+ Nothing -> failWith $ T.pack $ "No valid, buildable packages found in " <> intercalate ", " dirs
253+ Just xs -> pure xs
254+
255+ unless (null pkgDirErrs) $
256+ putLog Warning $ T.pack $ "Failed to find buildable packages in " <> intercalate ", " pkgDirErrs
257+
258+ pure packageInfos
259+
260+packageInfoToNamePathMap :: [CabalPackageInfo] -> Map Text FilePath
261+packageInfoToNamePathMap = Map.fromList . map (_cabalPackageInfo_packageName &&& _cabalPackageInfo_packageRoot)
262+
171263-- | Create ghci configuration to load the given packages
172264withGhciScript
173265 :: MonadObelisk m
174− => [FilePath] -- ^ List of packages to load into ghci
266+ => [CabalPackageInfo] -- ^ List of packages to load into ghci
267+ -> FilePath -- ^ All paths written to the .ghci file will be relative to this path
175268 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
176269 -> m ()
177−withGhciScript dirs f = do
178− (pkgDirErrs, packageInfos) <- fmap partitionEithers $ forM dirs $ \dir -> do
179− flip fmap (parseCabalPackage dir) $ \case
180− Nothing -> Left dir
181− Just packageInfo -> Right packageInfo
182−
183− when (null packageInfos) $
184− failWith $ T.pack $ "No valid pkgs found in " <> intercalate ", " dirs
185− unless (null pkgDirErrs) $
186− putLog Warning $ T.pack $ "Failed to find pkgs in " <> intercalate ", " pkgDirErrs
187−
188− let extensions = packageInfos >>= _cabalPackageInfo_defaultExtensions
189− languageFromPkgs = L.nub $ mapMaybe _cabalPackageInfo_defaultLanguage packageInfos
190− -- NOTE when no default-language is present cabal sets Haskell98
191− language = NE.toList $ fromMaybe (Haskell98 NE.:| []) $ NE.nonEmpty languageFromPkgs
192− extensionsLine = if extensions == mempty
193− then ""
194− else ":set " <> unwords (("-X" <>) . prettyShow <$> extensions)
195− ghcOptions = concat $ mapMaybe (\case (GHC, xs) -> Just xs; _ -> Nothing) $
196− packageInfos >>= _cabalPackageInfo_compilerOptions
197− dotGhci = unlines
198− [ ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
199− , case ghcOptions of
200− [] -> ""
201− xs -> ":set " <> unwords xs
202− , extensionsLine
203− , ":set " <> unwords (("-X" <>) . prettyShow <$> language)
204− , ":load Backend Frontend"
205− , "import Obelisk.Run"
206− , "import qualified Frontend"
207− , "import qualified Backend"
208− ]
209− warnDifferentLanguages language
270+withGhciScript packageInfos pathBase f = do
271+ selfExe <- liftIO getExecutablePath
272+ let
273+ packageNames = Set.fromList $ map _cabalPackageInfo_packageName packageInfos
274+ modulesToLoad = mconcat
275+ [ [ "Obelisk.Run" | "obelisk-run" `Set.member` packageNames ]
276+ , [ "Backend" | "backend" `Set.member` packageNames ]
277+ , [ "Frontend" | "frontend" `Set.member` packageNames ]
278+ ]
279+ dotGhci = unlines
280+ -- TODO: Shell escape
281+ [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos)
282+ , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
283+ , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
284+ , "import qualified Obelisk.Run"
285+ , "import qualified Frontend"
286+ , "import qualified Backend"
287+ ]
210288 withSystemTempDirectory "ob-ghci" $ \fp -> do
211289 let dotGhciPath = fp </> ".ghci"
212290 liftIO $ writeFile dotGhciPath dotGhci
@@ -214,35 +292,44 @@ withGhciScript dirs f = do
214292
215293 where
216294 rootedSourceDirs pkg = NE.toList $
217− (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
218−
219−warnDifferentLanguages :: MonadObelisk m => [Language] -> m ()
220−warnDifferentLanguages (_:_:_) = putLog Warning "Different languages detected across packages which may result in errors when loading the repl"
221−warnDifferentLanguages _ = return ()
295+ makeRelative pathBase . (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
222296
223297-- | Run ghci repl
224298runGhciRepl
225299 :: MonadObelisk m
226− => FilePath -- ^ Path to .ghci
300+ => FilePath -- ^ Path to project root
301+ -> [CabalPackageInfo]
302+ -> FilePath -- ^ Path to .ghci
227303 -> m ()
228−runGhciRepl dotGhci = inProjectShell "ghc" $ "ghci " <> makeBaseGhciOptions dotGhci
304+runGhciRepl root packages dotGhci =
305+ -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
306+ -- ghc that is provided by the shell in the user's project.
307+ nixShellWithPkgs root True False (packageInfoToNamePathMap packages) $ Just $ "ghci " <> makeBaseGhciOptions dotGhci -- TODO: Shell escape
229308
230309-- | Run ghcid
231310runGhcid
232311 :: MonadObelisk m
233− => FilePath -- ^ Path to .ghci
312+ => FilePath -- ^ Path to project root
313+ -> Bool -- ^ Should we chdir to root when running this process?
314+ -> FilePath -- ^ Path to .ghci
315+ -> [CabalPackageInfo]
234316 -> Maybe String -- ^ Optional command to run at every reload
235317 -> m ()
236−runGhcid dotGhci mcmd = callCommand $ unwords $ $(staticWhich "ghcid") : opts
318+runGhcid root chdirToRoot dotGhci packages mcmd =
319+ nixShellWithPkgs root True chdirToRoot (packageInfoToNamePathMap packages) (Just $ unwords $ ghcidExePath : opts) -- TODO: Shell escape
237320 where
238321 opts =
239322 [ "-W"
240− --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user
241− , "--command='ghci -Wall -ignore-dot-ghci -fwarn-redundant-constraints " <> makeBaseGhciOptions dotGhci <> "' "
242− , "--reload=config"
323+ , "--command='ghci -ignore-dot-ghci " <> makeBaseGhciOptions dotGhci <> "' "
243324 , "--outputfile=ghcid-output.txt"
244− ] <> testCmd
245− testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'")
325+ ] <> map (\x -> "--reload='" <> x <> "'") reloadFiles
326+ <> map (\x -> "--restart='" <> x <> "'") restartFiles
327+ <> testCmd
328+ testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
329+
330+ adjustRoot x = if chdirToRoot then makeRelative root x else x
331+ reloadFiles = map adjustRoot [root </> "config"]
332+ restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
246333
247334makeBaseGhciOptions :: FilePath -> String
248335makeBaseGhciOptions dotGhci =