MAKEPPGRAPH(1) | Makepp | MAKEPPGRAPH(1) |
makeppgraph -- Graphical analysis of the dependency graph
?: -?, A: -A,
--args-file,
--arguments-file, B: -b,
--because,
--build-reasons, C: &cwd,
D: -D,
-d,
--dependencies,
&dir,
--dot,
--down,
--downwards, G: -g,
--graphviz, H: -h,
--help,
&home,
--html, I: -I,
-i,
--include,
--include-dir,
--includes, L: -l,
--log,
--log-file, M: -M,
-m,
&makepp,
$MAKEPPGRAPHFLAGS,
--merge,
--module, O: -o,
--output, P: -p,
--plain, R: -r,
--rename, S: -s,
--separate-directions,
&suf, T: -t,
--text, U: -u,
--up,
--upwards,
&usr, V: -V,
--version
makeppgraph [ option ... ] [ pattern ... ]
mppg [ option ... ] [ pattern ... ]
They say "A picture is worth a thousand words". So let's draw your dependency or include graph from various viewpoints. Check out the gallery <http://makepp.sourceforge.net/gallery/> to get some ideas of what you can do.
Each node represents a source file or a target, colored according to file name patterns. A file's node is rectangular. A phony target's node is oval. Each solid edge represents a direct dependency. Alternately or additionally you can display include relationships as dotted lines. For a more detailed but not so pretty textual view see makepplog.
But beware, even for a small build the complexity can be staggering! This is because with .o files and system includes you easily have twice as many nodes as source files. But that is nothing -- the number of edges often far exceeds that of nodes, due to multiple include statements. A crossing-free layout is usually impossible.
In real projects the complexity becomes insane. Techniques like template based source file generation, preprocessors (e.g. embedded SQL, interface definition languages, Qt library) or publishing of files to central directories (e.g. to have only one "-I" or "-L" option) make the graph explode. Even if edges are just one pixel wide, you end up with broad black stripes of criss-crossing edges.
Once you realize what really goes on, if you're lucky, you may be able to find a way of simplifying your build setup. But before you get there, you must drastically reduce the amount of information you display. To that end there are various selection, renaming and merging possibilities in "makeppgraph". When you fail to strike a balance between reducing the graph so far that it becomes sensibly displayable, while still showing what you want to see, you may fall back to a textual graph.
Graph layouting, display and manipulation are complex tasks, which are beyond the scope of "makeppgraph". Instead it produces input files for specialized tools. It creates an output file replacing or adding the appropriate suffix to its first input file. If that is .makepp/log, the default, the output file will skip the .makepp directory, leading to log.udg. If the first input is - (stdin), the output goes to stdout.
With uDraw(Graph) <http://www.informatik.uni-bremen.de/uDrawGraph/> you get a fairly modern GUI, which allows to select parents or children, find the other end of an edge, or hide subgraphs. Tweaking the options like the node distances, and using splines for edges can make the graph prettier.
While the above features make this a tremendously useful tool, there are a few small hitches:
export UDG_HOME=/where/ever/uDrawGraph-3.1 TMP=`mktemp -t udg.XXXXXX` || exit 1 trap "rm -f $TMP" EXIT echo "[menu(file(open_graph(\"${1-log.udg}\"))),menu(layout(orientation(left_right)))]" >$TMP $UDG_HOME/bin/uDrawGraph -init $TMP
Graphviz <http://www.graphviz.org/> consists of several command line tools, which allow many more export formats than uDraw(Graph). That includes not only static image formats but also input for designer programs like dia. There is a utility "twopi" for creating a radial layout, which is nice if your graph comes close to a true tree, i.e. your dependencies fan out, but few nodes have common dependencies with others. There are a few viewers available, none of which helps you to navigate along the structure of the graph:
export J2D_PIXMAPS=shared USE_DGA_PIXMAPS=1
Selecting an edge makes it bold red, so you can manually scroll its other end into view without loosing it out of sight. Other than that and zooming and deleting nodes it seems to have no useful features. It ignores valid hexadecimal color specifications.
This is a simple unordered list tree format that can be perused with any browser. You should have JavaScript and CSS, which allows folding subtrees and seeing colors. Usually your graph will not be a tree, which is worked around by repeating nodes in every subtree needed, but as a link to the first occurrence where you can see all its attributes. Due to IE's limited Unicode support, vertical arrows are used for include relations, instead of the usual dotted arrows.
This is a simple indentation-based format that can be perused with any text viewer. This means you can usually study much bigger graphs than with the other formats. In Emacs you can use outline and foldout for very powerful graph navigation with this little wrapper mode:
(define-derived-mode textgraph-mode outline-mode "Graph" (view-mode) (set (make-local-variable 'outline-regexp) " *.") (set (make-local-variable 'outline-level) (lambda () (/ (- (match-end 0) (match-beginning 0) -1) 2))) (set (make-local-variable 'outline-font-lock-keywords) '(("^ *\\(?:{[a-z,]+} \\)?\\([^{\n]+\\)" (1 (outline-font-lock-face) nil t)))) (setq imenu-generic-expression '((nil "^ *\\(?:{[a-z,]+} \\)?\\(.+?\\)\\(?:{[a-z,]+}\\)?$" 1))))
The lines can have comma separated annotations between braces, unless you also give the "-p, --plain" option. When these come before the target they pertain to the relationship with the parent, i.e. the previous line indented less. When they come after the target, they pertain to the target itself. They are as follows:
If you give no patterns, makeppgraph will start operating with all the nodes it can extract from makepp's log. When given one or more patterns (using "?", "*", "**" and/or "[...]"), it will match those in the file system and operate on any that also occur in the log. For these it will by default select "upwards", i.e. all targets that depend on and/or include any of them and "downwards", i.e. all targets and/or sources, which any of them depends on and/or includes. (The directions are metaphorical, because the graph is best displayed from left "top" to right "bottom" due to the width of the nodes.)
This option can be given multiple times, e.g. for merging all the logs from "--traditional-recursive-make". But the dependencies you hid from makepp through the evil recursion paradigm can't of course show up here.
The techniques in this chapter are usually essential to get a reasonably sized graph. As they are formulated as Perl code, knowing the language is helpful. But you should be able to achieve quite a lot with the examples here or in the gallery <http://makepp.sourceforge.net/gallery/>.
This is the first name rewriting that occurs, if the "-r, --rename" option is given. For every name encountered, perlcode gets called. It gets a filename in $_, and it may modify it. This is often needed, because makepp logs fully qualified file names, so one node can easily be half a screen wide.
For one thing, you can rewrite names to "undef" or the empty string. This will eliminate the node from the graph. Note that eliminating a node in this first stage will break a chain of dependency if this node was in the middle.
You can also rewrite various names to the same string, coercing them all into the same node, which accumulates the combined dependencies and dependents.
On the other hand you can just rename names to (usually) shorter names, so as to reduce the width of nodes, which can be far to wide with absolute filenames. There are a few predefined functions in package "Mpp::Rewrite", in which your code also runs, you can use for this. These return true if they did something so you can combine them as in:
--rename='cwd( 1 ) || &home || &usr'
If you give no "--rename" option, &cwd is the default. Should you want no renaming, you can give some perlcode like "--rename=1" that does nothing.
Unlike the other functions in this section, this is not exclusive with the others. So you may not want to logically combine it:
--rename='&dir; &cwd || &home'
&suf *.y suf 0 /*.y suf 1 /a/*.y suf 2 /a/b/*.y suf -1 /a/b/c/d/*.y suf -2 /a/b/c/*.y
For a relative a/b/c/d/e/x.y you get:
&suf *.y suf 0 *.y suf 1 a/*.y suf -1 a/b/c/d/*.y
E.g. /usr/local/bin/foobar becomes |ulb|foobar or /usr/include/net/if.h becomes |ui|net/if.h. Note that `l' stands for `local' when between two letters and for `lib' as the last letter.
This is the second name rewriting that occurs, if the "-m, --merge" option is given. This API is still under development! Currently the target is passed in $_ and the dependency as an argument. If perlcode returns a value, that value replaces both the target and the dependency, merging them into one node. A few predefined functions can help you:
Makeppgraph looks at the following environment variable:
Daniel Pfeiffer (occitan@esperanto.org)
2021-01-06 | perl v5.32.0 |