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.vmod
|
||||||
import v.checker
|
import v.checker
|
||||||
import v.parser
|
import v.parser
|
||||||
import v.depgraph
|
|
||||||
import v.markused
|
import v.markused
|
||||||
|
import v.depgraph
|
||||||
import v.callgraph
|
import v.callgraph
|
||||||
|
import v.dotgraph
|
||||||
|
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
pub:
|
pub:
|
||||||
|
@ -52,6 +53,9 @@ pub fn new_builder(pref &pref.Preferences) Builder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
util.timing_set_should_print(pref.show_timings || pref.is_verbose)
|
util.timing_set_should_print(pref.show_timings || pref.is_verbose)
|
||||||
|
if pref.show_callgraph || pref.show_depgraph {
|
||||||
|
dotgraph.start_digraph()
|
||||||
|
}
|
||||||
return Builder{
|
return Builder{
|
||||||
pref: pref
|
pref: pref
|
||||||
table: table
|
table: table
|
||||||
|
@ -180,6 +184,9 @@ pub fn (mut b Builder) resolve_deps() {
|
||||||
eprintln(deps_resolved.display())
|
eprintln(deps_resolved.display())
|
||||||
eprintln('------------------------------------------')
|
eprintln('------------------------------------------')
|
||||||
}
|
}
|
||||||
|
if b.pref.show_depgraph {
|
||||||
|
depgraph.show(deps_resolved, b.pref.path)
|
||||||
|
}
|
||||||
cycles := deps_resolved.display_cycles()
|
cycles := deps_resolved.display_cycles()
|
||||||
if cycles.len > 1 {
|
if cycles.len > 1 {
|
||||||
verror('error: import cycle detected between the following modules: \n' + cycles)
|
verror('error: import cycle detected between the following modules: \n' + cycles)
|
||||||
|
|
|
@ -3,7 +3,7 @@ module callgraph
|
||||||
import v.ast
|
import v.ast
|
||||||
import v.ast.walker
|
import v.ast.walker
|
||||||
import v.pref
|
import v.pref
|
||||||
import strings
|
import v.dotgraph
|
||||||
|
|
||||||
// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls
|
// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls
|
||||||
// that function make transitively
|
// 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{
|
mut mapper := &Mapper{
|
||||||
pref: pref
|
pref: pref
|
||||||
table: table
|
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"];
|
// Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
|
||||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||||
for afile in ast_files {
|
for afile in ast_files {
|
||||||
walker.walk(mapper, afile)
|
walker.walk(mapper, afile)
|
||||||
}
|
}
|
||||||
mapper.sb.writeln('}')
|
mapper.dg.finish()
|
||||||
println(mapper.sb.str())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[heap]
|
[heap]
|
||||||
|
@ -37,7 +33,7 @@ mut:
|
||||||
caller_name string
|
caller_name string
|
||||||
dot_caller_name string
|
dot_caller_name string
|
||||||
is_caller_used bool
|
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 {
|
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.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)
|
m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method)
|
||||||
if m.is_caller_used {
|
if m.is_caller_used {
|
||||||
if m.caller_name == 'main.main' {
|
m.dg.new_node(m.caller_name,
|
||||||
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];')
|
node_name: m.dot_caller_name
|
||||||
} else {
|
should_highlight: m.caller_name == 'main.main'
|
||||||
m.sb.writeln('\t$m.dot_caller_name [shape="box",label="$m.caller_name"];')
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {}
|
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,
|
dot_called_name := m.dot_fn_name(node.name, node.receiver_type,
|
||||||
node.is_method)
|
node.is_method)
|
||||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||||
if m.caller_name == 'main.main' {
|
m.dg.new_edge(m.dot_caller_name, dot_called_name,
|
||||||
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name [color="blue"];')
|
should_highlight: m.caller_name == 'main.main'
|
||||||
} else {
|
)
|
||||||
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {}
|
else {}
|
||||||
|
@ -134,13 +127,3 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
|
||||||
else {}
|
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
|
// this implementation is specifically suited to ordering dependencies
|
||||||
module depgraph
|
module depgraph
|
||||||
|
|
||||||
|
import v.dotgraph
|
||||||
|
|
||||||
struct DepGraphNode {
|
struct DepGraphNode {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string
|
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
|
nn.is_cycle[name] = false
|
||||||
return false, new_already_seen
|
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
|
// verbosity VerboseLevel
|
||||||
is_verbose bool
|
is_verbose bool
|
||||||
// nofmt bool // disable vfmt
|
// nofmt bool // disable vfmt
|
||||||
is_test bool // `v test string_test.v`
|
is_test bool // `v test string_test.v`
|
||||||
is_script bool // single file mode (`v program.v`), main function can be skipped
|
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_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_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_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_shared bool // an ordinary shared library, -shared, no matter if it is live or not
|
||||||
is_prof bool // benchmark every function
|
is_prof bool // benchmark every function
|
||||||
profile_file string // the profile results will be stored inside profile_file
|
profile_file string // the profile results will be stored inside profile_file
|
||||||
profile_no_inline bool // when true, [inline] functions would not be profiled
|
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
|
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
|
||||||
is_prod bool // use "-O2"
|
is_prod bool // use "-O2"
|
||||||
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
|
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
|
||||||
is_repl bool
|
is_repl bool
|
||||||
is_run bool
|
is_run bool
|
||||||
sanitize bool // use Clang's new "-fsanitize" option
|
is_debug bool // turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
|
||||||
is_debug bool // false by default, 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).
|
||||||
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
|
|
||||||
// NB: passing -cg instead of -g will set is_vlines to false and is_debug to true, thus making v generate cleaner C files,
|
// 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).
|
// 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.
|
sanitize bool // use Clang's new "-fsanitize" option
|
||||||
dump_c_flags string // `-dump-c-flags file.txt` - let V store all C flags, passed to the backend C compiler
|
sourcemap bool // JS Backend: -sourcemap will create a source map - default false
|
||||||
// in `file.txt`, one C flag/value per line.
|
sourcemap_inline bool = true // JS Backend: -sourcemap-inline will embed the source map in the generated JaaScript file - currently default true only implemented
|
||||||
use_cache bool // = true
|
sourcemap_src_included bool // JS Backend: -sourcemap-src-included includes V source code in source map - default false
|
||||||
retry_compilation bool = true // retry the compilation with another C compiler, if tcc fails.
|
show_cc bool // -showcc, print cc command
|
||||||
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
|
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
|
// TODO Convert this into a []string
|
||||||
cflags string // Additional options which will be passed to the C compiler.
|
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.
|
// 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
|
building_v bool
|
||||||
autofree bool // `v -manualfree` => false, `v -autofree` => true; false by default for now.
|
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)
|
// 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
|
// skip_builtin bool // Skips re-compilation of the builtin module
|
||||||
// to increase compilation time.
|
// to increase compilation time.
|
||||||
// This is on by default, since a vast majority of users do not
|
// 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' {
|
'-show-callgraph' {
|
||||||
res.show_callgraph = true
|
res.show_callgraph = true
|
||||||
}
|
}
|
||||||
|
'-show-depgraph' {
|
||||||
|
res.show_depgraph = true
|
||||||
|
}
|
||||||
'-dump-c-flags' {
|
'-dump-c-flags' {
|
||||||
res.dump_c_flags = cmdline.option(current_args, arg, '-')
|
res.dump_c_flags = cmdline.option(current_args, arg, '-')
|
||||||
i++
|
i++
|
||||||
|
|
Loading…
Reference in New Issue