Coverage for daklib/packagelist.py: 94%
107 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
1"""parse Package-List field
3@copyright: 2014, Ansgar Burchardt <ansgar@debian.org>
4@license: GPL-2+
5"""
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21from typing import TYPE_CHECKING, Optional
23from daklib.architecture import match_architecture
24from daklib.utils import extract_component_from_section
26if TYPE_CHECKING:
27 from collections.abc import Mapping
29 from daklib.dbconn import MetadataProxy, Suite
32class InvalidSource(Exception):
33 pass
36class PackageListEntry:
37 def __init__(
38 self,
39 name: str,
40 package_type: str | None,
41 section: str | None,
42 component: str | None,
43 priority: str | None,
44 **other: str,
45 ):
46 self.name = name
47 self.type = package_type
48 self.section = section
49 self.component = component
50 self.priority = priority
51 self.other = other
53 self.architectures = self._architectures()
55 def _architectures(self) -> Optional[list[str]]:
56 archs = self.other.get("arch", None)
57 if archs is None:
58 return None
59 return archs.split(",")
61 def built_on_architecture(self, architecture: str) -> Optional[bool]:
62 archs = self.architectures
63 if archs is None:
64 return None
65 for arch in archs:
66 if match_architecture(architecture, arch):
67 return True
68 return False
70 def built_in_suite(self, suite: "Suite") -> Optional[bool]:
71 built: Optional[bool] = False
72 for arch in suite.architectures:
73 if arch.arch_string == "source":
74 continue
75 built_on_arch = self.built_on_architecture(arch.arch_string)
76 if built_on_arch:
77 return True
78 if built_on_arch is None:
79 built = None
80 return built
82 def built_in_default_profile(self) -> bool:
83 # See man:dsc(5) and https://bugs.debian.org/913965#77
84 profiles_and = self.other.get("profile")
85 if profiles_and is None:
86 return True
87 return all(
88 any(profile.startswith("!") for profile in profiles_or.split("+"))
89 for profiles_or in profiles_and.split(",")
90 )
93class PackageList:
94 def __init__(self, source: "Mapping[str, str] | MetadataProxy"):
95 if "Package-List" in source:
96 self.package_list = self._parse(source)
97 elif "Binary" in source: 97 ↛ 100line 97 didn't jump to line 100 because the condition on line 97 was always true
98 self.package_list = self._parse_fallback(source)
99 else:
100 raise InvalidSource(
101 "Source package has neither Package-List nor Binary field."
102 )
104 self.fallback = any(entry.architectures is None for entry in self.package_list)
106 def _binaries(self, source: "Mapping[str, str] | MetadataProxy") -> set[str]:
107 return set(name.strip() for name in source["Binary"].split(","))
109 def _parse(
110 self, source: "Mapping[str, str] | MetadataProxy"
111 ) -> list[PackageListEntry]:
112 package_list = []
114 binaries_binary = self._binaries(source)
115 binaries_package_list = set()
117 for line in source["Package-List"].split("\n"):
118 if not line:
119 continue
120 fields = line.split()
121 if len(fields) < 4: 121 ↛ 122line 121 didn't jump to line 122 because the condition on line 121 was never true
122 raise InvalidSource("Package-List entry has less than four fields.")
124 # <name> <type> <component/section> <priority> [arch=<arch>[,<arch>]...]
125 name = fields[0]
126 package_type = fields[1]
127 section, component = extract_component_from_section(fields[2])
128 priority = fields[3]
129 other = dict(kv.split("=", 1) for kv in fields[4:])
131 if name in binaries_package_list: 131 ↛ 132line 131 didn't jump to line 132 because the condition on line 131 was never true
132 raise InvalidSource(
133 "Package-List has two entries for '{0}'.".format(name)
134 )
135 if name not in binaries_binary: 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true
136 raise InvalidSource(
137 "Package-List lists {0} which is not listed in Binary.".format(name)
138 )
139 binaries_package_list.add(name)
141 entry = PackageListEntry(
142 name, package_type, section, component, priority, **other
143 )
144 package_list.append(entry)
146 if len(binaries_binary) != len(binaries_package_list): 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true
147 raise InvalidSource(
148 "Package-List and Binaries fields have a different number of entries."
149 )
151 return package_list
153 def _parse_fallback(
154 self, source: "Mapping[str, str] | MetadataProxy"
155 ) -> list[PackageListEntry]:
156 return [
157 PackageListEntry(binary, None, None, None, None)
158 for binary in self._binaries(source)
159 ]
161 def packages_for_suite(
162 self, suite: "Suite", only_default_profile=True
163 ) -> list[PackageListEntry]:
164 packages = []
165 for entry in self.package_list:
166 if only_default_profile and not entry.built_in_default_profile():
167 continue
168 built = entry.built_in_suite(suite)
169 if built or built is None:
170 packages.append(entry)
171 return packages
173 def has_arch_indep_packages(self) -> Optional[bool]:
174 has_arch_indep: Optional[bool] = False
175 for entry in self.package_list:
176 built = entry.built_on_architecture("all")
177 if built:
178 return True
179 if built is None:
180 has_arch_indep = None
181 return has_arch_indep
183 def has_arch_dep_packages(self) -> Optional[bool]:
184 has_arch_dep: Optional[bool] = False
185 for entry in self.package_list:
186 built_on_all = entry.built_on_architecture("all")
187 if built_on_all is False:
188 return True
189 if built_on_all is None:
190 has_arch_dep = None
191 return has_arch_dep