App: Miscellaneous fixes and improvements based on AI code analysis #44

Merged
Jan Lindemann merged 8 commits from jan/feature/20260630-app-get-values-filter-out-empty-split-results into master 2026-06-30 15:21:31 +02:00 AGit
2 changed files with 40 additions and 21 deletions

View file

@ -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}')
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)

View file

@ -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,