summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/debputy/analysis/debian_dir.py1
-rw-r--r--src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py11
-rw-r--r--src/debputy/linting/lint_report_junit.py8
-rw-r--r--src/debputy/lsp/debputy_ls.py6
-rw-r--r--src/debputy/lsp/lsp_debian_control.py39
-rw-r--r--src/debputy/packager_provided_files.py63
-rw-r--r--tests/test_packager_provided_files.py416
7 files changed, 504 insertions, 40 deletions
diff --git a/src/debputy/analysis/debian_dir.py b/src/debputy/analysis/debian_dir.py
index 7e5b37a..68a5c1b 100644
--- a/src/debputy/analysis/debian_dir.py
+++ b/src/debputy/analysis/debian_dir.py
@@ -152,6 +152,7 @@ def scan_debian_dir(
)
details: PackagingFileInfo = {
"path": key,
+ "binary-package": ppf.package_name,
"pkgfile-stem": ppf.definition.stem,
"pkgfile-explicit-package-name": ppf.uses_explicit_package_name,
"pkgfile-is-active-in-build": ppf.definition.has_active_command,
diff --git a/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py b/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py
index d715e5a..e8e380e 100644
--- a/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py
+++ b/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py
@@ -1,3 +1,4 @@
+import random
import textwrap
from argparse import BooleanOptionalAction
@@ -219,9 +220,17 @@ def lsp_editor_glue(context: CommandContext) -> None:
alias_of = f" (short for: {payload})"
content.append((editor_name, alias_of))
max_name = max(len(c[0]) for c in content)
- print("This version of debputy has editor snippets for the following editors: ")
+ print(
+ "This version of debputy has instructions or editor config snippets for the following editors: "
+ )
+ print()
for editor_name, alias_of in content:
print(f" * {editor_name:<{max_name}}{alias_of}")
+ print()
+ choice = random.Random().choice(list(_EDITOR_SNIPPETS))
+ print(
+ f"Use `debputy editor-config {choice}` (as an example) to see the instructions for a concrete editor."
+ )
return
result = _EDITOR_SNIPPETS[editor_name]
while result in _EDITOR_SNIPPETS:
diff --git a/src/debputy/linting/lint_report_junit.py b/src/debputy/linting/lint_report_junit.py
index 8b1d635..2ad993a 100644
--- a/src/debputy/linting/lint_report_junit.py
+++ b/src/debputy/linting/lint_report_junit.py
@@ -51,6 +51,8 @@ class JunitLintReport(LintReport):
diagnostic_results: Iterable[LintDiagnosticResult],
duration: float,
) -> "TestCase":
+ if not duration:
+ duration = 0.000001
case = TestCase(
filename,
# The JUnit schema has `classname` as mandatory
@@ -63,9 +65,13 @@ class JunitLintReport(LintReport):
continue
diagnostic = diagnostic_result.diagnostic
severity = debputy_severity(diagnostic)
+ if diagnostic_result.is_file_level_diagnostic:
+ range_desc = "entire file"
+ else:
+ range_desc = str(diagnostic.range)
output = textwrap.dedent(
f"""\
- {filename} [{severity}] ({diagnostic.range}): {diagnostic.message}
+ {filename} [{severity}] ({range_desc}): {diagnostic.message}
"""
)
case.add_failure_info(
diff --git a/src/debputy/lsp/debputy_ls.py b/src/debputy/lsp/debputy_ls.py
index e195208..ed076cd 100644
--- a/src/debputy/lsp/debputy_ls.py
+++ b/src/debputy/lsp/debputy_ls.py
@@ -447,6 +447,10 @@ class DebputyLanguageServer(LanguageServer):
cleaned_filename = path[last_idx:]
if self.trust_language_ids and lang_id and not lang_id.isspace():
- return "editor-provided", lang_id, cleaned_filename
+ if lang_id not in ("fundamental",):
+ return "editor-provided", lang_id, cleaned_filename
+ _info(
+ f"Ignoring editor provided language ID: {lang_id} (reverting to filename based detection instead)"
+ )
return "filename", cleaned_filename, cleaned_filename
diff --git a/src/debputy/lsp/lsp_debian_control.py b/src/debputy/lsp/lsp_debian_control.py
index 370a14f..e91a43e 100644
--- a/src/debputy/lsp/lsp_debian_control.py
+++ b/src/debputy/lsp/lsp_debian_control.py
@@ -959,7 +959,7 @@ def _lint_debian_control(
def _package_range_of_stanza(
lint_state: LintState,
binary_stanzas: List[Tuple[Deb822ParagraphElement, TEPosition]],
-) -> Iterable[Tuple[str, Range]]:
+) -> Iterable[Tuple[str, Optional[str], Range]]:
for stanza, stanza_position in binary_stanzas:
kvpair = stanza.get_kvpair_element("Package")
if kvpair is None:
@@ -971,7 +971,7 @@ def _package_range_of_stanza(
lint_state.lines,
te_range_to_lsp(representation_field_range),
)
- yield stanza["Package"], representation_field_range
+ yield stanza["Package"], stanza.get("Architecture"), representation_field_range
def _detect_misspelled_packaging_files(
@@ -989,7 +989,8 @@ def _detect_misspelled_packaging_files(
debian_dir,
)
stanza_ranges = {
- p: r for p, r in _package_range_of_stanza(lint_state, binary_stanzas_w_pos)
+ p: (a, r)
+ for p, a, r in _package_range_of_stanza(lint_state, binary_stanzas_w_pos)
}
for pkg_file_data in all_pkg_file_data:
@@ -999,13 +1000,15 @@ def _detect_misspelled_packaging_files(
stem = pkg_file_data.get("pkgfile-stem")
if binary_package is None or stem is None:
continue
- diag_range = stanza_ranges.get(binary_package)
+ declared_arch, diag_range = stanza_ranges.get(binary_package)
if diag_range is None:
continue
-
path = pkg_file_data["path"]
likely_typo_of = pkg_file_data.get("likely-typo-of")
+ arch_restriction = pkg_file_data.get("pkgfile-architecture-restriction")
if likely_typo_of is not None:
+ # Handles arch_restriction == 'all' at the same time due to how
+ # the `likely-typo-of` is created
diagnostics.append(
Diagnostic(
diag_range,
@@ -1018,6 +1021,31 @@ def _detect_misspelled_packaging_files(
)
)
continue
+ if declared_arch == "all" and arch_restriction is not None:
+ diagnostics.append(
+ Diagnostic(
+ diag_range,
+ f'The file "{path}" has an architecture restriction but is for an `arch:all` package, so'
+ f" the restriction does not make sense.",
+ severity=DiagnosticSeverity.Warning,
+ source="debputy",
+ data=DiagnosticData(
+ report_for_related_file=path,
+ ),
+ )
+ )
+ elif arch_restriction == "all":
+ diagnostics.append(
+ Diagnostic(
+ diag_range,
+ f'The file "{path}" has an architecture restriction of `all` rather than a real architecture',
+ severity=DiagnosticSeverity.Warning,
+ source="debputy",
+ data=DiagnosticData(
+ report_for_related_file=path,
+ ),
+ )
+ )
if not pkg_file_data.get("pkgfile-is-active-in-build", True):
diagnostics.append(
@@ -1037,7 +1065,6 @@ def _detect_misspelled_packaging_files(
if not explicit_package and name_segment is not None:
basename = os.path.basename(path)
alt_name = f"{binary_package}.{stem}"
- arch_restriction = pkg_file_data.get("pkgfile-architecture-restriction")
if arch_restriction is not None:
alt_name = f"{alt_name}.{arch_restriction}"
diagnostics.append(
diff --git a/src/debputy/packager_provided_files.py b/src/debputy/packager_provided_files.py
index 85fff11..8e0e0af 100644
--- a/src/debputy/packager_provided_files.py
+++ b/src/debputy/packager_provided_files.py
@@ -86,42 +86,42 @@ def _find_package_name_prefix(
yield main_binary_package, path.name, False, False
+def _iterate_stem_splits(basename: str) -> Tuple[str, str, int]:
+ stem = basename
+ period_count = stem.count(".")
+ yield stem, None, period_count
+ install_as_name = ""
+ while period_count > 0:
+ period_count -= 1
+ install_as_name_part, stem = stem.split(".", 1)
+ install_as_name = (
+ install_as_name + "." + install_as_name_part
+ if install_as_name != ""
+ else install_as_name_part
+ )
+ yield stem, install_as_name, period_count
+
+
def _find_definition(
packager_provided_files: Mapping[str, PackagerProvidedFileClassSpec],
basename: str,
*,
period2stems: Optional[Mapping[int, Sequence[str]]] = None,
) -> Tuple[Optional[str], Optional[PackagerProvidedFileClassSpec], Optional[str]]:
- definition = packager_provided_files.get(basename)
- if definition is not None:
- return None, definition, None
- if period2stems and "." not in basename:
- stems = period2stems.get(0)
- matches = detect_possible_typo(basename, stems) if stems else None
- if matches and len(matches) == 1:
- definition = packager_provided_files[matches[0]]
- return None, definition, basename
- install_as_name = basename
- file_class = ""
- period_count = -1
- while "." in install_as_name:
- period_count += 1
- install_as_name, file_class_part = install_as_name.rsplit(".", 1)
- file_class = (
- file_class_part + "." + file_class if file_class != "" else file_class_part
- )
- definition = packager_provided_files.get(file_class)
+ for stem, install_as_name, period_count in _iterate_stem_splits(basename):
+ definition = packager_provided_files.get(stem)
if definition is not None:
return install_as_name, definition, None
if not period2stems:
continue
stems = period2stems.get(period_count)
+
if not stems:
continue
- matches = detect_possible_typo(file_class, stems)
- if matches and len(matches) == 1:
+ matches = detect_possible_typo(stem, stems)
+ if matches is not None and len(matches) == 1:
definition = packager_provided_files[matches[0]]
- return install_as_name, definition, file_class
+ return install_as_name, definition, stem
return None, None, None
@@ -260,13 +260,16 @@ def _split_path(
if bug_950723 and not definition.bug_950723:
continue
- _check_mismatches(
- path,
- definition,
- owning_package,
- install_as_name,
- arch_restriction is not None,
- )
+ if not allow_fuzzy_matches:
+ # LSP/Lint checks here but should not use `_check_mismatches` as
+ # the hard error disrupts them.
+ _check_mismatches(
+ path,
+ definition,
+ owning_package,
+ install_as_name,
+ arch_restriction is not None,
+ )
expected_path: Optional[str] = None
if (
@@ -321,7 +324,7 @@ def _split_path(
basename = f"{install_as_name}.{basename}"
if explicit_package:
basename = f"{package_prefix}.{basename}"
- if arch_restriction is not None:
+ if arch_restriction is not None and arch_restriction != "all":
basename = f"{basename}.{arch_restriction}"
expected_path = f"{parent_path}{basename}"
if fuzzy_match and path.name.endswith(".in"):
diff --git a/tests/test_packager_provided_files.py b/tests/test_packager_provided_files.py
index b0e075f..caf4bd1 100644
--- a/tests/test_packager_provided_files.py
+++ b/tests/test_packager_provided_files.py
@@ -1,11 +1,17 @@
+import os
import random
-from typing import cast, TYPE_CHECKING
+from typing import cast, TYPE_CHECKING, Sequence, Optional, Mapping
import pytest
from debputy.packager_provided_files import detect_all_packager_provided_files
from debputy.plugin.api import DebputyPluginInitializer
from debputy.plugin.api.feature_set import PluginProvidedFeatureSet
+from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin
+from debputy.plugin.api.impl_types import (
+ PackagerProvidedFileClassSpec,
+ DebputyPluginMetadata,
+)
from debputy.plugin.api.test_api import (
InitializedPluginUnderTest,
build_virtual_file_system,
@@ -13,6 +19,7 @@ from debputy.plugin.api.test_api import (
from debputy.plugin.api.test_api.test_impl import (
initialize_plugin_under_test_preloaded,
)
+from lint_tests.lint_tutil import requires_levenshtein
from tutil import faked_binary_package, binary_package_table
if TYPE_CHECKING:
@@ -210,9 +217,416 @@ def test_packager_provided_files_priority(
assert expected_basename == matched.path.name
+@pytest.mark.parametrize(
+ "main_package,secondary_package",
+ [
+ ("foo", "foo-doc"),
+ ("foo.bar", "foo.bar-doc"),
+ ],
+)
+def test_detect_ppf_simple(main_package: str, secondary_package: str) -> None:
+ plugin_metadata = plugin_metadata_for_debputys_own_plugin()
+ binary_packages = binary_package_table(
+ faked_binary_package(main_package, is_main_package=True),
+ faked_binary_package(secondary_package),
+ )
+ debian_dir = build_virtual_file_system(
+ [
+ "install",
+ f"{secondary_package}.install",
+ f"{main_package}.docs",
+ f"{secondary_package}.docs",
+ "copyright",
+ ]
+ )
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "install",
+ ),
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "docs",
+ ),
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "copyright",
+ packageless_is_fallback_for_all_packages=True,
+ ),
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ )
+ assert main_package in results
+ main_matches = results[main_package].auto_installable
+ assert {m.path.name for m in main_matches} == {
+ "install",
+ f"{main_package}.docs",
+ "copyright",
+ }
+
+ assert secondary_package in results
+ sec_matches = results[secondary_package].auto_installable
+ expected = {
+ f"{secondary_package}.install",
+ f"{secondary_package}.docs",
+ "copyright",
+ }
+ assert {m.path.name for m in sec_matches} == expected
+
+
+def test_detect_ppf_name_segment() -> None:
+ plugin_metadata = plugin_metadata_for_debputys_own_plugin()
+ binary_packages = binary_package_table(
+ faked_binary_package("foo", is_main_package=True),
+ faked_binary_package("bar"),
+ )
+ debian_dir = build_virtual_file_system(
+ [
+ "fuu.install",
+ ]
+ )
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "install",
+ ),
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {
+ "fuu.install",
+ }
+ foo_install = foo_matches[0]
+ assert foo_install.name_segment == "fuu"
+ assert foo_install.package_name == "foo"
+ assert foo_install.architecture_restriction is None
+ assert not foo_install.fuzzy_match
+
+ assert "bar" in results
+ assert not results["bar"].auto_installable
+
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "install",
+ allow_name_segment=False,
+ ),
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ allow_fuzzy_matches=True,
+ )
+
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"fuu.install"}
+ foo_install = foo_matches[0]
+ assert foo_install.name_segment == "fuu"
+ assert foo_install.package_name == "foo"
+ assert foo_install.architecture_restriction is None
+
+ assert "bar" in results
+ assert not results["bar"].auto_installable
+
+
+def test_detect_ppf_fuzzy_match_bug_950723() -> None:
+ plugin_metadata = plugin_metadata_for_debputys_own_plugin()
+ binary_packages = binary_package_table(
+ faked_binary_package("foo", is_main_package=True),
+ faked_binary_package("bar"),
+ )
+ debian_dir = build_virtual_file_system(
+ [
+ "bar.service",
+ "foo@.service",
+ ]
+ )
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "service",
+ bug_950723=True,
+ ),
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"foo@.service"}
+ foo_at_service = foo_matches[0]
+ # Without bug#950723 AND fuzzy_match, it counts a name segment for the main package.
+ assert foo_at_service.name_segment == "foo@"
+ assert foo_at_service.package_name == "foo"
+ assert foo_at_service.architecture_restriction is None
+ assert not foo_at_service.fuzzy_match
+
+ assert "bar" in results
+ bar_matches = results["bar"].auto_installable
+ assert {m.path.name for m in bar_matches} == {"bar.service"}
+ bar_service = bar_matches[0]
+ assert bar_service.name_segment is None
+ assert bar_service.package_name == "bar"
+ assert bar_service.architecture_restriction is None
+ assert not bar_service.fuzzy_match
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ allow_fuzzy_matches=True,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"foo@.service"}
+ foo_at_service = foo_matches[0]
+ assert foo_at_service.name_segment is None
+ assert foo_at_service.package_name == "foo"
+ assert foo_at_service.architecture_restriction is None
+
+ assert "bar" in results
+ bar_matches = results["bar"].auto_installable
+ assert {m.path.name for m in bar_matches} == {"bar.service"}
+ bar_service = bar_matches[0]
+ assert bar_service.name_segment is None
+ assert bar_service.package_name == "bar"
+ assert bar_service.architecture_restriction is None
+ assert not bar_service.fuzzy_match
+
+
+@requires_levenshtein
+def test_detect_ppf_typo() -> None:
+ plugin_metadata = plugin_metadata_for_debputys_own_plugin()
+ binary_packages = binary_package_table(
+ faked_binary_package("foo", is_main_package=True),
+ faked_binary_package("bar"),
+ )
+ debian_dir = build_virtual_file_system(
+ [
+ "fuu.install",
+ "bar.intsall",
+ ]
+ )
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "install",
+ ),
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "logcheck.violations.d",
+ ),
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ detect_typos=True,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"fuu.install"}
+ foo_logcheck = foo_matches[0]
+ # Not a typo due to how name segments works with debhelper compat <= 13
+ # (but should probably have been one.
+ assert foo_logcheck.name_segment == "fuu"
+ assert foo_logcheck.package_name == "foo"
+ assert foo_logcheck.architecture_restriction is None
+
+ assert "bar" in results
+ bar_matches = results["bar"].auto_installable
+ assert {m.path.name for m in bar_matches} == {"bar.intsall"}
+ bar_logcheck = bar_matches[0]
+ assert os.path.basename(bar_logcheck.expected_path) == "bar.install"
+ assert bar_logcheck.name_segment is None
+ assert bar_logcheck.package_name == "bar"
+ assert bar_logcheck.architecture_restriction is None
+
+ debian_dir = build_virtual_file_system(
+ [
+ # Typo'ed by intention
+ "logchcek.violations.d",
+ "bar.logchcek.violations.d",
+ ]
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ detect_typos=True,
+ )
+
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"logchcek.violations.d"}
+ foo_logcheck = foo_matches[0]
+ assert foo_logcheck.name_segment is None
+ assert foo_logcheck.package_name == "foo"
+ assert foo_logcheck.architecture_restriction is None
+ assert os.path.basename(foo_logcheck.expected_path) == "logcheck.violations.d"
+
+ assert "bar" in results
+ bar_matches = results["bar"].auto_installable
+ assert {m.path.name for m in bar_matches} == {
+ "bar.logchcek.violations.d",
+ }
+ bar_logcheck = bar_matches[0]
+ assert bar_logcheck.name_segment is None
+ assert bar_logcheck.package_name == "bar"
+ assert bar_logcheck.architecture_restriction is None
+ assert os.path.basename(bar_logcheck.expected_path) == "bar.logcheck.violations.d"
+
+
+def test_debhelper_overlapping_stems() -> None:
+ plugin_metadata = plugin_metadata_for_debputys_own_plugin()
+ binary_packages = binary_package_table(
+ faked_binary_package("foo", is_main_package=True),
+ )
+ debian_dir = build_virtual_file_system(
+ [
+ "foo.user.service",
+ ]
+ )
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "service",
+ ),
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "user.service",
+ ),
+ )
+
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {
+ "foo.user.service",
+ }
+ matched_file = foo_matches[0]
+ assert matched_file.definition.stem == "user.service"
+ assert matched_file.name_segment is None
+ assert matched_file.package_name == "foo"
+ assert matched_file.architecture_restriction is None
+
+ debian_dir = build_virtual_file_system(
+ [
+ "foo.named.service",
+ ]
+ )
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"foo.named.service"}
+ matched_file = foo_matches[0]
+ assert matched_file.definition.stem == "service"
+ assert matched_file.name_segment == "named"
+ assert matched_file.package_name == "foo"
+ assert matched_file.architecture_restriction is None
+
+
+@requires_levenshtein
+def test_debhelper_overlapping_stems_typo_check() -> None:
+ plugin_metadata = plugin_metadata_for_debputys_own_plugin()
+ binary_packages = binary_package_table(
+ faked_binary_package("foo", is_main_package=True),
+ )
+ debian_dir = build_virtual_file_system(
+ [
+ "foo.uxxr.service",
+ ]
+ )
+ ppf_defs = _ppfs(
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "service",
+ ),
+ _fake_PPFClassSpec(
+ plugin_metadata,
+ "user.service",
+ ),
+ )
+ results = detect_all_packager_provided_files(
+ ppf_defs,
+ debian_dir,
+ binary_packages,
+ detect_typos=True,
+ )
+ assert "foo" in results
+ foo_matches = results["foo"].auto_installable
+ assert {m.path.name for m in foo_matches} == {"foo.uxxr.service"}
+ foo_install = foo_matches[0]
+ # While it could just as well be a named segment, we assume it is a typo.
+ assert foo_install.definition.stem == "user.service"
+ assert foo_install.name_segment is None
+ assert foo_install.package_name == "foo"
+ assert foo_install.architecture_restriction is None
+ assert os.path.basename(foo_install.expected_path) == "foo.user.service"
+
+
def _fetch_debputy_plugin_feature_set(
plugin: InitializedPluginUnderTest,
) -> PluginProvidedFeatureSet:
# Very much not public API, but we need it to avoid testing on production data (also, it is hard to find
# relevant debputy files for all the cases we want to test).
return cast("InitializedPluginUnderTestImpl", plugin)._feature_set
+
+
+def _fake_PPFClassSpec(
+ debputy_plugin_metadata: DebputyPluginMetadata,
+ stem: str,
+ *,
+ default_priority: Optional[int] = None,
+ packageless_is_fallback_for_all_packages: bool = False,
+ bug_950723: bool = False,
+ has_active_command: bool = False,
+ allow_name_segment: bool = True,
+) -> PackagerProvidedFileClassSpec:
+ return PackagerProvidedFileClassSpec(
+ debputy_plugin_metadata,
+ stem,
+ "not-a-real-ppf",
+ allow_architecture_segment=True,
+ allow_name_segment=allow_name_segment,
+ default_priority=default_priority,
+ default_mode=0o644,
+ post_formatting_rewrite=None,
+ packageless_is_fallback_for_all_packages=packageless_is_fallback_for_all_packages,
+ reservation_only=False,
+ formatting_callback=None,
+ bug_950723=bug_950723,
+ has_active_command=has_active_command,
+ )
+
+
+def _ppfs(
+ *ppfs: PackagerProvidedFileClassSpec,
+) -> Mapping[str, PackagerProvidedFileClassSpec]:
+ return {ppf.stem: ppf for ppf in ppfs}