summaryrefslogtreecommitdiffstats
path: root/src/debputy/lsp/lsp_debian_control_reference_data.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/debputy/lsp/lsp_debian_control_reference_data.py')
-rw-r--r--src/debputy/lsp/lsp_debian_control_reference_data.py2067
1 files changed, 2067 insertions, 0 deletions
diff --git a/src/debputy/lsp/lsp_debian_control_reference_data.py b/src/debputy/lsp/lsp_debian_control_reference_data.py
new file mode 100644
index 0000000..f4791cb
--- /dev/null
+++ b/src/debputy/lsp/lsp_debian_control_reference_data.py
@@ -0,0 +1,2067 @@
+import dataclasses
+import functools
+import itertools
+import textwrap
+from abc import ABC
+from enum import Enum, auto
+from typing import (
+ FrozenSet,
+ Optional,
+ cast,
+ Mapping,
+ Iterable,
+ List,
+ Generic,
+ TypeVar,
+ Union,
+)
+
+from debian.debian_support import DpkgArchTable
+from lsprotocol.types import DiagnosticSeverity, Diagnostic, DiagnosticTag
+
+from debputy.lsp.quickfixes import (
+ propose_correct_text_quick_fix,
+ propose_remove_line_quick_fix,
+)
+from debputy.lsp.text_util import (
+ normalize_dctrl_field_name,
+ LintCapablePositionCodec,
+ detect_possible_typo,
+ te_range_to_lsp,
+)
+from debputy.lsp.vendoring._deb822_repro.parsing import (
+ Deb822KeyValuePairElement,
+ LIST_SPACE_SEPARATED_INTERPRETATION,
+ Deb822ParagraphElement,
+ Deb822FileElement,
+)
+from debputy.lsp.vendoring._deb822_repro.tokens import Deb822FieldNameToken
+
+try:
+ from debputy.lsp.vendoring._deb822_repro.locatable import (
+ Position as TEPosition,
+ Range as TERange,
+ START_POSITION,
+ )
+except ImportError:
+ pass
+
+
+F = TypeVar("F", bound="Deb822KnownField")
+S = TypeVar("S", bound="StanzaMetadata")
+
+
+ALL_SECTIONS_WITHOUT_COMPONENT = frozenset(
+ [
+ "admin",
+ "cli-mono",
+ "comm",
+ "database",
+ "debian-installer",
+ "debug",
+ "devel",
+ "doc",
+ "editors",
+ "education",
+ "electronics",
+ "embedded",
+ "fonts",
+ "games",
+ "gnome",
+ "gnu-r",
+ "gnustep",
+ "graphics",
+ "hamradio",
+ "haskell",
+ "interpreters",
+ "introspection",
+ "java",
+ "javascript",
+ "kde",
+ "kernel",
+ "libdevel",
+ "libs",
+ "lisp",
+ "localization",
+ "mail",
+ "math",
+ "metapackages",
+ "misc",
+ "net",
+ "news",
+ "ocaml",
+ "oldlibs",
+ "otherosfs",
+ "perl",
+ "php",
+ "python",
+ "ruby",
+ "rust",
+ "science",
+ "shells",
+ "sound",
+ "tasks",
+ "tex",
+ "text",
+ "utils",
+ "vcs",
+ "video",
+ "virtual",
+ "web",
+ "x11",
+ "xfce",
+ "zope",
+ ]
+)
+
+ALL_COMPONENTS = frozenset(
+ [
+ "main",
+ "restricted", # Ubuntu
+ "non-free",
+ "non-free-firmware",
+ "contrib",
+ ]
+)
+
+
+def _fields(*fields: F) -> Mapping[str, F]:
+ return {normalize_dctrl_field_name(f.name.lower()): f for f in fields}
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class Keyword:
+ value: str
+ hover_text: Optional[str] = None
+ is_obsolete: bool = False
+ replaced_by: Optional[str] = None
+
+
+def _allowed_values(*values: Union[str, Keyword]) -> Mapping[str, Keyword]:
+ as_keywords = (k if isinstance(k, Keyword) else Keyword(k) for k in values)
+ return {k.value: k for k in as_keywords}
+
+
+ALL_SECTIONS = _allowed_values(
+ *[
+ s if c is None else f"{c}/{s}"
+ for c, s in itertools.product(
+ itertools.chain(cast("Iterable[Optional[str]]", [None]), ALL_COMPONENTS),
+ ALL_SECTIONS_WITHOUT_COMPONENT,
+ )
+ ]
+)
+
+
+def all_architectures_and_wildcards(arch2table) -> Iterable[Union[str, Keyword]]:
+ wildcards = set()
+ yield Keyword(
+ "any",
+ hover_text=textwrap.dedent(
+ """\
+ The package is an architecture dependent package and need to be compiled for each and every
+ architecture it.
+
+ The name `any` refers to the fact that this is an architecture *wildcard* matching
+ *any machine architecture* supported by dpkg.
+ """
+ ),
+ )
+ yield Keyword(
+ "all",
+ hover_text=textwrap.dedent(
+ """\
+ The package is an architecture independent package. This is typically fitting for packages containing
+ only scripts, data or documentation.
+
+ This name `all` refers to the fact that the package can be used for *all* architectures at the same.
+ Though note that it is still subject to the rules of the `Multi-Arch` field.
+ """
+ ),
+ )
+ for arch_name, quad_tuple in arch2table.items():
+ yield arch_name
+ cpu_wc = "any-" + quad_tuple.cpu_name
+ os_wc = quad_tuple.os_name + "-any"
+ if cpu_wc not in wildcards:
+ yield cpu_wc
+ wildcards.add(cpu_wc)
+ if os_wc not in wildcards:
+ yield os_wc
+ wildcards.add(os_wc)
+ # Add the remaining wildcards
+
+
+@functools.lru_cache
+def dpkg_arch_and_wildcards() -> FrozenSet[str]:
+ dpkg_arch_table = DpkgArchTable.load_arch_table()
+ return frozenset(all_architectures_and_wildcards(dpkg_arch_table._arch2table))
+
+
+class FieldValueClass(Enum):
+ SINGLE_VALUE = auto()
+ SPACE_SEPARATED_LIST = auto()
+ BUILD_PROFILES_LIST = auto()
+ COMMA_SEPARATED_LIST = auto()
+ COMMA_SEPARATED_EMAIL_LIST = auto()
+ FREE_TEXT_FIELD = auto()
+ DEP5_FILE_LIST = auto()
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class Deb822KnownField:
+ name: str
+ field_value_class: FieldValueClass
+ warn_if_default: bool = True
+ replaced_by: Optional[str] = None
+ deprecated_with_no_replacement: bool = False
+ missing_field_severity: Optional[DiagnosticSeverity] = None
+ default_value: Optional[str] = None
+ known_values: Optional[Mapping[str, Keyword]] = None
+ unknown_value_diagnostic_severity: Optional[DiagnosticSeverity] = (
+ DiagnosticSeverity.Error
+ )
+ hover_text: Optional[str] = None
+ spellcheck_value: bool = False
+ is_stanza_name: bool = False
+ is_single_value_field: bool = True
+
+ def field_diagnostics(
+ self,
+ kvpair: Deb822KeyValuePairElement,
+ stanza_position: "TEPosition",
+ position_codec: "LintCapablePositionCodec",
+ lines: List[str],
+ *,
+ field_name_typo_reported: bool = False,
+ ) -> Iterable[Diagnostic]:
+ field_name_token = kvpair.field_token
+ field_range_te = kvpair.range_in_parent().relative_to(stanza_position)
+ field_position_te = field_range_te.start_pos
+ yield from self._diagnostics_for_field_name(
+ field_name_token,
+ field_position_te,
+ field_name_typo_reported,
+ position_codec,
+ lines,
+ )
+ if not self.spellcheck_value:
+ yield from self._known_value_diagnostics(
+ kvpair, field_position_te, position_codec, lines
+ )
+
+ def _diagnostics_for_field_name(
+ self,
+ token: Deb822FieldNameToken,
+ token_position: "TEPosition",
+ typo_detected: bool,
+ position_codec: "LintCapablePositionCodec",
+ lines: List[str],
+ ) -> Iterable[Diagnostic]:
+ field_name = token.text
+ # Defeat the case-insensitivity from python-debian
+ field_name_cased = str(field_name)
+ token_range_server_units = te_range_to_lsp(
+ TERange.from_position_and_size(token_position, token.size())
+ )
+ token_range = position_codec.range_to_client_units(
+ lines,
+ token_range_server_units,
+ )
+ if self.deprecated_with_no_replacement:
+ yield Diagnostic(
+ token_range,
+ f"{field_name_cased} is deprecated and no longer used",
+ severity=DiagnosticSeverity.Warning,
+ source="debputy",
+ tags=[DiagnosticTag.Deprecated],
+ data=propose_remove_line_quick_fix(),
+ )
+ elif self.replaced_by is not None:
+ yield Diagnostic(
+ token_range,
+ f"{field_name_cased} is a deprecated name for {self.replaced_by}",
+ severity=DiagnosticSeverity.Warning,
+ source="debputy",
+ tags=[DiagnosticTag.Deprecated],
+ data=propose_correct_text_quick_fix(self.replaced_by),
+ )
+
+ if not typo_detected and field_name_cased != self.name:
+ yield Diagnostic(
+ token_range,
+ f"Non-canonical spelling of {self.name}",
+ severity=DiagnosticSeverity.Information,
+ source="debputy",
+ data=propose_correct_text_quick_fix(self.name),
+ )
+
+ def _known_value_diagnostics(
+ self,
+ kvpair: Deb822KeyValuePairElement,
+ field_position_te: "TEPosition",
+ position_codec: "LintCapablePositionCodec",
+ lines: List[str],
+ ) -> Iterable[Diagnostic]:
+ unknown_value_severity = self.unknown_value_diagnostic_severity
+ allowed_values = self.known_values
+ if not allowed_values:
+ return
+ hint_text = None
+ values = kvpair.interpret_as(LIST_SPACE_SEPARATED_INTERPRETATION)
+ value_off = kvpair.value_element.position_in_parent().relative_to(
+ field_position_te
+ )
+ first_value = True
+ for value_ref in values.iter_value_references():
+ value = value_ref.value
+ if (
+ not first_value
+ and self.field_value_class == FieldValueClass.SINGLE_VALUE
+ ):
+ value_loc = value_ref.locatable
+ value_position_te = value_loc.position_in_parent().relative_to(
+ value_off
+ )
+ value_range_in_server_units = te_range_to_lsp(
+ TERange.from_position_and_size(value_position_te, value_loc.size())
+ )
+ value_range = position_codec.range_to_client_units(
+ lines,
+ value_range_in_server_units,
+ )
+ yield Diagnostic(
+ value_range,
+ f"The field {self.name} can only have exactly one value.",
+ severity=DiagnosticSeverity.Error,
+ source="debputy",
+ )
+ # TODO: Add quickfix if the value is also invalid
+ continue
+ first_value = False
+
+ known_value = self.known_values.get(value)
+ if known_value is None:
+ candidates = detect_possible_typo(
+ value,
+ self.known_values,
+ )
+ if hint_text is None:
+ if len(self.known_values) < 5:
+ values = ", ".join(sorted(self.known_values))
+ hint_text = f" Known values for this field: {values}"
+ else:
+ hint_text = ""
+ fix_data = None
+ severity = unknown_value_severity
+ fix_text = hint_text
+ if candidates:
+ match = candidates[0]
+ fix_text = f' It is possible that the value is a typo of "{match}".{fix_text}'
+ fix_data = [propose_correct_text_quick_fix(m) for m in candidates]
+ elif severity is None:
+ continue
+ if severity is None:
+ severity = DiagnosticSeverity.Warning
+ message = fix_text
+ else:
+ message = f'The value "{value}" is not supported in {self.name}.{fix_text}'
+ elif known_value.is_obsolete:
+ replacement = known_value.replaced_by
+ if replacement is not None:
+ message = f'The value "{value}" has been replaced by {replacement}'
+ severity = DiagnosticSeverity.Warning
+ fix_data = [propose_correct_text_quick_fix(replacement)]
+ else:
+ message = (
+ f'The value "{value}" is obsolete without a single replacement'
+ )
+ severity = DiagnosticSeverity.Warning
+ fix_data = None
+ else:
+ # All good
+ continue
+
+ value_loc = value_ref.locatable
+ value_position_te = value_loc.position_in_parent().relative_to(value_off)
+ value_range_in_server_units = te_range_to_lsp(
+ TERange.from_position_and_size(value_position_te, value_loc.size())
+ )
+ value_range = position_codec.range_to_client_units(
+ lines,
+ value_range_in_server_units,
+ )
+ yield Diagnostic(
+ value_range,
+ message,
+ severity=severity,
+ source="debputy",
+ data=fix_data,
+ )
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class DctrlKnownField(Deb822KnownField):
+ inherits_from_source: bool = False
+
+
+SOURCE_FIELDS = _fields(
+ DctrlKnownField(
+ "Source",
+ FieldValueClass.SINGLE_VALUE,
+ missing_field_severity=DiagnosticSeverity.Error,
+ is_stanza_name=True,
+ hover_text=textwrap.dedent(
+ """\
+ Declares the name of the source package.
+
+ Note this must match the name in the first entry of debian/changelog file.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Standards-Version",
+ FieldValueClass.SINGLE_VALUE,
+ missing_field_severity=DiagnosticSeverity.Error,
+ hover_text=textwrap.dedent(
+ """\
+ Declares the last semantic version of the Debian Policy this package as last checked against.
+
+ **Example*:
+ ```
+ Standards-Version: 4.5.2
+ ```
+
+ Note that the last version part of the full Policy version (the **.X** in 4.5.2**.X**) is
+ typically omitted as it is used solely for editorial changes to the policy (e.g. typo fixes).
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Section",
+ FieldValueClass.SINGLE_VALUE,
+ known_values=ALL_SECTIONS,
+ unknown_value_diagnostic_severity=DiagnosticSeverity.Warning,
+ hover_text=textwrap.dedent(
+ """\
+ Define the default section for packages in this source package.
+
+ Example:
+ ```
+ Section: devel
+ ```
+
+ Please see https://packages.debian.org/unstable for more details about the sections.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Priority",
+ FieldValueClass.SINGLE_VALUE,
+ default_value="optional",
+ warn_if_default=False,
+ known_values=_allowed_values(
+ Keyword(
+ "required",
+ hover_text=textwrap.dedent(
+ """\
+ The package is necessary for the proper functioning of the system (read: dpkg needs it).
+
+ Applicable if dpkg *needs* this package to function and it is not a library.
+
+ No two packages that both have a priority of *standard* or higher may conflict with
+ each other.
+ """
+ ),
+ ),
+ Keyword(
+ "important",
+ hover_text=textwrap.dedent(
+ """\
+ The *important* packages are a bare minimum of commonly-expected and necessary tools.
+
+ Applicable if 99% of all users in the distribution needs this package and it is not a library.
+
+ No two packages that both have a priority of *standard* or higher may conflict with
+ each other.
+ """
+ ),
+ ),
+ Keyword(
+ "standard",
+ hover_text=textwrap.dedent(
+ """\
+ These packages provide a reasonable small but not too limited character-mode system. This is
+ what will be installed by default (by the debian-installer) if the user does not select anything
+ else. This does not include many large applications.
+
+ Applicable if your distribution installer will install this package by default on a new system
+ and it is not a library.
+
+ No two packages that both have a priority of *standard* or higher may conflict with
+ each other.
+ """
+ ),
+ ),
+ Keyword(
+ "optional",
+ hover_text="This is the default priority and used by the majority of all packages"
+ " in the Debian archive",
+ ),
+ Keyword(
+ "extra",
+ is_obsolete=True,
+ replaced_by="optional",
+ hover_text="Obsolete alias of `optional`.",
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ Define the default priority for packages in this source package.
+
+ The priority field describes how important the package is for the functionality of the system.
+
+ Example:
+ ```
+ Priority: optional
+ ```
+
+ Unless you know you need a different value, you should choose <b>optional</b> for your packages.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Maintainer",
+ FieldValueClass.SINGLE_VALUE,
+ missing_field_severity=DiagnosticSeverity.Error,
+ hover_text=textwrap.dedent(
+ """\
+ The maintainer of the package.
+
+ **Example**:
+ ```
+ Maintainer: Jane Contributor <jane@janes.email-provider.org>
+ ```
+
+ Note: If a person is listed in the Maintainer field, they should *not* be listed in Uploaders field.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Uploaders",
+ FieldValueClass.COMMA_SEPARATED_EMAIL_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Comma separated list of uploaders associated with the package.
+
+ **Example**:
+ ```
+ Uploaders:
+ John Doe &lt;john@doe.org&gt;,
+ Lisbeth Worker &lt;lis@worker.org&gt;,
+ ```
+
+ Formally uploaders are considered co-maintainers for the package with the party listed in the
+ **Maintainer** field being the primary maintainer. In practice, each maintainer or maintenance
+ team can have their own ruleset about the difference between the **Maintainer** and the
+ **Uploaders**. As an example, the Python packaging team has a different rule set for how to
+ react to a package depending on whether the packaging team is the **Maintainer** or in the
+ **Uploaders** field.
+
+ Note: If a person is listed in the Maintainer field, they should *not* be listed in Uploaders field.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Browser",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ URL to the Version control system repo used for the packaging. The URL should be usable with a
+ browser *without* requiring any login.
+
+ This should be used together with one of the other **Vcs-** fields.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Git",
+ FieldValueClass.SPACE_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable with `git clone`
+ *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+
+ Note it is possible to specify a branch via the `-b` option.
+
+ ```
+ Vcs-Git: https://salsa.debian.org/some/packaging-repo -b debian/unstable
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Svn",
+ FieldValueClass.SPACE_SEPARATED_LIST, # TODO: Might be a single value
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable with `svn checkout`
+ *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Arch",
+ FieldValueClass.SPACE_SEPARATED_LIST, # TODO: Might be a single value
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable for getting a copy of the
+ sources *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Cvs",
+ FieldValueClass.SPACE_SEPARATED_LIST, # TODO: Might be a single value
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable for getting a copy of the
+ sources *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Darcs",
+ FieldValueClass.SPACE_SEPARATED_LIST, # TODO: Might be a single value
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable for getting a copy of the
+ sources *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Hg",
+ FieldValueClass.SPACE_SEPARATED_LIST, # TODO: Might be a single value
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable for getting a copy of the
+ sources *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Vcs-Mtn",
+ FieldValueClass.SPACE_SEPARATED_LIST, # TODO: Might be a single value
+ hover_text=textwrap.dedent(
+ """\
+ URL to the git repo used for the packaging. The URL should be usable for getting a copy of the
+ sources *without* requiring any login.
+
+ This should be used together with the **Vcs-Browser** field provided there is a web UI for the repo.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "DM-Upload-Allowed",
+ FieldValueClass.SINGLE_VALUE,
+ deprecated_with_no_replacement=True,
+ default_value="no",
+ known_values=_allowed_values("yes", "no"),
+ hover_text=textwrap.dedent(
+ """\
+ Obsolete field
+
+ It was used to enabling Debian Maintainers to upload the package without requiring a Debian Developer
+ to sign the package. This mechanism has been replaced by a new authorization mechanism.
+
+ Please see https://lists.debian.org/debian-devel-announce/2012/09/msg00008.html for details about the
+ replacement.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Depends",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ All minimum build-dependencies for this source package. Needed for any target including **clean**.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Depends-Arch",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Build-dependencies required for building the architecture dependent binary packages of this source
+ package.
+
+ These build-dependencies must be satisfied when executing the **build-arch** and **binary-arch**
+ targets either directly or indirectly in addition to those listed in **Build-Depends**.
+
+ Note that these dependencies are <em>not</em> available during **clean**.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Depends-Indep",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Build-dependencies required for building the architecture independent binary packages of this source
+ package.
+
+ These build-dependencies must be satisfied when executing the **build-indep** and **binary-indep**
+ targets either directly or indirectly in addition to those listed in **Build-Depends**.
+
+ Note that these dependencies are <em>not</em> available during **clean**.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Conflicts",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Packages that must **not** be installed during **any** part of the build, including the **clean**
+ target **clean**.
+
+ Where possible, it is often better to configure the build so that it does not react to the package
+ being present in the first place. Usually this is a question of using a `--without-foo` or
+ `--disable-foo` or such to the build configuration.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Conflicts-Arch",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Packages that must **not** be installed during the **build-arch** or **binary-arch** targets.
+ This also applies when these targets are run implicitly such as via the **binary** target.
+
+ Where possible, it is often better to configure the build so that it does not react to the package
+ being present in the first place. Usually this is a question of using a `--without-foo` or
+ `--disable-foo` or such to the build configuration.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Conflicts-Indep",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Packages that must **not** be installed during the **build-indep** or **binary-indep** targets.
+ This also applies when these targets are run implicitly such as via the **binary** target.
+
+ Where possible, it is often better to configure the build so that it does not react to the package
+ being present in the first place. Usually this is a question of using a `--without-foo` or
+ `--disable-foo` or such to the build configuration.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Testsuite",
+ FieldValueClass.SPACE_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Declares that this package provides or should run install time tests via `autopkgtest`.
+
+ This field can be used to request an automatically generated autopkgtests via the **autodep8** package.
+ Please refer to the documentation of the **autodep8** package for which values you can put into
+ this field and what kind of testsuite the keywords will provide.
+
+ Declaring this field in *debian/control* is only necessary when you want additional tests beyond
+ those in *debian/tests/control* as **dpkg** automatically records the package provided ones from
+ *debian/tests/control*.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Homepage",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ Link to the upstream homepage for this source package.
+
+ **Example**:
+ ```
+ Homepage: https://www.janes-tools.org/frob-cleaner
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Rules-Requires-Root",
+ FieldValueClass.SPACE_SEPARATED_LIST,
+ unknown_value_diagnostic_severity=None,
+ known_values=_allowed_values(
+ Keyword(
+ "no",
+ hover_text=textwrap.dedent(
+ """\
+ The build process will not require root or fakeroot during any step. This enables
+ dpkg-buildpackage and debhelper to perform several optimizations during the build.
+
+ This is the default with dpkg-build-api at version 1 or later.
+ """
+ ),
+ ),
+ Keyword(
+ "no",
+ hover_text=textwrap.dedent(
+ """\
+ The build process assumes that dpkg-buildpackage will run the relevant binary
+ target with root or fakeroot. This was the historical default behaviour.
+
+ This is the default with dpkg-build-api at version 0.
+ """
+ ),
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ Declare if and when the package build assumes it is run as root or fakeroot.
+
+ Most packages do not need to run as root or fakeroot and the legacy behaviour comes with a
+ performance cost. This field can be used to explicitly declare that the legacy behaviour is
+ unnecessary.
+
+ **Example:**
+ ```
+ Rules-Requires-Root: no
+ ```
+
+ Setting this field to `no` *can* cause the package to stop building if it requires root.
+ Depending on the situation, it might require some trivial or some complicated changes to fix that.
+ If it breaks and you cannot figure out how to fix it, then reset the field to `binary-targets`
+ and move on until you have time to fix it.
+
+ The default value for this field depends on the ``dpkg-build-api`` version. If the package
+ `` Build-Depends`` on ``dpkg-build-api (>= 1)`` or later, the default is ``no``. Otherwise,
+ the default is ``binary-target``
+
+ Note it is **not** possible to require running the package as "true root".
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Bugs",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ Provide a custom bug tracker URL
+
+ This field is *not* used by packages uploaded to Debian or most derivatives as the distro tooling
+ has a default bugtracker built-in. It is primarily useful for third-party provided packages such
+ that bug reporting tooling can redirect the user to their bug tracker.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Origin",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ Declare the origin of the package.
+
+ This field is *not* used by packages uploaded to Debian or most derivatives as the origin would
+ be the distribution. It is primarily useful for third-party provided packages as some tools will
+ detect this field.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "X-Python-Version",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ replaced_by="X-Python3-Version",
+ hover_text=textwrap.dedent(
+ """\
+ Obsolete field for declaring the supported Python2 versions
+
+ Since Python2 is no longer supported, this field is now redundant. For Python3, the field is
+ called **X-Python3-Version**.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "X-Python3-Version",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ # Too lazy to provide a better description
+ """\
+ For declaring the supported Python3 versions
+
+ This is used by the tools from `dh-python` package. Please see the documentation of that package
+ for when and how to use it.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "XS-Autobuild",
+ FieldValueClass.SINGLE_VALUE,
+ known_values=_allowed_values("yes"),
+ hover_text=textwrap.dedent(
+ """\
+ Used for non-free packages to denote that they may be auto-build on the Debian build infrastructure
+
+ Note that adding this field **must** be combined with following the instructions at
+ https://www.debian.org/doc/manuals/developers-reference/pkgs.html#non-free-buildd
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Description",
+ FieldValueClass.FREE_TEXT_FIELD,
+ spellcheck_value=True,
+ hover_text=textwrap.dedent(
+ """\
+ This field contains a human-readable description of the package. However, it is not used directly.
+
+ Binary packages can reference parts of it via the `${source:Synopsis}` and the
+ `${source:Extended-Description}` substvars. Without any of these substvars, the `Description` field
+ of the `Source` stanza remains unused.
+
+ The first line immediately after the field is called the *Synopsis* and is a short "noun-phrase"
+ intended to provide a one-line summary of a package. The lines after the **Synopsis** is known
+ as the **Extended Description** and is intended as a longer summary of a package.
+
+ **Example:**
+ ```
+ Description: documentation generator for Python projects
+ Sphinx is a tool for producing documentation for Python projects, using
+ reStructuredText as markup language.
+ .
+ Sphinx features:
+ * HTML, CHM, LaTeX output,
+ * Cross-referencing source code,
+ * Automatic indices,
+ * Code highlighting, using Pygments,
+ * Extensibility. Existing extensions:
+ - automatic testing of code snippets,
+ - including docstrings from Python modules.
+ .
+ Build-depend on sphinx if your package uses /usr/bin/sphinx-*
+ executables. Build-depend on python3-sphinx if your package uses
+ the Python API (for instance by calling python3 -m sphinx).
+ ```
+
+ The **Synopsis** is usually displayed in cases where there is limited space such as when reviewing
+ the search results from `apt search foo`. It is often a good idea to imagine that the **Synopsis**
+ part is inserted into a sentence like "The package provides {{Synopsis-goes-here}}". The
+ **Extended Description** is a standalone description that should describe what the package does and
+ how it relates to the rest of the system (in terms of, for example, which subsystem it is which part of).
+ Please see https://www.debian.org/doc/debian-policy/ch-controlfields.html#description for more details
+ about the description field and suggestions for how to write it.
+ """
+ ),
+ ),
+)
+
+BINARY_FIELDS = _fields(
+ DctrlKnownField(
+ "Package",
+ FieldValueClass.SINGLE_VALUE,
+ is_stanza_name=True,
+ missing_field_severity=DiagnosticSeverity.Error,
+ hover_text="Declares the name of a binary package",
+ ),
+ DctrlKnownField(
+ "Package-Type",
+ FieldValueClass.SINGLE_VALUE,
+ default_value="deb",
+ known_values=_allowed_values(
+ Keyword("deb", hover_text="The package will be built as a regular deb."),
+ Keyword(
+ "udeb",
+ hover_text="The package will be built as a micro-deb (also known as a udeb). These are solely used by the debian-installer.",
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ **Special-purpose only**. *This field is a special purpose field and is rarely needed.*
+ *You are recommended to omit unless you know you need it or someone told you to use it.*
+
+ Determines the type of package. This field can be used to declare that a given package is a different
+ type of package than usual. The primary case where this is known to be useful is for building
+ micro-debs ("udeb") to be consumed by the debian-installer.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Architecture",
+ FieldValueClass.SPACE_SEPARATED_LIST,
+ missing_field_severity=DiagnosticSeverity.Error,
+ unknown_value_diagnostic_severity=None,
+ known_values=_allowed_values(*dpkg_arch_and_wildcards()),
+ hover_text=textwrap.dedent(
+ """\
+ Determines which architectures this package can be compiled for or if it is an architecture-independent
+ package. The value is a space-separated list of dpkg architecture names or wildcards.
+
+ **Example**:
+ ```
+ Package: architecture-specific-package
+ Architecture: any
+ # ...
+
+
+ Package: data-only-package
+ Architecture: all
+ Multi-Arch: foreign
+ # ...
+
+
+ Package: linux-only-package
+ Architecture: linux-any
+ # ...
+ ```
+
+ When in doubt, stick to the values **all** (for scripts, data or documentation, etc.) or **any**
+ (for anything that can be compiled). For official Debian packages, it is often easier to attempt the
+ compilation for unsupported architectures than to maintain the list of machine architectures that work.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Essential",
+ FieldValueClass.SINGLE_VALUE,
+ default_value="no",
+ known_values=_allowed_values(
+ Keyword(
+ "yes",
+ hover_text="The package is essential and uninstalling it will completely and utterly break the"
+ " system beyond repair.",
+ ),
+ Keyword(
+ "no",
+ hover_text=textwrap.dedent(
+ """\
+ The package is a regular package. This is the default and recommended.</p>
+
+ Note that declaring a package to be "Essential: no" is the same as not having the field except omitting
+ the field wastes fewer bytes on everyone's hard disk.
+ """
+ ),
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ **Special-purpose only**. *This field is a special purpose field and is rarely needed.*
+ *You are recommended to omit unless you know you need it or someone told you to use it.*
+
+ Whether the package should be considered Essential as defined by Debian Policy.
+
+ Essential packages are subject to several distinct but very important rules:
+
+ * Essential packages are considered essential for the system to work. The packaging system
+ (APT and dpkg) will refuse to uninstall it without some very insisting force options and warnings.
+
+ * Other packages are not required to declare explicit dependencies on essential packages as a
+ side-effect of the above except as to ensure a that the given essential package is upgraded
+ to a given minimum version.
+
+ * Once installed, essential packages function must at all time no matter where dpkg is in its
+ installation or upgrade process. During bootstrapping or installation, this requirement is
+ relaxed.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "XB-Important",
+ FieldValueClass.SINGLE_VALUE,
+ replaced_by="Protected",
+ default_value="no",
+ known_values=_allowed_values(
+ Keyword(
+ "yes",
+ hover_text="The package is protected and attempts to uninstall it will cause strong warnings to the"
+ " user that they might be breaking the system.",
+ ),
+ Keyword(
+ "no",
+ hover_text=textwrap.dedent(
+ """\
+ The package is a regular package. This is the default and recommended.</p>
+
+ Note that declaring a package to be `XB-Important: no` is the same as not having the field
+ except omitting the field wastes fewer bytes on everyone's hard-disk.
+ """
+ ),
+ ),
+ ),
+ ),
+ DctrlKnownField(
+ "Protected",
+ FieldValueClass.SINGLE_VALUE,
+ default_value="no",
+ known_values=_allowed_values(
+ Keyword(
+ "yes",
+ hover_text="The package is protected and attempts to uninstall it will cause strong warnings to the"
+ " user that they might be breaking the system.",
+ ),
+ Keyword(
+ "no",
+ hover_text=textwrap.dedent(
+ """\
+ The package is a regular package. This is the default and recommended.</p>
+
+ Note that declaring a package to be `Protected: no` is the same as not having the field
+ except omitting the field wastes fewer bytes on everyone's hard-disk.
+ """
+ ),
+ ),
+ ),
+ ),
+ DctrlKnownField(
+ "Pre-Depends",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ **Advanced field**. *This field covers an advanced topic. If you are new to packaging, you are*
+ *probably not looking for this field (except to set a **${misc:Pre-Depends}** relation. Incorrect use*
+ *of this field can cause issues - among other causing issues during upgrades that users cannot work*
+ *around without passing `--force-*` options to dpkg.*
+
+ This field is like *Depends*, except that is also forces dpkg to complete installation of the packages
+ named before even starting the installation of the package which declares the pre-dependency.
+
+ **Example**:
+ ```
+ Pre-Depends: ${misc:Pre-Depends}
+ ```
+
+ Note this is a very strong dependency and not all packages support being a pre-dependency because it
+ puts additional requirements on the package being depended on. Use of **${misc:Pre-Depends}** is
+ pre-approved and recommended. Essential packages are known to support being in **Pre-Depends**.
+ However, careless use of **Pre-Depends** for essential packages can still cause dependency resolvers
+ problems.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Depends",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Lists the packages that must be installed, before this package is installed.
+
+ **Example**:
+ ```
+ Package: foo
+ Architecture: any
+ Depends: ${misc:Depends},
+ ${shlibs:Depends},
+ libfoo1 (= ${binary:Version}),
+ foo-data (= ${source:Version}),
+ ```
+
+ This field declares an absolute dependency. Before installing the package, **dpkg** will require
+ all dependencies to be in state `configured` first. Though, if there is a circular dependency between
+ two or more packages, **dpkg** will break that circle at an arbitrary point where necessary based on
+ built-in heuristics.
+
+ This field should be used if the depended-on package is required for the depending package to provide a
+ *significant amount of functionality* or when it is used in the **postinst** or **prerm** maintainer
+ scripts.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Recommends",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Lists the packages that *should* be installed when this package is installed in all but
+ *unusual installations*.</p>
+
+ **Example**:
+ ```
+ Recommends: foo-optional
+ ```
+
+ By default, APT will attempt to install recommends unless they cannot be installed or the user
+ has configured APT skip recommends. Notably, during automated package builds for the Debian
+ archive, **Recommends** are **not** installed.
+
+ As implied, the package must have some core functionality that works **without** the
+ **Recommends** being satisfied as they are not guaranteed to be there. If the package cannot
+ provide any functionality without a given package, that package should be in **Depends**.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Suggests",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Lists the packages that may make this package more useful but not installing them is perfectly
+ reasonable as well. Suggests can also be useful for add-ons that only make sense in particular
+ corner cases like supporting a non-standard file format.
+
+ **Example**:
+ ```
+ Suggests: bar
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Enhances",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ This field is similar to Suggests but works in the opposite direction. It is used to declare that
+ this package can enhance the functionality of another package.
+
+ **Example**:
+ ```
+ Package: foo
+ Provide: debputy-plugin-foo
+ Enhances: debputy
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Provides",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ Declare this package also provide one or more other packages. This means that this package can
+ substitute for the provided package in some relations.
+
+ *Example*:
+ ```
+ Package: foo
+ ...
+
+ Package: foo-plus
+ Provides: foo (= ${source:Upstream-Version})
+ ```
+
+ If the provides relation is versioned, it must use a "strictly equals" version. If it does not
+ declare a version, then it *cannot* be used to satisfy a dependency with a version restriction.
+ Consider the following example:
+
+ **Archive scenario*: (This is *not* a debian/control file, despite the resemblance)
+ ```
+ Package foo
+ Depends: bar (>= 1.0)
+
+ Package: bar
+ Version: 0.9
+
+ Package: bar-plus
+ Provides: bar (= 1.0)
+
+ Package: bar-clone
+ Provides: bar
+ ```
+
+ In this archive scenario, the `bar-plus` package will satisfy the dependency of `foo` as the
+ only one. The `bar` package fails because the version is only *0.9* and `bar-clone` because
+ the provides is unversioned, but the dependency clause is versioned.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Conflicts",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ **Warning**: *You may be looking for Breaks instead of Conflicts*.
+
+ This package cannot be installed together with the packages listed in the Conflicts field. This
+ is a *bigger hammer* than **Breaks** and is used sparingly. Notably, if you want to do a versioned
+ **Conflicts** then you *almost certainly* want **Breaks** instead.
+
+ **Example**:
+ ```
+ Conflicts: bar
+ ```
+
+ Please check the description of the **Breaks** field for when you would use **Breaks** vs.
+ **Conflicts**.
+
+ Note if a package conflicts with itself (indirectly or via **Provides**), then it is using a
+ special rule for **Conflicts**. See section
+ 7.6.2 "[Replacing whole packages, forcing their removal]" in the Debian Policy Manual.
+
+ [Replacing whole packages, forcing their removal]: https://www.debian.org/doc/debian-policy/ch-relationships.html#replacing-whole-packages-forcing-their-removal
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Breaks",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ This package cannot be installed together with the packages listed in the `Breaks` field.
+
+ This is often use to declare versioned issues such as "This package does not work with foo if
+ it is version 1.0 or less". In comparison, `Conflicts` is generally used to declare that
+ "This package does not work at all as long as foo is installed".
+
+ **Example**:
+ ```
+ Breaks: bar (<= 1.0~)
+ ````
+
+ **Breaks vs. Conflicts**:
+
+ * I moved files from **foo** to **bar** in version X, what should I do?
+
+ Add `Breaks: foo (<< X~)` + `Replaces: foo (<< X~)` to **bar**
+
+ * Upgrading **bar** while **foo** is version X or less causes problems **foo** or **bar** to break.
+ How do I solve this?
+
+ Add `Breaks: foo (<< X~)` to **bar**
+
+ * The **foo** and **bar** packages provide the same functionality (interface) but different
+ implementations and there can be at most one of them. What should I do?
+
+ See section 7.6.2 [Replacing whole packages, forcing their removal] in the Debian Policy Manual.
+
+ * How to handle when **foo** and **bar** packages are unrelated but happen to provide the same binary?
+
+ Attempt to resolve the name conflict by renaming the clashing files in question on either (or both) sides.
+
+ Note the use of *~* in version numbers in the answers are generally used to ensure this works correctly in
+ case of a backports (in the Debian archive), where the package is rebuilt with the "~bpo" suffix in its
+ version.
+
+ [Replacing whole packages, forcing their removal]: https://www.debian.org/doc/debian-policy/ch-relationships.html#replacing-whole-packages-forcing-their-removal
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Replaces",
+ FieldValueClass.COMMA_SEPARATED_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ This package either replaces another package or overwrites files that used to be provided by
+ another package.
+
+ **Attention**: The `Replaces` field is **always** used with either `Breaks` or `Conflicts` field.
+
+ **Example**:
+ ```
+ Package: foo
+ ...
+
+ # The foo package was split to move data files into foo-data in version 1.2-3
+ Package: foo-data
+ Replaces: foo (<< 1.2-3~)
+ Breaks: foo (<< 1.2-3~)
+ ```
+
+ Please check the description of the `Breaks` field for when you would use `Breaks` vs. `Conflicts`.
+ It also covers common uses of `Replaces`.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Build-Profiles",
+ FieldValueClass.BUILD_PROFILES_LIST,
+ hover_text=textwrap.dedent(
+ """\
+ **Advanced field**. *This field covers an advanced topic. If you are new to packaging, you are*
+ *advised to leave it at its default until you have a working basic package or lots of time to understand*
+ *this topic.*
+
+ Declare that the package will only built when the given build-profiles are satisfied.
+
+ This field is primarily used in combination with build profiles inside the build dependency related fields
+ to reduce the number of build dependencies required during bootstrapping of a new architecture.
+
+ **Example*:
+ ```
+ Package: foo
+ ...
+
+ Package: foo-udeb
+ Package-Type: udeb
+ # Skip building foo-udeb when the build profile "noudeb" is set (e.g., via dpkg-buildpackage -Pnoudeb)
+ Build-Profiles: <!noudeb>
+ ```
+
+ Note that there is an official list of "common" build profiles with predefined purposes along with rules
+ for how and when the can be used. This list can be found at
+ https://wiki.debian.org/BuildProfileSpec#Registered_profile_names.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Section",
+ FieldValueClass.SINGLE_VALUE,
+ missing_field_severity=DiagnosticSeverity.Error,
+ inherits_from_source=True,
+ known_values=ALL_SECTIONS,
+ unknown_value_diagnostic_severity=DiagnosticSeverity.Warning,
+ hover_text=textwrap.dedent(
+ """\
+ Define the section for this package.
+
+ Example:
+ ```
+ Section: devel
+ ```
+
+ Please see https://packages.debian.org/unstable for more details about the sections.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Priority",
+ FieldValueClass.SINGLE_VALUE,
+ default_value="optional",
+ warn_if_default=False,
+ missing_field_severity=DiagnosticSeverity.Error,
+ inherits_from_source=True,
+ known_values=_allowed_values(
+ Keyword(
+ "required",
+ hover_text=textwrap.dedent(
+ """\
+ The package is necessary for the proper functioning of the system (read: dpkg needs it).
+
+ Applicable if dpkg *needs* this package to function and it is not a library.
+
+ No two packages that both have a priority of *standard* or higher may conflict with
+ each other.
+ """
+ ),
+ ),
+ Keyword(
+ "important",
+ hover_text=textwrap.dedent(
+ """\
+ The *important* packages are a bare minimum of commonly-expected and necessary tools.
+
+ Applicable if 99% of all users in the distribution needs this package and it is not a library.
+
+ No two packages that both have a priority of *standard* or higher may conflict with
+ each other.
+ """
+ ),
+ ),
+ Keyword(
+ "standard",
+ hover_text=textwrap.dedent(
+ """\
+ These packages provide a reasonable small but not too limited character-mode system. This is
+ what will be installed by default (by the debian-installer) if the user does not select anything
+ else. This does not include many large applications.
+
+ Applicable if your distribution installer will install this package by default on a new system
+ and it is not a library.
+
+ No two packages that both have a priority of *standard* or higher may conflict with
+ each other.
+ """
+ ),
+ ),
+ Keyword(
+ "optional",
+ hover_text="This is the default priority and used by the majority of all packages"
+ " in the Debian archive",
+ ),
+ Keyword(
+ "extra",
+ is_obsolete=True,
+ replaced_by="optional",
+ hover_text="Obsolete alias of `optional`.",
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ Define the priority this package.
+
+ The priority field describes how important the package is for the functionality of the system.
+
+ Example:
+ ```
+ Priority: optional
+ ```
+
+ Unless you know you need a different value, you should choose <b>optional</b> for your packages.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Multi-Arch",
+ FieldValueClass.SINGLE_VALUE,
+ # Explicit "no" tends to be used as "someone reviewed this and concluded no", so we do
+ # not warn about it being explicitly "no".
+ warn_if_default=False,
+ default_value="no",
+ known_values=_allowed_values(
+ Keyword(
+ "no",
+ hover_text=textwrap.dedent(
+ """\
+ The default. The package can be installed for at most one architecture at the time. It can
+ *only* satisfy relations for the same architecture as itself. Note that `Architecture: all`
+ packages are considered as a part of the system's "primary" architecture (see output of
+ `dpkg --print-architecture`).
+
+ Note: Despite the "no", the package *can* be installed for a foreign architecture (as an example,
+ you can install a 32-bit version of a package on a 64-bit system). However, packages depending
+ on it must also be installed for the foreign architecture.
+ """
+ ),
+ ),
+ Keyword(
+ "foreign",
+ hover_text=textwrap.dedent(
+ """\
+ The package can be installed for at most one architecture at the time. However, it can
+ satisfy relations for packages regardless of their architecture. This is often useful for packages
+ solely providing data or binaries that have "Multi-Arch neutral interfaces".
+
+ Sadly, describing a "Multi-Arch neutral interface" is hard and often only done by Multi-Arch
+ experts on a case-by-case basis. Some programs and scripts have "Multi-Arch dependent interfaces"
+ and are not safe to declare as `Multi-Arch: foreign`.
+
+ The name "foreign" refers to the fact that the package can satisfy relations for native
+ *and foreign* architectures at the same time.
+ """
+ ),
+ ),
+ Keyword(
+ "same",
+ hover_text=textwrap.dedent(
+ """\
+ The same version of the package can be co-installed for multiple architecture. However,
+ for this to work, the package *must* ship all files in architecture unique paths (usually
+ beneath `/usr/lib/<DEB_HOST_MULTIARCH>`) or have bit-for-bit identical content
+ in files that are in non-architecture unique paths (such as files beneath `/usr/share/doc`).
+
+ The name `same` refers to the fact that the package can satisfy relations only for the `same`
+ architecture as itself. However, in this case, it is co-installable with itself as noted above.
+ Note: This value **cannot** be used with `Architecture: all`.
+ """
+ ),
+ ),
+ Keyword(
+ "allowed",
+ hover_text=textwrap.dedent(
+ """\
+ **Advanced value**. The package is *not* co-installable with itself but can satisfy Multi-Arch
+ foreign and Multi-Arch same relations at the same. This is useful for implementations of
+ scripting languages (such as Perl or Python). Here the interpreter contextually need to
+ satisfy some relations as `Multi-Arch: foreign` and others as `Multi-Arch: same`.
+
+ Typically, native extensions or plugins will need a `Multi-Arch: same`-relation as they only
+ work with the interpreter compiled for the same machine architecture as themselves whereas
+ scripts are usually less picky and can rely on the `Multi-Arch: foreign` relation. Packages
+ wanting to rely on the "Multi-Arch: foreign" interface must explicitly declare this adding a
+ `:any` suffix to the package name in the dependency relation (e.g. `Depends: python3:any`).
+ However, the `:any"`suffix cannot be used unconditionally and should not be used unless you
+ know you need it.
+ """
+ ),
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ **Advanced field**. *This field covers an advanced topic. If you are new to packaging, you are*
+ *advised to leave it at its default until you have a working basic package or lots of time to understand*
+ *this topic.*
+
+ This field is used to declare the Multi-Arch interface of the package.
+
+ The `Multi-Arch` field is used to inform the installation system (APT and dpkg) about how it should handle
+ dependency relations involving this package and foreign architectures. This is useful for multiple purposes
+ such as cross-building without emulation and installing 32-bit packages on a 64-bit system. The latter is
+ often done to use legacy apps or old games that was never ported to 64-bit machines.
+
+ **Example**:
+ ```
+ Multi-Arch: foreign
+ ```
+
+ The rules for `Multi-Arch` can be quite complicated, but in many cases the following simple rules of thumb
+ gets you a long way:
+
+ * If the [Multi-Arch hinter] comes with a hint, then it almost certainly correct. You are recommended
+ to check the hint for further details (some changes can be complicated to do). Note that the
+ Multi-Arch hinter is only run for official Debian packages and may not be applicable to your case.
+
+ * If you have an `Architecture: all` data-only package, then it often want to be `Multi-Arch: foreign`
+
+ * If you have an architecture dependent package, where everything is installed in
+ `/usr/lib/${DEB_HOST_MULTIARCH}` (plus a bit of standard documentation in `/usr/share/doc`), then
+ you *probably* want `Multi-Arch: same`
+
+ * If none of the above applies, then omit the field unless you know what you are doing or you are
+ receiving advice from a Multi-Arch expert.
+
+
+ There are 4 possible values for the Multi-Arch field, though not all values are applicable to all packages:
+
+
+ * `no` - The default. The package can be installed for at most one architecture at the time. It can
+ *only* satisfy relations for the same architecture as itself. Note that `Architecture: all` packages
+ are considered as a part of the system's "primary" architecture (see output of `dpkg --print-architecture`).
+
+ Use of an explicit `no` over omitting the field is commonly done to signal that someone took the
+ effort to understand the situation and concluded `no` was the right answer.
+
+ Note: Despite the `no`, the package *can* be installed for a foreign architecture (e.g. you can
+ install a 32-bit version of a package on a 64-bit system). However, packages depending on it must also
+ be installed for the foreign architecture.
+
+
+ * `foreign` - The package can be installed for at most one architecture at the time. However, it can
+ satisfy relations for packages regardless of their architecture. This is often useful for packages
+ solely providing data or binaries that have "Multi-Arch neutral interfaces". Sadly, describing
+ a "Multi-Arch neutral interface" is hard and often only done by Multi-Arch experts on a case-by-case
+ basis. Among other, scripts despite being the same on all architectures can still have a "non-neutral"
+ "Multi-Arch" interface if their output is architecture dependent or if they dependencies force them
+ out of the `foreign` role. The dependency issue usually happens when depending indirectly on an
+ `Multi-Arch: allowed` package.
+
+ Some programs are have "Multi-Arch dependent interfaces" and are not safe to declare as
+ `Multi-Arch: foreign`. The name `foreign` refers to the fact that the package can satisfy relations
+ for native *and foreign* architectures at the same time.
+
+
+ * `same` - The same version of the package can be co-installed for multiple architecture. However,
+ for this to work, the package **must** ship all files in architecture unique paths (usually
+ beneath `/usr/lib/${DEB_HOST_MULTIARCH}`) **or** have bit-for-bit identical content in files
+ that are in non-architecture unique paths (e.g. `/usr/share/doc`). Note that these packages
+ typically do not contain configuration files or **dpkg** `conffile`s.
+
+ The name `same` refers to the fact that the package can satisfy relations only for the "same"
+ architecture as itself. However, in this case, it is co-installable with itself as noted above.
+
+ Note: This value **cannot** be used with `Architecture: all`.
+
+
+ * `allowed` - **Advanced value**. This value is for a complex use-case that most people does not
+ need. Consider it only if none of the other values seem to do the trick.
+
+ The package is **NOT** co-installable with itself but can satisfy Multi-Arch foreign and Multi-Arch same
+ relations at the same. This is useful for implementations of scripting languages (e.g. Perl or Python).
+ Here the interpreter contextually need to satisfy some relations as `Multi-Arch: foreign` and others as
+ `Multi-Arch: same` (or `Multi-Arch: no`).
+
+ Typically, native extensions or plugins will need a `Multi-Arch: same`-relation as they only work with
+ the interpreter compiled for the same machine architecture as themselves whereas scripts are usually
+ less picky and can rely on the `Multi-Arch: foreign` relation. Packages wanting to rely on the
+ `Multi-Arch: foreign` interface must explicitly declare this adding a `:any` suffix to the package name
+ in the dependency relation (such as `Depends: python3:any`). However, the `:any` suffix cannot be used
+ unconditionally and should not be used unless you know you need it.
+
+ Note that depending indirectly on a `Multi-Arch: allowed` package can require a `Architecture: all` +
+ `Multi-Arch: foreign` package to be converted to a `Architecture: any` package. This case is named
+ the "Multi-Arch interpreter problem", since it is commonly seen with script interpreters. However,
+ despite the name, it can happen to any kind of package. The bug [Debian#984701] is an example of
+ this happen in practice.
+
+ [Multi-Arch hinter]: https://wiki.debian.org/MultiArch/Hints
+ [Debian#984701]: https://bugs.debian.org/984701
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "X-DH-Build-For-Type",
+ FieldValueClass.SINGLE_VALUE,
+ default_value="host",
+ known_values=_allowed_values(
+ Keyword(
+ "host",
+ hover_text="The package should be compiled for `DEB_HOST_TARGET` (the default).",
+ ),
+ Keyword(
+ "target",
+ hover_text="The package should be compiled for `DEB_TARGET_ARCH`.",
+ ),
+ ),
+ hover_text=textwrap.dedent(
+ """\
+ **Special-purpose only**. *This field is a special purpose field and is rarely needed.*
+ *You are recommended to omit unless you know you need it or someone told you to use it.*
+
+ This field is used when building a cross-compiling C-compiler (or similar cases), some packages need
+ to be build for target (DEB_**TARGET**_ARCH) rather than the host (DEB_**HOST**_ARCH) architecture.
+
+ **Example**:
+ ```
+ Package: gcc
+ Architecture: any
+ # ...
+
+ Package: libgcc-s1
+ Architecture: any
+ # When building a cross-compiling gcc, then this library needs to be built for the target architecture
+ # as binaries compiled by gcc will link with this library.
+ X-DH-Build-For-Type: target
+ # ...
+ ```
+
+ If you are in doubt, then you probably do **not** need this field.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "X-Time64-Compat",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ Special purpose field renamed to the 64-bit time transition.
+
+ It is used to inform packaging helpers what the original (non-transitioned) package name
+ was when the auto-detection is inadequate. The non-transitioned package name is then
+ conditionally provided in the `${t64:Provides}` substitution variable.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Homepage",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ Link to the upstream homepage for this binary package.
+
+ This field is rarely used in Package stanzas as most binary packages should have the
+ same homepage as the source package. Though, in the exceptional case where a particular
+ binary package should have a more specific homepage than the source package, you can
+ use this field to override the source package field.
+ ```
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "Description",
+ FieldValueClass.FREE_TEXT_FIELD,
+ spellcheck_value=True,
+ # It will build just fine. But no one will know what it is for, so it probably won't be installed
+ missing_field_severity=DiagnosticSeverity.Warning,
+ hover_text=textwrap.dedent(
+ """\
+ A human-readable description of the package. This field consists of two related but distinct parts.
+
+
+ The first line immediately after the field is called the *Synopsis* and is a short "noun-phrase"
+ intended to provide a one-line summary of the package. The lines after the **Synopsis** is known
+ as the **Extended Description** and is intended as a longer summary of the package.
+
+ **Example:**
+ ```
+ Description: documentation generator for Python projects
+ Sphinx is a tool for producing documentation for Python projects, using
+ reStructuredText as markup language.
+ .
+ Sphinx features:
+ * HTML, CHM, LaTeX output,
+ * Cross-referencing source code,
+ * Automatic indices,
+ * Code highlighting, using Pygments,
+ * Extensibility. Existing extensions:
+ - automatic testing of code snippets,
+ - including docstrings from Python modules.
+ .
+ Build-depend on sphinx if your package uses /usr/bin/sphinx-*
+ executables. Build-depend on python3-sphinx if your package uses
+ the Python API (for instance by calling python3 -m sphinx).
+ ```
+
+ The **Synopsis** is usually displayed in cases where there is limited space such as when reviewing
+ the search results from `apt search foo`. It is often a good idea to imagine that the **Synopsis**
+ part is inserted into a sentence like "The package provides {{Synopsis-goes-here}}". The
+ **Extended Description** is a standalone description that should describe what the package does and
+ how it relates to the rest of the system (in terms of, for example, which subsystem it is which part of).
+ Please see https://www.debian.org/doc/debian-policy/ch-controlfields.html#description for more details
+ about the description field and suggestions for how to write it.
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "XB-Cnf-Visible-Pkgname",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ **Special-case field**: *This field is only useful in very special circumstances.*
+ *Consider whether you truly need it before adding this field.*
+
+ This field is used by `command-not-found` and can be used to override which package
+ `command-not-found` should propose the user to install.
+
+ Normally, when `command-not-found` detects a missing command, it will suggest the
+ user to install the package name listed in the `Package` field. In most cases, this
+ is what you want. However, in certain special-cases, the binary is provided by a
+ minimal package for technical reasons (like `python3-minimal`) and the user should
+ really install a package that provides more features (such as `python3` to follow
+ the example).
+
+ **Example**:
+ ```
+ Package: python3-minimal
+ XB-Cnf-Visible-Pkgname: python3
+ ```
+
+ Related bug: https://bugs.launchpad.net/ubuntu/+source/python-defaults/+bug/1867157
+ """
+ ),
+ ),
+ DctrlKnownField(
+ "X-DhRuby-Root",
+ FieldValueClass.SINGLE_VALUE,
+ hover_text=textwrap.dedent(
+ """\
+ Used by `dh_ruby` to request "multi-binary" layout and where the root for the given
+ package is.
+
+ Please refer to the documentation of `dh_ruby` for more details.
+
+ https://manpages.debian.org/dh_ruby
+ """
+ ),
+ ),
+)
+_DEP5_HEADER_FIELDS = _fields(
+ Deb822KnownField(
+ "Format",
+ FieldValueClass.SINGLE_VALUE,
+ is_stanza_name=True,
+ missing_field_severity=DiagnosticSeverity.Error,
+ ),
+ Deb822KnownField(
+ "Upstream-Name",
+ FieldValueClass.FREE_TEXT_FIELD,
+ ),
+ Deb822KnownField(
+ "Upstream-Contract",
+ FieldValueClass.FREE_TEXT_FIELD,
+ ),
+ Deb822KnownField(
+ "Source",
+ FieldValueClass.FREE_TEXT_FIELD,
+ ),
+ Deb822KnownField(
+ "Disclaimer",
+ FieldValueClass.FREE_TEXT_FIELD,
+ spellcheck_value=True,
+ ),
+ Deb822KnownField(
+ "Comment",
+ FieldValueClass.FREE_TEXT_FIELD,
+ spellcheck_value=True,
+ ),
+ Deb822KnownField(
+ "License",
+ FieldValueClass.FREE_TEXT_FIELD,
+ # Do not tempt people to change legal text because the spellchecker wants to do a typo fix.
+ spellcheck_value=False,
+ ),
+)
+_DEP5_FILES_FIELDS = _fields(
+ Deb822KnownField(
+ "Files",
+ FieldValueClass.DEP5_FILE_LIST,
+ is_stanza_name=True,
+ missing_field_severity=DiagnosticSeverity.Error,
+ ),
+ Deb822KnownField(
+ "Copyright",
+ FieldValueClass.FREE_TEXT_FIELD,
+ # Mostly going to be names with very little free-text; high risk of false positives with low value
+ spellcheck_value=False,
+ missing_field_severity=DiagnosticSeverity.Error,
+ ),
+ Deb822KnownField(
+ "License",
+ FieldValueClass.FREE_TEXT_FIELD,
+ missing_field_severity=DiagnosticSeverity.Error,
+ # Do not tempt people to change legal text because the spellchecker wants to do a typo fix.
+ spellcheck_value=False,
+ ),
+ Deb822KnownField(
+ "Comment",
+ FieldValueClass.FREE_TEXT_FIELD,
+ spellcheck_value=True,
+ ),
+)
+_DEP5_LICENSE_FIELDS = _fields(
+ Deb822KnownField(
+ "License",
+ FieldValueClass.FREE_TEXT_FIELD,
+ is_stanza_name=True,
+ # Do not tempt people to change legal text because the spellchecker wants to do a typo fix.
+ spellcheck_value=False,
+ missing_field_severity=DiagnosticSeverity.Error,
+ ),
+ Deb822KnownField(
+ "Comment",
+ FieldValueClass.FREE_TEXT_FIELD,
+ spellcheck_value=True,
+ ),
+)
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class StanzaMetadata(Mapping[str, F], Generic[F], ABC):
+ stanza_type_name: str
+ stanza_fields: Mapping[str, F]
+
+ def stanza_diagnostics(
+ self,
+ stanza: Deb822ParagraphElement,
+ stanza_position_in_file: "TEPosition",
+ ) -> Iterable[Diagnostic]:
+ raise NotImplementedError
+
+ def __getitem__(self, key: str) -> F:
+ key_lc = key.lower()
+ key_norm = normalize_dctrl_field_name(key_lc)
+ return self.stanza_fields[key_norm]
+
+ def __len__(self) -> int:
+ return len(self.stanza_fields)
+
+ def __iter__(self):
+ return iter(self.stanza_fields.keys())
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class Dep5StanzaMetadata(StanzaMetadata[Deb822KnownField]):
+ def stanza_diagnostics(
+ self,
+ stanza: Deb822ParagraphElement,
+ stanza_position_in_file: "TEPosition",
+ ) -> Iterable[Diagnostic]:
+ pass
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class DctrlStanzaMetadata(StanzaMetadata[DctrlKnownField]):
+
+ def stanza_diagnostics(
+ self,
+ stanza: Deb822ParagraphElement,
+ stanza_position_in_file: "TEPosition",
+ ) -> Iterable[Diagnostic]:
+ pass
+
+
+class Deb822FileMetadata(Generic[S]):
+ def classify_stanza(self, stanza: Deb822ParagraphElement, stanza_idx: int) -> S:
+ return self.guess_stanza_classification_by_idx(stanza_idx)
+
+ def guess_stanza_classification_by_idx(self, stanza_idx: int) -> S:
+ raise NotImplementedError
+
+ def stanza_types(self) -> Iterable[S]:
+ raise NotImplementedError
+
+ def __getitem__(self, item: str) -> S:
+ raise NotImplementedError
+
+ def file_diagnostics(
+ self,
+ file: Deb822FileElement,
+ ) -> Iterable[Diagnostic]:
+ raise NotImplementedError
+
+ def get(self, item: str) -> Optional[S]:
+ try:
+ return self[item]
+ except KeyError:
+ return None
+
+
+_DCTRL_SOURCE_STANZA = DctrlStanzaMetadata(
+ "Source",
+ SOURCE_FIELDS,
+)
+_DCTRL_PACKAGE_STANZA = DctrlStanzaMetadata("Package", BINARY_FIELDS)
+
+_DEP5_HEADER_STANZA = Dep5StanzaMetadata(
+ "Header",
+ _DEP5_HEADER_FIELDS,
+)
+_DEP5_FILES_STANZA = Dep5StanzaMetadata(
+ "Files",
+ _DEP5_FILES_FIELDS,
+)
+_DEP5_LICENSE_STANZA = Dep5StanzaMetadata(
+ "License",
+ _DEP5_LICENSE_FIELDS,
+)
+
+
+class Dep5FileMetadata(Deb822FileMetadata[Dep5StanzaMetadata]):
+ def classify_stanza(self, stanza: Deb822ParagraphElement, stanza_idx: int) -> S:
+ if stanza_idx == 0:
+ return _DEP5_HEADER_STANZA
+ if stanza_idx > 0:
+ if "Files" in stanza:
+ return _DEP5_FILES_STANZA
+ return _DEP5_LICENSE_STANZA
+ raise ValueError("The stanza_idx must be 0 or greater")
+
+ def guess_stanza_classification_by_idx(self, stanza_idx: int) -> S:
+ if stanza_idx == 0:
+ return _DEP5_HEADER_STANZA
+ if stanza_idx > 0:
+ return _DEP5_FILES_STANZA
+ raise ValueError("The stanza_idx must be 0 or greater")
+
+ def stanza_types(self) -> Iterable[S]:
+ yield _DEP5_HEADER_STANZA
+ yield _DEP5_FILES_STANZA
+ yield _DEP5_LICENSE_STANZA
+
+ def __getitem__(self, item: str) -> S:
+ if item == "Header":
+ return _DEP5_FILES_STANZA
+ if item == "Files":
+ return _DEP5_FILES_STANZA
+ if item == "License":
+ return _DEP5_LICENSE_STANZA
+ raise KeyError(item)
+
+
+class DctrlFileMetadata(Deb822FileMetadata[DctrlStanzaMetadata]):
+ def guess_stanza_classification_by_idx(self, stanza_idx: int) -> S:
+ if stanza_idx == 0:
+ return _DCTRL_SOURCE_STANZA
+ if stanza_idx > 0:
+ return _DCTRL_PACKAGE_STANZA
+ raise ValueError("The stanza_idx must be 0 or greater")
+
+ def stanza_types(self) -> Iterable[S]:
+ yield _DCTRL_SOURCE_STANZA
+ yield _DCTRL_PACKAGE_STANZA
+
+ def __getitem__(self, item: str) -> S:
+ if item == "Source":
+ return _DCTRL_SOURCE_STANZA
+ if item == "Package":
+ return _DCTRL_PACKAGE_STANZA
+ raise KeyError(item)