App.__get_project_refs(): Fix broken detection of missing build dependencies #94

Merged
Jan Lindemann merged 4 commits from jan/fix/20260910-broken-detection-of-missing-build-dependencies into master 2026-09-10 13:15:30 +02:00 AGit
2 changed files with 77 additions and 10 deletions

View file

@ -21,7 +21,7 @@ if TYPE_CHECKING:
import argparse import argparse
from argparse import ArgumentParser from argparse import ArgumentParser
from typing import TypeAlias from typing import Iterable, TypeAlias
from .lib.PackageFilter import PackageFilter from .lib.PackageFilter import PackageFilter
@ -77,12 +77,17 @@ class App(Base):
raise Exception('Tried to access undefined pretty top directory') raise Exception('Tried to access undefined pretty top directory')
return self.___pretty_topdir return self.___pretty_topdir
def __proj_dir(self, name: str, pretty: bool) -> str | None: def __proj_dir(
self,
name: str,
pretty: bool,
projs_roots: Iterable[str | None] | None = None,
) -> str | None:
if name == self.__top_name: if name == self.__top_name:
if pretty: if pretty:
return self.__pretty_topdir return self.__pretty_topdir
return self.__topdir return self.__topdir
for d in [self.__projs_root, self.___opt_root]: for d in projs_roots or [self.__projs_root, self.___opt_root]:
if d is None: if d is None:
continue continue
ret = d + '/' + name ret = d + '/' + name
@ -93,12 +98,36 @@ class App(Base):
return None return None
raise Exception('No project path found for module "{}"'.format(name)) raise Exception('No project path found for module "{}"'.format(name))
@cache
def __is_installed(self, name: str, devel: bool) -> bool:
# devel: the project is in the dev tree or the -devel
# package is installed: make/project.conf is present.
# run: the -run package is installed: a VERSION file is
# present in the project directory or
# /usr/share/doc/packages.
if devel and name == self.__top_name:
# the topdir is the project's own buildable checkout
if os.path.exists(f'{self.__topdir}/make/project.conf'):
return True
search, file = (
(self.__projs_root, self.___opt_root),
'/make/project.conf'
) if devel else (
(self.__projs_root, '/usr/share/doc/packages'),
'/VERSION'
)
for root in search:
if root is not None and os.path.exists(f'{root}/{name}{file}'):
return True
return False
def __find_dir( def __find_dir(
self, self,
name: str, name: str,
search_subdirs: list[str] | None = None, search_subdirs: list[str] | None = None,
search_absdirs: list[str] | None = None, search_absdirs: list[str] | None = None,
pretty: bool = True, pretty: bool = True,
projs_roots: Iterable[str | None] | None = None,
) -> str | None: ) -> str | None:
if search_subdirs is None: if search_subdirs is None:
search_subdirs = [] search_subdirs = []
@ -134,7 +163,7 @@ class App(Base):
f'Tried to pretty-format directory {pd}, not implemented' f'Tried to pretty-format directory {pd}, not implemented'
) )
pd = self.__proj_dir(name, False) pd = self.__proj_dir(name, pretty = False, projs_roots = projs_roots)
if pd is None: if pd is None:
return None return None
if not search_subdirs and not search_absdirs: if not search_subdirs and not search_absdirs:
@ -181,6 +210,7 @@ class App(Base):
names_only: bool, names_only: bool,
) -> None: ) -> None:
name = self.strip_module_from_spec(spec) name = self.strip_module_from_spec(spec)
mod = re.split('([=><]+)', spec)[0].strip()
if names_only: if names_only:
spec = name spec = name
if spec in buf: if spec in buf:
@ -190,6 +220,15 @@ class App(Base):
buf.append(spec) buf.append(spec)
return return
visited.add(spec) visited.add(spec)
needed_subpackage = 'devel' if mod.endswith(('-dev', '-devel')) else 'run'
if not self.is_installed(name, devel = True):
if not mod.endswith(('-dev', '-devel')):
if self.is_installed(name, devel = False):
return
raise Exception(
f'Unmet dependency on {mod}: the -{needed_subpackage} package '
f'of project {name} is not installed'
)
vals = self.get_value(name, section, key) vals = self.get_value(name, section, key)
log( log(
DEBUG, DEBUG,
@ -398,6 +437,18 @@ class App(Base):
raise Exception('No distro object') raise Exception('No distro object')
return self.__distro return self.__distro
def is_installed(
self,
name: str,
devel: bool = False,
) -> bool:
"""True if the project is installed: for devel,
make/project.conf is present in the dev tree or /opt; for
run, a VERSION file is present in the project directory
or /usr/share/doc/packages.
"""
return self.__is_installed(name, devel)
def find_dir( def find_dir(
self, self,
name: str, name: str,
@ -405,8 +456,9 @@ class App(Base):
search_absdirs: list[str] | None = None, search_absdirs: list[str] | None = None,
pretty: bool = True, pretty: bool = True,
throw: bool = False, throw: bool = False,
projs_roots: Iterable[str | None] | None = None,
) -> str | None: ) -> str | None:
ret = self.__find_dir(name, search_subdirs, search_absdirs, pretty) ret = self.__find_dir(name, search_subdirs, search_absdirs, pretty, projs_roots)
if ret is not None: if ret is not None:
return ret return ret
if not throw: if not throw:

View file

@ -94,6 +94,14 @@ class CmdBuild(Cmd): # export
@override @override
async def _run(self, args: Namespace) -> None: async def _run(self, args: Namespace) -> None:
@lru_cache(maxsize = None)
def proj_dir(module: str) -> str | None:
return self.app.find_dir(
module,
pretty = False,
projs_roots = [self.app.projs_root],
)
@lru_cache(maxsize = None) @lru_cache(maxsize = None)
def read_deps(cur: str, dep_flavour: str) -> list[str]: def read_deps(cur: str, dep_flavour: str) -> list[str]:
# dep cache doesn't make a difference at all # dep cache doesn't make a difference at all
@ -172,20 +180,27 @@ class CmdBuild(Cmd): # export
dep_tree[k].remove(d) dep_tree[k].remove(d)
return 1 return 1
def log_skip(module: str, msg: str) -> None:
title = f'---- {module}'
log(NOTICE, f',{title} >')
log(NOTICE, f'| {msg}<')
log(NOTICE, f'`{title} <')
async def run_make( async def run_make(
module: str, target: str, cur_project: int, num_projects: int module: str, target: str, cur_project: int, num_projects: int
) -> None: ) -> None:
patt = self.app.is_excluded_from_build(module) patt = self.app.is_excluded_from_build(module)
if patt is not None: if patt is not None:
title = f'---- {module}' log_skip(module, f'Configured to skip build on platform >{patt}<')
log(NOTICE, f',{title} >') return
log(NOTICE, f'| Configured to skip build on platform >{patt}<')
log(NOTICE, f'`{title} <') wd = proj_dir(module)
if wd is None:
log_skip(module, 'Skipping: No buildable project directory')
return return
make_cmd = ['make', target] make_cmd = ['make', target]
wd = self.app.find_dir(module, pretty = False)
title = '---- [%d/%d]: Running "%s" in %s -' % ( title = '---- [%d/%d]: Running "%s" in %s -' % (
cur_project, cur_project,
num_projects, num_projects,