Nicolas Boulenguez:
Package: dh-debputy Version: 0.1.78 Severity: wishlist Tags: patchHello. The attachment lets debputy generate the (Static-)Built-Using fields from manifest rules, replacing the dh-builtusing debhelper plugin. It seems ready for a first review, if you are interested.
Hi Nicolas, Thanks for proposing this patch and thanks for using `debputy`.I am interested, but I am also finding myself without a lot of Debian time these days. You have to prod a bit for updates on my end.
'bug1120283.py' should only exist until the patch with the same contents is applied to the python3-debian package.
Noted. Personally, I think `python3-debian` could do itself a favor and turn `PkgRelation.ParsedRelation` into a proper entity rather than a typed dict, so those features can become instance methods rather than functions.
But either way, I appreciate `debputy` will have to carry some kind of shim/backwards compat code unless `python3-debian` is backported and I am happy that this part of it has been thought into the patch. I have some overdue merging to do myself on that front.
I have made the source_package and dpkg_arch_query_table HighLevelManifest attributes public. Their value is accessible via various tricks like manifest.condition_context(pkg).source_package anyway.
Ok. I think this is fine. The `HighLevelManifest` is a bit of a god-object, but that is my mess to sort out later. I cherry-picked the `source_package` attribute in my attached patch.
'test_built_using.py' demonstrates various possible use cases. Advices about the way to merge it into the test suite would be welcome.
My dream for this was to have this kind of feature be a "metadata detector". The key "problem" was that currently "metadata detectors" do not have access to configuration from the manifest. The main thing keeping me back is that I want to keep the two very loosely coupled.
I have tried to make a solution for this in attached prototype (also as the branch accessible-manifest-configuration if you are ok with git/salsa; note, might be rebased). With your patch then being rebased on top of that patch, then it could be re-implemented something like this:
```
class BuiltUsingBase:
...
class StaticBuiltUsing(StaticBuiltUsingBase)
pass
class BuiltUsing(StaticBuiltUsingBase)
pass
def _unpack_built_using(
_name: str,
parsed_data: list[BuiltUsingParsedFormat],
_attribute_path: AttributePath,
_parser_context: ParserContextData,
) -> BuiltUsing:
return StaticBuiltUsing(...)
# This part is still a bit more WET than I want it to be
def _unpack_static_built_using(
_name: str,
parsed_data: list[BuiltUsingParsedFormat],
_attribute_path: AttributePath,
_parser_context: ParserContextData,
) -> BuiltUsing:
return StaticBuiltUsing(...)
# This would be split into different files most likely
# - one part for binary_package_rules.py
# - the other part for metadata_detectors.py
def initialize(api: DebputyPluginInitializer) -> None:
api.pluggable_manifest_rule(
rule_type=OPARSER_PACKAGES,
rule_name="built-using",
parsed_format=list[BuiltUsingParsedFormat],
_unpack_built_using,
)
api.pluggable_manifest_rule(
rule_type=OPARSER_PACKAGES,
rule_name="static-built-using",
parsed_format=list[BuiltUsingParsedFormat],
_unpack_static_built_using,
)
api.metadata_or_maintscript_detector(
"detect-built-using",
detect_built_using,
)
def detect_built_using(
fs_root: "VirtualPath",
ctrl: "BinaryCtrlAccessor",
context: "PackageProcessingContext",
) -> None:
built_using_conf = context.manifestEntity(
context.binary_package,
BuiltUsing,
)
static_built_using_conf = context.manifestEntity(
context.binary_package,
StaticBuiltUsing,
)
# built_using_conf is an instance of BuiltUsing | None
# static_built_using_conf is an instance of StaticBuiltUsing | None
built_using = ...
static_built_using = ...
if built_using:
ctrl.substvars.add_dependency("debputy:Built-Using", ...)
if static_built_using:
ctrl.substvars.add_dependency("debputy:Static-Built-Using", ...)
```
If this is accomplished, the feature could be installed via the
register_package_metadata_detectors in
`src/debputy/plugins/debputy/debputy_plugin.py` with the implementation
going into in `.../metadata_detectors.py`. Related tests are in
`tests/test_debputy_plugin.py` (search for `run_metadata_detector`). You
can also find some examples in `tests/plugin_tests/*` (which are tests
for "non-core" plugins, so they load their plugin differently, but is
otherwise the same).
I might need to make some testing infrastructure to make it easier to create real manifest with arbitrary d/control contents.
The main downside with this approach is that your work on avoiding calls to `dpkg` will be difficult. I think you can at best get it to be once per BinaryPackage with this approach. The slowness of `dpkg` is one of the things I need to look at, but I think that can come after.
I hope this was interesting to you as well. Best regards, Niels
From 84515366607854034d6aa7b24944adc6d29fa887 Mon Sep 17 00:00:00 2001 From: Niels Thykier <[email protected]> Date: Wed, 26 Nov 2025 20:24:47 +0000 Subject: [PATCH] Plugin API: Make some pluggable manifest rules accessible via context With this change, plugins are able to register a pluggable manifest rule (PMR) and then access the value from it in a metadata-detector or a package-processor. The PMR must be registered directly on an OPARSER (accordingly, rule type must be of type `str`). The declared return type of the handler is as part of the key for resolving the value later. Every registered type must be unique as a consequence. Example being: ``` @dataclasses.dataclass class Foo: ... def initialize(api: DebputyPluginInitializer) -> None: api.pluggable_manifest_rule( OPARSER_PACKAGES, "my-foo", str, _parse_my_foo, ) api.metadata_or_maintscript_detector("foobinator", foobinate) def _parse_my_foo( ... ) -> Foo: return Foo(...) def foobinate( fs_root: VirtualPath, ctrl: BinaryCtrlAccessor, context: PackageProcessingContext, ) -> None: foo = context.manifest_configuration( context.binary_package, Foo, ) if foo is None: return ... # Foobinate with Foo ``` --- src/debputy/highlevel_manifest.py | 18 ++++- src/debputy/highlevel_manifest_parser.py | 48 +++++++++---- src/debputy/plugin/api/impl.py | 67 ++++++++++++++----- src/debputy/plugin/api/impl_types.py | 22 +++--- src/debputy/plugin/api/spec.py | 26 ++++++- src/debputy/plugin/api/test_api/test_impl.py | 35 ++++++---- src/debputy/plugin/plugin_state.py | 65 +++++++++++++++++- .../plugins/debputy/binary_package_rules.py | 6 ++ .../plugins/debputy/build_system_rules.py | 3 + .../plugins/debputy/manifest_root_rules.py | 4 ++ .../plugins/debputy/metadata_detectors.py | 2 +- 11 files changed, 238 insertions(+), 58 deletions(-) diff --git a/src/debputy/highlevel_manifest.py b/src/debputy/highlevel_manifest.py index c5255626..fc657a06 100644 --- a/src/debputy/highlevel_manifest.py +++ b/src/debputy/highlevel_manifest.py @@ -2,6 +2,7 @@ import dataclasses import functools import os import textwrap +import typing from contextlib import suppress from dataclasses import dataclass, field from typing import ( @@ -1197,6 +1198,10 @@ class HighLevelManifest: build_env: DebBuildOptionsAndProfiles, build_environments: BuildEnvironments, build_rules: list[BuildRule] | None, + value_table: Mapping[ + tuple[SourcePackage | BinaryPackage, type[Any]], + Any, + ], plugin_provided_feature_set: PluginProvidedFeatureSet, debian_dir: VirtualPath, ) -> None: @@ -1206,7 +1211,7 @@ class HighLevelManifest: remove_during_clean_rules ) self._install_rules = install_rules - self._source_package = source_package + self.source_package = source_package self._binary_packages = binary_packages self.substitution = substitution self.package_transformations = package_transformations @@ -1216,6 +1221,7 @@ class HighLevelManifest: self._used_for: set[str] = set() self.build_environments = build_environments self.build_rules = build_rules + self._value_table = value_table self._plugin_provided_feature_set = plugin_provided_feature_set self._debian_dir = debian_dir self._source_condition_context = ConditionContext( @@ -1270,6 +1276,14 @@ class HighLevelManifest: def all_packages(self) -> Iterable[BinaryPackage]: yield from self._binary_packages.values() + def manifest_configuration[T]( + self, + context_package: SourcePackage | BinaryPackage, + value_type: type[T], + ) -> T | None: + res = self._value_table.get((context_package, value_type)) + return typing.cast("T | None", res) + def package_state_for(self, package: str) -> PackageTransformationDefinition: return self.package_transformations[package] @@ -1574,7 +1588,7 @@ class HighLevelManifest: ) package_data_dict[package] = BinaryPackageData( - self._source_package, + self.source_package, dctrl_bin, build_system_pkg_staging_dir, fs_root, diff --git a/src/debputy/highlevel_manifest_parser.py b/src/debputy/highlevel_manifest_parser.py index 73280956..fcbb7ef8 100644 --- a/src/debputy/highlevel_manifest_parser.py +++ b/src/debputy/highlevel_manifest_parser.py @@ -1,16 +1,12 @@ import collections import contextlib +from collections.abc import Callable, Mapping, Iterator from typing import ( - Optional, - Dict, - List, Any, - Union, IO, cast, - Tuple, + TYPE_CHECKING, ) -from collections.abc import Callable, Mapping, Iterator from debian.debian_support import DpkgArchTable @@ -30,6 +26,8 @@ from debputy.path_matcher import ( ExactFileSystemPath, MatchRule, ) +from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT +from debputy.plugins.debputy.build_system_rules import BuildRule from debputy.substitution import Substitution from debputy.util import ( _normalize_path, @@ -68,11 +66,15 @@ from .plugin.api.impl_types import ( DispatchingTableParser, PackageContextData, ) -from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT from .plugin.api.spec import DebputyIntegrationMode -from debputy.plugins.debputy.build_system_rules import BuildRule +from .plugin.plugin_state import with_binary_pkg_parsing_context, begin_parsing_context from .yaml import YAMLError, MANIFEST_YAML + +if TYPE_CHECKING: + from .plugins.debputy.binary_package_rules import BinaryVersion + + try: from Levenshtein import distance except ImportError: @@ -158,6 +160,10 @@ class HighLevelManifestParser(ParserContextData): self._has_set_default_build_environment = False self._read_build_environment = False self._build_rules: list[BuildRule] | None = None + self._value_table: dict[ + tuple[SourcePackage | BinaryPackage, type[Any]], + Any, + ] = {} if isinstance(debian_dir, str): debian_dir = OSFSROOverlay.create_root_dir("debian", debian_dir) @@ -243,6 +249,13 @@ class HighLevelManifestParser(ParserContextData): if self._used: raise TypeError("build_manifest can only be called once!") self._used = True + return begin_parsing_context( + self._value_table, + self._source_package, + self._build_manifest, + ) + + def _build_manifest(self) -> HighLevelManifest: self._ensure_package_states_is_initialized() for var, attribute_path in self._declared_variables.items(): if not self.substitution.is_used(var): @@ -291,13 +304,15 @@ class HighLevelManifestParser(ParserContextData): self._deb_options_and_profiles, build_environments, self._build_rules, + self._value_table, self._plugin_provided_feature_set, self._debian_dir, ) @contextlib.contextmanager def binary_package_context( - self, package_name: str + self, + package_name: str, ) -> Iterator[PackageTransformationDefinition]: if package_name not in self._package_states: self._error( @@ -308,7 +323,8 @@ class HighLevelManifestParser(ParserContextData): package_state = self._package_states[package_name] self._package_state_stack.append(package_state) ps_len = len(self._package_state_stack) - yield package_state + with with_binary_pkg_parsing_context(package_state.binary_package): + yield package_state if ps_len != len(self._package_state_stack): raise RuntimeError("Internal error: Unbalanced stack manipulation detected") self._package_state_stack.pop() @@ -552,7 +568,7 @@ class YAMLManifestParser(HighLevelManifestParser): ) return v - def from_yaml_dict(self, yaml_data: object) -> "HighLevelManifest": + def _from_yaml_dict(self, yaml_data: object) -> "HighLevelManifest": attribute_path = AttributePath.root_path(yaml_data) parser_generator = self._plugin_provided_feature_set.manifest_parser_generator dispatchable_object_parsers = parser_generator.dispatchable_object_parsers @@ -587,7 +603,7 @@ class YAMLManifestParser(HighLevelManifestParser): f'Cannot define rules for package "{package_name}" (at {definition_source.path}). It is an' " auto-generated package." ) - binary_version = parsed.get(MK_BINARY_VERSION) + binary_version: str | None = parsed.get(MK_BINARY_VERSION) if binary_version is not None: package_state.binary_version = ( package_state.substitution.substitute( @@ -638,7 +654,13 @@ class YAMLManifestParser(HighLevelManifestParser): f"Could not parse {self.manifest_path} as a YAML document: {msg}" ) from e self._mutable_yaml_manifest = MutableYAMLManifest(data) - return self.from_yaml_dict(data) + + return begin_parsing_context( + self._value_table, + self._source_package, + self._from_yaml_dict, + data, + ) def parse_manifest( self, diff --git a/src/debputy/plugin/api/impl.py b/src/debputy/plugin/api/impl.py index f88a3d5a..aa407665 100644 --- a/src/debputy/plugin/api/impl.py +++ b/src/debputy/plugin/api/impl.py @@ -4,6 +4,7 @@ import functools import importlib import importlib.resources import importlib.util +import inspect import itertools import json import os @@ -11,22 +12,16 @@ import re import subprocess import sys from abc import ABC +from collections.abc import Callable, Iterable, Sequence, Iterator, Mapping, Container from importlib.resources.abc import Traversable -from io import IOBase, BytesIO +from io import IOBase from json import JSONDecodeError from pathlib import Path +from types import NoneType from typing import ( - Optional, - Dict, - Tuple, - Type, - List, - Union, - Set, IO, AbstractSet, cast, - FrozenSet, Any, Literal, TYPE_CHECKING, @@ -34,7 +29,6 @@ from typing import ( AnyStr, overload, ) -from collections.abc import Callable, Iterable, Sequence, Iterator, Mapping, Container import debputy from debputy import DEBPUTY_DOC_ROOT_DIR @@ -86,6 +80,7 @@ from debputy.plugin.api.impl_types import ( PluginProvidedTypeMapping, PluginProvidedBuildSystemAutoDetection, BSR, + TP, ) from debputy.plugin.api.plugin_parser import ( PLUGIN_METADATA_PARSER, @@ -126,15 +121,16 @@ from debputy.plugin.api.spec import ( DebputyPluginDefinition, ) from debputy.plugin.api.std_docs import _STD_ATTR_DOCS -from debputy.plugins.debputy.to_be_api_types import ( - BuildRuleParsedFormat, - BSPF, - debputy_build_system, -) from debputy.plugin.plugin_state import ( run_in_context_of_plugin, run_in_context_of_plugin_wrap_errors, wrap_plugin_code, + register_manifest_type_value_in_context, +) +from debputy.plugins.debputy.to_be_api_types import ( + BuildRuleParsedFormat, + BSPF, + debputy_build_system, ) from debputy.substitution import ( Substitution, @@ -218,6 +214,7 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): "_unloaders", "_is_doc_cache_resolved", "_doc_cache", + "_registered_manifest_types", "_load_started", ) @@ -234,6 +231,7 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): self._unloaders: list[Callable[[], None]] = [] self._is_doc_cache_resolved: bool = False self._doc_cache: DebputyParsedDoc | None = None + self._registered_manifest_types: dict[type[Any], DebputyPluginMetadata] = {} self._load_started = False @property @@ -961,6 +959,7 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): Container[DebputyIntegrationMode] ) = None, apply_standard_attribute_documentation: bool = False, + register_value: bool = True, ) -> None: # When changing this, consider which types will be unrestricted self._restricted_api() @@ -978,7 +977,18 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): f"The rule_type was not a supported type. It must be one of {types}" ) dispatching_parser = parser_generator.dispatchable_object_parsers[rule_type] + signature = inspect.signature(handler) + if ( + signature.return_annotation is signature.empty + or signature.return_annotation == NoneType + ): + raise ValueError( + "The handler must have a return type (that is not None)" + ) + register_as_type = signature.return_annotation else: + # Dispatchable types cannot be resolved + register_as_type = None if rule_type not in parser_generator.dispatchable_table_parsers: types = ", ".join( sorted( @@ -990,6 +1000,19 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): ) dispatching_parser = parser_generator.dispatchable_table_parsers[rule_type] + if register_as_type is not None and not register_value: + register_as_type = None + + if register_as_type is not None: + existing_registration = self._registered_manifest_types.get( + register_as_type + ) + if existing_registration is not None: + raise ValueError( + f"Cannot register rule {rule_name!r} for plugin {self._plugin_name}. The plugin {existing_registration.plugin_name} already registered a manifest rule with type {register_as_type!r}" + ) + self._registered_manifest_types[register_as_type] = self._plugin_metadata + inline_reference_documentation = self._pluggable_manifest_docs_for( rule_type, rule_name, @@ -1008,10 +1031,22 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): expected_debputy_integration_mode=expected_debputy_integration_mode, automatic_docs=docs, ) + + def _registering_handler( + name: str, + parsed_data: PF, + attribute_path: AttributePath, + parser_context: ParserContextData, + ) -> TP: + value = handler(name, parsed_data, attribute_path, parser_context) + if register_as_type is not None: + register_manifest_type_value_in_context(register_as_type, value) + return value + dispatching_parser.register_parser( rule_name, parser, - wrap_plugin_code(self._plugin_name, handler), + wrap_plugin_code(self._plugin_name, _registering_handler), self._plugin_metadata, ) diff --git a/src/debputy/plugin/api/impl_types.py b/src/debputy/plugin/api/impl_types.py index 0ef3646e..88190ff0 100644 --- a/src/debputy/plugin/api/impl_types.py +++ b/src/debputy/plugin/api/impl_types.py @@ -1,27 +1,20 @@ import dataclasses import os.path +from collections.abc import Callable, Sequence, Iterable, Mapping, Iterator, Container from importlib.resources.abc import Traversable from pathlib import Path from typing import ( Optional, - FrozenSet, - Dict, - List, - Tuple, Generic, TYPE_CHECKING, TypeVar, cast, Any, - Union, - Type, TypedDict, NotRequired, Literal, - Set, Protocol, ) -from collections.abc import Callable, Sequence, Iterable, Mapping, Iterator, Container from weakref import ref from debputy.exceptions import ( @@ -36,7 +29,7 @@ from debputy.filesystem_scan import as_path_def from debputy.manifest_parser.exceptions import ManifestParseException from debputy.manifest_parser.tagging_types import DebputyParsedContent, TypeMapping from debputy.manifest_parser.util import AttributePath, check_integration_mode -from debputy.packages import BinaryPackage +from debputy.packages import BinaryPackage, SourcePackage from debputy.plugin.api import ( VirtualPath, BinaryCtrlAccessor, @@ -1253,6 +1246,10 @@ class PackageProcessingContextProvider(PackageProcessingContext): include_binnmu_version=not package.is_arch_all ) + @property + def source_package(self) -> SourcePackage: + return self._manifest.source_package + @property def binary_package(self) -> BinaryPackage: return self._binary_package @@ -1297,6 +1294,13 @@ class PackageProcessingContextProvider(PackageProcessingContext): self._cross_check_cache = cache return cache + def manifest_configuration[T]( + self, + context_package: SourcePackage | BinaryPackage, + value_type: type[T], + ) -> T | None: + return self._manifest.manifest_configuration(context_package, value_type) + @dataclasses.dataclass(slots=True, frozen=True) class PluginProvidedTrigger: diff --git a/src/debputy/plugin/api/spec.py b/src/debputy/plugin/api/spec.py index 928a9140..05aaaab4 100644 --- a/src/debputy/plugin/api/spec.py +++ b/src/debputy/plugin/api/spec.py @@ -36,7 +36,7 @@ from debputy.exceptions import ( from debputy.interpreter import Interpreter, extract_shebang_interpreter_from_file from debputy.manifest_parser.tagging_types import DebputyDispatchableType from debputy.manifest_parser.util import parse_symbolic_mode -from debputy.packages import BinaryPackage +from debputy.packages import BinaryPackage, SourcePackage from debputy.types import S if TYPE_CHECKING: @@ -332,6 +332,11 @@ class PackageProcessingContext: __slots__ = () + @property + def source_package(self) -> SourcePackage: + """The source package stanza from `debian/control`""" + raise NotImplementedError + @property def binary_package(self) -> BinaryPackage: """The binary package stanza from `debian/control`""" @@ -361,8 +366,23 @@ class PackageProcessingContext: def accessible_package_roots(self) -> Iterable[tuple[BinaryPackage, "VirtualPath"]]: raise NotImplementedError - # """The source package stanza from `debian/control`""" - # source_package: SourcePackage + def manifest_configuration[T]( + self, + context_package: SourcePackage | BinaryPackage, + value_type: type[T], + ) -> T | None: + """Request access to configuration from the manifest + + This method will return the value associated with a pluggable manifest rule assuming + said configuration was provided. + + + :param context_package: The context in which the configuration will be. Generally, it will be + the binary package for anything under `packages:` and the source package otherwise. + :param value_type: The + :return: + """ + raise NotImplementedError class DebputyPluginDefinition: diff --git a/src/debputy/plugin/api/test_api/test_impl.py b/src/debputy/plugin/api/test_api/test_impl.py index e29bba35..a3209d4f 100644 --- a/src/debputy/plugin/api/test_api/test_impl.py +++ b/src/debputy/plugin/api/test_api/test_impl.py @@ -2,21 +2,13 @@ import contextlib import dataclasses import inspect import os.path +from collections.abc import Mapping, Sequence, Iterator, KeysView, Callable from importlib.resources.abc import Traversable from io import BytesIO from pathlib import Path from typing import ( - Dict, - Optional, - Tuple, - List, - cast, - FrozenSet, - Union, - Type, - Set, + cast, TYPE_CHECKING, ) -from collections.abc import Mapping, Sequence, Iterator, KeysView, Callable from debian.deb822 import Deb822 from debian.debian_support import DpkgArchTable @@ -25,7 +17,7 @@ from debian.substvars import Substvars from debputy import DEBPUTY_PLUGIN_ROOT_DIR from debputy.architecture_support import faked_arch_table from debputy.filesystem_scan import OSFSROOverlay, FSRootDir -from debputy.packages import BinaryPackage +from debputy.packages import BinaryPackage, SourcePackage from debputy.plugin.api import ( PluginInitializationEntryPoint, VirtualPath, @@ -34,6 +26,7 @@ from debputy.plugin.api import ( Maintscript, ) from debputy.plugin.api.example_processing import process_discard_rule_example +from debputy.plugin.api.feature_set import PluginProvidedFeatureSet from debputy.plugin.api.impl import ( plugin_metadata_for_debputys_own_plugin, DebputyPluginInitializerProvider, @@ -50,7 +43,6 @@ from debputy.plugin.api.impl_types import ( PluginProvidedTrigger, ServiceManagerDetails, ) -from debputy.plugin.api.feature_set import PluginProvidedFeatureSet from debputy.plugin.api.spec import ( MaintscriptAccessor, FlushableSubstvars, @@ -72,16 +64,25 @@ from debputy.plugins.debputy.debputy_plugin import initialize_debputy_features from debputy.substitution import SubstitutionImpl, VariableContext, Substitution from debputy.util import package_cross_check_precheck +if TYPE_CHECKING: + from debputy.highlevel_manifest import HighLevelManifest + + RegisteredPackagerProvidedFile.register(PackagerProvidedFileClassSpec) +type ManifestConfigurationImplementation[T] = Callable[[SourcePackage | BinaryPackage, type[T]], T] + + @dataclasses.dataclass(frozen=True, slots=True) class PackageProcessingContextTestProvider(PackageProcessingContext): + source_package: SourcePackage binary_package: BinaryPackage binary_package_version: str related_udeb_package: BinaryPackage | None related_udeb_package_version: str | None accessible_package_roots: Callable[[], Sequence[tuple[BinaryPackage, VirtualPath]]] + manifest_configuration: ManifestConfigurationImplementation def _initialize_plugin_under_test( @@ -280,6 +281,8 @@ def package_metadata_context( should_be_acted_on: bool = True, related_udeb_fs_root: VirtualPath | None = None, accessible_package_roots: Sequence[tuple[Mapping[str, str], VirtualPath]] = tuple(), + source_package_fields: dict[str, str] | None = None, + manifest_configuration: ManifestConfigurationImplementation = lambda x, y: None, ) -> PackageProcessingContext: process_table = faked_arch_table(host_arch) f = { @@ -297,6 +300,12 @@ def package_metadata_context( should_be_acted_on=should_be_acted_on, ) udeb_package = None + s = { + "Source": bin_package.name, + } + if source_package_fields is not None: + s.update(source_package_fields) + source_package = SourcePackage(Deb822(s)) if related_udeb_package_fields is not None: uf = dict(related_udeb_package_fields) uf.setdefault("Package", f'{f["Package"]}-udeb') @@ -354,11 +363,13 @@ def package_metadata_context( final_apr = tuple() return PackageProcessingContextTestProvider( + source_package=source_package, binary_package=bin_package, related_udeb_package=udeb_package, binary_package_version=binary_package_version, related_udeb_package_version=related_udeb_package_version, accessible_package_roots=lambda: final_apr, + manifest_configuration=manifest_configuration, ) diff --git a/src/debputy/plugin/plugin_state.py b/src/debputy/plugin/plugin_state.py index 91f87c94..c074eb9e 100644 --- a/src/debputy/plugin/plugin_state.py +++ b/src/debputy/plugin/plugin_state.py @@ -1,25 +1,86 @@ +import collections.abc +import contextlib import contextvars import functools import inspect -from contextvars import ContextVar -from typing import Optional, ParamSpec, TypeVar, NoReturn, Union from collections.abc import Callable +from contextvars import ContextVar +from typing import ParamSpec, TypeVar, NoReturn, Any from debputy.exceptions import ( UnhandledOrUnexpectedErrorFromPluginError, DebputyRuntimeError, ) +from debputy.packages import SourcePackage, BinaryPackage from debputy.util import _trace_log, _is_trace_log_enabled _current_debputy_plugin_cxt_var: ContextVar[str | None] = ContextVar( "current_debputy_plugin", default=None, ) +_current_debputy_parsing_context: ContextVar[ + tuple[ + dict[tuple[SourcePackage | BinaryPackage, type[Any]], Any], + SourcePackage | BinaryPackage, + ] + | None +] = ContextVar( + "current_debputy_parsing_context", + default=None, +) P = ParamSpec("P") R = TypeVar("R") +def register_manifest_type_value_in_context( + value_type: type[Any], + value: Any, +) -> None: + context_vars = _current_debputy_parsing_context.get() + if context_vars is None: + raise AssertionError( + "register_manifest_type_value_in_context() was called, but no context was set." + ) + value_table, context_pkg = context_vars + if (context_pkg, value_type) in value_table: + raise AssertionError( + f"The type {value_type!r} was already registered for {context_pkg}, which the plugin API should have prevented" + ) + value_table[(context_pkg, value_type)] = value + + +def begin_parsing_context( + value_table: dict[tuple[SourcePackage | BinaryPackage, type[Any]], Any], + context_pkg: SourcePackage, + func: Callable[P, R], + *args: P.args, + **kwargs: P.kwargs, +) -> R: + context = contextvars.copy_context() + # Wish we could just do a regular set without wrapping it in `context.run` + context.run(_current_debputy_parsing_context.set, (value_table, context_pkg)) + assert context.get(_current_debputy_parsing_context) == (value_table, context_pkg) + return context.run(func, *args, **kwargs) + + [email protected] +def with_binary_pkg_parsing_context( + context_pkg: BinaryPackage, +) -> collections.abc.Iterator[None]: + context_vars = _current_debputy_parsing_context.get() + if context_vars is None: + raise AssertionError( + "with_binary_pkg_parsing_context() was called, but no context was set." + ) + value_table, _ = context_vars + token = _current_debputy_parsing_context.set((value_table, context_pkg)) + try: + yield + finally: + _current_debputy_parsing_context.reset(token) + + def current_debputy_plugin_if_present() -> str | None: return _current_debputy_plugin_cxt_var.get() diff --git a/src/debputy/plugins/debputy/binary_package_rules.py b/src/debputy/plugins/debputy/binary_package_rules.py index e6cd1169..fddc67e7 100644 --- a/src/debputy/plugins/debputy/binary_package_rules.py +++ b/src/debputy/plugins/debputy/binary_package_rules.py @@ -78,6 +78,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None BinaryVersionParsedFormat, _parse_binary_version, source_format=str, + register_value=False, ) api.pluggable_manifest_rule( @@ -85,6 +86,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None "transformations", list[TransformationRule], _unpack_list, + register_value=False, ) api.pluggable_manifest_rule( @@ -95,6 +97,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None expected_debputy_integration_mode=not_integrations( INTEGRATION_MODE_DH_DEBPUTY_RRR ), + register_value=False, ) api.pluggable_manifest_rule( @@ -106,6 +109,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None expected_debputy_integration_mode=not_integrations( INTEGRATION_MODE_DH_DEBPUTY_RRR ), + register_value=False, ) api.pluggable_manifest_rule( @@ -119,6 +123,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None expected_debputy_integration_mode=not_integrations( INTEGRATION_MODE_DH_DEBPUTY_RRR ), + register_value=False, ) api.pluggable_manifest_rule( @@ -130,6 +135,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None expected_debputy_integration_mode=not_integrations( INTEGRATION_MODE_DH_DEBPUTY_RRR ), + register_value=False, ) diff --git a/src/debputy/plugins/debputy/build_system_rules.py b/src/debputy/plugins/debputy/build_system_rules.py index bc2f580a..2813e089 100644 --- a/src/debputy/plugins/debputy/build_system_rules.py +++ b/src/debputy/plugins/debputy/build_system_rules.py @@ -98,6 +98,7 @@ def register_build_keywords(api: DebputyPluginInitializerProvider) -> None: "build-environments", list[NamedEnvironmentSourceFormat], _parse_build_environments, + register_value=False, expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), inline_reference_documentation=reference_documentation( title="Build Environments (`build-environments`)", @@ -192,6 +193,7 @@ def register_build_keywords(api: DebputyPluginInitializerProvider) -> None: "default-build-environment", EnvironmentSourceFormat, _parse_default_environment, + register_value=False, expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), inline_reference_documentation=reference_documentation( title="Default Build Environment (`default-build-environment`)", @@ -272,6 +274,7 @@ def register_build_keywords(api: DebputyPluginInitializerProvider) -> None: MK_BUILDS, list[BuildRule], _handle_build_rules, + register_value=False, expected_debputy_integration_mode=only_integrations(INTEGRATION_MODE_FULL), inline_reference_documentation=reference_documentation( title=f"Build rules (`{MK_BUILDS}`)", diff --git a/src/debputy/plugins/debputy/manifest_root_rules.py b/src/debputy/plugins/debputy/manifest_root_rules.py index 8e54edf9..513b4f34 100644 --- a/src/debputy/plugins/debputy/manifest_root_rules.py +++ b/src/debputy/plugins/debputy/manifest_root_rules.py @@ -45,6 +45,7 @@ def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: ManifestVersionFormat, _handle_version, source_format=ManifestVersion, + register_value=False, ) api.pluggable_object_parser( OPARSER_MANIFEST_ROOT, @@ -58,12 +59,14 @@ def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: ManifestVariablesParsedFormat, _handle_manifest_variables, source_format=dict[str, str], + register_value=False, ) api.pluggable_manifest_rule( OPARSER_MANIFEST_ROOT, MK_INSTALLATIONS, list[InstallRule], _handle_installation_rules, + register_value=False, expected_debputy_integration_mode=not_integrations( INTEGRATION_MODE_DH_DEBPUTY_RRR ), @@ -73,6 +76,7 @@ def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: MK_MANIFEST_REMOVE_DURING_CLEAN, list[RemoveDuringCleanParsedFormat], _handle_remove_during_clean, + register_value=False, expected_debputy_integration_mode=only_integrations( INTEGRATION_MODE_FULL, ), diff --git a/src/debputy/plugins/debputy/metadata_detectors.py b/src/debputy/plugins/debputy/metadata_detectors.py index 3a08a18a..3f3a1726 100644 --- a/src/debputy/plugins/debputy/metadata_detectors.py +++ b/src/debputy/plugins/debputy/metadata_detectors.py @@ -17,7 +17,7 @@ from debputy.plugins.debputy.paths import ( SYSTEMD_SYSUSERS_DIR, ) from debputy.plugins.debputy.types import DebputyCapability -from debputy.util import assume_not_none, _warn +from debputy.util import assume_not_none, _warn, _error DPKG_ROOT = '"${DPKG_ROOT}"' DPKG_ROOT_UNQUOTED = "${DPKG_ROOT}" -- 2.51.0
OpenPGP_signature.asc
Description: OpenPGP digital signature

