v.depgraph: fix detection of indirect module dependency cycles
parent
9ddf1ec327
commit
ac469f5eff
|
@ -174,12 +174,12 @@ pub fn (mut b Builder) parse_imports() {
|
||||||
pub fn (mut b Builder) resolve_deps() {
|
pub fn (mut b Builder) resolve_deps() {
|
||||||
graph := b.import_graph()
|
graph := b.import_graph()
|
||||||
deps_resolved := graph.resolve()
|
deps_resolved := graph.resolve()
|
||||||
cycles := deps_resolved.display_cycles()
|
|
||||||
if b.pref.is_verbose {
|
if b.pref.is_verbose {
|
||||||
eprintln('------ resolved dependencies graph: ------')
|
eprintln('------ resolved dependencies graph: ------')
|
||||||
eprintln(deps_resolved.display())
|
eprintln(deps_resolved.display())
|
||||||
eprintln('------------------------------------------')
|
eprintln('------------------------------------------')
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@ -210,6 +210,7 @@ pub fn (b &Builder) import_graph() &depgraph.DepGraph {
|
||||||
builtins := util.builtin_module_parts.clone()
|
builtins := util.builtin_module_parts.clone()
|
||||||
mut graph := depgraph.new_dep_graph()
|
mut graph := depgraph.new_dep_graph()
|
||||||
for p in b.parsed_files {
|
for p in b.parsed_files {
|
||||||
|
// eprintln('p.path: $p.path')
|
||||||
mut deps := []string{}
|
mut deps := []string{}
|
||||||
if p.mod.name !in builtins {
|
if p.mod.name !in builtins {
|
||||||
deps << 'builtin'
|
deps << 'builtin'
|
||||||
|
@ -228,6 +229,9 @@ pub fn (b &Builder) import_graph() &depgraph.DepGraph {
|
||||||
}
|
}
|
||||||
graph.add(p.mod.name, deps)
|
graph.add(p.mod.name, deps)
|
||||||
}
|
}
|
||||||
|
$if trace_import_graph ? {
|
||||||
|
eprintln(graph.display())
|
||||||
|
}
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,8 +96,10 @@ pub fn (graph &DepGraph) resolve() &DepGraph {
|
||||||
node_names.add(node.name, node.deps)
|
node_names.add(node.name, node.deps)
|
||||||
node_deps.add(node.name, node.deps)
|
node_deps.add(node.name, node.deps)
|
||||||
}
|
}
|
||||||
|
mut iterations := 0
|
||||||
mut resolved := new_dep_graph()
|
mut resolved := new_dep_graph()
|
||||||
for node_deps.size() != 0 {
|
for node_deps.size() != 0 {
|
||||||
|
iterations++
|
||||||
mut ready_set := []string{}
|
mut ready_set := []string{}
|
||||||
for name in node_deps.keys {
|
for name in node_deps.keys {
|
||||||
deps := node_deps.get(name)
|
deps := node_deps.get(name)
|
||||||
|
@ -130,31 +132,63 @@ pub fn (graph &DepGraph) last_node() DepGraphNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (graph &DepGraph) display() string {
|
pub fn (graph &DepGraph) display() string {
|
||||||
mut out := '\n'
|
mut out := []string{}
|
||||||
for node in graph.nodes {
|
for node in graph.nodes {
|
||||||
for dep in node.deps {
|
for dep in node.deps {
|
||||||
out += ' * $node.name -> $dep\n'
|
out << ' * $node.name -> $dep'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NodeNames {
|
||||||
|
mut:
|
||||||
|
is_cycle map[string]bool
|
||||||
|
names map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (graph &DepGraph) display_cycles() string {
|
pub fn (graph &DepGraph) display_cycles() string {
|
||||||
mut node_names := map[string]DepGraphNode{}
|
mut out := []string{}
|
||||||
|
mut nn := NodeNames{}
|
||||||
for node in graph.nodes {
|
for node in graph.nodes {
|
||||||
node_names[node.name] = node
|
nn.names[node.name] = node.deps
|
||||||
}
|
}
|
||||||
mut out := '\n'
|
for k, _ in nn.names {
|
||||||
for node in graph.nodes {
|
mut cycle_names := []string{}
|
||||||
for dep in node.deps {
|
if k in nn.is_cycle {
|
||||||
if dep !in node_names {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if nn.is_part_of_cycle(k, mut cycle_names) {
|
||||||
dn := node_names[dep]
|
out << ' * ' + cycle_names.join(' -> ')
|
||||||
if node.name in dn.deps {
|
nn.is_cycle = map[string]bool{}
|
||||||
out += ' * $node.name -> $dep\n'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut nn NodeNames) is_part_of_cycle(name string, mut already_seen []string) bool {
|
||||||
|
if name in nn.is_cycle {
|
||||||
|
return nn.is_cycle[name]
|
||||||
|
}
|
||||||
|
if name in already_seen {
|
||||||
|
already_seen << name
|
||||||
|
nn.is_cycle[name] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
already_seen << name
|
||||||
|
deps := nn.names[name]
|
||||||
|
if deps.len == 0 {
|
||||||
|
nn.is_cycle[name] = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for d in deps {
|
||||||
|
mut d_already_seen := already_seen.clone()
|
||||||
|
if nn.is_part_of_cycle(d, mut d_already_seen) {
|
||||||
|
already_seen = d_already_seen.clone()
|
||||||
|
nn.is_cycle[name] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nn.is_cycle[name] = false
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import os
|
||||||
import benchmark
|
import benchmark
|
||||||
import term
|
import term
|
||||||
|
|
||||||
|
const is_verbose = os.getenv('VTEST_SHOW_CMD') != ''
|
||||||
|
|
||||||
// TODO some logic copy pasted from valgrind_test.v and compiler_test.v, move to a module
|
// TODO some logic copy pasted from valgrind_test.v and compiler_test.v, move to a module
|
||||||
fn test_native() {
|
fn test_native() {
|
||||||
$if !amd64 {
|
$if !amd64 {
|
||||||
|
@ -33,7 +35,11 @@ fn test_native() {
|
||||||
relative_test_path := full_test_path.replace(vroot + '/', '')
|
relative_test_path := full_test_path.replace(vroot + '/', '')
|
||||||
work_test_path := '$wrkdir/x.v'
|
work_test_path := '$wrkdir/x.v'
|
||||||
os.cp(full_test_path, work_test_path) or {}
|
os.cp(full_test_path, work_test_path) or {}
|
||||||
res_native := os.execute('$vexe -o exe -native $work_test_path')
|
cmd := '$vexe -o exe -native $work_test_path'
|
||||||
|
if is_verbose {
|
||||||
|
println(cmd)
|
||||||
|
}
|
||||||
|
res_native := os.execute(cmd)
|
||||||
if res_native.exit_code != 0 {
|
if res_native.exit_code != 0 {
|
||||||
bench.fail()
|
bench.fail()
|
||||||
eprintln(bench.step_message_fail('native $test failed'))
|
eprintln(bench.step_message_fail('native $test failed'))
|
||||||
|
|
|
@ -3,6 +3,7 @@ module util
|
||||||
import os
|
import os
|
||||||
import v.pref
|
import v.pref
|
||||||
|
|
||||||
|
[if trace_mod_path_to_full_name]
|
||||||
fn trace_mod_path_to_full_name(line string, mod string, file_path string, res string) {
|
fn trace_mod_path_to_full_name(line string, mod string, file_path string, res string) {
|
||||||
eprintln('> $line ${@FN} mod: ${mod:-20} | file_path: ${file_path:-30} | result: $res')
|
eprintln('> $line ${@FN} mod: ${mod:-20} | file_path: ${file_path:-30} | result: $res')
|
||||||
}
|
}
|
||||||
|
@ -15,17 +16,13 @@ pub fn qualify_import(pref &pref.Preferences, mod string, file_path string) stri
|
||||||
try_path := os.join_path(search_path, mod_path)
|
try_path := os.join_path(search_path, mod_path)
|
||||||
if os.is_dir(try_path) {
|
if os.is_dir(try_path) {
|
||||||
if m1 := mod_path_to_full_name(mod, try_path) {
|
if m1 := mod_path_to_full_name(mod, try_path) {
|
||||||
$if trace_mod_path_to_full_name ? {
|
trace_mod_path_to_full_name(@LINE, mod, try_path, m1)
|
||||||
trace_mod_path_to_full_name(@LINE, mod, try_path, m1)
|
|
||||||
}
|
|
||||||
return m1
|
return m1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m1 := mod_path_to_full_name(mod, file_path) {
|
if m1 := mod_path_to_full_name(mod, file_path) {
|
||||||
$if trace_mod_path_to_full_name ? {
|
trace_mod_path_to_full_name(@LINE, mod, file_path, m1)
|
||||||
trace_mod_path_to_full_name(@LINE, mod, file_path, m1)
|
|
||||||
}
|
|
||||||
return m1
|
return m1
|
||||||
}
|
}
|
||||||
return mod
|
return mod
|
||||||
|
@ -42,9 +39,7 @@ pub fn qualify_module(mod string, file_path string) string {
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
if m1 := mod_path_to_full_name(mod, clean_file_path) {
|
if m1 := mod_path_to_full_name(mod, clean_file_path) {
|
||||||
$if trace_mod_path_to_full_name ? {
|
trace_mod_path_to_full_name(@LINE, mod, clean_file_path, m1)
|
||||||
trace_mod_path_to_full_name(@LINE, mod, clean_file_path, m1)
|
|
||||||
}
|
|
||||||
return m1
|
return m1
|
||||||
}
|
}
|
||||||
return mod
|
return mod
|
||||||
|
|
Loading…
Reference in New Issue