PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/server/Scion/Server/Commands.hs

https://github.com/eBZaRuG/scion
Haskell | 598 lines | 474 code | 74 blank | 50 comment | 3 complexity | c20acab5e96fa87ba69c3bf48fb44778 MD5 | raw file
  1. {-# LANGUAGE ScopedTypeVariables, CPP, PatternGuards,
  2. ExistentialQuantification #-} -- for 'Cmd'
  3. {-# OPTIONS_GHC -fno-warn-orphans #-}
  4. -- |
  5. -- Module : Scion.Server.Commands
  6. -- Copyright : (c) Thomas Schilling 2008
  7. -- License : BSD-style
  8. --
  9. -- Maintainer : nominolo@gmail.com
  10. -- Stability : experimental
  11. -- Portability : portable
  12. --
  13. -- Commands provided by the server.
  14. --
  15. -- TODO: Need some way to document the wire protocol. Autogenerate?
  16. --
  17. module Scion.Server.Commands (
  18. handleRequest, malformedRequest, -- allCommands, allCommands',
  19. -- these are reused in the vim interface
  20. supportedPragmas, allExposedModules,
  21. ) where
  22. import Prelude as P
  23. import Scion.Types
  24. import Scion.Types.Notes
  25. import Scion.Utils
  26. import Scion.Session
  27. import Scion.Server.Protocol
  28. import Scion.Inspect
  29. import Scion.Inspect.DefinitionSite
  30. import Scion.Configure
  31. import DynFlags ( supportedLanguages, allFlags )
  32. import Exception
  33. import FastString
  34. import GHC
  35. import PprTyThing ( pprTypeForUser )
  36. import Outputable ( ppr, showSDoc, showSDocDump, dcolon, showSDocForUser,
  37. showSDocDebug, printDump )
  38. import qualified Outputable as O ( (<+>), ($$) )
  39. import Control.Applicative
  40. import Control.Monad
  41. import Data.List ( nub )
  42. import Data.Time.Clock ( NominalDiffTime )
  43. import System.Exit ( ExitCode(..) )
  44. import Text.JSON
  45. import qualified Data.Map as M
  46. import qualified Data.MultiSet as MS
  47. import Distribution.Text ( display )
  48. import qualified Distribution.PackageDescription as PD
  49. import GHC.SYB.Utils
  50. #ifndef HAVE_PACKAGE_DB_MODULES
  51. import UniqFM ( eltsUFM )
  52. import Packages ( pkgIdMap )
  53. import Distribution.InstalledPackageInfo
  54. #endif
  55. type KeepGoing = Bool
  56. -- a scion request is JS object with 3 keys:
  57. -- method: the method to be called
  58. -- params: arguments to be passed
  59. -- id : this value will be passed back to the client
  60. -- to identify a reply to a specific request
  61. -- asynchronous requests will be implemented in the future
  62. handleRequest :: JSValue -> ScionM (JSValue, KeepGoing)
  63. handleRequest (JSObject req) =
  64. let request = do JSString method <- lookupKey req "method"
  65. params <- lookupKey req "params"
  66. seq_id <- lookupKey req "id"
  67. return (fromJSString method, params, seq_id)
  68. in
  69. case request of
  70. Error _ -> return (malformedRequest, True)
  71. Ok (method, params, seq_id)
  72. | method == "quit" -> return (makeObject
  73. [("version", str "0.1")
  74. ,("result", JSNull)
  75. ,("id", seq_id)], False)
  76. | otherwise ->
  77. case M.lookup method allCmds of
  78. Nothing -> return (unknownCommand seq_id, True)
  79. Just (Cmd _ arg_parser) ->
  80. decode_params params arg_parser seq_id
  81. where
  82. decode_params JSNull arg_parser seq_id =
  83. decode_params (makeObject []) arg_parser seq_id
  84. decode_params (JSObject args) arg_parser seq_id =
  85. case unPa arg_parser args of
  86. Left err -> return (paramParseError seq_id err, True)
  87. Right act -> do
  88. r <- handleScionException act
  89. case r of
  90. Error msg -> return (commandExecError seq_id msg, True)
  91. Ok a ->
  92. return (makeObject
  93. [("version", str "0.1")
  94. ,("id", seq_id)
  95. ,("result", showJSON a)], True)
  96. decode_params _ _ seq_id =
  97. return (paramParseError seq_id "Params not an object", True)
  98. handleRequest _ = do
  99. return (malformedRequest, True)
  100. malformedRequest :: JSValue
  101. malformedRequest = makeObject
  102. [("version", str "0.1")
  103. ,("error", makeObject
  104. [("name", str "MalformedRequest")
  105. ,("message", str "Request was not a proper request object.")])]
  106. unknownCommand :: JSValue -> JSValue
  107. unknownCommand seq_id = makeObject
  108. [("version", str "0.1")
  109. ,("id", seq_id)
  110. ,("error", makeObject
  111. [("name", str "UnknownCommand")
  112. ,("message", str "The requested method is not supported.")])]
  113. paramParseError :: JSValue -> String -> JSValue
  114. paramParseError seq_id msg = makeObject
  115. [("version", str "0.1")
  116. ,("id", seq_id)
  117. ,("error", makeObject
  118. [("name", str "ParamParseError")
  119. ,("message", str msg)])]
  120. commandExecError :: JSValue -> String -> JSValue
  121. commandExecError seq_id msg = makeObject
  122. [("version", str "0.1")
  123. ,("id", seq_id)
  124. ,("error", makeObject
  125. [("name", str "CommandFailed")
  126. ,("message", str msg)])]
  127. allCmds :: M.Map String Cmd
  128. allCmds = M.fromList [ (cmdName c, c) | c <- allCommands ]
  129. ------------------------------------------------------------------------
  130. -- | All Commands supported by this Server.
  131. allCommands :: [Cmd]
  132. allCommands =
  133. [ cmdConnectionInfo
  134. , cmdOpenCabalProject
  135. , cmdConfigureCabalProject
  136. , cmdLoadComponent
  137. , cmdListSupportedLanguages
  138. , cmdListSupportedPragmas
  139. , cmdListSupportedFlags
  140. , cmdListCabalComponents
  141. , cmdListCabalConfigurations
  142. , cmdWriteSampleConfig
  143. , cmdListRdrNamesInScope
  144. , cmdListExposedModules
  145. , cmdCurrentComponent
  146. , cmdCurrentCabalFile
  147. , cmdSetVerbosity
  148. , cmdGetVerbosity
  149. , cmdLoad
  150. , cmdDumpSources
  151. , cmdThingAtPoint
  152. , cmdSetGHCVerbosity
  153. , cmdBackgroundTypecheckFile
  154. , cmdAddCmdLineFlag
  155. , cmdForceUnload
  156. , cmdDumpDefinedNames
  157. , cmdDefinedNames
  158. , cmdNameDefinitions
  159. , cmdIdentify
  160. , cmdDumpModuleGraph
  161. ]
  162. ------------------------------------------------------------------------------
  163. type OkErr a = Result a
  164. -- encode expected errors as proper return values
  165. handleScionException :: ScionM a -> ScionM (OkErr a)
  166. handleScionException m = ((((do
  167. r <- m
  168. return (Ok r)
  169. `gcatch` \(e :: SomeScionException) -> return (Error (show e)))
  170. `gcatch` \(e' :: GhcException) ->
  171. case e' of
  172. Panic _ -> throw e'
  173. InstallationError _ -> throw e'
  174. Interrupted -> throw e'
  175. _ -> return (Error (show e')))
  176. `gcatch` \(e :: ExitCode) ->
  177. -- client code may not exit the server!
  178. return (Error (show e)))
  179. `gcatch` \(e :: IOError) ->
  180. return (Error (show e)))
  181. -- `gcatch` \(e :: SomeException) ->
  182. -- liftIO (print e) >> liftIO (throwIO e)
  183. ------------------------------------------------------------------------------
  184. newtype Pa a = Pa { unPa :: JSObject JSValue -> Either String a }
  185. instance Monad Pa where
  186. return x = Pa $ \_ -> Right x
  187. m >>= k = Pa $ \req ->
  188. case unPa m req of
  189. Left err -> Left err
  190. Right a -> unPa (k a) req
  191. fail msg = Pa $ \_ -> Left msg
  192. withReq :: (JSObject JSValue -> Pa a) -> Pa a
  193. withReq f = Pa $ \req -> unPa (f req) req
  194. reqArg' :: JSON a => String -> (a -> b) -> (b -> r) -> Pa r
  195. reqArg' name trans f = withReq $ \req ->
  196. case lookupKey req name of
  197. Error _ -> fail $ "required arg missing: " ++ name
  198. Ok x ->
  199. case readJSON x of
  200. Error m -> fail $ "could not decode: " ++ name ++ " - " ++ m
  201. Ok a -> return (f (trans a))
  202. optArg' :: JSON a => String -> b -> (a -> b) -> (b -> r) -> Pa r
  203. optArg' name dflt trans f = withReq $ \req ->
  204. case lookupKey req name of
  205. Error _ -> return (f dflt)
  206. Ok x ->
  207. case readJSON x of
  208. Error n -> fail $ "could not decode: " ++ name ++ " - " ++ n
  209. Ok a -> return (f (trans a))
  210. reqArg :: JSON a => String -> (a -> r) -> Pa r
  211. reqArg name f = reqArg' name id f
  212. optArg :: JSON a => String -> a -> (a -> r) -> Pa r
  213. optArg name dflt f = optArg' name dflt id f
  214. noArgs :: r -> Pa r
  215. noArgs = return
  216. infixr 1 <&>
  217. -- | Combine two arguments.
  218. --
  219. -- TODO: explain type
  220. (<&>) :: (a -> Pa b)
  221. -> (b -> Pa c)
  222. -> a -> Pa c
  223. a1 <&> a2 = \f -> do f' <- a1 f; a2 f'
  224. data Cmd = forall a. JSON a => Cmd String (Pa (ScionM a))
  225. cmdName :: Cmd -> String
  226. cmdName (Cmd n _) = n
  227. ------------------------------------------------------------------------
  228. -- | Used by the client to initialise the connection.
  229. cmdConnectionInfo :: Cmd
  230. cmdConnectionInfo = Cmd "connection-info" $ noArgs worker
  231. where
  232. worker = let pid = 0 :: Int in -- TODO for linux: System.Posix.Internals (c_getpid)
  233. return $ makeObject
  234. [("version", showJSON scionVersion)
  235. ,("pid", showJSON pid)]
  236. cmdOpenCabalProject :: Cmd
  237. cmdOpenCabalProject =
  238. Cmd "open-cabal-project" $
  239. reqArg' "root-dir" fromJSString <&>
  240. optArg' "dist-dir" ".dist-scion" fromJSString <&>
  241. optArg' "extra-args" [] decodeExtraArgs $ worker
  242. where
  243. worker root_dir dist_dir extra_args = do
  244. openOrConfigureCabalProject root_dir dist_dir extra_args
  245. preprocessPackage dist_dir
  246. (toJSString . display . PD.package) `fmap` currentCabalPackage
  247. cmdConfigureCabalProject :: Cmd
  248. cmdConfigureCabalProject =
  249. Cmd "configure-cabal-project" $
  250. reqArg' "root-dir" fromJSString <&>
  251. optArg' "dist-dir" ".dist-scion" fromJSString <&>
  252. optArg' "extra-args" [] decodeExtraArgs $ cmd
  253. where
  254. cmd path rel_dist extra_args = do
  255. configureCabalProject path rel_dist extra_args
  256. preprocessPackage rel_dist
  257. (toJSString . display . PD.package) `fmap` currentCabalPackage
  258. decodeBool :: JSValue -> Bool
  259. decodeBool (JSBool b) = b
  260. decodeBool _ = error "no bool"
  261. decodeExtraArgs :: JSValue -> [String]
  262. decodeExtraArgs JSNull = []
  263. decodeExtraArgs (JSString s) =
  264. words (fromJSString s) -- TODO: check shell-escaping
  265. decodeExtraArgs (JSArray arr) =
  266. [ fromJSString s | JSString s <- arr ]
  267. instance JSON Component where
  268. readJSON (JSObject obj)
  269. | Ok JSNull <- lookupKey obj "library" = return Library
  270. | Ok s <- lookupKey obj "executable" =
  271. return $ Executable (fromJSString s)
  272. | Ok s <- lookupKey obj "file" =
  273. return $ File (fromJSString s)
  274. readJSON _ = fail "component"
  275. showJSON Library = makeObject [("library", JSNull)]
  276. showJSON (Executable n) =
  277. makeObject [("executable", JSString (toJSString n))]
  278. showJSON (File n) =
  279. makeObject [("file", JSString (toJSString n))]
  280. instance JSON CompilationResult where
  281. showJSON (CompilationResult suc notes time) =
  282. makeObject [("succeeded", JSBool suc)
  283. ,("notes", showJSON notes)
  284. ,("duration", showJSON time)]
  285. readJSON (JSObject obj) = do
  286. JSBool suc <- lookupKey obj "succeeded"
  287. notes <- readJSON =<< lookupKey obj "notes"
  288. dur <- readJSON =<< lookupKey obj "duration"
  289. return (CompilationResult suc notes dur)
  290. readJSON _ = fail "compilation-result"
  291. instance (Ord a, JSON a) => JSON (MS.MultiSet a) where
  292. showJSON ms = showJSON (MS.toList ms)
  293. readJSON o = MS.fromList <$> readJSON o
  294. instance JSON Note where
  295. showJSON (Note note_kind loc msg) =
  296. makeObject [("kind", showJSON note_kind)
  297. ,("location", showJSON loc)
  298. ,("message", JSString (toJSString msg))]
  299. readJSON (JSObject obj) = do
  300. note_kind <- readJSON =<< lookupKey obj "kind"
  301. loc <- readJSON =<< lookupKey obj "location"
  302. JSString s <- lookupKey obj "message"
  303. return (Note note_kind loc (fromJSString s))
  304. readJSON _ = fail "note"
  305. str :: String -> JSValue
  306. str = JSString . toJSString
  307. instance JSON NoteKind where
  308. showJSON ErrorNote = JSString (toJSString "error")
  309. showJSON WarningNote = JSString (toJSString "warning")
  310. showJSON InfoNote = JSString (toJSString "info")
  311. showJSON OtherNote = JSString (toJSString "other")
  312. readJSON (JSString s) =
  313. case lookup (fromJSString s)
  314. [("error", ErrorNote), ("warning", WarningNote)
  315. ,("info", InfoNote), ("other", OtherNote)]
  316. of Just x -> return x
  317. Nothing -> fail "note-kind"
  318. readJSON _ = fail "note-kind"
  319. instance JSON Location where
  320. showJSON loc | not (isValidLoc loc) =
  321. makeObject [("no-location", str (noLocText loc))]
  322. showJSON loc | (src, l0, c0, l1, c1) <- viewLoc loc =
  323. makeObject [case src of
  324. FileSrc f -> ("file", str (toFilePath f))
  325. OtherSrc s -> ("other", str s)
  326. ,("region", JSArray (map showJSON [l0,c0,l1,c1]))]
  327. readJSON (JSObject obj) = do
  328. src <- (do JSString f <- lookupKey obj "file"
  329. return (FileSrc (mkAbsFilePath "/" (fromJSString f))))
  330. <|>
  331. (do JSString s <- lookupKey obj "other"
  332. return (OtherSrc (fromJSString s)))
  333. JSArray ls <- lookupKey obj "region"
  334. case mapM readJSON ls of
  335. Ok [l0,c0,l1,c1] -> return (mkLocation src l0 c0 l1 c1)
  336. _ -> fail "region"
  337. readJSON _ = fail "location"
  338. instance JSON NominalDiffTime where
  339. showJSON t = JSRational True (fromRational (toRational t))
  340. readJSON (JSRational _ n) = return $ fromRational (toRational n)
  341. readJSON _ = fail "diff-time"
  342. cmdLoadComponent :: Cmd
  343. cmdLoadComponent =
  344. Cmd "load-component" $
  345. reqArg "component" $ cmd
  346. where
  347. cmd comp = do
  348. loadComponent comp
  349. instance Sexp CompilationResult where
  350. toSexp (CompilationResult success notes time) = toSexp $
  351. ExactSexp $ parens $
  352. showString "compilation-result" <+>
  353. toSexp success <+>
  354. toSexp notes <+>
  355. toSexp (ExactSexp (showString (show
  356. (fromRational (toRational time) :: Float))))
  357. cmdListSupportedLanguages :: Cmd
  358. cmdListSupportedLanguages = Cmd "list-supported-languages" $ noArgs cmd
  359. where cmd = return (map toJSString supportedLanguages)
  360. cmdListSupportedPragmas :: Cmd
  361. cmdListSupportedPragmas =
  362. Cmd "list-supported-pragmas" $ noArgs $ return supportedPragmas
  363. supportedPragmas :: [String]
  364. supportedPragmas =
  365. [ "OPTIONS_GHC", "LANGUAGE", "INCLUDE", "WARNING", "DEPRECATED"
  366. , "INLINE", "NOINLINE", "RULES", "SPECIALIZE", "UNPACK", "SOURCE"
  367. , "SCC"
  368. , "LINE" -- XXX: only used by code generators, still include?
  369. ]
  370. cmdListSupportedFlags :: Cmd
  371. cmdListSupportedFlags =
  372. Cmd "list-supported-flags" $ noArgs $ return (nub allFlags)
  373. cmdListRdrNamesInScope :: Cmd
  374. cmdListRdrNamesInScope =
  375. Cmd "list-rdr-names-in-scope" $ noArgs $ cmd
  376. where cmd = do
  377. rdr_names <- getNamesInScope
  378. return (map (showSDoc . ppr) rdr_names)
  379. -- FIXME: we want the results from a configured cabal file dist/ * because
  380. -- some components may be skipped due to compilation flags (buildable : False) ?
  381. cmdListCabalComponents :: Cmd
  382. cmdListCabalComponents =
  383. Cmd "list-cabal-components" $ reqArg' "cabal-file" fromJSString $ cmd
  384. where cmd cabal_file = cabalProjectComponents cabal_file
  385. -- return all cabal configurations.
  386. -- currently this just globs for * /setup-config
  387. -- in the future you may write a config file describing the most common configuration settings
  388. cmdListCabalConfigurations :: Cmd
  389. cmdListCabalConfigurations =
  390. Cmd "list-cabal-configurations" $
  391. reqArg' "cabal-file" fromJSString <&>
  392. optArg' "type" "uniq" id <&>
  393. optArg' "scion-default" False decodeBool $ cmd
  394. where cmd cabal_file type' scionDefault = liftM showJSON $ cabalConfigurations cabal_file type' scionDefault
  395. cmdWriteSampleConfig :: Cmd
  396. cmdWriteSampleConfig =
  397. Cmd "write-sample-config" $
  398. reqArg "file" $ cmd
  399. where cmd fp = liftIO $ writeSampleConfig fp
  400. allExposedModules :: ScionM [ModuleName]
  401. #ifdef HAVE_PACKAGE_DB_MODULES
  402. allExposedModules = map moduleName `fmap` packageDbModules True
  403. #else
  404. -- This implementation requires our Cabal to be the same as GHC's.
  405. allExposedModules = do
  406. dflags <- getSessionDynFlags
  407. let pkg_db = pkgIdMap (pkgState dflags)
  408. return $ P.concat (map exposedModules (filter exposed (eltsUFM pkg_db)))
  409. #endif
  410. cmdListExposedModules :: Cmd
  411. cmdListExposedModules = Cmd "list-exposed-modules" $ noArgs $ cmd
  412. where cmd = do
  413. mod_names <- allExposedModules
  414. return $ map (showSDoc . ppr) mod_names
  415. cmdSetGHCVerbosity :: Cmd
  416. cmdSetGHCVerbosity =
  417. Cmd "set-ghc-verbosity" $ reqArg "level" $ setGHCVerbosity
  418. cmdBackgroundTypecheckFile :: Cmd
  419. cmdBackgroundTypecheckFile =
  420. Cmd "background-typecheck-file" $ reqArg' "file" fromJSString $ cmd
  421. where cmd fname = backgroundTypecheckFile fname
  422. cmdForceUnload :: Cmd
  423. cmdForceUnload = Cmd "force-unload" $ noArgs $ unload
  424. cmdAddCmdLineFlag :: Cmd
  425. cmdAddCmdLineFlag =
  426. Cmd "add-command-line-flag" $
  427. optArg' "flag" "" fromJSString <&>
  428. optArg' "flags" [] (map fromJSString) $ cmd
  429. where cmd flag flags' = do
  430. addCmdLineFlags $ (if flag == "" then [] else [flag]) ++ flags'
  431. return JSNull
  432. cmdThingAtPoint :: Cmd
  433. cmdThingAtPoint =
  434. Cmd "thing-at-point" $
  435. reqArg "file" <&> reqArg "line" <&> reqArg "column" $ cmd
  436. where
  437. cmd fname line col = do
  438. let loc = srcLocSpan $ mkSrcLoc (fsLit fname) line col
  439. tc_res <- gets bgTcCache
  440. -- TODO: don't return something of type @Maybe X@. The default
  441. -- serialisation sucks.
  442. case tc_res of
  443. Just (Typechecked tcm) -> do
  444. --let Just (src, _, _, _, _) = renamedSource tcm
  445. let src = typecheckedSource tcm
  446. --let in_range = const True
  447. let in_range = overlaps loc
  448. let r = findHsThing in_range src
  449. --return (Just (showSDoc (ppr $ S.toList r)))
  450. unqual <- unqualifiedForModule tcm
  451. case pathToDeepest r of
  452. Nothing -> return (Just "no info")
  453. Just (x,xs) ->
  454. --return $ Just (showSDoc (ppr x O.$$ ppr xs))
  455. case typeOf (x,xs) of
  456. Just t ->
  457. return $ Just $ showSDocForUser unqual
  458. (prettyResult x O.<+> dcolon O.<+>
  459. pprTypeForUser True t)
  460. _ -> return (Just "No info") --(Just (showSDocDebug (ppr x O.$$ ppr xs )))
  461. _ -> return Nothing
  462. cmdDumpSources :: Cmd
  463. cmdDumpSources = Cmd "dump-sources" $ noArgs $ cmd
  464. where
  465. cmd = do
  466. tc_res <- gets bgTcCache
  467. case tc_res of
  468. Just (Typechecked tcm) -> do
  469. let Just (rn, _, _, _, _) = renamedSource tcm
  470. let tc = typecheckedSource tcm
  471. liftIO $ putStrLn $ showSDocDump $ ppr rn
  472. liftIO $ putStrLn $ showData TypeChecker 2 tc
  473. return ()
  474. _ -> return ()
  475. -- remove this func, obsolete. there is also load-component
  476. cmdLoad :: Cmd
  477. cmdLoad = Cmd "load" $ reqArg "component" $ cmd
  478. where
  479. cmd comp = do
  480. liftIO (putStrLn $ "Loading " ++ show comp)
  481. loadComponent comp
  482. cmdSetVerbosity :: Cmd
  483. cmdSetVerbosity =
  484. Cmd "set-verbosity" $ reqArg "level" $ cmd
  485. where cmd v = setVerbosity (intToVerbosity v)
  486. cmdGetVerbosity :: Cmd
  487. cmdGetVerbosity = Cmd "get-verbosity" $ noArgs $ verbosityToInt <$> getVerbosity
  488. -- rename to GetCurrentComponent?
  489. cmdCurrentComponent :: Cmd
  490. cmdCurrentComponent = Cmd "current-component" $ noArgs $ getActiveComponent
  491. cmdCurrentCabalFile :: Cmd
  492. cmdCurrentCabalFile = Cmd "current-cabal-file" $ noArgs $ cmd
  493. where cmd = do
  494. r <- gtry currentCabalFile
  495. case r of
  496. Right f -> return (showJSON f)
  497. Left (_::SomeScionException) -> return JSNull
  498. cmdDumpDefinedNames :: Cmd
  499. cmdDumpDefinedNames = Cmd "dump-defined-names" $ noArgs $ cmd
  500. where
  501. cmd = do db <- gets defSiteDB
  502. liftIO $ putStrLn $ dumpDefSiteDB db
  503. cmdDefinedNames :: Cmd
  504. cmdDefinedNames = Cmd "defined-names" $ noArgs $ cmd
  505. where cmd = definedNames <$> gets defSiteDB
  506. cmdNameDefinitions :: Cmd
  507. cmdNameDefinitions =
  508. Cmd "name-definitions" $ reqArg' "name" fromJSString $ cmd
  509. where cmd nm = do
  510. db <- gets defSiteDB
  511. let locs = map fst $ lookupDefSite db nm
  512. return locs
  513. cmdIdentify :: Cmd
  514. cmdIdentify =
  515. Cmd "client-identify" $ reqArg' "name" fromJSString $ cmd
  516. where cmd c = modifySessionState $ \s -> s { client = c }
  517. cmdDumpModuleGraph :: Cmd
  518. cmdDumpModuleGraph =
  519. Cmd "dump-module-graph" $ noArgs $ cmd
  520. where
  521. cmd = do
  522. mg <- getModuleGraph
  523. liftIO $ printDump (ppr mg)
  524. return ()