summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorShayne Fletcher <shayne@shaynefletcher.org>2021-06-03 20:34:39 +1000
committerMarge Bot <ben+marge-bot@smart-cactus.org>2021-06-05 03:47:48 -0400
commit1713cbb038116c2d703238b47f78c4861232db8e (patch)
tree9ef7ba336a7c36defe90ce31c5211666f715b47e /utils
parent737b0ae194ca33f9bea9a150dada0c933fd75d4d (diff)
downloadhaskell-1713cbb038116c2d703238b47f78c4861232db8e.tar.gz
Make 'count-deps' a ghc/util standalone program
- Move 'count-deps' into 'ghc/utils' so that it can be called standalone. - Move 'testsuite/tests/parser/should_run/' tests 'CountParserDeps' and 'CountAstDeps' to 'testsuite/tests/count-deps' and reimplement in terms of calling the utility - Document how to use 'count-deps' in 'ghc/utils/count-deps/README'
Diffstat (limited to 'utils')
-rw-r--r--utils/count-deps/Main.hs80
-rw-r--r--utils/count-deps/README.md43
-rw-r--r--utils/count-deps/count-deps.cabal24
-rw-r--r--utils/count-deps/ghc.mk18
4 files changed, 165 insertions, 0 deletions
diff --git a/utils/count-deps/Main.hs b/utils/count-deps/Main.hs
new file mode 100644
index 0000000000..2ce6ea9f5b
--- /dev/null
+++ b/utils/count-deps/Main.hs
@@ -0,0 +1,80 @@
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+
+module Main where
+
+import GHC.Driver.Env
+import GHC.Unit.Module
+import GHC.Driver.Session
+import GHC.Driver.Main
+import GHC
+import Control.Monad
+import Control.Monad.IO.Class
+import System.Environment
+import GHC.Unit.Module.Deps
+import Data.Map.Strict qualified as Map
+
+-- Example invocation:
+-- inplace/bin/count-deps `inplace/bin/ghc-stage2 --print-libdir` "GHC.Parser"
+main :: IO ()
+main = do
+ args <- getArgs
+ case args of
+ [libdir, modName, "--dot"] -> printDeps libdir modName True
+ [libdir, modName] -> printDeps libdir modName False
+ _ -> fail "usage: count-deps libdir module [--dot]"
+
+dotSpec :: String -> Map.Map String [String] -> String
+dotSpec name g =
+ "digraph \"" ++ name ++ "\" {\n" ++
+ Map.foldlWithKey' f "" g ++ "}\n"
+ where
+ f acc k ns = acc ++ concat [" " ++ show k ++ " -> " ++ show n ++ ";\n" | n <- ns]
+
+printDeps :: String -> String -> Bool -> IO ()
+printDeps libdir modName dot = do
+ modGraph <-
+ Map.map (map moduleNameString) .
+ Map.mapKeys moduleNameString <$> calcDeps modName libdir
+ if not dot then
+ do
+ let modules = Map.keys modGraph
+ num = length modules
+ putStrLn $ "Found " ++ show num ++ " " ++ modName ++ " module dependencies"
+ forM_ modules putStrLn
+ else
+ -- * Copy the digraph output to a file ('deps.dot' say)
+ -- * To render it, use a command along the lines of
+ -- 'tred deps.dot > deps-tred.dot && dot -Tpdf -o deps.pdf deps-tred.dot'
+ putStr $ dotSpec modName modGraph
+
+calcDeps :: String -> FilePath -> IO (Map.Map ModuleName [ModuleName])
+calcDeps modName libdir =
+ defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
+ runGhc (Just libdir) $ do
+ df <- getSessionDynFlags
+ logger <- getLogger
+ (df, _, _) <- parseDynamicFlags logger df [noLoc "-package=ghc"]
+ setSessionDynFlags df
+ env <- getSession
+ loop env Map.empty [mkModuleName modName]
+ where
+ -- Source imports are only guaranteed to show up in the 'mi_deps'
+ -- of modules that import them directly and don’t propagate
+ -- transitively so we loop.
+ loop :: HscEnv -> Map.Map ModuleName [ModuleName] -> [ModuleName] -> Ghc (Map.Map ModuleName [ModuleName])
+ loop env modules (m : ms) =
+ if m `Map.member` modules
+ then loop env modules ms
+ else do
+ mi <- liftIO $ hscGetModuleInterface env (mkModule m)
+ let deps = modDeps mi
+ modules <- return $ Map.insert m [] modules
+ loop env (Map.insert m deps modules) $ ms ++ filter (not . (`Map.member` modules)) deps
+ loop _ modules [] = return modules
+
+ mkModule :: ModuleName -> Module
+ mkModule = Module (stringToUnit "ghc")
+
+ modDeps :: ModIface -> [ModuleName]
+ modDeps mi = map gwib_mod $ dep_direct_mods (mi_deps mi)
diff --git a/utils/count-deps/README.md b/utils/count-deps/README.md
new file mode 100644
index 0000000000..67d8ed4842
--- /dev/null
+++ b/utils/count-deps/README.md
@@ -0,0 +1,43 @@
+# README
+
+The `count-deps` executable is used in the test-suite to detect when
+the number of dependencies of certain modules change ([this blog
+post](https://blog.shaynefletcher.org/2020/10/ghc-lib-parser-module-count.html)
+gives one example of where and why this can be useful).
+
+More generally it's useful for obtaining insight into a modules'
+dependency graph. As used in the tests it produces (1) a count of a
+modules' dependencies together with the list of depended upon modules
+. However, it can also (2), print a modules' dependency graph in dot
+language syntax suitable for rendering with "graphviz" (open source
+graph visualization software).
+
+## Installing graphviz
+
+To render graphs generated by `count-deps`, first visit [the graphviz
+downloads page](https://graphviz.org/download/) to download and
+install graphviz on your system.
+
+## `count-deps` usage examples:
+
+ - `make`:
+
+ (1) ``inplace/bin/count-deps `inplace/bin/ghc-stage2 --print-libdir` "GHC.Parser"``
+ (2) ``inplace/bin/count-deps `inplace/bin/ghc-stage2 --print-libdir` "GHC.Parser" --dot``
+
+ - `hadrian`:
+ (1) ``_build/stage1/bin/count-deps `_build/stage1/bin/ghc --print-libdir` "GHC.Parser"``
+ (2) ``_build/stage1/bin/count-deps `_build/stage1/bin/ghc --print-libdir` "GHC.Parser" --dot``
+
+## Rendering dependency graphs
+
+To render a graph obtained using a type (2) command:
+
+ - Copy the output to a file ('`deps.dot`' say)
+
+ - Render it with a command like (preprocess with `tred` to remove
+ edges implied by transitivity)
+
+ ```bash
+ tred deps.dot > deps-tred.dot&& dot -Tpdf -o deps.pdf deps-tred.dot
+ ```
diff --git a/utils/count-deps/count-deps.cabal b/utils/count-deps/count-deps.cabal
new file mode 100644
index 0000000000..38a7621aaa
--- /dev/null
+++ b/utils/count-deps/count-deps.cabal
@@ -0,0 +1,24 @@
+Name: count-deps
+Version: 0.1
+Copyright: XXX
+License: BSD3
+-- XXX License-File: LICENSE
+Author: XXX
+Maintainer: XXX
+Synopsis: A utilities for analyzing module dependencies
+Description:
+ This utility is used inspect the dependencies of a module
+ @utils/count-deps/README@ in GHC's source distribution for
+ details.
+Category: Development
+build-type: Simple
+cabal-version: >=1.10
+
+Executable count-deps
+ Default-Language: Haskell2010
+ Main-Is: Main.hs
+ Ghc-Options: -Wall
+ other-modules:
+ Build-Depends: base >= 4 && < 5,
+ containers,
+ ghc
diff --git a/utils/count-deps/ghc.mk b/utils/count-deps/ghc.mk
new file mode 100644
index 0000000000..45259efee6
--- /dev/null
+++ b/utils/count-deps/ghc.mk
@@ -0,0 +1,18 @@
+# -----------------------------------------------------------------------------
+#
+# (c) 2009 The University of Glasgow
+#
+# This file is part of the GHC build system.
+#
+# To understand how the build system works and how to modify it, see
+# https://gitlab.haskell.org/ghc/ghc/wikis/building/architecture
+# https://gitlab.haskell.org/ghc/ghc/wikis/building/modifying
+#
+# -----------------------------------------------------------------------------
+
+utils/count-deps_USES_CABAL = YES
+utils/count-deps_PACKAGE = count-deps
+utils/count-deps_dist-install_PROGNAME = count-deps
+utils/count-deps_dist-install_INSTALL = NO
+utils/count-deps_dist-install_INSTALL_INPLACE = YES
+$(eval $(call build-prog,utils/count-deps,dist-install,2))