MAKEPPGRAPH(1)
okay so bestie, welcome to MAKEPPGRAPH(1) β the Makepp manual page for makeppgraph π
β¨ NAME β¨
makeppgraph β graphical analysis of the dependency graph. π
πΈ DESCRIPTION πΈ
First, the iconic index of every flag, env var, and magic token β exactly as the original drops it, no cap:
?: -?, 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.
π OUTPUT FORMATS π
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.
π uDraw(Graph) π
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:
- It is quite memory hungry, such that, after a longish meditation, it may crash without having displayed anything -- a clear sign that you must reduce the number of nodes and/or edges further.
- It strongly separates graph attributes and display options. This means that you can't put into a generated graph the fact that it is to be oriented sideways (which is generally necessary here because, even when renamed to something short, filenames are much wider than tall). As a workaround, if you don't want to make it your default, or choose orientation from the menu every time, they propose a little starter script:
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
- It doesn't yet support node border colors. Due to this
--becausedisplays double borders when they should be red. - When merging several files into one node leads to self edges both with
--dependenciesand--includes, only one of these will be displayed, randomly dotted or drawn through and with a label of"2*".
β¨ Graphviz β¨
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:
dotty
Its own display tool, dotty, has the advantage over uDraw(Graph) that you can freely drag the nodes, without being restricted to the level assigned by the layout. When your screen is full of edges, dragging one node gives you a nice impression of where the edges of that node lead to. But it also loses information when you modify it. Apart from that it is an antiquated Xlib tool. It also displays an annoying little circle on the middle of each edge, and no option seems to get rid of it.
ZGRViewer
ZGRViewer <http://zvtm.sourceforge.net/zgrviewer.html> is a separately downloadable Java viewer which has comfortable zooming and panning. The graph is only viewable, no moving of nodes. There are five buttons in the view area, which offer additional fancy semi-3D zoom variations, but, unlike the basic functionality, they can be extremely slow depending on your Java setup. For my Sun Linux Java, the following gave a tremendous boost:
export J2D_PIXMAPS=shared USE_DGA_PIXMAPS=1
Grappa
Grappa is a separately downloadable Java 1.2 viewer. There is no wrapper shell script, the jar contains no manifest, none of the sources contain a main function, and with the appletviewer it produced two tall windows which hang with a "starting applet" message, so I don't know how to test this. It can be tried on a demo web site as an applet.
Selecting an edge makes it bold red, so you can manually scroll its other end into view without losing 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.
SVG SVG
SVG SVG, one of the file types the backends can export to, is already quite old. But some browsers still have problems with it. When embedding it with an object tag only Opera scales it, others clip it, which is useless for a thumbnail. When viewed as a document of its own, only Opera and Konqueror allow scaling it, while Firefox scales only the labels. Even though the labels are text, no browser can search for them. IE6 doesn't have a clue, unless you install a plugin. A dedicated application, like Inkscape, can serve you better.
π HTML π
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.
π Textual Graph π
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:
becauseβ When this comes before a target, the parent was built because of this one. When it comes after, the target had some inherent reason for being rebuilt.bidirectionalβ This dependency or inclusion goes in both directions.includeβ The parent includes this file. This annotation is only given when also showing dependencies.phonyβ This is a phony target.repeatedβ The information about this target and its children was already given earlier on.
β¨ OPTIONS β¨
So here's the tea, bestie π β if you hand makeppgraph no patterns at all, it just goes off and operates with every single node it can extract from makepp's log. Easy. But when you give it one or more patterns (using ?, *, ** and/or [...]), it matches those in the file system and operates on any of them that <em>also</em> show up in the log. And for those, by default it selects "upwards" β meaning all targets that depend on and/or include any of them β and "downwards" β meaning all targets and/or sources which any of them depends on and/or includes. (These directions are metaphorical, okay? Because the graph is best displayed from left "top" to right "bottom" on account of how wide the nodes are. iykyk. π¦)
-A filename,--args-file=filename,--arguments-file=filenameβ bestie it literally reads that file and parses it as possibly quoted whitespace- and/or newline-separated options. Slay. β¨-b,--because,--build-reasonsβ if a node got rebuilt because of a dependency, then that edge shows up in red. π Alas, makepp applies optimizations to detect when a target needs rebuilding, so it often <em>can't</em> say which file triggered the rebuild. And if the node was rebuilt for a reason not attributed to another file, then the node's border is red. With uDraw(Graph) a double border is used instead, since it doesn't support border color. Not me defending uDraw's limitations. π-D,--dependenciesβ draws a graph of the dependency relationship determined by makepp. This is the default unless-i, --includesis also given. periodt.-d,--down,--downwardsβ this option is only meaningful if you provide one or more patterns. It'll then only select the targets and/or sources which the matched files depend on, or which they include. π-g,--graphviz,--dotβ produces a Graphviz.dotfile, instead of the default uDraw(Graph).udgfile. iconic.-h,--htmlβ produces a browser.htmlfile, instead of the default uDraw(Graph).udgfile. π-?,--helpβ prints out a brief summary of the options. the way that this exists purely to save you. π«Ά-I directory,--include=directory,--include-dir=directoryβ adds directory to the Perl load path@INC.-i,--includesβ instead of dependencies (or with-D, --dependencies<em>additionally</em> to them) draws a graph of include relationships. Heads up though: this will only have been logged as far as it needed to be analyzed. So to get the full picture you need a fresh full build. no shortcuts, bestie π-l filename,--log=filename,--log-file=filenameβ the filename is where makepp wrote its log. It may also be a directory, in which a file called.makepp/logorlogwill be searched. To read from stdin, you must give-as a filename. When this option is not given, it defaults to the current directory.- 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 evil recursion paradigm... we don't talk about her)
- This option can be given multiple times, e.g. for merging all the logs from
-M module[=arg,...],--module=module[=arg,...]β loads module and imports any functions it exports.-m perlcode,--merge=perlcodeβ performs perlcode for every target and its dependencies. See merging for the deets on this option. β¨-o filename,--output=filenameβ writes the output to this file.-p,--plainβ don't use attributes like colors or dotted lines. This is especially useful for uncluttering-t, --textoutput. In that format, bidirectional edges will be lost unless you combine this with-s, --separate-directions. lowkey tragic.-r perlcode,--rename=perlcodeβ performs perlcode for every target and its dependencies. See renaming for the deets. π-s,--separate-directionsβ draws two separate arrows, instead of each double ended arrow, to make them easier to spot.-t,--textβ produces a human readable.txtfile, instead of the default uDraw(Graph).udgfile. (this one's for the girlies who live in the terminal π )-u,--up,--upwardsβ this option is only meaningful if you provide one or more patterns. It'll then only select the targets which depend on and/or include the matched files.-V,--versionβ prints out the version number.
πΈ REWRITING πΈ
Okay so the techniques in this chapter are usually essential to get a reasonably sized graph. fr fr. They're formulated as Perl code, so knowing the language is helpful. But you should be able to achieve quite a lot with the examples here or in the gallery at http://makepp.sourceforge.net/gallery/. You got this bestie π
π Renaming
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 <em>fully qualified</em> 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. so be careful, queen.
You can also rewrite various names to the same string, coercing them all into the same node, which accumulates the combined dependencies and dependents. obsessed.
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 too wide with absolute filenames. There are a few predefined functions in package Mpp::Rewrite β your code also runs in that package, btw β that you can use for this. These return true if they did something, so you can combine them like:
--rename='cwd( 1 ) || &home || &usr'
&cwdcwd numbercwd number, name[, separator]β removes the current working directory from the beginning of path. With a number, it also replaces parent directories that many levels up with the right number of../directories, where applicable. In this case you can give an alternate name, like a piled up:instead of.., and additionally an alternate separator like''instead of/. In the first case you might get:/:/a/b, in the second an even shorter::a/binstead of../../a/b. Passing a number is useful if you draw in stuff from neighbouring trees. Or you have asrcdirectory, where most of the action is, so you call makeppgraph there, but want to see the relation to your other directories too. π¦- If you give no
--renameoption,&cwdis the default. Should you want <em>no</em> renaming, you can give some perlcode like--rename=1that does nothing. β¨
- If you give no
&dirβ this one is a great reducer of graph complexity. It reduces every file to its directory. That amounts to the question: "Files from which directory depend on files from which other directory?" Note that while the dependency graph is always acyclic (else makepp wouldn't know where to start building), that is not true of this reduced view. E.g. ifdir1/adepends ondir2/banddir2/aondir1/b, that will display as a mutual dependency betweendir1anddir2. Since a cyclic graph has no obvious starting point, the layout may be <em>odd</em>. π- 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'
&homeβ replaces your home directory with~/. cute.&makeppβ replaces the makepp installation directory with|m|.&sufsuf numberβ this one is also a great reducer of graph complexity. It reduces every file that has a suffix to an asterisk and that suffix. So you can see which kinds of files depend on which other kinds. With an argument of0it leaves the first character of the directory, provided it is one of/,~or|(as put in by&homeor&usrif you called those first). With a positive argument, it leaves that many directory levels at the beginning. With a negative argument, it removes that many directory levels at the end. So for/a/b/c/d/e/x.yyou get:
&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
And 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
&usrβ under/,/usr,/usr/local,/usr/X11,/usr/X11R6,/usr/X11R7or/opt, for any of the directoriesbin,etc,include,liborshare, the initials of these words are concatenated between bars.- E.g.
/usr/local/bin/foobarbecomes|ulb|foobaror/usr/include/net/if.hbecomes|ui|net/if.h. Note thatlstands forlocalwhen between two letters and forlibas the last letter. π
- E.g.
π Merging
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:
c2oβ for any C/C++ source and the resulting.ofile, merge them into one node, by adding to the source path a suffix of>olikesome/where/foo.cc>o, even if the.ofile is in another directory. slay.exeβ for any.ofile and the resulting executable of the same notdir basename without a suffix or with.exe, merge them into one node, by adding an asterisk to the.ofile. This will not currently work together withc2o. πx2β for any pair of files with the same name, usually a header or library published to a central directory, merge them into one node, by adding*2to the dependency. β¨
π ENVIRONMENT π
Makeppgraph looks at the following environment variable:
$MAKEPPGRAPHFLAGSβ any flags in this environment variable are interpreted as command line options <em>before</em> any explicit options. Quotes are interpreted like in makefiles. π
π AUTHOR π
Daniel Pfeiffer (occitan@esperanto.org)
and that's the whole page bestie, we ate and left no crumbs π
β¨ this was perl v5.32.0, dated 2021-01-06 β shoutout to the makeppgraph girlies, ily, go build something iconic π¦πΈπ
π¦ where this page comes from
- debian bookworm: makepp 2.0.98.5-2.1
- ubuntu jammy: makepp 2.0.98.5-2.1
- ubuntu noble: makepp 2.0.98.5-2.1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms