Skip to content

Commit cf62715

Browse files
committed
commit
1 parent 643d068 commit cf62715

File tree

5 files changed

+73
-57
lines changed

5 files changed

+73
-57
lines changed

src/build/build_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub struct Module {
7070
pub compile_dirty: bool,
7171
pub last_compiled_cmi: Option<SystemTime>,
7272
pub last_compiled_cmt: Option<SystemTime>,
73+
pub deps_dirty: bool,
7374
}
7475

7576
impl Module {
Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,88 @@
11
use super::super::build_types::*;
22
use crate::helpers;
3-
use ahash::AHashSet;
3+
use std::collections::{HashMap, VecDeque};
44

55
pub fn find(modules: &Vec<(&String, &Module)>) -> Vec<String> {
6-
let mut visited: AHashSet<String> = AHashSet::new();
7-
let mut stack: Vec<String> = vec![];
6+
// If a cycle was found, find the shortest cycle using BFS
7+
find_shortest_cycle(modules)
8+
}
89

9-
// we want to sort the module names so that we always return the same
10-
// dependency cycle (there can be more than one)
11-
let mut module_names = modules
12-
.iter()
13-
.map(|(name, _)| name.to_string())
14-
.collect::<Vec<String>>();
10+
fn find_shortest_cycle(modules: &Vec<(&String, &Module)>) -> Vec<String> {
11+
let mut shortest_cycle: Vec<String> = Vec::new();
1512

16-
module_names.sort();
17-
for module_name in module_names {
18-
if find_dependency_cycle_helper(&module_name, modules, &mut visited, &mut stack) {
19-
return stack;
13+
// Build a graph representation for easier traversal
14+
let mut graph: HashMap<String, Vec<String>> = HashMap::new();
15+
for (name, module) in modules {
16+
let deps = module.deps.iter().cloned().collect();
17+
graph.insert(name.to_string(), deps);
18+
}
19+
20+
// Try BFS from each node to find the shortest cycle
21+
for start_node in graph.keys() {
22+
let start = start_node.clone();
23+
if let Some(cycle) = find_cycle_bfs(&start, &graph) {
24+
if shortest_cycle.is_empty() || cycle.len() < shortest_cycle.len() {
25+
shortest_cycle = cycle;
26+
}
2027
}
21-
visited.clear();
22-
stack.clear();
2328
}
24-
stack
29+
30+
shortest_cycle
2531
}
2632

27-
fn find_dependency_cycle_helper(
28-
module_name: &String,
29-
modules: &Vec<(&String, &Module)>,
30-
visited: &mut AHashSet<String>,
31-
stack: &mut Vec<String>,
32-
) -> bool {
33-
if let Some(module) = modules
34-
.iter()
35-
.find(|(name, _)| *name == module_name)
36-
.map(|(_, module)| module)
37-
{
38-
visited.insert(module_name.to_string());
39-
// if the module is a mlmap (namespace), we don't want to show this in the path
40-
// because the namespace is not a module the user created, so only add source files
41-
// to the stack
42-
if let SourceType::SourceFile(_) = module.source_type {
43-
stack.push(module_name.to_string())
44-
}
45-
for dep in &module.deps {
46-
if !visited.contains(dep) {
47-
if find_dependency_cycle_helper(dep, modules, visited, stack) {
48-
return true;
33+
fn find_cycle_bfs(start: &String, graph: &HashMap<String, Vec<String>>) -> Option<Vec<String>> {
34+
// Use a BFS to find the shortest cycle
35+
let mut queue = VecDeque::new();
36+
// Store node -> (distance, parent)
37+
let mut visited: HashMap<String, (usize, Option<String>)> = HashMap::new();
38+
39+
// Initialize with start node
40+
visited.insert(start.clone(), (0, None));
41+
queue.push_back(start.clone());
42+
43+
while let Some(current) = queue.pop_front() {
44+
let (dist, _) = *visited.get(&current).unwrap();
45+
46+
// Check all neighbors
47+
if let Some(neighbors) = graph.get(&current) {
48+
for neighbor in neighbors {
49+
// If we found the start node again, we have a cycle
50+
if neighbor == start {
51+
// Reconstruct the cycle
52+
let mut path = Vec::new();
53+
path.push(start.clone());
54+
55+
// Backtrack from current to start using parent pointers
56+
let mut curr = current.clone();
57+
while curr != *start {
58+
path.push(curr.clone());
59+
curr = visited.get(&curr).unwrap().1.clone().unwrap();
60+
}
61+
62+
return Some(path);
63+
}
64+
65+
// If not visited, add to queue
66+
if !visited.contains_key(neighbor) {
67+
visited.insert(neighbor.clone(), (dist + 1, Some(current.clone())));
68+
queue.push_back(neighbor.clone());
4969
}
50-
} else if stack.contains(dep) {
51-
stack.push(dep.to_string());
52-
return true;
5370
}
5471
}
55-
// because we only pushed source files to the stack, we also only need to
56-
// pop these from the stack if we don't find a dependency cycle
57-
if let SourceType::SourceFile(_) = module.source_type {
58-
let _ = stack.pop();
59-
}
60-
return false;
6172
}
62-
false
73+
74+
None
6375
}
6476

6577
pub fn format(cycle: &[String]) -> String {
78+
let mut cycle = cycle.to_vec();
79+
cycle.reverse();
80+
// add the first module to the end of the cycle
81+
cycle.push(cycle[0].clone());
82+
6683
cycle
6784
.iter()
6885
.map(|s| helpers::format_namespaced_module_name(s))
6986
.collect::<Vec<String>>()
70-
.join(" -> ")
87+
.join("\n ")
7188
}

src/build/deps.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use super::build_types::*;
2-
use super::is_dirty;
32
use super::packages;
43
use crate::helpers;
54
use ahash::AHashSet;
@@ -76,7 +75,7 @@ pub fn get_deps(build_state: &mut BuildState, deleted_modules: &AHashSet<String>
7675
.get_package(&module.package_name)
7776
.expect("Package not found");
7877
let ast_path = package.get_ast_path(&source_file.implementation.path);
79-
if is_dirty(module) || !build_state.deps_initialized {
78+
if module.deps_dirty || !build_state.deps_initialized {
8079
let mut deps = get_dep_modules(
8180
&ast_path,
8281
package.namespace.to_suffix(),
@@ -114,6 +113,7 @@ pub fn get_deps(build_state: &mut BuildState, deleted_modules: &AHashSet<String>
114113
.for_each(|(module_name, deps)| {
115114
if let Some(module) = build_state.modules.get_mut(&module_name) {
116115
module.deps = deps.clone();
116+
module.deps_dirty = false;
117117
}
118118
deps.iter().for_each(|dep_name| {
119119
if let Some(module) = build_state.modules.get_mut(dep_name) {

src/build/packages.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ pub fn parse_packages(build_state: &mut BuildState) {
645645
build_state.insert_module(
646646
&helpers::file_path_to_module_name(&mlmap.to_owned(), &packages::Namespace::NoNamespace),
647647
Module {
648+
deps_dirty: false,
648649
source_type: SourceType::MlMap(MlMap { parse_dirty: false }),
649650
deps,
650651
dependents: AHashSet::new(),
@@ -685,6 +686,7 @@ pub fn parse_packages(build_state: &mut BuildState) {
685686
}
686687
})
687688
.or_insert(Module {
689+
deps_dirty: true,
688690
source_type: SourceType::SourceFile(SourceFile {
689691
implementation: Implementation {
690692
path: file.to_owned(),
@@ -732,6 +734,7 @@ pub fn parse_packages(build_state: &mut BuildState) {
732734
}
733735
})
734736
.or_insert(Module {
737+
deps_dirty: true,
735738
source_type: SourceType::SourceFile(SourceFile {
736739
// this will be overwritten later
737740
implementation: Implementation {

src/build/parse.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub fn generate_asts(
9898
// the compile_dirty flag if it was set before
9999
if is_dirty {
100100
module.compile_dirty = true;
101+
module.deps_dirty = true;
101102
}
102103
let package = build_state
103104
.packages
@@ -113,9 +114,6 @@ pub fn generate_asts(
113114
Ok((_path, Some(stderr_warnings))) if package.is_pinned_dep => {
114115
source_file.implementation.parse_state = ParseState::Warning;
115116
source_file.implementation.parse_dirty = true;
116-
if let Some(interface) = source_file.interface.as_mut() {
117-
interface.parse_dirty = false;
118-
}
119117
logs::append(package, &stderr_warnings);
120118
stderr.push_str(&stderr_warnings);
121119
}
@@ -124,9 +122,6 @@ pub fn generate_asts(
124122
// dependency (so some external dep). We can ignore those
125123
source_file.implementation.parse_state = ParseState::Success;
126124
source_file.implementation.parse_dirty = false;
127-
if let Some(interface) = source_file.interface.as_mut() {
128-
interface.parse_dirty = false;
129-
}
130125
}
131126
Err(err) => {
132127
// Some compilation error

0 commit comments

Comments
 (0)