v: support -show-depgraph in addition to -show-callgraph
parent
e3cf95b058
commit
d25bd95a0e
|
@ -8,9 +8,10 @@ import v.ast
|
|||
import v.vmod
|
||||
import v.checker
|
||||
import v.parser
|
||||
import v.depgraph
|
||||
import v.markused
|
||||
import v.depgraph
|
||||
import v.callgraph
|
||||
import v.dotgraph
|
||||
|
||||
pub struct Builder {
|
||||
pub:
|
||||
|
@ -52,6 +53,9 @@ pub fn new_builder(pref &pref.Preferences) Builder {
|
|||
}
|
||||
}
|
||||
util.timing_set_should_print(pref.show_timings || pref.is_verbose)
|
||||
if pref.show_callgraph || pref.show_depgraph {
|
||||
dotgraph.start_digraph()
|
||||
}
|
||||
return Builder{
|
||||
pref: pref
|
||||
table: table
|
||||
|
@ -180,6 +184,9 @@ pub fn (mut b Builder) resolve_deps() {
|
|||
eprintln(deps_resolved.display())
|
||||
eprintln('------------------------------------------')
|
||||
}
|
||||
if b.pref.show_depgraph {
|
||||
depgraph.show(deps_resolved, b.pref.path)
|
||||
}
|
||||
cycles := deps_resolved.display_cycles()
|
||||
if cycles.len > 1 {
|
||||
verror('error: import cycle detected between the following modules: \n' + cycles)
|
||||
|
|
|
@ -3,7 +3,7 @@ module callgraph
|
|||
import v.ast
|
||||
import v.ast.walker
|
||||
import v.pref
|
||||
import strings
|
||||
import v.dotgraph
|
||||
|
||||
// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls
|
||||
// that function make transitively
|
||||
|
@ -11,18 +11,14 @@ pub fn show(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.File)
|
|||
mut mapper := &Mapper{
|
||||
pref: pref
|
||||
table: table
|
||||
dg: dotgraph.new('CallGraph', 'CallGraph for $pref.path', 'green')
|
||||
}
|
||||
mapper.sb.writeln('digraph G {')
|
||||
mapper.sb.writeln('\tedge [fontname="Helvetica",fontsize="10",labelfontname="Helvetica",labelfontsize="10",style="solid",color="black"];')
|
||||
mapper.sb.writeln('\tnode [fontname="Helvetica",fontsize="10",style="filled",fontcolor="black",fillcolor="white",color="black",shape="box"];')
|
||||
mapper.sb.writeln('\trankdir="LR";')
|
||||
// Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
|
||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||
for afile in ast_files {
|
||||
walker.walk(mapper, afile)
|
||||
}
|
||||
mapper.sb.writeln('}')
|
||||
println(mapper.sb.str())
|
||||
mapper.dg.finish()
|
||||
}
|
||||
|
||||
[heap]
|
||||
|
@ -37,7 +33,7 @@ mut:
|
|||
caller_name string
|
||||
dot_caller_name string
|
||||
is_caller_used bool
|
||||
sb strings.Builder = strings.new_builder(1024)
|
||||
dg dotgraph.DotGraph
|
||||
}
|
||||
|
||||
fn (mut m Mapper) dot_normalise_node_name(name string) string {
|
||||
|
@ -104,11 +100,10 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
|
|||
m.caller_name = m.fn_name(node.name, node.receiver.typ, node.is_method)
|
||||
m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method)
|
||||
if m.is_caller_used {
|
||||
if m.caller_name == 'main.main' {
|
||||
m.sb.writeln('\t$m.dot_caller_name [label="fn main()",color="blue",height=0.2,width=0.4,fillcolor="#00FF00",tooltip="The main program entry point.",shape=oval];')
|
||||
} else {
|
||||
m.sb.writeln('\t$m.dot_caller_name [shape="box",label="$m.caller_name"];')
|
||||
}
|
||||
m.dg.new_node(m.caller_name,
|
||||
node_name: m.dot_caller_name
|
||||
should_highlight: m.caller_name == 'main.main'
|
||||
)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
|
@ -121,11 +116,9 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
|
|||
dot_called_name := m.dot_fn_name(node.name, node.receiver_type,
|
||||
node.is_method)
|
||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||
if m.caller_name == 'main.main' {
|
||||
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name [color="blue"];')
|
||||
} else {
|
||||
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name')
|
||||
}
|
||||
m.dg.new_edge(m.dot_caller_name, dot_called_name,
|
||||
should_highlight: m.caller_name == 'main.main'
|
||||
)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
|
@ -134,13 +127,3 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
|
|||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
mut fpath := ''
|
||||
if m.file != 0 {
|
||||
fpath = m.file.path
|
||||
}
|
||||
node_pos := node.position()
|
||||
fpos := '$fpath:$node_pos.line_nr:$node_pos.col:'
|
||||
println('$fpos $node.type_name() | $node')
|
||||
*/
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
// this implementation is specifically suited to ordering dependencies
|
||||
module depgraph
|
||||
|
||||
import v.dotgraph
|
||||
|
||||
struct DepGraphNode {
|
||||
pub mut:
|
||||
name string
|
||||
|
@ -197,3 +199,20 @@ fn (mut nn NodeNames) is_part_of_cycle(name string, already_seen []string) (bool
|
|||
nn.is_cycle[name] = false
|
||||
return false, new_already_seen
|
||||
}
|
||||
|
||||
pub fn show(graph &DepGraph, path string) {
|
||||
mut dg := dotgraph.new('ModGraph', 'ModGraph for $path', 'blue')
|
||||
mbuiltin := 'builtin'
|
||||
for node in graph.nodes {
|
||||
is_main := node.name == 'main'
|
||||
dg.new_node(node.name, should_highlight: is_main)
|
||||
mut deps := node.deps.clone()
|
||||
if node.name != mbuiltin && mbuiltin !in deps {
|
||||
deps << mbuiltin
|
||||
}
|
||||
for dep in deps {
|
||||
dg.new_edge(node.name, dep, should_highlight: is_main)
|
||||
}
|
||||
}
|
||||
dg.finish()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
module dotgraph
|
||||
|
||||
pub fn start_digraph() {
|
||||
println('digraph G {')
|
||||
C.atexit(fn () {
|
||||
println('}')
|
||||
})
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
module dotgraph
|
||||
|
||||
import strings
|
||||
|
||||
[heap]
|
||||
struct DotGraph {
|
||||
mut:
|
||||
sb strings.Builder
|
||||
}
|
||||
|
||||
pub fn new(name string, label string, color string) &DotGraph {
|
||||
mut res := &DotGraph{
|
||||
sb: strings.new_builder(1024)
|
||||
}
|
||||
res.writeln(' subgraph cluster_$name {')
|
||||
res.writeln('\tedge [fontname="Helvetica",fontsize="10",labelfontname="Helvetica",labelfontsize="10",style="solid",color="black"];')
|
||||
res.writeln('\tnode [fontname="Helvetica",fontsize="10",style="filled",fontcolor="black",fillcolor="white",color="black",shape="box"];')
|
||||
res.writeln('\trankdir="LR";')
|
||||
res.writeln('\tcolor="$color";')
|
||||
res.writeln('\tlabel="$label";')
|
||||
// Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
|
||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||
return res
|
||||
}
|
||||
|
||||
pub fn (mut d DotGraph) writeln(line string) {
|
||||
d.sb.writeln(line)
|
||||
}
|
||||
|
||||
pub fn (mut d DotGraph) finish() {
|
||||
d.sb.writeln(' }')
|
||||
println(d.sb.str())
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct NewNodeConfig {
|
||||
node_name string
|
||||
should_highlight bool
|
||||
tooltip string
|
||||
ctx voidptr = voidptr(0)
|
||||
name2node_fn FnLabel2NodeName = node_name
|
||||
}
|
||||
|
||||
pub fn (mut d DotGraph) new_node(nlabel string, cfg NewNodeConfig) {
|
||||
mut nname := cfg.name2node_fn(nlabel, cfg.ctx)
|
||||
if cfg.node_name != '' {
|
||||
nname = cfg.node_name
|
||||
}
|
||||
if cfg.should_highlight {
|
||||
d.writeln('\t$nname [label="$nlabel",color="blue",height=0.2,width=0.4,fillcolor="#00FF00",tooltip="$cfg.tooltip",shape=oval];')
|
||||
} else {
|
||||
d.writeln('\t$nname [shape="box",label="$nlabel"];')
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct NewEdgeConfig {
|
||||
should_highlight bool
|
||||
ctx voidptr = voidptr(0)
|
||||
name2node_fn FnLabel2NodeName = node_name
|
||||
}
|
||||
|
||||
pub fn (mut d DotGraph) new_edge(source string, target string, cfg NewEdgeConfig) {
|
||||
nsource := cfg.name2node_fn(source, cfg.ctx)
|
||||
ntarget := cfg.name2node_fn(target, cfg.ctx)
|
||||
if cfg.should_highlight {
|
||||
d.writeln('\t$nsource -> $ntarget [color="blue"];')
|
||||
} else {
|
||||
d.writeln('\t$nsource -> $ntarget;')
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub type FnLabel2NodeName = fn (string, voidptr) string
|
||||
|
||||
pub fn node_name(name string, context voidptr) string {
|
||||
return name.replace('.', '_')
|
||||
}
|
|
@ -98,37 +98,36 @@ pub mut:
|
|||
// verbosity VerboseLevel
|
||||
is_verbose bool
|
||||
// nofmt bool // disable vfmt
|
||||
is_test bool // `v test string_test.v`
|
||||
is_script bool // single file mode (`v program.v`), main function can be skipped
|
||||
is_vsh bool // v script (`file.vsh`) file, the `os` module should be made global
|
||||
is_livemain bool // main program that contains live/hot code
|
||||
is_liveshared bool // a shared library, that will be used in a -live main program
|
||||
is_shared bool // an ordinary shared library, -shared, no matter if it is live or not
|
||||
is_prof bool // benchmark every function
|
||||
profile_file string // the profile results will be stored inside profile_file
|
||||
profile_no_inline bool // when true, [inline] functions would not be profiled
|
||||
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
|
||||
is_prod bool // use "-O2"
|
||||
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
|
||||
is_repl bool
|
||||
is_run bool
|
||||
sanitize bool // use Clang's new "-fsanitize" option
|
||||
is_debug bool // false by default, turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
|
||||
sourcemap bool // JS Backend: -sourcemap will create a source map - default false
|
||||
sourcemap_inline bool = true // JS Backend: -sourcemap-inline will embed the source map in the generated JaaScript file - currently default true only implemented
|
||||
sourcemap_src_included bool // JS Backend: -sourcemap-src-included includes V source code in source map - default false
|
||||
is_vlines bool // turned on by -g, false by default (it slows down .tmp.c generation slightly).
|
||||
show_cc bool // -showcc, print cc command
|
||||
show_c_output bool // -show-c-output, print all cc output even if the code was compiled correctly
|
||||
show_callgraph bool // -show-callgraph, print the program callgraph, in a Graphviz DOT format to stdout
|
||||
is_test bool // `v test string_test.v`
|
||||
is_script bool // single file mode (`v program.v`), main function can be skipped
|
||||
is_vsh bool // v script (`file.vsh`) file, the `os` module should be made global
|
||||
is_livemain bool // main program that contains live/hot code
|
||||
is_liveshared bool // a shared library, that will be used in a -live main program
|
||||
is_shared bool // an ordinary shared library, -shared, no matter if it is live or not
|
||||
is_prof bool // benchmark every function
|
||||
profile_file string // the profile results will be stored inside profile_file
|
||||
profile_no_inline bool // when true, [inline] functions would not be profiled
|
||||
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
|
||||
is_prod bool // use "-O2"
|
||||
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
|
||||
is_repl bool
|
||||
is_run bool
|
||||
is_debug bool // turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
|
||||
is_vlines bool // turned on by -g (it slows down .tmp.c generation slightly).
|
||||
// NB: passing -cg instead of -g will set is_vlines to false and is_debug to true, thus making v generate cleaner C files,
|
||||
// which are sometimes easier to debug / inspect manually than the .tmp.c files by plain -g (when/if v line number generation breaks).
|
||||
// use cached modules to speed up compilation.
|
||||
dump_c_flags string // `-dump-c-flags file.txt` - let V store all C flags, passed to the backend C compiler
|
||||
// in `file.txt`, one C flag/value per line.
|
||||
use_cache bool // = true
|
||||
retry_compilation bool = true // retry the compilation with another C compiler, if tcc fails.
|
||||
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
|
||||
sanitize bool // use Clang's new "-fsanitize" option
|
||||
sourcemap bool // JS Backend: -sourcemap will create a source map - default false
|
||||
sourcemap_inline bool = true // JS Backend: -sourcemap-inline will embed the source map in the generated JaaScript file - currently default true only implemented
|
||||
sourcemap_src_included bool // JS Backend: -sourcemap-src-included includes V source code in source map - default false
|
||||
show_cc bool // -showcc, print cc command
|
||||
show_c_output bool // -show-c-output, print all cc output even if the code was compiled correctly
|
||||
show_callgraph bool // -show-callgraph, print the program callgraph, in a Graphviz DOT format to stdout
|
||||
show_depgraph bool // -show-depgraph, print the program module dependency graph, in a Graphviz DOT format to stdout
|
||||
dump_c_flags string // `-dump-c-flags file.txt` - let V store all C flags, passed to the backend C compiler in `file.txt`, one C flag/value per line.
|
||||
use_cache bool // when set, use cached modules to speed up subsequent compilations, at the cost of slower initial ones (while the modules are cached)
|
||||
retry_compilation bool = true // retry the compilation with another C compiler, if tcc fails.
|
||||
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
|
||||
// TODO Convert this into a []string
|
||||
cflags string // Additional options which will be passed to the C compiler.
|
||||
// For example, passing -cflags -Os will cause the C compiler to optimize the generated binaries for size.
|
||||
|
@ -141,7 +140,7 @@ pub mut:
|
|||
building_v bool
|
||||
autofree bool // `v -manualfree` => false, `v -autofree` => true; false by default for now.
|
||||
// Disabling `free()` insertion results in better performance in some applications (e.g. compilers)
|
||||
compress bool
|
||||
compress bool // when set, use `upx` to compress the generated executable
|
||||
// skip_builtin bool // Skips re-compilation of the builtin module
|
||||
// to increase compilation time.
|
||||
// This is on by default, since a vast majority of users do not
|
||||
|
@ -444,6 +443,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
|
|||
'-show-callgraph' {
|
||||
res.show_callgraph = true
|
||||
}
|
||||
'-show-depgraph' {
|
||||
res.show_depgraph = true
|
||||
}
|
||||
'-dump-c-flags' {
|
||||
res.dump_c_flags = cmdline.option(current_args, arg, '-')
|
||||
i++
|
||||
|
|
Loading…
Reference in New Issue