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

1import dataclasses 

2import re 

3from typing import Sequence, Optional, FrozenSet, Tuple, List, cast 

4 

5from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable 

6from debputy.highlevel_manifest import MutableYAMLManifest 

7from debputy.substitution import Substitution 

8 

9_DH_VAR_RE = re.compile(r"([$][{])([A-Za-z0-9][-_:0-9A-Za-z]*)([}])") 

10 

11 

12class AcceptableMigrationIssues: 

13 def __init__(self, values: FrozenSet[str]): 

14 self._values = values 

15 

16 def __contains__(self, item: str) -> bool: 

17 return item in self._values or "ALL" in self._values 

18 

19 

20class UnsupportedFeature(RuntimeError): 

21 @property 

22 def message(self) -> str: 

23 return cast("str", self.args[0]) 

24 

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]) 

30 

31 

32class ConflictingChange(RuntimeError): 

33 @property 

34 def message(self) -> str: 

35 return cast("str", self.args[0]) 

36 

37 

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) 

50 

51 def warn(self, msg: str) -> None: 

52 self.warnings.append(msg) 

53 

54 def rename_on_success(self, source: str, dest: str) -> None: 

55 self.rename_paths_on_success.append((source, dest)) 

56 

57 def remove_on_success(self, path: str) -> None: 

58 self.remove_paths_on_success.append(path) 

59 

60 def require_plugin(self, debputy_plugin: str) -> None: 

61 self.required_plugins.append(debputy_plugin) 

62 

63 @property 

64 def anything_to_do(self) -> bool: 

65 return bool(self.total_changes_involved) 

66 

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 ) 

74 

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 ) 

83 

84 

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) 

113 

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 

152 

153 return "{{" + key + "}}" 

154 

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("${}", "$") 

171 

172 def with_extra_substitutions(self, **extra_substitutions: str) -> "Substitution": 

173 return self