diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index 03fb3f7b..3dfee93c 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -114,7 +114,7 @@ class App(Base): if pretty: return self.__pretty_topdir return self.__topdir - for d in [self.__projs_root, '/opt']: + for d in [self.__projs_root, self.___opt_root]: ret = d + '/' + name if os.path.exists(ret): return ret @@ -126,10 +126,14 @@ class App(Base): def __find_dir( self, name: str, - search_subdirs: list[str] = [], - search_absdirs: list[str] = [], + search_subdirs: list[str] | None = None, + search_absdirs: list[str] | None = None, pretty: bool = True, ) -> str | None: + if search_subdirs is None: + search_subdirs = [] + if search_absdirs is None: + search_absdirs = [] def __format_relpath(path: str): if path.startswith('./'): @@ -181,16 +185,18 @@ class App(Base): return ret return None - @cache def __read_project_conf(self, project_dir: str) -> ProjectConf: return ProjectConf.read(project_dir + '/make/project.conf') @cache - def __get_project_conf(self, project: str) -> ProjectConf: + def __get_project_conf(self, project: str) -> ProjectConf | None: pd = self.__proj_dir(project, False) if pd is None: raise Exception(f'Failed to find directory of project {project}') - return self.__read_project_conf(pd) + try: + return self.__read_project_conf(pd) + except FileNotFoundError: + return None def __get_project_refs_cached( self, buf, visited, spec, section, key, add_self, scope, names_only @@ -272,8 +278,6 @@ class App(Base): add_self = False, names_only = True, ) - if deps is None: - continue graph[project] = set(deps) for dep in deps: self.__read_dep_graph([dep], sections, graph) @@ -313,22 +317,25 @@ class App(Base): temp.remove(project) return None - def __find_circular_deps(self, projects: list[str], flavours: list[str]) -> bool: + def __find_circular_deps(self, projects: list[str], + flavours: list[str]) -> list[str]: graph: Graph = {} ret: list[str] = [] self.__read_dep_graph(projects, flavours, graph) unvisited = list(graph.keys()) temp: set[str] = set() + flipped = self.__flip_dep_graph(graph) while unvisited: project = unvisited[0] log(DEBUG, 'Checking circular dependency of', project) last = self.__find_circular_deps_recursive( - project, self.__flip_dep_graph(graph), unvisited, temp, ret + project, flipped, unvisited, temp, ret ) if last is not None: log(DEBUG, f'Found circular dependency below {project}, last is {last}') - return True - return False + ret.append(last) + return ret + return [] def __init__(self, distro: Distro | None = None) -> None: @@ -347,6 +354,7 @@ class App(Base): # -- Members with default values self.__topdir_fmt = 'absolute' self.__projs_root = pwd.getpwuid(os.getuid()).pw_dir + '/local/src/jw.dev/proj' + self.___opt_root = '/opt' self.__pretty_projs_root = None async def __init_async(self) -> None: @@ -489,8 +497,8 @@ class App(Base): def find_dir( self, name: str, - search_subdirs: list[str] = [], - search_absdirs: list[str] = [], + search_subdirs: list[str] | None = None, + search_absdirs: list[str] | None = None, pretty: bool = True, throw: bool = False, ) -> str | None: @@ -541,13 +549,15 @@ class App(Base): try: with open(version_path) as fd: ret = fd.read().replace('\n', '').replace('-dev', '') - fd.close() return ret except EnvironmentError: log(DEBUG, f'Ignoring unreadable file "{version_path}"') continue raise Exception(f'No version file found for project "{project}"') - ret = self.__get_project_conf(project).get_str_or_none(section, key) + proj_conf = self.__get_project_conf(project) + if proj_conf is None: + return None + ret = proj_conf.get_str_or_none(section, key) log( DEBUG, 'Lookup %s -> %s / [%s%s] -> "%s"' % @@ -574,7 +584,10 @@ class App(Base): for key in keys: vals = self.get_value(p, section, key) if vals: - ret += [val.strip() for val in vals.split(',')] + for val in vals.split(','): + stripped = val.strip() + if stripped: + ret.append(stripped) return list(dict.fromkeys(ret)) # Remove duplicates, keep ordering def get_project_refs( @@ -624,5 +637,5 @@ class App(Base): return ', '.join(intersection) return None - def find_circular_deps(self, projects: list[str], flavours: list[str]) -> bool: + def find_circular_deps(self, projects: list[str], flavours: list[str]) -> list[str]: return self.__find_circular_deps(projects, flavours) diff --git a/src/python/jw/pkg/cmds/projects/check/CmdDep.py b/src/python/jw/pkg/cmds/projects/check/CmdDep.py index e6469b90..478b26ae 100644 --- a/src/python/jw/pkg/cmds/projects/check/CmdDep.py +++ b/src/python/jw/pkg/cmds/projects/check/CmdDep.py @@ -1,8 +1,9 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from ....lib.log import NOTICE, log from .Cmd import Cmd, Parent -from typing import TYPE_CHECKING if TYPE_CHECKING: from argparse import ArgumentParser, Namespace @@ -22,8 +23,13 @@ class CmdDep(Cmd): # export parser.add_argument('-f', '--flavour', nargs = '?', default = 'build') async def _run(self, args: Namespace) -> None: - if self.app.find_circular_deps(args.module, args.flavour): - log(NOTICE, f'Found circular dependency in flavour {args.flavour}') + cycle = self.app.find_circular_deps(args.module, args.flavour) + if cycle: + log( + NOTICE, + f'Found circular dependency in flavour {args.flavour}: ' + ' -> '.join(cycle) + ) exit(1) log( NOTICE,