Coverage for src/debputy/dh_migration/models.py: 84%
86 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
1import dataclasses
2import re
3from typing import Sequence, Optional, FrozenSet, Tuple, List, cast
5from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable
6from debputy.highlevel_manifest import MutableYAMLManifest
7from debputy.substitution import Substitution
9_DH_VAR_RE = re.compile(r"([$][{])([A-Za-z0-9][-_:0-9A-Za-z]*)([}])")
12class AcceptableMigrationIssues:
13 def __init__(self, values: FrozenSet[str]):
14 self._values = values
16 def __contains__(self, item: str) -> bool:
17 return item in self._values or "ALL" in self._values
20class UnsupportedFeature(RuntimeError):
21 @property
22 def message(self) -> str:
23 return cast("str", self.args[0])
25 @property
26 def issue_keys(self) -> Optional[Sequence[str]]:
27 if len(self.args) < 2:
28 return None
29 return cast("Sequence[str]", self.args[1])
32class ConflictingChange(RuntimeError):
33 @property
34 def message(self) -> str:
35 return cast("str", self.args[0])
38@dataclasses.dataclass(slots=True)
39class FeatureMigration:
40 tagline: str
41 successful_manifest_changes: int = 0
42 already_present: int = 0
43 warnings: List[str] = dataclasses.field(default_factory=list)
44 remove_paths_on_success: List[str] = dataclasses.field(default_factory=list)
45 rename_paths_on_success: List[Tuple[str, str]] = dataclasses.field(
46 default_factory=list
47 )
48 assumed_compat: Optional[int] = None
49 required_plugins: List[str] = dataclasses.field(default_factory=list)
51 def warn(self, msg: str) -> None:
52 self.warnings.append(msg)
54 def rename_on_success(self, source: str, dest: str) -> None:
55 self.rename_paths_on_success.append((source, dest))
57 def remove_on_success(self, path: str) -> None:
58 self.remove_paths_on_success.append(path)
60 def require_plugin(self, debputy_plugin: str) -> None:
61 self.required_plugins.append(debputy_plugin)
63 @property
64 def anything_to_do(self) -> bool:
65 return bool(self.total_changes_involved)
67 @property
68 def performed_changes(self) -> int:
69 return (
70 self.successful_manifest_changes
71 + len(self.remove_paths_on_success)
72 + len(self.rename_paths_on_success)
73 )
75 @property
76 def total_changes_involved(self) -> int:
77 return (
78 self.successful_manifest_changes
79 + len(self.warnings)
80 + len(self.remove_paths_on_success)
81 + len(self.rename_paths_on_success)
82 )
85class DHMigrationSubstitution(Substitution):
86 def __init__(
87 self,
88 dpkg_arch_table: DpkgArchitectureBuildProcessValuesTable,
89 acceptable_migration_issues: AcceptableMigrationIssues,
90 feature_migration: FeatureMigration,
91 mutable_manifest: MutableYAMLManifest,
92 ) -> None:
93 self._acceptable_migration_issues = acceptable_migration_issues
94 self._dpkg_arch_table = dpkg_arch_table
95 self._feature_migration = feature_migration
96 self._mutable_manifest = mutable_manifest
97 # TODO: load 1:1 variables from the real subst instance (less stuff to keep in sync)
98 one2one = [
99 "DEB_SOURCE",
100 "DEB_VERSION",
101 "DEB_VERSION_EPOCH_UPSTREAM",
102 "DEB_VERSION_UPSTREAM_REVISION",
103 "DEB_VERSION_UPSTREAM",
104 "SOURCE_DATE_EPOCH",
105 ]
106 self._builtin_substs = {
107 "Tab": "{{token:TAB}}",
108 "Space": " ",
109 "Newline": "{{token:NEWLINE}}",
110 "Dollar": "${}",
111 }
112 self._builtin_substs.update((x, "{{" + x + "}}") for x in one2one)
114 def _replacement(self, key: str, definition_source: str) -> str:
115 if key in self._builtin_substs: 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true
116 return self._builtin_substs[key]
117 if key in self._dpkg_arch_table:
118 return "{{" + key + "}}"
119 if key.startswith("env:"): 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true
120 if "dh-subst-env" not in self._acceptable_migration_issues:
121 raise UnsupportedFeature(
122 "Use of environment based substitution variable {{"
123 + key
124 + "}} is not"
125 f" supported in debputy. The variable was spotted at {definition_source}",
126 ["dh-subst-env"],
127 )
128 elif "dh-subst-unknown-variable" not in self._acceptable_migration_issues: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true
129 raise UnsupportedFeature(
130 "Unknown substitution variable {{"
131 + key
132 + "}}, which does not have a known"
133 f" counter part in debputy. The variable was spotted at {definition_source}",
134 ["dh-subst-unknown-variable"],
135 )
136 manifest_definitions = self._mutable_manifest.manifest_definitions(
137 create_if_absent=False
138 )
139 manifest_variables = manifest_definitions.manifest_variables(
140 create_if_absent=False
141 )
142 if key not in manifest_variables.variables: 142 ↛ 153line 142 didn't jump to line 153, because the condition on line 142 was never false
143 manifest_definitions.create_definition_if_missing()
144 manifest_variables[key] = "TODO: Provide variable value for " + key
145 self._feature_migration.warn(
146 "TODO: MANUAL MIGRATION of unresolved substitution variable {{"
147 + key
148 + "}} from"
149 + f" {definition_source}"
150 )
151 self._feature_migration.successful_manifest_changes += 1
153 return "{{" + key + "}}"
155 def substitute(
156 self,
157 value: str,
158 definition_source: str,
159 /,
160 escape_glob_characters: bool = False,
161 ) -> str:
162 if "${" not in value:
163 return value
164 replacement = self._apply_substitution(
165 _DH_VAR_RE,
166 value,
167 definition_source,
168 escape_glob_characters=escape_glob_characters,
169 )
170 return replacement.replace("${}", "$")
172 def with_extra_substitutions(self, **extra_substitutions: str) -> "Substitution":
173 return self