diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2025-09-02 12:54:25 +0400 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2025-09-03 16:18:12 +0400 |
commit | bbcb92960abf67d01da056c95bfc88067fd61e26 (patch) | |
tree | d4f535a11ff4f9fcbf8562598dc6ea0f80f9b701 | |
parent | fce4fb9b8840b84f0f3d966e54fbef73ec602aea (diff) | |
download | libvirt-ci-bbcb92960abf67d01da056c95bfc88067fd61e26.zip libvirt-ci-bbcb92960abf67d01da056c95bfc88067fd61e26.tar.gz libvirt-ci-bbcb92960abf67d01da056c95bfc88067fd61e26.tar.bz2 |
formatters: add type annotations
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
-rw-r--r-- | lcitool/formatters.py | 234 | ||||
-rw-r--r-- | pyproject.toml | 1 |
2 files changed, 164 insertions, 71 deletions
diff --git a/lcitool/formatters.py b/lcitool/formatters.py index 33c12c6..35a27c7 100644 --- a/lcitool/formatters.py +++ b/lcitool/formatters.py @@ -11,6 +11,9 @@ import shlex from lcitool import util, LcitoolError from lcitool.packages import package_names_by_type +from lcitool.projects import Projects +from lcitool.targets import BuildTarget +from typing import Any, Dict, List, Optional, Union log = logging.getLogger(__name__) @@ -28,17 +31,17 @@ class FormatterError(LcitoolError): class DockerfileError(FormatterError): - def __init__(self, message): + def __init__(self, message: str): super().__init__(message, "Docker formatter") class VariablesError(FormatterError): - def __init__(self, message): + def __init__(self, message: str): super().__init__(message, "Variables formatter") class ShellBuildEnvError(FormatterError): - def __init__(self, message): + def __init__(self, message: str): super().__init__(message, "Shell build env formatter") @@ -47,11 +50,11 @@ class Formatter(metaclass=abc.ABCMeta): This an abstract base class that each formatter must subclass. """ - def __init__(self, projects): + def __init__(self, projects: Projects) -> None: self._projects = projects @abc.abstractmethod - def format(self): + def format(self, target: BuildTarget, selected_projects: List[str]) -> str: """ Outputs a recipe using format implemented by a Foo(Formatter) subclass @@ -63,12 +66,14 @@ class Formatter(metaclass=abc.ABCMeta): """ pass - def _get_meson_cross(self, cross_abi): + def _get_meson_cross(self, cross_abi: str) -> str: cross_path = util.package_resource(__package__, f"cross/{cross_abi}.meson") with open(cross_path, "r") as c: return c.read().rstrip() - def _generator_build_varmap(self, target, selected_projects): + def _generator_build_varmap( + self, target: BuildTarget, selected_projects: List[str] + ) -> Dict[str, Union[str, List[str]]]: projects = self._projects # we need the 'base' internal project here, but packages for internal @@ -107,7 +112,9 @@ class Formatter(metaclass=abc.ABCMeta): cross_arch_deb = util.native_arch_to_deb_arch(target.cross_arch) varmap["cross_arch_deb"] = cross_arch_deb - if "rust" in varmap["mappings"]: + mappings = varmap["mappings"] + assert isinstance(mappings, list) + if "rust" in mappings: varmap["cross_rust_target"] = util.native_arch_to_rust_target( varmap["cross_arch"] ) @@ -118,13 +125,19 @@ class Formatter(metaclass=abc.ABCMeta): class BuildEnvFormatter(Formatter): - def __init__(self, inventory, indent=0, pkgcleanup=False, nosync=False): + def __init__( + self, + inventory: Projects, + indent: int = 0, + pkgcleanup: bool = False, + nosync: bool = False, + ) -> None: super().__init__(inventory) self._indent = indent self._pkgcleanup = pkgcleanup self._nosync = nosync - def _align(self, command, strings): + def _align(self, command: str, strings: List[str]) -> str: if len(strings) == 1: return strings[0] @@ -132,7 +145,9 @@ class BuildEnvFormatter(Formatter): strings = [shlex.quote(x) for x in strings] return align[1:] + align.join(strings) - def _generator_build_varmap(self, target, selected_projects): + def _generator_build_varmap( + self, target: BuildTarget, selected_projects: List[str] + ) -> Dict[str, Union[str, List[str]]]: varmap = super()._generator_build_varmap(target, selected_projects) varmap["nosync"] = "" @@ -153,36 +168,53 @@ class BuildEnvFormatter(Formatter): pass nosync = varmap["nosync"] + assert isinstance(nosync, str) + + pkgs = varmap["pkgs"] + assert isinstance(pkgs, list) varmap["pkgs"] = self._align( - nosync + target.facts["packaging"]["command"], varmap["pkgs"] + nosync + target.facts["packaging"]["command"], pkgs ) - if varmap["cross_pkgs"]: + cross_pkgs = varmap["cross_pkgs"] + if cross_pkgs: + assert isinstance(cross_pkgs, list) varmap["cross_pkgs"] = self._align( - nosync + target.facts["packaging"]["command"], varmap["cross_pkgs"] + nosync + target.facts["packaging"]["command"], cross_pkgs ) - if varmap["pypi_pkgs"]: + + pypi_pkgs = varmap["pypi_pkgs"] + if pypi_pkgs: + assert isinstance(pypi_pkgs, list) varmap["pypi_pkgs"] = self._align( - nosync + target.facts["paths"]["pip3"], varmap["pypi_pkgs"] + nosync + target.facts["paths"]["pip3"], pypi_pkgs ) - if varmap["cpan_pkgs"]: - varmap["cpan_pkgs"] = self._align(nosync + "cpanm", varmap["cpan_pkgs"]) + + cpan_pkgs = varmap["cpan_pkgs"] + if cpan_pkgs: + assert isinstance(cpan_pkgs, list) + varmap["cpan_pkgs"] = self._align(nosync + "cpanm", cpan_pkgs) return varmap - def _format_commands_ccache(self, target, varmap): + def _format_commands_ccache( + self, target: BuildTarget, varmap: Dict[str, Union[str, List[str]]] + ) -> List[Union[Any, str]]: commands = [] compilers = set() - if "ccache" not in varmap["mappings"]: + mappings = varmap["mappings"] + assert isinstance(mappings, list) + + if "ccache" not in mappings: return [] for compiler in ["gcc", "clang"]: - if compiler in varmap["mappings"]: + if compiler in mappings: compilers.add(compiler) compilers.add("cc") for compiler in ["g++"]: - if compiler in varmap["mappings"]: + if compiler in mappings: compilers.add(compiler) compilers.add("c++") @@ -203,7 +235,7 @@ class BuildEnvFormatter(Formatter): ) return commands - def _format_commands_pkglist(self, target): + def _format_commands_pkglist(self, target: BuildTarget) -> List[str]: facts = target.facts commands = [] if facts["packaging"]["format"] == "apk": @@ -218,12 +250,17 @@ class BuildEnvFormatter(Formatter): commands.extend(["rpm -qa | sort > /packages.txt"]) return commands - def _format_commands_native(self, target, varmap): + def _format_commands_native( + self, target: BuildTarget, varmap: Dict[str, Union[str, List[str]]] + ) -> List[List[str]]: facts = target.facts commands = [] osname = facts["os"]["name"] osversion = facts["os"]["version"] + mappings = varmap["mappings"] + assert isinstance(mappings, list) + if facts["packaging"]["format"] == "apk": # See earlier comment about adding this later # "{packaging_command} add libeatmydata", @@ -362,7 +399,7 @@ class BuildEnvFormatter(Formatter): # If distro forces "pip" to use a venv by default, # then undo that, because our CI env is expected to # have a mix of distro & pip installed pieces - if "python3" in varmap["mappings"]: + if "python3" in mappings: commands.extend(["rm -f /usr/lib*/python3*/EXTERNALLY-MANAGED"]) if not target.cross_arch: @@ -380,22 +417,35 @@ class BuildEnvFormatter(Formatter): return groups - def _format_env_native(self, varmap): + def _format_env_native( + self, varmap: Dict[str, Union[str, List[str]]] + ) -> Dict[str, str]: env = {} + mappings = varmap["mappings"] + assert isinstance(mappings, list) + env["LANG"] = "en_US.UTF-8" - if "make" in varmap["mappings"]: - env["MAKE"] = varmap["paths_make"] - if "meson" in varmap["mappings"]: - env["NINJA"] = varmap["paths_ninja"] - if "python3" in varmap["mappings"]: - env["PYTHON"] = varmap["paths_python"] - if "ccache" in varmap["mappings"]: + if "make" in mappings: + make_path = varmap["paths_make"] + assert isinstance(make_path, str) + env["MAKE"] = make_path + if "meson" in mappings: + ninja_path = varmap["paths_ninja"] + assert isinstance(ninja_path, str) + env["NINJA"] = ninja_path + if "python3" in mappings: + python_path = varmap["paths_python"] + assert isinstance(python_path, str) + env["PYTHON"] = python_path + if "ccache" in mappings: env["CCACHE_WRAPPERSDIR"] = "/usr/libexec/ccache-wrappers" return env - def _format_commands_foreign(self, target, varmap): + def _format_commands_foreign( + self, target: BuildTarget, varmap: Dict[str, Union[str, List[str]]] + ) -> List[str]: facts = target.facts cross_commands = [] @@ -448,7 +498,7 @@ class BuildEnvFormatter(Formatter): ] ) - if not target.cross_arch.startswith("mingw"): + if target.cross_arch is not None and not target.cross_arch.startswith("mingw"): cross_commands.extend( [ "mkdir -p /usr/local/share/meson/cross", @@ -456,7 +506,9 @@ class BuildEnvFormatter(Formatter): ] ) - cross_meson = self._get_meson_cross(varmap["cross_abi"]) + cross_abi = varmap["cross_abi"] + assert isinstance(cross_abi, str) + cross_meson = self._get_meson_cross(cross_abi) varmap["cross_meson"] = cross_meson.replace("\n", "\\n\\\n") cross_commands.extend(self._format_commands_pkglist(target)) @@ -466,45 +518,57 @@ class BuildEnvFormatter(Formatter): return cross_commands - def _format_env_foreign(self, target, varmap): + def _format_env_foreign( + self, target: BuildTarget, varmap: Dict[str, Union[str, List[str]]] + ) -> Dict[str, str]: env = {} - env["ABI"] = varmap["cross_abi"] - if "autoconf" in varmap["mappings"]: - env["CONFIGURE_OPTS"] = "--host=" + varmap["cross_abi"] + mappings = varmap["mappings"] + assert isinstance(mappings, list) + cross_abi = varmap["cross_abi"] + assert isinstance(cross_abi, str) - if "meson" in varmap["mappings"]: - if target.cross_arch.startswith("mingw"): + env["ABI"] = cross_abi + + if "autoconf" in mappings: + env["CONFIGURE_OPTS"] = "--host=" + cross_abi + + if "meson" in mappings: + if target.cross_arch is not None and target.cross_arch.startswith("mingw"): + cross_arch = varmap["cross_arch"] + assert isinstance(cross_arch, str) env["MESON_OPTS"] = ( - "--cross-file=/usr/share/mingw/toolchain-" - + varmap["cross_arch"] - + ".meson" + "--cross-file=/usr/share/mingw/toolchain-" + cross_arch + ".meson" ) else: - env["MESON_OPTS"] = "--cross-file=" + varmap["cross_abi"] + env["MESON_OPTS"] = "--cross-file=" + cross_abi - if varmap["cross_rust_target"] is not None: - env["RUST_TARGET"] = varmap["cross_rust_target"] + cross_rust_target = varmap["cross_rust_target"] + if cross_rust_target is not None: + assert isinstance(cross_rust_target, str) + env["RUST_TARGET"] = cross_rust_target return env class DockerfileFormatter(BuildEnvFormatter): - def __init__(self, inventory, base=None, layers="all"): + def __init__( + self, inventory: Projects, base: Optional[str] = None, layers: str = "all" + ): super().__init__(inventory, indent=len("RUN "), pkgcleanup=True, nosync=True) self._base = base self._layers = layers @staticmethod - def _format_env(env): + def _format_env(env: Dict[str, str]) -> str: lines = [] for key in sorted(env.keys()): val = env[key] lines.append(f'\nENV {key} "{val}"') return "".join(lines) - def _format_section_base(self, target): + def _format_section_base(self, target: BuildTarget) -> List[str]: strings = [] if self._base: base = self._base @@ -513,7 +577,9 @@ class DockerfileFormatter(BuildEnvFormatter): strings.append(f"FROM {base}") return strings - def _format_section_native(self, target, varmap): + def _format_section_native( + self, target: BuildTarget, varmap: Dict[str, Union[str, List[str]]] + ) -> List[str]: groups = self._format_commands_native(target, varmap) strings = [] @@ -524,7 +590,9 @@ class DockerfileFormatter(BuildEnvFormatter): strings.append(self._format_env(env)) return strings - def _format_section_foreign(self, target, varmap): + def _format_section_foreign( + self, target: BuildTarget, varmap: Dict[str, Union[str, List[str]]] + ) -> List[str]: commands = self._format_commands_foreign(target, varmap) strings = ["\nRUN " + " && \\\n ".join(commands)] @@ -533,7 +601,12 @@ class DockerfileFormatter(BuildEnvFormatter): strings.append(self._format_env(env)) return strings - def _format_dockerfile(self, target, project, varmap): + def _format_dockerfile( + self, + target: BuildTarget, + project: List[str], + varmap: Dict[str, Union[str, List[str]]], + ) -> List[str]: strings = [] strings.extend(self._format_section_base(target)) if self._layers in ["all", "native"]: @@ -542,7 +615,7 @@ class DockerfileFormatter(BuildEnvFormatter): strings.extend(self._format_section_foreign(target, varmap)) return strings - def format(self, target, selected_projects): + def format(self, target: BuildTarget, selected_projects: List[str]) -> str: """ Generates and formats a Dockerfile. @@ -574,7 +647,9 @@ class DockerfileFormatter(BuildEnvFormatter): class VariablesFormatter(Formatter): @staticmethod - def _normalize_variables(varmap): + def _normalize_variables( + varmap: Dict[str, Union[str, List[str]]], + ) -> Dict[str, Union[str, List[str]]]: normalized_vars = {} for key in varmap: if varmap[key] is None: @@ -588,16 +663,18 @@ class VariablesFormatter(Formatter): name = key[len("paths_") :] else: name = key - normalized_vars[name] = varmap[key] + value = varmap[key] + assert value is not None # We already checked for None above + normalized_vars[name] = value return normalized_vars @staticmethod @abc.abstractmethod - def _format_variables(varmap): + def _format_variables(varmap: Dict[str, Union[str, List[str]]]) -> str: pass - def format(self, target, selected_projects): + def format(self, target: BuildTarget, selected_projects: List[str]) -> str: """ Generates and formats environment variables as KEY=VAL pairs. @@ -615,17 +692,17 @@ class VariablesFormatter(Formatter): ) try: - varmap = self._generator_build_varmap(target, selected_projects) + raw_varmap = self._generator_build_varmap(target, selected_projects) except FormatterError as ex: raise VariablesError(str(ex)) - varmap = self._normalize_variables(varmap) + varmap = self._normalize_variables(raw_varmap) return self._format_variables(varmap) class ShellVariablesFormatter(VariablesFormatter): @staticmethod - def _format_variables(varmap): + def _format_variables(varmap: Dict[str, Union[str, List[str]]]) -> str: strings = [] for key in sorted(varmap.keys()): @@ -640,47 +717,62 @@ class ShellVariablesFormatter(VariablesFormatter): class JSONVariablesFormatter(VariablesFormatter): @staticmethod - def _format_variables(varmap): + def _format_variables(varmap: Dict[str, Union[str, List[str]]]) -> str: return json.dumps(varmap, indent=" ", sort_keys=True) class YamlVariablesFormatter(VariablesFormatter): @staticmethod - def _format_variables(varmap): - packages = varmap.get("pkgs", []) + varmap.get("cross_pkgs", []) + def _format_variables(varmap: Dict[str, Union[str, List[str]]]) -> str: + pkgs = varmap.get("pkgs", []) + cross_pkgs = varmap.get("cross_pkgs", []) + + # Ensure both are lists + if isinstance(pkgs, str): + pkgs = [pkgs] + if isinstance(cross_pkgs, str): + cross_pkgs = [cross_pkgs] + + packages = pkgs + cross_pkgs yaml_output = "packages:\n" for pkg in packages: yaml_output += f" - {pkg}\n" return yaml_output - def format(self, target, selected_projects): + def format(self, target: BuildTarget, selected_projects: List[str]) -> str: log.debug( f"Generating YAML package list for projects " f"'{selected_projects} on target {target}" ) try: - varmap = self._generator_build_varmap(target, selected_projects) + raw_varmap = self._generator_build_varmap(target, selected_projects) except FormatterError as ex: raise VariablesError(str(ex)) + varmap = self._normalize_variables(raw_varmap) return self._format_variables(varmap) class ShellBuildEnvFormatter(BuildEnvFormatter): - def __init__(self, inventory, base=None, layers="all"): + def __init__(self, inventory: Projects, base: None = None, layers: str = "all"): super().__init__(inventory, indent=len(" "), pkgcleanup=False, nosync=False) @staticmethod - def _format_env(env): + def _format_env(env: Dict[str, str]) -> str: exp = [] for key in sorted(env.keys()): val = env[key] exp.append(f'export {key}="{val}"') return "\n" + "\n".join(exp) - def _format_buildenv(self, target, project, varmap): + def _format_buildenv( + self, + target: BuildTarget, + project: List[str], + varmap: Dict[str, Union[str, List[str]]], + ) -> List[str]: strings = [ "function install_buildenv() {", ] @@ -697,7 +789,7 @@ class ShellBuildEnvFormatter(BuildEnvFormatter): strings.append(self._format_env(self._format_env_foreign(target, varmap))) return strings - def format(self, target, selected_projects): + def format(self, target: BuildTarget, selected_projects: List[str]) -> str: """ Generates and formats a Shell script for preparing a build env. diff --git a/pyproject.toml b/pyproject.toml index 8be152e..1e315d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ files = [ "lcitool/config.py", "lcitool/containers/*.py", "lcitool/install/*.py", + "lcitool/formatters.py", "lcitool/gitlab.py", "lcitool/inventory.py", "lcitool/libvirt_wrapper.py", |