on master help sign in
Rename hack->interpret, use set-like merge for interpret paths
commit0d62eeedec5f8f243b4eaa214461ae556283e892 authorTom Smalley <tfsmalley@gmail.com> dateFri 10 Apr 2020, 13:53 (7 years ago) parent84cb2fe
2 files changed+92−76
lib/command/src/Obelisk/Command.hs+45−46 lib/command/src/Obelisk/Command/Run.hs+47−30
lib/command/src/Obelisk/Command.hs+45−46
@@ -85,11 +85,11 @@ initForce = switch (long "force" <> help "Allow ob init to overwrite files")
8585data ObCommand
8686 = ObCommand_Init InitSource Bool
8787 | ObCommand_Deploy DeployCommand
88− | ObCommand_Run [(FilePath, HackOn)]
88+ | ObCommand_Run [(FilePath, Interpret)]
8989 | ObCommand_Profile String [String]
9090 | ObCommand_Thunk ThunkOption
91− | ObCommand_Repl [(FilePath, HackOn)]
92− | ObCommand_Watch [(FilePath, HackOn)]
91+ | ObCommand_Repl [(FilePath, Interpret)]
92+ | ObCommand_Watch [(FilePath, Interpret)]
9393 | ObCommand_Shell ShellOpts
9494 | ObCommand_Doc String [String] -- shell and list of packages
9595 | ObCommand_Hoogle String Int -- shell and port
@@ -100,7 +100,7 @@ data ObInternal
100100 -- the preprocessor argument syntax is also handled outside
101101 -- optparse-applicative, but it shouldn't ever conflict with another syntax
102102 = ObInternal_ApplyPackages String String String [String]
103− | ObInternal_ExportGhciConfig [(FilePath, HackOn)]
103+ | ObInternal_ExportGhciConfig [(FilePath, Interpret)]
104104 deriving Show
105105
106106obCommand :: ArgsConfig -> Parser ObCommand
@@ -108,11 +108,11 @@ obCommand cfg = hsubparser
108108 (mconcat
109109 [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project"
110110 , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project"
111− , command "run" $ info (ObCommand_Run <$> hackOnOpts) $ progDesc "Run current project in development mode"
111+ , command "run" $ info (ObCommand_Run <$> interpretOpts) $ progDesc "Run current project in development mode"
112112 , command "profile" $ info (uncurry ObCommand_Profile <$> profileCommand) $ progDesc "Run current project with profiling enabled"
113113 , command "thunk" $ info (ObCommand_Thunk <$> thunkOption) $ progDesc "Manipulate thunk directories"
114− , command "repl" $ info (ObCommand_Repl <$> hackOnOpts) $ progDesc "Open an interactive interpreter"
115− , command "watch" $ info (ObCommand_Watch <$> hackOnOpts) $ progDesc "Watch current project for errors and warnings"
114+ , command "repl" $ info (ObCommand_Repl <$> interpretOpts) $ progDesc "Open an interactive interpreter"
115+ , command "watch" $ info (ObCommand_Watch <$> interpretOpts) $ progDesc "Watch current project for errors and warnings"
116116 , command "shell" $ info (ObCommand_Shell <$> shellOpts) $ progDesc "Enter a shell with project dependencies"
117117 , command "doc" $ info (ObCommand_Doc <$> shellFlags <*> packageNames) $
118118 progDesc "List paths to haddock documentation for specified packages"
@@ -125,7 +125,7 @@ obCommand cfg = hsubparser
125125
126126internalCommand :: Parser ObInternal
127127internalCommand = hsubparser $ mconcat
128− [ command "export-ghci-configuration" $ info (ObInternal_ExportGhciConfig <$> hackOnOpts) $ progDesc "Export the GHCi configuration used by ob run, etc.; useful for IDE integration"
128+ [ command "export-ghci-configuration" $ info (ObInternal_ExportGhciConfig <$> interpretOpts) $ progDesc "Export the GHCi configuration used by ob run, etc.; useful for IDE integration"
129129 ]
130130
131131packageNames :: Parser [String]
@@ -258,7 +258,7 @@ thunkOption = hsubparser $ mconcat
258258data ShellOpts
259259 = ShellOpts
260260 { _shellOpts_shell :: String
261− , _shellOpts_hackPaths :: [(FilePath, HackOn)]
261+ , _shellOpts_interpretPaths :: [(FilePath, Interpret)]
262262 , _shellOpts_command :: Maybe String
263263 }
264264 deriving Show
@@ -269,18 +269,21 @@ shellFlags =
269269 <|> flag "ghc" "ghcjs" (long "ghcjs" <> help "Enter a shell having ghcjs rather than ghc")
270270 <|> strOption (short 'A' <> long "argument" <> metavar "NIXARG" <> help "Use the environment specified by the given nix argument of `shells'")
271271
272−hackOnOpts :: Parser [(FilePath, HackOn)]
273−hackOnOpts = many
274− ( (, HackOn_HackOn) <$>
275− strOption (common <> long "hack-on" <> help
272+interpretOpts :: Parser [(FilePath, Interpret)]
273+interpretOpts = many
274+ ( (, Interpret_Interpret) <$>
275+ strOption (common <> long "interpret" <> help
276276 "Don't pre-build packages found in DIR when constructing the package database. The default behavior is \
277− \'--hack-on <project-root>'. Use --hack-on and --no-hack-on multiple times to add or remove multiple trees \
278− \ from the environment. Right-most directories will override directories on the left in as much as they overlap."
277+ \'--interpret <project-root>', which will load everything which is unpacked into GHCi. \
278+ \ Use --interpret and --no-interpret multiple times to add or remove multiple trees \
279+ \ from the environment. Settings for right-most directories will \
280+ \ override settings for any identical directories given earlier."
279281 )
280− <|> (, HackOn_NoHackOn) <$>
281− strOption (common <> long "no-hack-on" <> help
282+ <|> (, Interpret_NoInterpret) <$>
283+ strOption (common <> long "no-interpret" <> help
282284 "Make packages found in DIR available in the package database (but only when they are used dependencies). \
283− \See help for --hack-on for how the two options are related."
285+ \ This will build the packages in DIR before loading GHCi. \
286+ \See help for ---on for how the two options are related."
284287 )
285288 )
286289 where
@@ -289,7 +292,7 @@ hackOnOpts = many
289292shellOpts :: Parser ShellOpts
290293shellOpts = ShellOpts
291294 <$> shellFlags
292− <*> hackOnOpts
295+ <*> interpretOpts
293296 <*> optional (strArgument (metavar "COMMAND"))
294297
295298portOpt :: Int -> Parser Int
@@ -407,7 +410,7 @@ ob = \case
407410 Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
408411 DeployCommand_Update -> deployUpdate "."
409412 DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs
410− ObCommand_Run hackPathsList -> withHackPaths hackPathsList run
413+ ObCommand_Run interpretPathsList -> withInterpretPaths interpretPathsList run
411414 ObCommand_Profile basePath rtsFlags -> profile basePath rtsFlags
412415 ObCommand_Thunk to -> case _thunkOption_command to of
413416 ThunkCommand_Update config -> for_ thunks (updateThunkToLatest config)
@@ -416,27 +419,27 @@ ob = \case
416419 ThunkCommand_Init -> for_ thunks initThunk
417420 where
418421 thunks = _thunkOption_thunks to
419− ObCommand_Repl hackPathsList -> withHackPaths hackPathsList runRepl
420− ObCommand_Watch hackPathsList -> withHackPaths hackPathsList runWatch
421− ObCommand_Shell (ShellOpts shellAttr hackPathsList cmd) -> withHackPaths hackPathsList $ \root hackPaths ->
422− nixShellForHackPaths False shellAttr root hackPaths cmd
422+ ObCommand_Repl interpretPathsList -> withInterpretPaths interpretPathsList runRepl
423+ ObCommand_Watch interpretPathsList -> withInterpretPaths interpretPathsList runWatch
424+ ObCommand_Shell (ShellOpts shellAttr interpretPathsList cmd) -> withInterpretPaths interpretPathsList $ \root interpretPaths ->
425+ nixShellForInterpretPaths False shellAttr root interpretPaths cmd
423426 ObCommand_Doc shellAttr pkgs -> withProjectRoot "." $ \root ->
424− nixShellForHackPaths True shellAttr root emptyPathTree $ Just $ haddockCommand pkgs
427+ nixShellForInterpretPaths True shellAttr root emptyPathTree $ Just $ haddockCommand pkgs
425428 ObCommand_Hoogle shell' port -> withProjectRoot "." $ \root -> do
426429 nixShellWithHoogle root True shell' $ Just $ "hoogle server -p " <> show port <> " --local"
427430 ObCommand_Internal icmd -> case icmd of
428431 ObInternal_ApplyPackages origPath inPath outPath packagePaths -> do
429432 liftIO $ Preprocessor.applyPackages origPath inPath outPath packagePaths
430− ObInternal_ExportGhciConfig hackPathsList -> liftIO . putStrLn . unlines =<< withHackPaths hackPathsList exportGhciConfig
433+ ObInternal_ExportGhciConfig interpretPathsList -> liftIO . putStrLn . unlines =<< withInterpretPaths interpretPathsList exportGhciConfig
431434
432435-- | A helper for the common case that the command you want to run needs the project root and a resolved
433−-- set of hacking paths.
434−withHackPaths :: MonadObelisk m => [(FilePath, HackOn)] -> (FilePath -> PathTree HackOn -> m a) -> m a
435−withHackPaths hackPathsList f = withProjectRoot "." $ \root -> do
436− hackPaths' <- resolveHackPaths $ (root, HackOn_HackOn) : hackPathsList
437− case hackPaths' of
436+-- set of interpret paths.
437+withInterpretPaths :: MonadObelisk m => [(FilePath, Interpret)] -> (FilePath -> PathTree Interpret -> m a) -> m a
438+withInterpretPaths interpretPathsList f = withProjectRoot "." $ \root -> do
439+ interpretPaths' <- resolveInterpretPaths $ (root, Interpret_Interpret) : interpretPathsList
440+ case interpretPaths' of
438441 Nothing -> failWith "No paths provided for finding packages"
439− Just hackPaths -> f root hackPaths
442+ Just interpretPaths -> f root interpretPaths
440443
441444haddockCommand :: [String] -> String
442445haddockCommand pkgs = unwords
@@ -451,25 +454,21 @@ haddockCommand pkgs = unwords
451454getArgsConfig :: IO ArgsConfig
452455getArgsConfig = pure $ ArgsConfig { _argsConfig_enableVmBuilderByDefault = System.Info.os == "darwin" }
453456
454−
455−-- | Resolves an ordered list of paths for use with @--hack-on@/@--no-hack-on@ by coalescing
457+-- | Resolves an ordered list of paths for use with @--interpret@/@--no-interpret@ by coalescing
456458-- paths into a non-ambiguous set of paths. Ambiguity is resolved by chosing right-most paths
457−-- over any preceeding paths where they overlap.
459+-- over any preceeding identical paths.
458460--
459−-- For example: @a/b=ON a/b/c=OFF@ produces @a/b=ON a/b/c=OFF@ (the same value) but flipping
460−-- the order does not as @a/b/c=OFF a/b=ON@ produces @a/b=ON@ since @a/b/c@ is inside @a/b@ and
461−-- @a/b@ is the right-most.
461+-- For example: @a/b=ON a/b/c=OFF@ and @a/b/c=OFF a/b=ON@ are the same.
462+-- @a/b=ON a/b=OFF@ is reduced to @a/b=OFF@. We prefer right-biased choice to
463+-- increase scriptability.
462464--
463465-- N.B. All the paths in the result will be canonicalized. It's impossible to determine path
464466-- overlap otherwise.
465−resolveHackPaths :: MonadIO m => [(FilePath, a)] -> m (Maybe (PathTree a))
466−resolveHackPaths ps = do
467+resolveInterpretPaths :: MonadIO m => [(FilePath, a)] -> m (Maybe (PathTree a))
468+resolveInterpretPaths ps = do
467469 trees <- liftIO $ for ps $ \(p, a) -> pathToTree a <$> canonicalizePath p
468− pure $ foldr1 mergeRightBias <$> nonEmpty trees
470+ pure $ foldr1 mergeTrees <$> nonEmpty trees
469471 where
470472 -- | Merge two 'PathTree's preferring leaves on the right in as much as they overlap with paths on the left.
471− mergeRightBias :: PathTree a -> PathTree a -> PathTree a
472− mergeRightBias = go
473− where
474− go _ b@(PathTree_Node (Just _) _) = b -- When a leaf is present on the right, right always wins.
475− go (PathTree_Node v a) (PathTree_Node Nothing b) = PathTree_Node v $ Map.unionWith go a b
473+ mergeTrees :: PathTree a -> PathTree a -> PathTree a
474+ mergeTrees (PathTree_Node ax x) (PathTree_Node ay y) = PathTree_Node (ay <|> ax) $ Map.unionWith mergeTrees x y
lib/command/src/Obelisk/Command/Run.hs+47−30
@@ -93,7 +93,12 @@ data CabalPackageInfo = CabalPackageInfo
9393 }
9494
9595-- | 'Bool' with a better name for it's purpose.
96−data HackOn = HackOn_HackOn | HackOn_NoHackOn deriving (Eq, Ord, Show)
96+data Interpret = Interpret_Interpret | Interpret_NoInterpret deriving (Eq, Ord, Show)
97+
98+textInterpret :: Interpret -> Text
99+textInterpret = \case
100+ Interpret_Interpret -> "Interpret"
101+ Interpret_NoInterpret -> "NoInterpret"
97102
98103-- | Describe a set of 'FilePath's as a tree to facilitate merging them in a convenient way.
99104data PathTree a = PathTree_Node
@@ -104,6 +109,17 @@ data PathTree a = PathTree_Node
104109emptyPathTree :: PathTree a
105110emptyPathTree = PathTree_Node Nothing mempty
106111
112+-- | 2D ASCII drawing of a 'PathTree'. Adapted from Data.Tree.draw.
113+drawPathTree :: (a -> Text) -> PathTree a -> Text
114+drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
115+ where
116+ annotated ma = maybe id (\a b -> b <> " [" <> showA a <> "]") ma . T.pack
117+ goTree (fp, PathTree_Node ma forest) = annotated ma fp : goForest (Map.toList forest)
118+ goForest [] = []
119+ goForest [tree] = shift "└─ " " " (goTree tree)
120+ goForest (tree:forest) = shift "├─ " "│ " (goTree tree) <> goForest forest
121+ shift first other = zipWith (<>) (first : repeat other)
122+
107123-- | Used to signal to obelisk that it's being invoked as a preprocessor
108124preprocessorIdentifier :: String
109125preprocessorIdentifier = "__preprocessor-apply-packages"
@@ -142,9 +158,9 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
142158 <> [ "-RTS" ]
143159 void $ waitForProcess ph
144160
145−run :: MonadObelisk m => FilePath -> PathTree HackOn -> m ()
146−run root hackPaths = do
147− pkgs <- getParsedLocalPkgs root hackPaths
161+run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
162+run root interpretPaths = do
163+ pkgs <- getParsedLocalPkgs root interpretPaths
148164 withGhciScript pkgs root $ \dotGhciPath -> do
149165 freePort <- getFreePort
150166 assets <- findProjectAssets root
@@ -157,51 +173,52 @@ run root hackPaths = do
157173 , "Frontend.frontend"
158174 ]
159175
160−runRepl :: MonadObelisk m => FilePath -> PathTree HackOn -> m ()
161−runRepl root hackPaths = do
162− pkgs <- getParsedLocalPkgs root hackPaths
176+runRepl :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
177+runRepl root interpretPaths = do
178+ pkgs <- getParsedLocalPkgs root interpretPaths
163179 withGhciScript pkgs "." $ \dotGhciPath ->
164180 runGhciRepl root pkgs dotGhciPath
165181
166−runWatch :: MonadObelisk m => FilePath -> PathTree HackOn -> m ()
167−runWatch root hackPaths = do
168− pkgs <- getParsedLocalPkgs root hackPaths
182+runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
183+runWatch root interpretPaths = do
184+ pkgs <- getParsedLocalPkgs root interpretPaths
169185 withGhciScript pkgs root $ \dotGhciPath ->
170186 runGhcid root True dotGhciPath pkgs Nothing
171187
172−exportGhciConfig :: MonadObelisk m => FilePath -> PathTree HackOn -> m [String]
173−exportGhciConfig root hackPaths = do
174− pkgs <- getParsedLocalPkgs root hackPaths
188+exportGhciConfig :: MonadObelisk m => FilePath -> PathTree Interpret -> m [String]
189+exportGhciConfig root interpretPaths = do
190+ pkgs <- getParsedLocalPkgs root interpretPaths
175191 getGhciSessionSettings pkgs "."
176192
177−nixShellForHackPaths :: MonadObelisk m => Bool -> String -> FilePath -> PathTree HackOn -> Maybe String -> m ()
178−nixShellForHackPaths isPure shell root hackPaths cmd = do
179− pkgs <- getParsedLocalPkgs root hackPaths
193+nixShellForInterpretPaths :: MonadObelisk m => Bool -> String -> FilePath -> PathTree Interpret -> Maybe String -> m ()
194+nixShellForInterpretPaths isPure shell root interpretPaths cmd = do
195+ pkgs <- getParsedLocalPkgs root interpretPaths
180196 nixShellWithPkgs root isPure False (packageInfoToNamePathMap pkgs) shell cmd
181197
182198-- | Like 'getLocalPkgs' but also parses them and fails if any of them can't be parsed.
183−getParsedLocalPkgs :: MonadObelisk m => FilePath -> PathTree HackOn -> m (NonEmpty CabalPackageInfo)
184−getParsedLocalPkgs root hackPaths = parsePackagesOrFail =<< getLocalPkgs root hackPaths
199+getParsedLocalPkgs :: MonadObelisk m => FilePath -> PathTree Interpret -> m (NonEmpty CabalPackageInfo)
200+getParsedLocalPkgs root interpretPaths = parsePackagesOrFail =<< getLocalPkgs root interpretPaths
185201
186202-- | Relative paths to local packages of an obelisk project.
187203--
188204-- These are a combination of the obelisk predefined local packages,
189205-- and any packages that the user has set with the @packages@ argument
190206-- to the Nix @project@ function.
191−getLocalPkgs :: forall m. MonadObelisk m => FilePath -> PathTree HackOn -> m (Set FilePath)
192−getLocalPkgs root hackPaths = do
193− putLog Debug [i|Finding packages with root ${root} and hacking paths ${hackPaths}|]
207+getLocalPkgs :: forall m. MonadObelisk m => FilePath -> PathTree Interpret -> m (Set FilePath)
208+getLocalPkgs root interpretPaths = do
209+ putLog Debug $ [i|Finding packages with root ${root} and interpret paths:|] <> "\n" <> drawPathTree textInterpret interpretPaths
194210 obeliskPackagePaths <- runFind ["-L", root, "-name", ".obelisk", "-type", "d"]
195211
196212 -- We do not want to find packages that are embedded inside other obelisk projects, unless that
197213 -- obelisk project is our own.
198− let obeliskPackageExclusions = Set.fromList $ filter (/= root) $ map takeDirectory obeliskPackagePaths
199− rootsAndExclusions = calcHackOnFinds "" hackPaths
214+ obeliskPackageExclusions <- liftIO $ fmap Set.fromList $ traverse canonicalizePath $ filter (/= root) $ map takeDirectory obeliskPackagePaths
215+ putLog Debug [i|Excluding obelisk packages: ${T.pack $ unwords $ Set.toList obeliskPackageExclusions}|]
216+ let rootsAndExclusions = calcIntepretFinds "" interpretPaths
200217
201− fmap fold $ for (Map.toAscList rootsAndExclusions) $ \(hackPathRoot, exclusions) ->
218+ fmap fold $ for (Map.toAscList rootsAndExclusions) $ \(interpretPathRoot, exclusions) ->
202219 let allExclusions = obeliskPackageExclusions <> exclusions <> Set.singleton ("*" </> attrCacheFileName)
203220 in fmap (Set.fromList . map normalise) $ runFind $
204− ["-L", hackPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
221+ ["-L", interpretPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
205222 <> concat [["-not", "-path", p </> "*"] | p <- toList allExclusions]
206223 where
207224 runFind args = do
@@ -211,12 +228,12 @@ getLocalPkgs root hackPaths = do
211228
212229-- | Calculates a set of root 'FilePath's along with each one's corresponding set of exclusions.
213230-- This is used when constructing a set of @find@ commands to run to produce a set of packages
214−-- that matches the user's @--hack-on@/@--no-hack-on@ settings.
215−calcHackOnFinds :: FilePath -> PathTree HackOn -> Map FilePath (Set FilePath)
216−calcHackOnFinds treeRoot0 tree0 = runIdentity $ go treeRoot0 tree0
231+-- that matches the user's @--interpret@/@--no-interpret@ settings.
232+calcIntepretFinds :: FilePath -> PathTree Interpret -> Map FilePath (Set FilePath)
233+calcIntepretFinds treeRoot0 tree0 = runIdentity $ go treeRoot0 tree0
217234 where
218− go treeRoot tree = foldPathTreeFor (== HackOn_HackOn) treeRoot tree $ \parent children -> do
219− exclusions <- foldPathTreeFor (== HackOn_NoHackOn) parent children $ \parent' children' ->
235+ go treeRoot tree = foldPathTreeFor (== Interpret_Interpret) treeRoot tree $ \parent children -> do
236+ exclusions <- foldPathTreeFor (== Interpret_NoInterpret) parent children $ \parent' children' ->
220237 pure $ Map.singleton parent' children'
221238 deeperFinds <- fmap fold $ Map.traverseWithKey go exclusions
222239 pure $ Map.singleton parent (Map.keysSet exclusions) <> deeperFinds