@@ -1,6 +1,3 @@
1−{-# LANGUAGE DeriveFoldable #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE DeriveTraversable #-}
41{-# LANGUAGE FlexibleContexts #-}
52{-# LANGUAGE LambdaCase #-}
63{-# LANGUAGE MultiParamTypeClasses #-}
@@ -18,19 +15,17 @@ import Control.Monad (filterM, unless, void)
1815import Control.Monad.Except (runExceptT, throwError)
1916import Control.Monad.IO.Class (liftIO)
2017import Control.Monad.Reader (MonadIO)
21−import Data.Bifoldable (bifoldr1)
22−import Data.Bifunctor (bimap)
2318import Data.Coerce (coerce)
2419import Data.Default (def)
2520import Data.Either (partitionEithers)
2621import Data.Foldable (fold, for_, toList)
27−import Data.Functor.Identity (runIdentity)
22+import Data.Functor ((<&>))
2823import Data.List.NonEmpty (NonEmpty)
2924import qualified Data.List.NonEmpty as NE
3025import Data.Map (Map)
3126import qualified Data.Map as Map
32−import qualified Data.Map.Monoidal as MMap
3327import Data.Maybe
28+import Data.Semigroup (Last (..))
3429import Data.Set (Set)
3530import qualified Data.Set as Set
3631import Data.String.Here.Interpolated (i)
@@ -55,9 +50,7 @@ import qualified Hpack.Render as Hpack
5550import qualified Hpack.Yaml as Hpack
5651import Language.Haskell.Extension
5752import Network.Socket hiding (Debug)
58−import System.Directory
5953import System.Environment (getExecutablePath)
60−import System.FilePath
6154import qualified System.Info
6255import System.IO.Temp (withSystemTempDirectory)
6356
@@ -68,7 +61,6 @@ import Obelisk.CliApp (
6861 failWith,
6962 proc,
7063 putLog,
71− readCreateProcessWithExitCode,
7264 readProcessAndLogStderr,
7365 setCwd,
7466 setDelegateCtlc,
@@ -76,9 +68,13 @@ import Obelisk.CliApp (
7668 withSpinner,
7769 )
7870import Obelisk.Command.Nix
79−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets)
71+import Obelisk.Command.Path
72+import Obelisk.Command.Project (findProjectAssets, nixShellWithoutPkgs, obeliskDirName, withProjectRoot)
8073import Obelisk.Command.Thunk (attrCacheFileName)
81−import Obelisk.Command.Utils (findExePath, ghcidExePath)
74+import Obelisk.Command.Utils (ghcidExePath)
75+
76+hpackFileName :: Path Relative
77+hpackFileName = rel Hpack.packageConfig
8278
8379data CabalPackageInfo = CabalPackageInfo
8480 { _cabalPackageInfo_packageFile :: FilePath
@@ -103,26 +99,6 @@ textInterpret = \case
10399 Interpret_Interpret -> "Interpret"
104100 Interpret_NoInterpret -> "NoInterpret"
105101
106−-- | Describe a set of 'FilePath's as a tree to facilitate merging them in a convenient way.
107−data PathTree a = PathTree_Node
108− (Maybe a) -- An optional leaf at this point in the tree
109− (Map FilePath (PathTree a)) -- Branches to deeper leaves
110− deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
111−
112−emptyPathTree :: PathTree a
113−emptyPathTree = PathTree_Node Nothing mempty
114−
115−-- | 2D ASCII drawing of a 'PathTree'. Adapted from Data.Tree.draw.
116−drawPathTree :: (a -> Text) -> PathTree a -> Text
117−drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
118− where
119− annotated ma = maybe id (\a b -> b <> " [" <> showA a <> "]") ma . T.pack
120− goTree (fp, PathTree_Node ma forest) = annotated ma fp : goForest (Map.toList forest)
121− goForest [] = []
122− goForest [tree] = shift "└─ " " " (goTree tree)
123− goForest (tree:forest) = shift "├─ " "│ " (goTree tree) <> goForest forest
124− shift first other = zipWith (<>) (first : repeat other)
125−
126102-- | Used to signal to obelisk that it's being invoked as a preprocessor
127103preprocessorIdentifier :: String
128104preprocessorIdentifier = "__preprocessor-apply-packages"
@@ -148,10 +124,10 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
148124 putLog Debug $ "Assets impurely loaded from: " <> assets
149125 time <- liftIO getCurrentTime
150126 let profileBaseName = formatTime defaultTimeLocale profileBasePattern time
151− liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
152− putLog Debug $ "Storing profiled data under base name of " <> T.pack (root </> profileBaseName)
127+ createDirectoryIfMissing True $ takeDirectory $ mkPath root </> rel profileBaseName
128+ putLog Debug [i|Storing profiled data under base name of ${root </> profileBaseName}|]
153129 freePort <- getFreePort
154− (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath </> "bin" </> "ob-run") $
130+ (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc (toFilePath $ outPath </> rel "bin" </> rel "ob-run") $
155131 [ show freePort
156132 , T.unpack assets
157133 , profileBaseName
@@ -161,7 +137,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
161137 <> [ "-RTS" ]
162138 void $ waitForProcess ph
163139
164−run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
140+run :: MonadObelisk m => FilePath -> Map (Path Canonical) Interpret -> m ()
165141run root interpretPaths = do
166142 pkgs <- getParsedLocalPkgs root interpretPaths
167143 withGhciScript pkgs $ \dotGhciPath -> do
@@ -177,32 +153,32 @@ run root interpretPaths = do
177153 , "Frontend.frontend"
178154 ]
179155
180−runRepl :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
156+runRepl :: MonadObelisk m => FilePath -> Map (Path Canonical) Interpret -> m ()
181157runRepl root interpretPaths = do
182158 pkgs <- getParsedLocalPkgs root interpretPaths
183− ghciArgs <- getGhciSessionSettings pkgs "." True
159+ ghciArgs <- getGhciSessionSettings pkgs (mkPath ".") True
184160 withGhciScript pkgs $ \dotGhciPath ->
185161 runGhciRepl root pkgs (ghciArgs <> mkGhciScriptArg dotGhciPath)
186162
187−runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
163+runWatch :: MonadObelisk m => FilePath -> Map (Path Canonical) Interpret -> m ()
188164runWatch root interpretPaths = do
189165 pkgs <- getParsedLocalPkgs root interpretPaths
190166 ghciArgs <- getGhciSessionSettings pkgs root True
191167 withGhciScript pkgs $ \dotGhciPath ->
192168 runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs Nothing
193169
194−exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
170+exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> Map (Path Canonical) Interpret -> m [String]
195171exportGhciConfig useRelativePaths root interpretPaths = do
196172 pkgs <- getParsedLocalPkgs root interpretPaths
197− getGhciSessionSettings pkgs "." useRelativePaths
173+ getGhciSessionSettings pkgs (rel ".") useRelativePaths
198174
199−nixShellForInterpretPaths :: MonadObelisk m => Bool -> String -> FilePath -> PathTree Interpret -> Maybe String -> m ()
175+nixShellForInterpretPaths :: MonadObelisk m => Bool -> String -> FilePath -> Map (Path Canonical) Interpret -> Maybe String -> m ()
200176nixShellForInterpretPaths isPure shell root interpretPaths cmd = do
201177 pkgs <- getParsedLocalPkgs root interpretPaths
202178 nixShellWithoutPkgs root isPure False (packageInfoToNamePathMap pkgs) shell cmd
203179
204180-- | Like 'getLocalPkgs' but also parses them and fails if any of them can't be parsed.
205−getParsedLocalPkgs :: MonadObelisk m => FilePath -> PathTree Interpret -> m (NonEmpty CabalPackageInfo)
181+getParsedLocalPkgs :: MonadObelisk m => FilePath -> Map (Path Canonical) Interpret -> m (NonEmpty CabalPackageInfo)
206182getParsedLocalPkgs root interpretPaths = parsePackagesOrFail =<< getLocalPkgs root interpretPaths
207183
208184-- | Relative paths to local packages of an obelisk project.
@@ -210,53 +186,36 @@ getParsedLocalPkgs root interpretPaths = parsePackagesOrFail =<< getLocalPkgs ro
210186-- These are a combination of the obelisk predefined local packages,
211187-- and any packages that the user has set with the @packages@ argument
212188-- to the Nix @project@ function.
213−getLocalPkgs :: forall m. MonadObelisk m => FilePath -> PathTree Interpret -> m (Set FilePath)
189+getLocalPkgs :: forall m. MonadObelisk m => FilePath -> Map (Path Canonical) Interpret -> m (Set (Path Canonical))
214190getLocalPkgs root interpretPaths = do
215− putLog Debug $ [i|Finding packages with root ${root} and interpret paths:|] <> "\n" <> drawPathTree textInterpret interpretPaths
216− obeliskPackagePaths <- runFind ["-L", root, "-name", ".obelisk", "-type", "d"]
191+ let pathTree :: PathTree Interpret = coerce $ foldMap (\(p, x) -> pathToTree (Last x) p) $ Map.toList interpretPaths
192+ putLog Debug $ [i|Finding packages with root ${root} and interpret paths:|] <> "\n" <> drawPathTree textInterpret pathTree
217193
218194 -- We do not want to find packages that are embedded inside other obelisk projects, unless that
219195 -- obelisk project is our own.
220− obeliskPackageExclusions <- liftIO $ fmap Set.fromList $ traverse canonicalizePath $
221− filter (/= root) $ map takeDirectory obeliskPackagePaths
222− putLog Debug [i|Excluding obelisk packages: ${T.pack $ unwords $ Set.toList obeliskPackageExclusions}|]
223− let rootsAndExclusions = calcIntepretFinds "" interpretPaths
224−
225− fmap fold $ for (MMap.toAscList rootsAndExclusions) $ \(interpretPathRoot, exclusions) ->
226− let allExclusions = obeliskPackageExclusions <> exclusions <> Set.singleton ("*" </> attrCacheFileName)
227− in fmap (Set.fromList . map normalise) $ runFind $
228− ["-L", interpretPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
229− <> concat [["-not", "-path", p </> "*"] | p <- toList allExclusions]
230− where
231− runFind args = do
232− (_exitCode, out, err) <- readCreateProcessWithExitCode $ proc findExePath args
233− putLog Debug $ T.strip $ T.pack err
234− pure $ map T.unpack $ T.lines $ T.strip $ T.pack out
235−
236−-- | Calculates a set of root 'FilePath's along with each one's corresponding set of exclusions.
237−-- This is used when constructing a set of @find@ commands to run to produce a set of packages
238−-- that matches the user's @--interpret@/@--no-interpret@ settings.
239−calcIntepretFinds :: FilePath -> PathTree Interpret -> MMap.MonoidalMap FilePath (Set FilePath)
240−calcIntepretFinds treeRoot0 tree0 = runIdentity $ go treeRoot0 tree0
241− where
242− go treeRoot tree = foldPathTreeFor (== Interpret_Interpret) treeRoot tree $ \parent children -> do
243− exclusions <- foldPathTreeFor (== Interpret_NoInterpret) parent children $ \parent' children' ->
244− pure $ Map.singleton parent' children'
245− deeperFinds <- Map.traverseWithKey go exclusions
246− pure $ MMap.singleton parent (Map.keysSet exclusions) <> fold (MMap.MonoidalMap deeperFinds)
247−
248−-- | Traverses a 'PathTree' and folds all leaves matching a given predicate.
249−foldPathTreeFor
250− :: forall m a b. (Applicative m, Monoid b)
251− => (a -> Bool)
252− -> FilePath
253− -> PathTree a
254− -> (FilePath -> PathTree a -> m b)
255− -> m b
256−foldPathTreeFor predicate parent children f = case children of
257− PathTree_Node (Just x) children' | predicate x -> f parent (PathTree_Node Nothing children')
258− PathTree_Node _ children' -> fmap fold $ flip Map.traverseWithKey children' $ \k children'' ->
259− foldPathTreeFor predicate (parent </> k) children'' f
196+ canonicalRoot <- canonicalizePath root
197+ obeliskPackageExclusions <- findFiles'
198+ (\_ p -> if takeFileName p == rel obeliskDirName && p /= canonicalRoot
199+ then liftIO (doesDirectoryExist p) <&> \case
200+ True -> FindMatch_Match $ Set.singleton p
201+ False -> FindMatch_SearchTree
202+ else pure FindMatch_SearchTree
203+ )
204+ root
205+
206+ let
207+ findRoots = Map.keysSet $ Map.filter (== Interpret_Interpret) interpretPaths
208+ exclusions = Map.keysSet $ Map.filter (== Interpret_NoInterpret) interpretPaths
209+ <> Map.fromSet (const Interpret_NoInterpret) obeliskPackageExclusions -- Add these last because we want left side to override
210+
211+ fmap fold $ for (toList findRoots) $ \interpretPathRoot -> do
212+ findFiles'
213+ (\_ p -> pure $ case () of
214+ _ | p `Set.member` exclusions || attrCacheFileName `elem` splitDirectories p -> FindMatch_SkipTree
215+ | takeExtension p == ".cabal" || takeFileName p == hpackFileName -> FindMatch_Match $ Set.singleton p
216+ | otherwise -> FindMatch_SearchTree
217+ )
218+ interpretPathRoot
260219
261220data GuessPackageFileError = GuessPackageFileError_Ambiguous [FilePath] | GuessPackageFileError_NotFound
262221 deriving (Eq, Ord, Show)
@@ -267,24 +226,24 @@ newtype CabalFilePath = CabalFilePath { unCabalFilePath :: FilePath } deriving (
267226
268227-- | Given a directory, try to guess what the appropriate @.cabal@ or @package.yaml@ file is for the package.
269228guessCabalPackageFile
270− :: (MonadIO m)
271− => FilePath -- ^ Directory or path to search for cabal package
229+ :: (MonadIO m, IsPath path)
230+ => path -- ^ Directory or path to search for cabal package
272231 -> m (Either GuessPackageFileError (Either CabalFilePath HPackFilePath))
273−guessCabalPackageFile pkg = do
232+guessCabalPackageFile (toFilePath -> pkg) = do
274233 liftIO (doesDirectoryExist pkg) >>= \case
275234 False -> case cabalOrHpackFile pkg of
276235 (Just hpack@(Right _)) -> pure $ Right hpack
277236 (Just cabal@(Left (CabalFilePath cabalFilePath))) -> do
278237 -- If the cabal file has a sibling hpack file, we use that instead
279238 -- since running hpack often generates a sibling cabal file
280− let possibleHpackSibling = takeDirectory cabalFilePath </> Hpack.packageConfig
239+ let possibleHpackSibling = takeDirectory $ mkPath cabalFilePath </> hpackFileName
281240 hasHpackSibling <- liftIO $ doesFileExist possibleHpackSibling
282− pure $ Right $ if hasHpackSibling then Right (HPackFilePath possibleHpackSibling) else cabal
241+ pure $ Right $ if hasHpackSibling then Right (HPackFilePath $ toFilePath possibleHpackSibling) else cabal
283242 Nothing -> pure $ Left GuessPackageFileError_NotFound
284243 True -> do
285244 candidates <- liftIO $
286245 filterM (doesFileExist . either unCabalFilePath unHPackFilePath)
287− =<< mapMaybe (cabalOrHpackFile . (pkg </>)) <$> listDirectory pkg
246+ =<< mapMaybe (cabalOrHpackFile . toFilePath . (pkg </>)) <$> listDirectory pkg
288247 pure $ case partitionEithers candidates of
289248 ([hpack], _) -> Right $ Left hpack
290249 ([], [cabal]) -> Right $ Right cabal
@@ -294,14 +253,14 @@ guessCabalPackageFile pkg = do
294253cabalOrHpackFile :: FilePath -> Maybe (Either CabalFilePath HPackFilePath)
295254cabalOrHpackFile = \case
296255 x | takeExtension x == ".cabal" -> Just (Left $ CabalFilePath x)
297− | takeFileName x == Hpack.packageConfig -> Just (Right $ HPackFilePath x)
256+ | takeFileName x == hpackFileName -> Just (Right $ HPackFilePath x)
298257 | otherwise -> Nothing
299258
300259-- | Parses the cabal package in a given directory.
301260-- This automatically figures out which .cabal file or package.yaml (hpack) file to use in the given directory.
302261parseCabalPackage
303− :: MonadObelisk m
304− => FilePath -- ^ Package directory
262+ :: (MonadObelisk m, IsPath path)
263+ => path
305264 -> m (Maybe CabalPackageInfo)
306265parseCabalPackage dir = parseCabalPackage' dir >>= \case
307266 Left err -> Nothing <$ putLog Error err
@@ -311,13 +270,13 @@ parseCabalPackage dir = parseCabalPackage' dir >>= \case
311270
312271-- | Like 'parseCabalPackage' but returns errors and warnings directly so as to avoid 'MonadObelisk'.
313272parseCabalPackage'
314− :: (MonadIO m)
315− => FilePath -- ^ Package directory
273+ :: (MonadIO m, IsPath path)
274+ => path -- ^ Package directory
316275 -> m (Either T.Text ([Dist.PWarning], CabalPackageInfo))
317−parseCabalPackage' pkg = runExceptT $ do
276+parseCabalPackage' (toFilePath -> pkg) = runExceptT $ do
318277 (cabalContents, packageFile, packageName) <- guessCabalPackageFile pkg >>= \case
319− Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack pkg
320− Left (GuessPackageFileError_Ambiguous _) -> throwError $ "Unable to determine which .cabal file to use in " <> T.pack pkg
278+ Left GuessPackageFileError_NotFound -> throwError [i|No .cabal or package.yaml file found in ${pkg}|]
279+ Left (GuessPackageFileError_Ambiguous _) -> throwError [i|Unable to determine which .cabal file to use in ${pkg}|]
321280 Right (Left (CabalFilePath file)) -> (, file, takeBaseName file) <$> liftIO (readUTF8File file)
322281 Right (Right (HPackFilePath file)) -> do
323282 let
@@ -344,7 +303,7 @@ parseCabalPackage' pkg = runExceptT $ do
344303 pure $ (warnings,) $ CabalPackageInfo
345304 { _cabalPackageInfo_packageName = T.pack packageName
346305 , _cabalPackageInfo_packageFile = packageFile
347− , _cabalPackageInfo_packageRoot = takeDirectory packageFile
306+ , _cabalPackageInfo_packageRoot = unPath $ takeDirectory (mkPath packageFile)
348307 , _cabalPackageInfo_buildable = buildable $ libBuildInfo lib
349308 , _cabalPackageInfo_sourceDirs =
350309 fromMaybe (pure ".") $ NE.nonEmpty $ hsSourceDirs $ libBuildInfo lib
@@ -356,9 +315,9 @@ parseCabalPackage' pkg = runExceptT $ do
356315 }
357316 Right Nothing -> throwError "Haskell package has no library component"
358317 Left (_, errors) ->
359− throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
318+ throwError $ [i|Failed to parse ${packageFile}:|] <> "\n" <> T.pack (unlines (map show errors))
360319
361−parsePackagesOrFail :: (MonadObelisk m, Foldable f) => f FilePath -> m (NE.NonEmpty CabalPackageInfo)
320+parsePackagesOrFail :: (MonadObelisk m, Foldable f, IsPath path) => f path -> m (NE.NonEmpty CabalPackageInfo)
362321parsePackagesOrFail dirs' = do
363322 (pkgDirErrs, packageInfos') <- fmap partitionEithers $ for dirs $ \dir -> do
364323 flip fmap (parseCabalPackage dir) $ \case
@@ -383,11 +342,11 @@ parsePackagesOrFail dirs' = do
383342
384343 packageInfos <- case NE.nonEmpty $ toList unambiguous of
385344 Nothing -> failWith $ T.pack $
386− "No valid, buildable packages found" <> (if null dirs then "" else " in " <> intercalate ", " dirs)
345+ "No valid, buildable packages found" <> (if null dirs then "" else " in " <> intercalate ", " (map toFilePath dirs))
387346 Just xs -> pure xs
388347
389348 unless (null pkgDirErrs) $
390− putLog Warning $ T.pack $ "Failed to find buildable packages in " <> intercalate ", " pkgDirErrs
349+ putLog Warning $ T.pack $ "Failed to find buildable packages in " <> intercalate ", " (map toFilePath pkgDirErrs)
391350
392351 pure packageInfos
393352 where
@@ -404,7 +363,7 @@ withGhciScript
404363 -> m ()
405364withGhciScript (toList -> packageInfos) f =
406365 withSystemTempDirectory "ob-ghci" $ \fp -> do
407− let dotGhciPath = fp </> ".ghci"
366+ let dotGhciPath = toFilePath $ fp </> rel ".ghci"
408367 liftIO $ writeFile dotGhciPath dotGhci
409368 f dotGhciPath
410369 where
@@ -424,17 +383,17 @@ withGhciScript (toList -> packageInfos) f =
424383-- | Builds a list of options to pass to ghci or set in .ghci file that configures
425384-- the preprocessor and source includes.
426385getGhciSessionSettings
427− :: (MonadObelisk m, Foldable f)
386+ :: (MonadObelisk m, Foldable f, IsPath path)
428387 => f CabalPackageInfo -- ^ List of packages to load into ghci
429− -> FilePath -- ^ All paths will be relative to this path
388+ -> path -- ^ All paths will be relative to this path
430389 -> Bool -- ^ Use relative paths
431390 -> m [String]
432391getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
433392 -- N.B. ghci settings do NOT support escaping in any way. To minimize the likelihood that
434393 -- paths-with-spaces ruin our day, we first canonicalize everything, and then relativize
435394 -- all paths to 'pathBase'.
436− selfExe <- liftIO $ canonicalizePath =<< getExecutablePath
437− canonicalPathBase <- liftIO $ canonicalizePath pathBase
395+ selfExe <- canonicalizePath =<< liftIO getExecutablePath
396+ canonicalPathBase <- canonicalizePath pathBase
438397
439398 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
440399 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
@@ -443,11 +402,12 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
443402
444403 pure
445404 $ baseGhciOptions
446− <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
405+ <> ["-F", "-pgmF", toFilePath selfExe, "-optF", preprocessorIdentifier]
447406 <> concatMap (\p -> ["-optF", p]) pkgFiles
448407 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
449408 where
450− relativeTo' = if useRelativePaths then relativeTo else const
409+ relativeTo' :: Path Canonical -> Path Canonical -> FilePath
410+ relativeTo' a b = if useRelativePaths then toFilePath $ relativeTo a b else toFilePath a
451411
452412baseGhciOptions :: [String]
453413baseGhciOptions =
@@ -492,11 +452,11 @@ runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
492452 testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
493453
494454 adjustRoot x = if chdirToRoot then makeRelative root x else x
495− reloadFiles = map adjustRoot [root </> "config"]
455+ reloadFiles = map adjustRoot [toFilePath $ root </> rel "config"]
496456 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
497457
498−mkGhciScriptArg :: FilePath -> [String]
499−mkGhciScriptArg dotGhci = ["-ghci-script", dotGhci]
458+mkGhciScriptArg :: IsPath path => path -> [String]
459+mkGhciScriptArg dotGhci = ["-ghci-script", toFilePath dotGhci]
500460
501461getFreePort :: MonadIO m => m PortNumber
502462getFreePort = liftIO $ withSocketsDo $ do
@@ -509,35 +469,3 @@ getFreePort = liftIO $ withSocketsDo $ do
509469 return sock
510470
511471
512−-- | Convert a 'FilePath' into a 'PathTree'.
513−pathToTree :: a -> FilePath -> PathTree a
514−pathToTree a p = go $ splitDirectories p
515− where
516− go [] = PathTree_Node (Just a) mempty
517− go (x : xs) = PathTree_Node Nothing $ Map.singleton x $ go xs
518−
519−-- | Like 'zipWith' but pads with a padding value instead of stopping on the shortest list.
520−zipDefaultWith :: a -> b -> (a -> b -> c) -> [a] -> [b] -> [c]
521−zipDefaultWith _da _db _f [] [] = []
522−zipDefaultWith da db f (a:as) [] = f a db : zipDefaultWith da db f as []
523−zipDefaultWith da db f [] (b:bs) = f da b : zipDefaultWith da db f [] bs
524−zipDefaultWith da db f (a:as) (b:bs) = f a b : zipDefaultWith da db f as bs
525−
526−-- | Makes the first absolute path relative to the second absolute path.
527−--
528−-- Both input paths MUST be absolute.
529−--
530−-- Unlike 'makeRelative' this does not merely strip prefixes. It will introduce
531−-- enough @..@ paths to make the resulting path truly relative in virtually every
532−-- case. The only exception is on Windows when the two paths are on different
533−-- drives. In this case the resulting path may be absolute.
534−relativeTo :: FilePath -> FilePath -> FilePath
535−relativeTo dir base
536− = bifoldr1 (</>)
537− $ bimap (collapse . (".." <$) . catMaybes) (collapse . catMaybes)
538− $ unzip
539− $ dropWhile (\(a,b) -> a == b)
540− $ zipDefaultWith Nothing Nothing (,)
541− (map Just $ splitDirectories base)
542− (map Just $ splitDirectories dir)
543− where collapse = foldr (</>) ""