152 lines
4.6 KiB
Python
152 lines
4.6 KiB
Python
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import yaml
|
||
|
|
|
||
|
|
def find_repos_yaml():
|
||
|
|
"""Traverse up from the current directory to find repos.yaml."""
|
||
|
|
current_dir = os.getcwd()
|
||
|
|
while current_dir != '/':
|
||
|
|
repos_yaml_path = os.path.join(current_dir, 'repos.yaml')
|
||
|
|
if os.path.exists(repos_yaml_path):
|
||
|
|
return repos_yaml_path
|
||
|
|
current_dir = os.path.dirname(current_dir)
|
||
|
|
return None
|
||
|
|
|
||
|
|
def parse_dependencies(repos_yaml_path):
|
||
|
|
"""Parses the repos.yaml file and returns a dependency graph."""
|
||
|
|
with open(repos_yaml_path, 'r') as f:
|
||
|
|
data = yaml.safe_load(f)
|
||
|
|
|
||
|
|
graph = {}
|
||
|
|
repos = data.get('repos', {})
|
||
|
|
for repo_name, details in repos.items():
|
||
|
|
graph[repo_name] = details.get('depends', []) or []
|
||
|
|
return graph
|
||
|
|
|
||
|
|
def find_circular_dependencies(graph):
|
||
|
|
"""Finds circular dependencies in the graph using DFS."""
|
||
|
|
visiting = set()
|
||
|
|
visited = set()
|
||
|
|
cycles = []
|
||
|
|
|
||
|
|
def dfs(node, path):
|
||
|
|
visiting.add(node)
|
||
|
|
path.append(node)
|
||
|
|
|
||
|
|
for neighbor in graph.get(node, []):
|
||
|
|
if neighbor in visiting:
|
||
|
|
cycle_start_index = path.index(neighbor)
|
||
|
|
cycles.append(path[cycle_start_index:] + [neighbor])
|
||
|
|
elif neighbor not in visited:
|
||
|
|
dfs(neighbor, path)
|
||
|
|
|
||
|
|
path.pop()
|
||
|
|
visiting.remove(node)
|
||
|
|
visited.add(node)
|
||
|
|
|
||
|
|
for node in graph:
|
||
|
|
if node not in visited:
|
||
|
|
dfs(node, [])
|
||
|
|
|
||
|
|
return cycles
|
||
|
|
|
||
|
|
def print_dependency_tree(graph, module, prefix=""):
|
||
|
|
"""Prints the dependency tree for a given module."""
|
||
|
|
if module not in graph:
|
||
|
|
print(f"Module '{module}' not found.")
|
||
|
|
return
|
||
|
|
|
||
|
|
print(f"{prefix}{module}")
|
||
|
|
dependencies = graph.get(module, [])
|
||
|
|
for i, dep in enumerate(dependencies):
|
||
|
|
is_last = i == len(dependencies) - 1
|
||
|
|
new_prefix = prefix.replace("├──", "│ ").replace("└──", " ")
|
||
|
|
connector = "└── " if is_last else "├── "
|
||
|
|
print_dependency_tree(graph, dep, new_prefix + connector)
|
||
|
|
|
||
|
|
def print_reverse_dependencies(graph, module):
|
||
|
|
"""Prints the modules that depend on a given module."""
|
||
|
|
if module not in graph:
|
||
|
|
print(f"Module '{module}' not found.")
|
||
|
|
return
|
||
|
|
|
||
|
|
reverse_deps = []
|
||
|
|
for repo, deps in graph.items():
|
||
|
|
if module in deps:
|
||
|
|
reverse_deps.append(repo)
|
||
|
|
|
||
|
|
if not reverse_deps:
|
||
|
|
print(f"(no modules depend on {module})")
|
||
|
|
else:
|
||
|
|
for i, dep in enumerate(sorted(reverse_deps)):
|
||
|
|
is_last = i == len(reverse_deps) - 1
|
||
|
|
print(f"{'└── ' if is_last else '├── '}{dep}")
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Main function to handle command-line arguments and execute logic."""
|
||
|
|
repos_yaml_path = find_repos_yaml()
|
||
|
|
if not repos_yaml_path:
|
||
|
|
print("Error: Could not find repos.yaml in the current directory or any parent directory.")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
try:
|
||
|
|
graph = parse_dependencies(repos_yaml_path)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"Error parsing repos.yaml: {e}")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
cycles = find_circular_dependencies(graph)
|
||
|
|
if cycles:
|
||
|
|
print("Error: Circular dependencies detected!")
|
||
|
|
for cycle in cycles:
|
||
|
|
print(" -> ".join(cycle))
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
args = sys.argv[1:]
|
||
|
|
|
||
|
|
if not args:
|
||
|
|
print("Dependency tree for all modules:")
|
||
|
|
for module in sorted(graph.keys()):
|
||
|
|
print(f"\n{module} dependencies:")
|
||
|
|
dependencies = graph.get(module, [])
|
||
|
|
if not dependencies:
|
||
|
|
print("└── (no dependencies)")
|
||
|
|
else:
|
||
|
|
for i, dep in enumerate(dependencies):
|
||
|
|
is_last = i == len(dependencies) - 1
|
||
|
|
print_dependency_tree(graph, dep, "└── " if is_last else "├── ")
|
||
|
|
return
|
||
|
|
|
||
|
|
reverse = "--reverse" in args
|
||
|
|
if reverse:
|
||
|
|
args.remove("--reverse")
|
||
|
|
|
||
|
|
if not args:
|
||
|
|
print("Usage: /core:deps [--reverse] [module_name]")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
module_name = args[0]
|
||
|
|
|
||
|
|
if module_name not in graph:
|
||
|
|
print(f"Error: Module '{module_name}' not found in repos.yaml.")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
if reverse:
|
||
|
|
print(f"Modules that depend on {module_name}:")
|
||
|
|
print_reverse_dependencies(graph, module_name)
|
||
|
|
else:
|
||
|
|
print(f"{module_name} dependencies:")
|
||
|
|
dependencies = graph.get(module_name, [])
|
||
|
|
if not dependencies:
|
||
|
|
print("└── (no dependencies)")
|
||
|
|
else:
|
||
|
|
for i, dep in enumerate(dependencies):
|
||
|
|
is_last = i == len(dependencies) - 1
|
||
|
|
connector = "└── " if is_last else "├── "
|
||
|
|
print_dependency_tree(graph, dep, connector)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|