From 1a1f750ccdc1d52a7b1bd648b2188fa6e7c1a4b8 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Wed, 28 Sep 2022 11:47:29 +0200 Subject: [builder] make builder functional again --- src/hydrilla/builder/build.py | 91 +++++++++++++++++++++++------------- src/hydrilla/builder/local_apt.py | 45 ++++++++++++------ src/hydrilla/builder/piggybacking.py | 17 ++++--- src/hydrilla/json_instances.py | 4 +- 4 files changed, 102 insertions(+), 55 deletions(-) diff --git a/src/hydrilla/builder/build.py b/src/hydrilla/builder/build.py index 6d784fd..58225d4 100644 --- a/src/hydrilla/builder/build.py +++ b/src/hydrilla/builder/build.py @@ -32,12 +32,13 @@ import json import re import zipfile import subprocess +import typing as t + from pathlib import Path, PurePosixPath from hashlib import sha256 from sys import stderr from contextlib import contextmanager from tempfile import TemporaryDirectory, TemporaryFile -from typing import Optional, Iterable, Iterator, Union import jsonschema # type: ignore import click @@ -111,9 +112,10 @@ class FileRef: } @contextmanager -def piggybacked_system(piggyback_def: Optional[dict], - piggyback_files: Optional[Path]) \ - -> Iterator[Piggybacked]: +def piggybacked_system( + piggyback_def: t.Optional[dict], + piggyback_files: t.Optional[Path] +)-> t.Iterator[Piggybacked]: """ Resolve resources from a foreign software packaging system. Optionally, use package files (.deb's, etc.) from a specified directory instead of resolving @@ -133,8 +135,12 @@ class Build: """ Build a Hydrilla package. """ - def __init__(self, srcdir: Path, index_json_path: Path, - piggyback_files: Optional[Path]=None): + def __init__( + self, + srcdir: Path, + index_json_path: Path, + piggyback_files: t.Optional[Path] = None + ) -> None: """ Initialize a build. All files to be included in a distribution package are loaded into memory, all data gets validated and all necessary @@ -157,17 +163,21 @@ class Build: index_obj = json_instances.read_instance(index_json_path) schema_fmt = 'package_source-{}.schema.json' - major = json_instances.validate_instance(index_obj, schema_fmt) + json_instances.validate_instance(index_obj, schema_fmt) index_desired_path = PurePosixPath('index.json') self.files_by_path[index_desired_path] = \ FileRef(index_desired_path, index_json_path.read_bytes()) - self._process_index_json(index_obj, major) + # We know from successful validation that instance is a dict. + self._process_index_json(t.cast('dict[str, t.Any]', index_obj)) - def _process_file(self, filename: Union[str, PurePosixPath], - piggybacked: Piggybacked, - include_in_distribution: bool=True): + def _process_file( + self, + filename: t.Union[str, PurePosixPath], + piggybacked: Piggybacked, + include_in_distribution: bool = True + ) -> dict[str, str]: """ Resolve 'filename' relative to srcdir, load it to memory (if not loaded before), compute its hash and store its information in @@ -184,8 +194,8 @@ class Build: system this way, it gets automatically excluded from inclusion in Hydrilla source package's zipfile. - Return file's reference object that can be included in JSON defintions - of various kinds. + Return value is file's reference object that can be included in JSON + defintions of various kinds. """ include_in_source_archive = True @@ -223,8 +233,11 @@ class Build: return file_ref.make_ref_dict() - def _prepare_source_package_zip(self, source_name: str, - piggybacked: Piggybacked) -> str: + def _prepare_source_package_zip( + self, + source_name: str, + piggybacked: Piggybacked + ) -> str: """ Create and store in memory a .zip archive containing files needed to build this source package. @@ -252,8 +265,12 @@ class Build: return sha256(self.source_zip_contents).digest().hex() - def _process_item(self, as_what: str, item_def: dict, - piggybacked: Piggybacked): + def _process_item( + self, + as_what: str, + item_def: dict, + piggybacked: Piggybacked + ) -> dict[str, t.Any]: """ Process 'item_def' as definition of a resource or mapping (determined by 'as_what' param) and store in memory its processed form and files used @@ -262,7 +279,7 @@ class Build: Return a minimal item reference suitable for using in source description. """ - resulting_schema_version = [1] + resulting_schema_version = versions.normalize([1]) copy_props = ['identifier', 'long_name', 'description', *filter(lambda p: p in item_def, ('comment', 'uuid'))] @@ -292,18 +309,22 @@ class Build: new_item_obj['payloads'] = payloads - new_item_obj['version'] = \ - versions.normalize_version(item_def['version']) + version = [*item_def['version']] if as_what == 'mapping' and item_def['type'] == "mapping_and_resource": - new_item_obj['version'].append(item_def['revision']) + version.append(item_def['revision']) + + new_item_obj['version'] = versions.normalize(version) - if self.source_schema_ver >= [2]: + if self.source_schema_ver >= (2,): # handle 'required_mappings' field required = [{'identifier': map_ref['identifier']} for map_ref in item_def.get('required_mappings', [])] if required: - resulting_schema_version = max(resulting_schema_version, [2]) + resulting_schema_version = max( + resulting_schema_version, + versions.normalize([2]) + ) new_item_obj['required_mappings'] = required # handle 'permissions' field @@ -317,7 +338,10 @@ class Build: if processed_permissions: new_item_obj['permissions'] = processed_permissions - resulting_schema_version = max(resulting_schema_version, [2]) + resulting_schema_version = max( + resulting_schema_version, + versions.normalize([2]) + ) # handle '{min,max}_haketilo_version' fields for minmax, default in ('min', [1]), ('max', [65536]): @@ -326,7 +350,10 @@ class Build: continue copy_props.append(f'{minmax}_haketilo_version') - resulting_schema_version = max(resulting_schema_version, [2]) + resulting_schema_version = max( + resulting_schema_version, + versions.normalize([2]) + ) new_item_obj.update((p, item_def[p]) for p in copy_props) @@ -347,16 +374,14 @@ class Build: props_in_ref = ('type', 'identifier', 'version', 'long_name') return dict([(prop, new_item_obj[prop]) for prop in props_in_ref]) - def _process_index_json(self, index_obj: dict, - major_schema_version: int) -> None: + def _process_index_json(self, index_obj: dict) -> None: """ Process 'index_obj' as contents of source package's index.json and store in memory this source package's zipfile as well as package's individual files and computed definitions of the source package and items defined in it. """ - self.source_schema_ver = \ - versions.normalize_version(get_schema_version(index_obj)) + self.source_schema_ver = json_instances.get_schema_version(index_obj) out_schema = f'{schemas_root}/api_source_description-1.schema.json' @@ -372,7 +397,7 @@ class Build: self.files_by_path[spdx_path] = spdx_ref piggyback_def = None - if self.source_schema_ver >= [2] and 'piggyback_on' in index_obj: + if self.source_schema_ver >= (2,) and 'piggyback_on' in index_obj: piggyback_def = index_obj['piggyback_on'] with piggybacked_system(piggyback_def, self.piggyback_files) \ @@ -418,7 +443,7 @@ class Build: if 'comment' in index_obj: self.source_description['comment'] = index_obj['comment'] - def write_source_package_zip(self, dstpath: Path): + def write_source_package_zip(self, dstpath: Path) -> None: """ Create a .zip archive containing files needed to build this source package and write it at 'dstpath'. @@ -426,7 +451,7 @@ class Build: with open(dstpath, 'wb') as output: output.write(self.source_zip_contents) - def write_package_files(self, dstpath: Path): + def write_package_files(self, dstpath: Path) -> None: """Write package files under 'dstpath' for distribution.""" file_dir_path = (dstpath / 'file' / 'sha256').resolve() file_dir_path.mkdir(parents=True, exist_ok=True) @@ -474,7 +499,7 @@ dir_type = click.Path(exists=True, file_okay=False, resolve_path=True) @click.version_option(version=_version.version, prog_name='Hydrilla builder', message=_('%(prog)s_%(version)s_license'), help=_('version_printing')) -def perform(srcdir, index_json, piggyback_files, dstdir): +def perform(srcdir, index_json, piggyback_files, dstdir) -> None: """ Execute Hydrilla builder to turn source package into a distributable one. diff --git a/src/hydrilla/builder/local_apt.py b/src/hydrilla/builder/local_apt.py index e8a45e8..925fd61 100644 --- a/src/hydrilla/builder/local_apt.py +++ b/src/hydrilla/builder/local_apt.py @@ -33,12 +33,13 @@ import shutil import re import subprocess CP = subprocess.CompletedProcess +import typing as t + from pathlib import Path, PurePosixPath from tempfile import TemporaryDirectory, NamedTemporaryFile from hashlib import sha256 from urllib.parse import unquote from contextlib import contextmanager -from typing import Optional, Iterable, Iterator from ..translations import smart_gettext as _ from .piggybacking import Piggybacked @@ -91,10 +92,15 @@ class AptError(SubprocessError): commands. """ -def run(command, **kwargs): +def run(command: t.Sequence[str], **kwargs) -> CP: """A wrapped around subprocess.run that sets some default options.""" - return subprocess.run(command, **kwargs, env={'LANG': 'en_US'}, - capture_output=True, text=True) + return subprocess.run( + command, + **kwargs, + env = {'LANG': 'en_US'}, + capture_output = True, + text = True + ) class Apt: """ @@ -135,8 +141,11 @@ def cache_dir() -> Path: class SourcesList: """Representation of apt's sources.list contents.""" - def __init__(self, list: list[str]=[], - codename: Optional[str]=None) -> None: + def __init__( + self, + list: list[str] = [], + codename: t.Optional[str] = None + ) -> None: """Initialize this SourcesList.""" self.codename = None self.list = [*list] @@ -273,7 +282,7 @@ def setup_local_apt(directory: Path, list: SourcesList, keys: list[str]) -> Apt: return apt @contextmanager -def local_apt(list: SourcesList, keys: list[str]) -> Iterator[Apt]: +def local_apt(list: SourcesList, keys: list[str]) -> t.Iterator[Apt]: """ Create a temporary directory with proper local APT configuration in it. Yield an Apt object that can be used to issue apt-get commands. @@ -285,9 +294,13 @@ def local_apt(list: SourcesList, keys: list[str]) -> Iterator[Apt]: td = Path(td_str) yield setup_local_apt(td, list, keys) -def download_apt_packages(list: SourcesList, keys: list[str], - packages: list[str], destination_dir: Path, - with_deps: bool) -> list[str]: +def download_apt_packages( + list: SourcesList, + keys: list[str], + packages: list[str], + destination_dir: Path, + with_deps: bool +) -> list[str]: """ Set up a local APT, update it using the specified sources.list configuration and use it to download the specified packages. @@ -362,8 +375,10 @@ def download_apt_packages(list: SourcesList, keys: list[str], return downloaded @contextmanager -def piggybacked_system(piggyback_def: dict, foreign_packages: Optional[Path]) \ - -> Iterator[Piggybacked]: +def piggybacked_system( + piggyback_def: dict, + foreign_packages: t.Optional[Path] +) -> t.Iterator[Piggybacked]: """ Resolve resources from APT. Optionally, use package files (.deb's, etc.) from a specified directory instead of resolving and downloading them. @@ -386,8 +401,10 @@ def piggybacked_system(piggyback_def: dict, foreign_packages: Optional[Path]) \ archives.mkdir(exist_ok=True) if [*archives.glob('*.deb')] == []: - sources_list = SourcesList(piggyback_def.get('sources_list', []), - piggyback_def.get('distribution')) + sources_list = SourcesList( + list = piggyback_def.get('sources_list', []), + codename = piggyback_def.get('distribution') + ) packages = piggyback_def['packages'] with_deps = piggyback_def['dependencies'] pgp_keys = [ diff --git a/src/hydrilla/builder/piggybacking.py b/src/hydrilla/builder/piggybacking.py index f732f3d..3e4084f 100644 --- a/src/hydrilla/builder/piggybacking.py +++ b/src/hydrilla/builder/piggybacking.py @@ -33,8 +33,9 @@ software system backends. # Enable using with Python 3.7. from __future__ import annotations +import typing as t + from pathlib import Path, PurePosixPath -from typing import Optional, Iterable from ..translations import smart_gettext as _ from .common_errors import * @@ -49,9 +50,13 @@ class Piggybacked: 'resource_must_depend' (read-only) 'package_license_files' (read-only) """ - def __init__(self, archives: dict[str, Path]={}, roots: dict[str, Path]={}, - package_license_files: list[PurePosixPath]=[], - resource_must_depend: list[dict]=[]): + def __init__( + self, + archives: dict[str, Path] = {}, + roots: dict[str, Path] = {}, + package_license_files: list[PurePosixPath] = [], + resource_must_depend: list[dict] = [] + ) -> None: """ Initialize this Piggybacked object. @@ -77,7 +82,7 @@ class Piggybacked: self.package_license_files = package_license_files self.resource_must_depend = resource_must_depend - def resolve_file(self, file_ref_name: PurePosixPath) -> Optional[Path]: + def resolve_file(self, file_ref_name: PurePosixPath) -> t.Optional[Path]: """ 'file_ref_name' is a path as may appear in an index.json file. Check if the file belongs to one of the roots we have and return either a path @@ -107,7 +112,7 @@ class Piggybacked: return path - def archive_files(self) -> Iterable[tuple[PurePosixPath, Path]]: + def archive_files(self) -> t.Iterator[tuple[PurePosixPath, Path]]: """ Yield all archive files in use. Each yielded tuple holds file's desired path relative to the piggybacked archives directory to be created and diff --git a/src/hydrilla/json_instances.py b/src/hydrilla/json_instances.py index 4f4421f..fb34d5c 100644 --- a/src/hydrilla/json_instances.py +++ b/src/hydrilla/json_instances.py @@ -191,7 +191,7 @@ def read_instance(instance_or_path: InstanceSource) -> object: else: raise HaketiloException(_('err.util.text_not_valid_json')) -def get_schema_version(instance: object) -> tuple[int, ...]: +def get_schema_version(instance: object) -> versions.VerTuple: """ Parse passed object's "$schema" property and return the schema version tuple. """ @@ -202,7 +202,7 @@ def get_schema_version(instance: object) -> tuple[int, ...]: ver_str = match.group('ver') if match else None if ver_str is not None: - return versions.parse(ver_str) + return versions.parse_normalize(ver_str) else: raise HaketiloException(_('no_schema_number_in_instance')) -- cgit v1.2.3