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