diff options
author | Shayne Fletcher <shayne@shaynefletcher.org> | 2021-06-03 20:34:39 +1000 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2021-06-05 03:47:48 -0400 |
commit | 1713cbb038116c2d703238b47f78c4861232db8e (patch) | |
tree | 9ef7ba336a7c36defe90ce31c5211666f715b47e /utils | |
parent | 737b0ae194ca33f9bea9a150dada0c933fd75d4d (diff) | |
download | haskell-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.hs | 80 | ||||
-rw-r--r-- | utils/count-deps/README.md | 43 | ||||
-rw-r--r-- | utils/count-deps/count-deps.cabal | 24 | ||||
-rw-r--r-- | utils/count-deps/ghc.mk | 18 |
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)) |