jw.pkg: Fix "make check" static code check fallout

The previous commits have put rules for linting and formatting via ruff, yapf, mypy and pyright into place. They are checked with the make check target, and this commit adds the fixes for the target to succeed.

It does some refactoring where type checking dug up dirty bits, and also adds lots of churn in the Python code. To a good deal, that's owed to mere formatting changes. It would have been better to seperate those from syntax and refactoring fixes into multiple commits, so that the interesting changes don't drown in the formatting nose. However, that would have been a lot of additional work only to be thrown away by later commits, hence this commit has a big diff in one piece. The size of the diff is regrettable but hopefully a one-off: What it buys is automatic format checking for CI and predictble formats for smaller diffs in the future.

Rules that "make check" enforces are, in the following order

- Syntax checkers:

- ruff check . - mypy . - pyright

- Format check:

- yapf --diff --recursive .

The refactoring includes:

- Turn the Result class into a more elaborate object, capable of doing more heavy lifting around stderr and stdout decoding, summarizing outcome, and matching error strings.
Aside from fixing broken type checks, this also removes lots of boilerplate calling code which is currently used for handling possible call outcome scenarios. Trying to access an inexistent, decoded string should raise a meaningful exception by itself now, which removes lots of code with case distinctions.

- Fix Cmd type hierarchy:

- Add the AbstractCmd class above Cmd. This is necessary because the checker rightfully complains it can't instantiate a Cmd instance where constructor arguments were needed. They never were, but the type used at the instantiating code's location in jw.pkg.App so claims.
- Lots of sub- and sub-subcommands are derived from the base class of the invoking command. That provides some properties shared across the ancestor hierarchy of a command, but is semantically unsound. Fix that by introducing jw.pkg.BaseCmd class as a place to provide basic helpers shared across all commands used in a jw.pkg.App's context, and derive all command classes from that afresh. The parent command is still reachable via a common parent property.

Formatting changes are conforming to PEP-8, mostly, with minor tweaks. All in all they include the following changes.

- Remove # -*- coding: utf-8 -*-

The line was needed by Python 2 which is not supported anylonger. For Python 3, the default encoding is UTF-8, anyway.
- Allow to run "make py-format" without having it produce any changes. It's basically "yapf --in-place --recursive ." with some code style settings, see conf/topdir/pyproject.toml. The settings may be debatable. I've had custom tweaks in place on that target, too, but then again, IDEs would have more hassle to integrate that.

- Introduce a 88 character line length limit

- One import per line, reshuffle them semantically, see [tool.isort] in pyproject.toml.

- Hide imports needed for type-checking only behind

if TYPE_CHECKING
- Spaces around assignments accounts for much churn. Having having no spaces in inline parameter list assignments and default parameter values would arguably be more compact where it's useful. On the other hand, I have not found a code formatter which allows spaces around assignments in parameter lists broken into one per line and that's often better than a wall of text.
- Add two spaces before # export, as this seems to be mandated by PEP-8

- Use single quotes by default

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-05-27 07:16:05 +02:00
commit 6db73873e7
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61
97 changed files with 3229 additions and 1893 deletions

View file

@ -1,29 +1,40 @@
# -*- coding: utf-8 -*-
#
# This source code file is a merge of various build tools and a horrible mess.
#
from __future__ import annotations
from typing import TYPE_CHECKING, Iterable
import argparse
import os
import pwd
import re
import sys
if TYPE_CHECKING:
from typing import TypeAlias
import os, sys, pwd, re
import os, sys, argparse, pwd, re
from functools import lru_cache, cached_property
from enum import Enum, auto
from functools import lru_cache
from typing import TYPE_CHECKING
from .lib.App import App as Base
from .lib.log import *
from .lib.Distro import Distro
from .lib.base import InputMode
from .lib.log import DEBUG, ERR, log
if TYPE_CHECKING:
import os
import pwd
import re
import sys
from typing import TypeAlias
from .lib.ExecContext import ExecContext
from .lib.PackageFilter import PackageFilter
# Meaning of pkg.requires.xxx variables
# build: needs to be built and installed before this can be built
# devel: needs to be installed before this-devel can be installed, i.e. before _other_ packages can be built against this
# run: needs to be installed before this-run can be installed, i.e. before this and other packages can run with this
# devel: needs to be installed before this-devel can be installed,
# i.e. before _other_ packages can be built against this
# run: needs to be installed before this-run can be installed,
# i.e. before this and other packages can run with this
# --------------------------------------------------------------------- Helpers
@ -35,32 +46,34 @@ class ResultCache(object):
def run(self, func, args):
d = self.__cache
depth = 0
keys = [ func.__name__ ] + args
l = len(keys)
keys = [func.__name__] + args
sz = len(keys)
for k in keys:
if k is None:
k = 'None'
else:
k = str(k)
depth += 1
#log(DEBUG, 'depth = ', depth, 'key = ', k, 'd = ', str(d))
# log(DEBUG, 'depth = ', depth, 'key = ', k, 'd = ', str(d))
if k in d:
if l == depth:
if sz == depth:
return d[k]
d = d[k]
continue
if l == depth:
if sz == depth:
r = func(*args)
d[k] = r
return r
d = d[k] = {}
#d = d[k]
raise Exception('cache algorithm failed for function', func.__name__, 'in depth', depth)
# d = d[k]
raise Exception(
'cache algorithm failed for function', func.__name__, 'in depth', depth
)
class Scope(Enum):
Self = auto()
One = auto()
Subtree = auto()
Self = auto()
One = auto()
Subtree = auto()
Graph: TypeAlias = dict[str, set[str]]
@ -68,27 +81,41 @@ Graph: TypeAlias = dict[str, set[str]]
class App(Base):
def __format_topdir(self, topdir: None|str, fmt: str) -> str:
if topdir is None:
def __format_topdir(self, path: None | str, fmt: str) -> str | None:
if path is None:
return None
match fmt:
case 'unaltered':
return topdir
return path
case None | 'absolute':
return os.path.abspath(self.__topdir)
return os.path.abspath(path)
case _:
m = re.search(r'^make:(\S+)$', fmt)
if m is None:
raise Exception(f'Can\'t interpret "{fmt}" as valid topdir ' +
'reference, expecting "unaltered", "absolute", or "make:<variable-name>"')
raise Exception(
f'Can\'t interpret "{fmt}" as valid topdir reference, '
'expecting "unaltered", "absolute", or "make:<variable-name>"'
)
return '$(' + m.group(1) + ')'
def __proj_dir(self, name: str, pretty) -> str:
@property
def __topdir(self) -> str:
if self.___topdir is None:
raise Exception('Tried to access undefined top directory')
return self.___topdir
@property
def __pretty_topdir(self) -> str:
if self.___pretty_topdir is None:
raise Exception('Tried to access undefined pretty top directory')
return self.___pretty_topdir
def __proj_dir(self, name: str, pretty: bool) -> str | None:
if name == self.__top_name:
if pretty:
return self.__pretty_topdir
return self.__topdir
for d in [ self.__projs_root, '/opt' ]:
for d in [self.__projs_root, '/opt']:
ret = d + '/' + name
if os.path.exists(ret):
return ret
@ -97,7 +124,14 @@ class App(Base):
return None
raise Exception('No project path found for module "{}"'.format(name))
def __find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True) -> str|None:
def __find_dir(
self,
name: str,
search_subdirs: list[str] = [],
search_absdirs: list[str] = [],
pretty: bool = True,
) -> str | None:
def format_pd(name: str, pd: str, pretty: bool):
if not pretty:
return pd
@ -107,7 +141,10 @@ class App(Base):
return pd
if name == self.__top_name:
return self.__pretty_topdir
raise NotImplementedError(f'Tried to pretty-format directory {pd}, not implemented')
raise NotImplementedError(
f'Tried to pretty-format directory {pd}, not implemented'
)
pd = self.__proj_dir(name, False)
if pd is None:
return None
@ -126,11 +163,25 @@ class App(Base):
return ret
return None
def __get_project_refs_cached(self, buf, visited, spec, section, key, add_self, scope, names_only):
return self.__res_cache.run(self.__get_project_refs, [buf, visited, spec, section, key, add_self, scope, names_only])
def __get_project_refs_cached(
self, buf, visited, spec, section, key, add_self, scope, names_only
):
return self.__res_cache.run(
self.__get_project_refs,
[buf, visited, spec, section, key, add_self, scope, names_only],
)
def __get_project_refs(self, buf: list[str], visited: set[str], spec: str,
section: str, key: str, add_self: bool, scope: Scope, names_only: bool) -> None:
def __get_project_refs(
self,
buf: list[str],
visited: set[str],
spec: str,
section: str,
key: str,
add_self: bool,
scope: Scope,
names_only: bool,
) -> None:
name = self.strip_module_from_spec(spec)
if names_only:
spec = name
@ -142,59 +193,95 @@ class App(Base):
return
visited.add(spec)
deps = self.get_value(name, section, key)
log(DEBUG, 'name = ', name, 'section = ', section, 'key = ', key, 'deps = ', deps, 'scope = ', scope.name, 'visited = ', visited)
log(
DEBUG,
(
f'name={name}, section={section}, key={key}, deps={deps}, '
f'scope={scope.name}, visited={visited}'
),
)
if deps and scope != Scope.Self:
if scope == Scope.One:
subscope = Scope.Self
else:
subscope = Scope.Subtree
deps = deps.split(',')
for dep in deps:
for dep in deps.split(','):
dep = dep.strip()
if not(len(dep)):
if not (len(dep)):
continue
self.__get_project_refs_cached(buf, visited, dep,
section, key, add_self=True, scope=subscope,
names_only=names_only)
self.__get_project_refs_cached(
buf,
visited,
dep,
section,
key,
add_self = True,
scope = subscope,
names_only = names_only,
)
if add_self:
buf.append(spec)
def __read_dep_graph(self, projects: list[str], section: str, graph: Graph) -> None:
def __read_dep_graph(
self,
projects: list[str],
sections: str | list[str],
graph: Graph,
) -> None:
if isinstance(sections, str):
sections = [sections]
for project in projects:
if project in graph:
continue
deps = self.get_project_refs([ project ], ['pkg.requires.jw'], section,
scope = Scope.One, add_self=False, names_only=True)
if not deps is None:
for section in sections:
deps = self.get_project_refs(
[project],
['pkg.requires.jw'],
sections,
scope = Scope.One,
add_self = False,
names_only = True,
)
if deps is None:
continue
graph[project] = set(deps)
for dep in deps:
self.__read_dep_graph([ dep ], section, graph)
self.__read_dep_graph([dep], sections, graph)
def __flip_dep_graph(self, graph: Graph):
ret: Graph = {}
for project, deps in graph.items():
for d in deps:
if not d in ret:
if d not in ret:
ret[d] = set()
ret[d].add(project)
return ret
def __find_circular_deps_recursive(self, project: str, graph: Graph, unvisited: list[str],
temp: set[str], path: str) -> str|None:
def __find_circular_deps_recursive(
self,
project: str,
graph: Graph,
unvisited: list[str],
temp: set[str],
path: list[str],
) -> str | None:
if project in temp:
log(DEBUG, 'found circular dependency at project', project)
return project
if not project in unvisited:
if project not in unvisited:
return None
temp.add(project)
if project in graph:
for dep in graph[project]:
last = self.__find_circular_deps_recursive(dep, graph, unvisited, temp, path)
last = self.__find_circular_deps_recursive(
dep, graph, unvisited, temp, path
)
if last is not None:
path.insert(0, dep)
return last
unvisited.remove(project)
temp.remove(project)
return None
def __find_circular_deps(self, projects: list[str], flavours: list[str]) -> bool:
graph: Graph = {}
@ -205,25 +292,27 @@ class App(Base):
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)
last = self.__find_circular_deps_recursive(
project, self.__flip_dep_graph(graph), unvisited, temp, ret
)
if last is not None:
log(DEBUG, f'Found circular dependency below {project}, last is {last}')
return True
return False
def __init__(self, distro: Distro|None=None) -> None:
def __init__(self, distro: Distro | None = None) -> None:
super().__init__('jw-pkg swiss army knife', modules=['jw.pkg.cmds'])
super().__init__('jw-pkg swiss army knife', modules = ['jw.pkg.cmds'])
# -- Members without default values
self.__opt_interactive: bool|None = None
self.__opt_verbose: bool|None = None
self.__top_name: str|None = None
self.__opt_interactive: bool | None = None
self.__opt_verbose: bool | None = None
self.__top_name: str | None = None
self.__distro = distro
self.__res_cache = ResultCache()
self.__topdir: str|None = None
self.__pretty_topdir: str|None = None
self.__exec_context: ExecContext|None = None
self.___topdir: str | None = None
self.___pretty_topdir: str | None = None
self.__exec_context: ExecContext | None = None
# -- Members with default values
self.__topdir_fmt = 'absolute'
@ -235,48 +324,83 @@ class App(Base):
pkg_filter_str = self.args.pkg_filter
if pkg_filter_str is None:
pkg_filter_str = os.getenv('JW_DEFAULT_PKG_FILTER')
pkg_filter: PackageFilter|None = None
pkg_filter: PackageFilter | None = None
if pkg_filter_str is not None:
from .lib.PackageFilter import PackageFilterString
pkg_filter = PackageFilterString(pkg_filter_str)
self.__distro = await Distro.instantiate(
ec = self.exec_context,
id = self.args.distro_id,
default_pkg_filter = pkg_filter,
)
ec = self.exec_context,
id = self.args.distro_id,
default_pkg_filter = pkg_filter,
)
async def __aexit__(self, exc_type, exc, tb) -> None:
if self.__exec_context is not None:
await self.__exec_context.close()
self.__exec_context = None
return super().__aexit__(exc_type, exc, tb)
def _add_arguments(self, parser) -> None:
super()._add_arguments(parser)
parser.add_argument('-t', '--topdir', default = None, help='Project Path')
parser.add_argument('--topdir-format', default = 'absolute', help='Output references to topdir as '
+ 'one of "make:<var-name>", "unaltered", "absolute". Absolute topdir by default')
parser.add_argument('-p', '--prefix', default = None,
help='Parent directory of project source directories')
parser.add_argument('--distro-id', default=None, help='Distribution ID (default is taken from /etc/os-release)')
parser.add_argument('--interactive', choices=['true', 'false', 'auto'], default='true', help='Wait for user input or try to proceed unattended')
parser.add_argument('--verbose', action='store_true', default=False, help='Be verbose on stderr about what\'s being done on the distro level')
parser.add_argument('--target', default='local', help='Run commands on this host')
parser.add_argument('--pkg-filter', help='Default filter for all distribution package-related operations')
parser.add_argument('-t', '--topdir', default = None, help = 'Project Path')
parser.add_argument(
'--topdir-format',
default = 'absolute',
help = (
'Output references to topdir as one of "make:<var-name>", '
'"unaltered", "absolute". Absolute topdir by default'
),
)
parser.add_argument(
'-p',
'--prefix',
default = None,
help = 'Parent directory of project source directories',
)
parser.add_argument(
'--distro-id',
default = None,
help = 'Distribution ID (default is taken from /etc/os-release)',
)
parser.add_argument(
'--interactive',
choices = ['true', 'false', 'auto'],
default = 'true',
help = 'Wait for user input or try to proceed unattended',
)
parser.add_argument(
'--verbose',
action = 'store_true',
default = False,
help = "Be verbose on stderr about what's being done on the distro level",
)
parser.add_argument(
'--target', default = 'local', help = 'Run commands on this host'
)
parser.add_argument(
'--pkg-filter',
help = 'Default filter for all distribution package-related operations',
)
async def _run(self, args: argparse.Namespace) -> None:
self.__topdir = args.topdir
self.__pretty_topdir = self.__format_topdir(self.__topdir, args.topdir_format)
self.___topdir = args.topdir
self.___pretty_topdir = self.__format_topdir(self.___topdir, args.topdir_format)
self.__topdir_fmt = args.topdir_format
if self.__topdir is not None:
self.__top_name = self.read_value(self.__topdir + '/make/project.conf', 'build', 'name')
if self.___topdir is not None:
self.__top_name = self.read_value(
self.___topdir + '/make/project.conf', 'build', 'name'
)
if not self.__top_name:
self.__top_name = re.sub('-[0-9.-]*$', '', os.path.basename(os.path.realpath(self.__topdir)))
self.__top_name = re.sub(
'-[0-9.-]*$',
'',
os.path.basename(os.path.realpath(self.___topdir))
)
if args.prefix is not None:
self.__projs_root = args.prefix
self.__pretty_projs_root = args.prefix
await self.__init_async()
return await super()._run(args)
await super()._run(args)
@property
def interactive(self) -> bool:
@ -288,20 +412,32 @@ class App(Base):
self.__opt_interactive = False
case 'auto':
self.__opt_interactive = sys.stdin.isatty()
case _:
raise ValueError(
f'Unknown --interactive value: {self.args.interactive}'
)
# Not logically possible to fail, but this keeps pyright happy
assert self.__opt_interactive is not None
return self.__opt_interactive
@property
def verbose(self) -> bool:
if self.__opt_verbose is None:
self.__opt_verbose = self.args.verbose
# Not logically possible to fail, but this keeps pyright happy
assert self.__opt_verbose is not None
return self.__opt_verbose
@property
def exec_context(self) -> str:
def exec_context(self) -> ExecContext:
if self.__exec_context is None:
from .lib.ExecContext import ExecContext
self.__exec_context = ExecContext.create(self.args.target, interactive=self.interactive,
verbose_default=self.verbose)
self.__exec_context = ExecContext.create(
self.args.target,
interactive = self.interactive,
verbose_default = self.verbose,
)
return self.__exec_context
@property
@ -318,29 +454,51 @@ class App(Base):
raise Exception('No distro object')
return self.__distro
def find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True):
return self.__find_dir(name, search_subdirs, search_absdirs, pretty)
def find_dir(
self,
name: str,
search_subdirs: list[str] = [],
search_absdirs: list[str] = [],
pretty: bool = True,
) -> str:
ret = self.__find_dir(name, search_subdirs, search_absdirs, pretty)
if ret is None:
msg = f'Failed to find directory for "{name}"'
log(ERR, msg)
for search_name, search in [
('subdirs', search_subdirs),
('absdirs', search_absdirs),
]:
if search:
log(ERR, f' Searched {search_name} in:')
for d in search:
log(ERR, f' - {d}')
raise FileNotFoundError(msg)
return ret
# TODO: add support for customizing this in project.conf
def htdocs_dir(self, project: str) -> str:
return self.find_dir(project, ['/src/html/htdocs', '/tools/html/htdocs', '/htdocs'],
['/srv/www/proj/' + project])
return self.find_dir(
project,
['/src/html/htdocs', '/tools/html/htdocs', '/htdocs'],
['/srv/www/proj/' + project],
)
# TODO: add support for customizing this in project.conf
def tmpl_dir(self, name: str) -> str:
return self.find_dir(name, ['/tmpl'], ['/opt/' + name + '/share/tmpl'])
return self.find_dir(name, ['/tmpl'], ['/opt/' + name + '/share/tmpl'])
def strip_module_from_spec(self, mod):
return re.sub(r'-dev$|-devel$|-run$', '', re.split('([=><]+)', mod)[0].strip())
@lru_cache(maxsize=None)
@lru_cache(maxsize = None)
def get_section(self, path: str, section: str) -> str:
ret = ''
pat = '[' + section + ']'
in_section = False
file = open(path)
for line in file:
if (line.rstrip() == pat):
if line.rstrip() == pat:
in_section = True
continue
if in_section:
@ -350,10 +508,10 @@ class App(Base):
file.close()
return ret.rstrip()
@lru_cache(maxsize=None)
def read_value(self, path: str, section: str, key: str) -> str|None:
@lru_cache(maxsize = None)
def read_value(self, path: str, section: str, key: str) -> str | None:
def scan_section(f, key: str) -> str|None:
def scan_section(f, key: str) -> str | None:
if key is None:
ret = ''
for line in f:
@ -374,43 +532,44 @@ class App(Base):
cont_line = ''
rx = re.compile(r'^\s*' + key + r'\s*=\s*(.*)\s*$')
for line in lines:
#log(DEBUG, ' looking for "%s" in line="%s"' % (key, line))
# log(DEBUG, ' looking for "%s" in line="%s"' % (key, line))
m = re.search(rx, line)
if m is not None:
return m.group(1)
return None
def scan_section_debug(f, key: str) -> str|None:
def scan_section_debug(f, key: str) -> str | None:
ret = scan_section(f, key)
#log(DEBUG, ' returning', rr)
# log(DEBUG, ' returning', rr)
return ret
try:
#log(DEBUG, 'looking for {}::[{}].{}'.format(path, section, key))
# log(DEBUG, 'looking for {}::[{}].{}'.format(path, section, key))
# TODO: Parse
with open(path, 'r') as f:
if not len(section):
rr = scan_section(f, key)
return scan_section(f, key)
pat = '[' + section + ']'
for line in f:
if line.rstrip() == pat:
return scan_section(f, key)
return None
except:
log(DEBUG, path, 'not found')
except Exception:
log(DEBUG, f'Not found: {path}')
# TODO: handle this special case cleaner somewhere up the stack
if section == 'build' and key == 'libname':
return 'none'
return None
@lru_cache(maxsize=None)
def get_value(self, project: str, section: str, key: str) -> str:
if self.__top_name and project == self.__top_name:
proj_root = self.__topdir
else:
proj_root = self.__projs_root + '/' + project
@lru_cache(maxsize = None)
def get_value(self, project: str, section: str, key: str) -> str | None:
ret: str | None
proj_dir = self.__proj_dir(project, pretty = False)
if proj_dir is None:
raise Exception(f"Can't get project directory for {project}")
if section == 'version':
proj_version_dirs = [ proj_root ]
if proj_root != self.__topdir:
proj_version_dirs = [proj_dir]
if proj_dir != self.___topdir:
proj_version_dirs.append('/usr/share/doc/packages/' + project)
for d in proj_version_dirs:
version_path = d + '/VERSION'
@ -423,13 +582,24 @@ class App(Base):
log(DEBUG, f'Ignoring unreadable file "{version_path}"')
continue
raise Exception(f'No version file found for project "{project}"')
path = proj_root + '/make/project.conf'
path = proj_dir + '/make/project.conf'
ret = self.read_value(path, section, key)
log(DEBUG, 'Lookup %s -> %s / [%s%s] -> "%s"' %
(self.__top_name, project, section, '.' + key if key else '', ret))
log(
DEBUG,
'Lookup %s -> %s / [%s%s] -> "%s"' %
(self.__top_name, project, section, '.' + key if key else '', ret),
)
return ret
def get_values(self, projects: list[str], sections: list[str], keys: list[str]) -> list[str]:
@lru_cache(maxsize = None)
def get_version(self, project) -> str:
ret = self.get_value(project, 'version', '')
if ret is None:
raise Exception(f"Can't get version of project {project}")
return ret
def get_values(self, projects: list[str], sections: list[str],
keys: list[str]) -> list[str]:
"""
Collect a list of values from a list of given projects, sections and
keys, maintaining order
@ -441,39 +611,60 @@ class App(Base):
vals = self.get_value(p, section, key)
if vals:
ret += [val.strip() for val in vals.split(',')]
return list(dict.fromkeys(ret)) # Remove duplicates, keep ordering
return list(dict.fromkeys(ret)) # Remove duplicates, keep ordering
def get_project_refs(self, projects: list[str], sections: list[str],
keys: str|list[str], add_self: bool, scope: Scope, names_only=True) -> list[str]:
def get_project_refs(
self,
projects: list[str],
sections: list[str],
keys: str | list[str],
add_self: bool,
scope: Scope,
names_only = True,
) -> list[str]:
if isinstance(keys, str):
keys = [ keys ]
keys = [keys]
ret: list[str] = []
for section in sections:
for key in keys:
visited = set()
visited: set[str] = set()
for name in projects:
rr: list[str] = []
self.__get_project_refs_cached(rr, visited, name, section, key, add_self, scope, names_only)
self.__get_project_refs_cached(
rr, visited, name, section, key, add_self, scope, names_only
)
# TODO: this looks like a performance hogger
for m in rr:
if not m in ret:
ret.append(m)
if m not in ret:
ret.append(m)
return ret
def get_libname(self, projects) -> str:
vals = self.get_project_refs(projects, ['build'], 'libname',
scope = Scope.One, add_self=False, names_only=True)
vals = self.get_project_refs(
projects,
['build'],
'libname',
scope = Scope.One,
add_self = False,
names_only = True,
)
if not vals:
return ' '.join(projects)
if 'none' in vals:
vals.remove('none')
return ' '.join(reversed(vals))
def is_excluded_from_build(self, project: str) -> str|None:
def is_excluded_from_build(self, project: str) -> str | None:
log(DEBUG, 'checking if project ' + project + ' is excluded from build')
exclude = self.get_project_refs([ project ], ['build'], 'exclude',
scope = Scope.One, add_self=False, names_only=True)
cascade = self.distro.os_cascade + [ 'all' ]
exclude = self.get_project_refs(
[project],
['build'],
'exclude',
scope = Scope.One,
add_self = False,
names_only = True,
)
cascade = self.distro.os_cascade + ['all']
for p1 in exclude:
for p2 in cascade:
if p1 == p2: