Merge ~apw/ubuntu-archive-tools:new-review--add-build-analysis2 into ubuntu-archive-tools:main
- Git
- lp:~apw/ubuntu-archive-tools
- new-review--add-build-analysis2
- Merge into main
Proposed by
Andy Whitcroft
Status: | Merged |
---|---|
Merged at revision: | 8a48f981b6477e6ec0a844303632b0488ae7a09b |
Proposed branch: | ~apw/ubuntu-archive-tools:new-review--add-build-analysis2 |
Merge into: | ubuntu-archive-tools:main |
Diff against target: |
484 lines (+232/-114) 2 files modified
new-review (+3/-1) new_review.py (+229/-113) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Package Archive Administrators | Pending | ||
Review via email: mp+455300@code.launchpad.net |
Commit message
new-review: add support for dumping build related information
Use the build logs to identify the applied archive pockets at build time. Also use the logs to reconstruct the binary control files for the new and old packages and produce a delta.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/new-review b/new-review |
2 | index 5a7ddd5..521df2f 100755 |
3 | --- a/new-review |
4 | +++ b/new-review |
5 | @@ -66,7 +66,8 @@ class NewReview: |
6 | signing_request = ReviewRequest.select(args, services=self.svcs) |
7 | if signing_request.task_assign(args): |
8 | signing_request.analyse(args) |
9 | - signing_request.binary_new(args) |
10 | + if not args.approve: |
11 | + signing_request.binary_new(args) |
12 | |
13 | signing_bot = SigningBotSubmit() |
14 | signing_bot.submit(args, signing_request) |
15 | @@ -102,6 +103,7 @@ class NewReview: |
16 | group.add_argument("--from-suite", dest="from_suite") |
17 | group.add_argument("--to", dest="to_route") |
18 | group.add_argument("--to-suite", dest="to_suite") |
19 | + group.add_argument("--existing-suite", dest="existing_suite") |
20 | group.add_argument("--embargo", action="store_true") |
21 | group = subcmd.add_argument_group(title='type/package options') |
22 | group.add_argument("--force-type") |
23 | diff --git a/new_review.py b/new_review.py |
24 | index b47bedf..23c82ad 100755 |
25 | --- a/new_review.py |
26 | +++ b/new_review.py |
27 | @@ -8,6 +8,7 @@ import hashlib |
28 | import json |
29 | import os |
30 | import re |
31 | +import shlex |
32 | import sys |
33 | from argparse import ArgumentParser |
34 | from copy import copy |
35 | @@ -81,6 +82,7 @@ class Package: |
36 | self.signing = signing |
37 | |
38 | self.series, self.pocket = Package.suite2series_pocket(self.suite) |
39 | + self.order = order |
40 | self.package_key = (order, name) |
41 | |
42 | @classmethod |
43 | @@ -274,9 +276,10 @@ class ReviewRequest: |
44 | def analyse(self, args): |
45 | logger.info("Promotion Review:") |
46 | |
47 | - def signing_reference_suite(self, reference, suite): |
48 | + def signing_reference_suite(self, reference, suite, via="unknown"): |
49 | self.signing_ref = reference |
50 | self.signing_suite = suite |
51 | + self.signing_via = via |
52 | |
53 | def proposed_reference_suite(self, reference, suite): |
54 | self.proposed_ref = reference |
55 | @@ -406,6 +409,63 @@ class ReviewRequest: |
56 | |
57 | return binaries |
58 | |
59 | + def build_analyse(self, build): |
60 | + lp = self.svcs.launchpad |
61 | + |
62 | + abi_re = re.compile(r"^([0-9]+\.[0-9]+\.[0-9]+)[\.-]([0-9]+)") |
63 | + |
64 | + version = build.source_package_version |
65 | + match = abi_re.search(version) |
66 | + if match: |
67 | + abi = match.group(1) + "-" + match.group(2) |
68 | + else: |
69 | + abi = None |
70 | + |
71 | + log_url = build.build_log_url |
72 | + if log_url is None: |
73 | + logger.warn("{} build log not available".format(build.arch_tag)) |
74 | + return None, None |
75 | + log_url = log_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/') |
76 | + log = lp._browser.get(log_url) |
77 | + control = [] |
78 | + in_package_contents = 0 |
79 | + in_package_control = False |
80 | + for line in log.decode('utf-8', 'backslashreplace').rstrip().split('\n'): |
81 | + if line.startswith("RUN: /usr/share/launchpad-buildd/bin/in-target override-sources-list"): |
82 | + pockets = set() |
83 | + bits = shlex.split(line) |
84 | + for bit in bits: |
85 | + if bit.startswith("deb http://ftpmaster.internal/ubuntu "): |
86 | + suite = bit.split()[2] |
87 | + if "-" in suite: |
88 | + pocket = suite.split("-")[-1] |
89 | + else: |
90 | + pocket = "release" |
91 | + pockets.add(pocket) |
92 | + |
93 | + elif line.startswith("| Package contents"): |
94 | + in_package_contents = 2 |
95 | + |
96 | + elif line.startswith("+-----------------"): |
97 | + in_package_contents -= 1 |
98 | + |
99 | + if in_package_contents > 0: |
100 | + if line.startswith(" Package:"): |
101 | + in_package_control = True |
102 | + elif line.startswith(" Installed-Size:"): |
103 | + continue |
104 | + elif len(line) == 0: |
105 | + if in_package_control: |
106 | + control.append("") |
107 | + in_package_control = False |
108 | + if in_package_control: |
109 | + line = line.replace(version, "VERSION") |
110 | + if abi is not None: |
111 | + line = line.replace(abi, "ABI") |
112 | + control.append(line) |
113 | + |
114 | + return pockets, control |
115 | + |
116 | def binary_new(self, args, against="updates"): |
117 | logger.info("New Review:") |
118 | existing_group = PackageGroup("control") |
119 | @@ -413,6 +473,8 @@ class ReviewRequest: |
120 | if pocket_ref is None: |
121 | continue |
122 | some = False |
123 | + if args.existing_suite is not None: |
124 | + pocket_suite = args.existing_suite |
125 | for package in self.package_group.packages: |
126 | if package.generate: |
127 | continue |
128 | @@ -433,6 +495,7 @@ class ReviewRequest: |
129 | src, |
130 | signing=package.signing, |
131 | main=package.main, |
132 | + order=package.order, |
133 | ) |
134 | ) |
135 | if some: |
136 | @@ -443,6 +506,7 @@ class ReviewRequest: |
137 | else: |
138 | logger.info(" No binaries found in updates/release, New source packages?") |
139 | |
140 | + logger.info(" published-binaries:") |
141 | old_bins = self.binary_list(existing_group, args, ignore_failures=True) |
142 | new_bins = self.binary_list(self.package_group, args) |
143 | if old_bins is not None and new_bins is not None: |
144 | @@ -452,17 +516,60 @@ class ReviewRequest: |
145 | if line.rstrip() in ("+++", "---"): |
146 | continue |
147 | some = True |
148 | - logger.info(" " + line) |
149 | + logger.info(" " + line) |
150 | if not some: |
151 | - logger.info(" No Changes ({} binaries)".format(len(new_bins))) |
152 | + logger.info(" No Changes ({} binaries)".format(len(new_bins))) |
153 | |
154 | elif self.have_autonew: |
155 | - logger.info(" binary builds not complete but automated new-review available") |
156 | + logger.info(" binary builds not complete but automated new-review available") |
157 | self.need_autonew = True |
158 | |
159 | else: |
160 | raise ReviewError("Binary Builds incomplete, cannot review") |
161 | |
162 | + logger.info(" built-against:") |
163 | + against = None |
164 | + against_detail = [] |
165 | + against_dump = False |
166 | + new_control = [] |
167 | + for package in self.package_group.packages: |
168 | + for build in package.spph.getBuilds(): |
169 | + pockets, control = self.build_analyse(build) |
170 | + if pockets is not None and not package.generate: |
171 | + if against is None: |
172 | + against = pockets |
173 | + if against != pockets: |
174 | + against_dump = True |
175 | + against_detail.append(" {}/{} ubuntu/primary {}".format(package.name, build.arch_tag, ",".join(sorted(pockets)))) |
176 | + new_control.append("=== {}/{} ===".format(package.name, build.arch_tag)) |
177 | + new_control += control |
178 | + |
179 | + if against_dump is True: |
180 | + for against in against_detail: |
181 | + logger.info(against) |
182 | + else: |
183 | + logger.info(" all ubuntu/primary {}".format(",".join(sorted(against)))) |
184 | + |
185 | + if old_bins is not None and new_bins is not None: |
186 | + logger.info(" control-delta:") |
187 | + old_control = [] |
188 | + for package in existing_group.packages: |
189 | + for build in package.spph.getBuilds(): |
190 | + pockets, control = self.build_analyse(build) |
191 | + if pockets is not None: |
192 | + old_control.append("=== {}/{} ===".format(package.name, build.arch_tag)) |
193 | + old_control += control |
194 | + |
195 | + delta = unified_diff(old_control, new_control, lineterm="") |
196 | + some = False |
197 | + for line in delta: |
198 | + if line.rstrip() in ("+++", "---"): |
199 | + continue |
200 | + some = True |
201 | + logger.info(" " + line) |
202 | + if not some: |
203 | + logger.info(" No Changes") |
204 | + |
205 | def lp_find_tracker_task(self, tracker_id, name): |
206 | try: |
207 | lp_tracker = self.svcs.launchpad.bugs[tracker_id] |
208 | @@ -586,113 +693,136 @@ class ReviewRequestKernel(ReviewRequest): |
209 | if package.main: |
210 | ucs_srcs.append(package.spph) |
211 | |
212 | - if ( |
213 | - self.signing_present |
214 | - and self.signing_ref is not None |
215 | - and self.signing_ref.startswith("signing:") |
216 | - ): |
217 | + if self.signing_present: |
218 | logger.info("Signing Review:") |
219 | - logger.info(" key {} selected".format(self.signing_ref[8:])) |
220 | - |
221 | - # VALIDATE signing routing. |
222 | - compatibility = [] |
223 | - if len(ucs_srcs) == 0: |
224 | - # Go find main... |
225 | - main_version_re = re.compile("(.*)\+[0-9]") |
226 | - main_name = self.ks_source.lookup_package(type="main").name |
227 | - for package in self.package_group.packages: |
228 | - if not package.signing: |
229 | - continue |
230 | - match = main_version_re.match(package.version) |
231 | - if match: |
232 | - main_version = match.group(1) |
233 | - else: |
234 | - main_version = package_version |
235 | - |
236 | - for ucs_ref, ucs_pocket in self.proposed_routes: |
237 | - lp_archive = lp.archives.getByReference(reference=ucs_ref) |
238 | - if lp_archive is None: |
239 | - raise ReviewError("{}: archive '{}' not found".format(tracker_id, ucs_ref)) |
240 | - srcs = lp_archive.getPublishedSources(exact_match=True, order_by_date=True, distro_series='/ubuntu/' + self.series, source_name=main_name, version=main_version) |
241 | - if len(srcs) == 0: |
242 | + if ( |
243 | + self.signing_ref is not None |
244 | + and self.signing_ref.startswith("signing:") |
245 | + ): |
246 | + signing_config = self.svcs.signing_config |
247 | + signing_descriptor = signing_config.lookup_stream(self.signing_ref[8:]) |
248 | + signing_reference = signing_descriptor.archive_reference |
249 | + |
250 | + # VALIDATE signing routing. |
251 | + compatibility = [] |
252 | + if len(ucs_srcs) == 0: |
253 | + # Go find main... |
254 | + main_version_re = re.compile("(.*)\+[0-9]") |
255 | + main_name = self.ks_source.lookup_package(type="main").name |
256 | + for package in self.package_group.packages: |
257 | + if not package.signing: |
258 | continue |
259 | - src = srcs[0] |
260 | - logger.info(" using {}:{} {} (main package) from {} for Ubuntu-Signing-Compatible".format(self.series, main_name, main_version, ucs_ref)) |
261 | - ucs_srcs.append(src) |
262 | - break |
263 | - for package_spph in ucs_srcs: |
264 | - changes_url = package_spph.changesFileUrl() |
265 | - if changes_url is None: |
266 | - raise ReviewError("{}: no changes file for {} not found".format(args.tracker, ks_package.type or "main")) |
267 | - |
268 | - # If we managed to find a changes file then we can extract the list. |
269 | - changes_url = changes_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/') |
270 | - changes = lp._browser.get(changes_url) |
271 | - compatibility = None |
272 | - for line in changes.decode('utf-8').rstrip().split('\n'): |
273 | - if line.startswith("Ubuntu-Compatible-Signing:"): |
274 | - compatibility = line.split()[1:] |
275 | - logger.info(" Ubuntu-Compatible-Signing {} source".format(signing_compat(compatibility))) |
276 | - |
277 | - # If there are builds available check they are matching compatibility. |
278 | - compatibility_match = True |
279 | - for build in package_spph.getBuilds(): |
280 | - # If we managed to find a changes file then we can extract the list. |
281 | - changes_url = build.changesfile_url |
282 | + match = main_version_re.match(package.version) |
283 | + if match: |
284 | + main_version = match.group(1) |
285 | + else: |
286 | + main_version = package_version |
287 | + |
288 | + for ucs_ref, ucs_pocket in self.proposed_routes: |
289 | + lp_archive = lp.archives.getByReference(reference=ucs_ref) |
290 | + if lp_archive is None: |
291 | + raise ReviewError("{}: archive '{}' not found".format(tracker_id, ucs_ref)) |
292 | + srcs = lp_archive.getPublishedSources(exact_match=True, order_by_date=True, distro_series='/ubuntu/' + self.series, source_name=main_name, version=main_version) |
293 | + if len(srcs) == 0: |
294 | + continue |
295 | + src = srcs[0] |
296 | + logger.info(" using {}:{} {} (main package) from {} for Ubuntu-Signing-Compatible".format(self.series, main_name, main_version, ucs_ref)) |
297 | + ucs_srcs.append(src) |
298 | + break |
299 | + for package_spph in ucs_srcs: |
300 | + changes_url = package_spph.changesFileUrl() |
301 | if changes_url is None: |
302 | - logger.warn("Ubuntu-Compatible-Signing - {} build not available".format(build.arch_tag)) |
303 | - continue |
304 | - changes_url = build.changesfile_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/') |
305 | + raise ReviewError("{}: no changes file for {} not found".format(args.tracker, ks_package.type or "main")) |
306 | + |
307 | + # If we managed to find a changes file then we can extract the list. |
308 | + changes_url = changes_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/') |
309 | changes = lp._browser.get(changes_url) |
310 | - build_compat = None |
311 | + compatibility = None |
312 | for line in changes.decode('utf-8').rstrip().split('\n'): |
313 | if line.startswith("Ubuntu-Compatible-Signing:"): |
314 | - build_compat = line.split()[1:] |
315 | - if compatibility != build_compat: |
316 | - compatibility_match = False |
317 | - logger.info(" Ubuntu-Compatible-Signing {} {}".format(signing_compat(build_compat), build.arch_tag)) |
318 | - |
319 | - # Report compatibility error. |
320 | - if not compatibility_match: |
321 | - raise ReviewError("compatibility missmatch".format(ks_package.type or "main")) |
322 | - |
323 | - signing_config = self.svcs.signing_config |
324 | - signing_descriptor = signing_config.lookup_stream(self.signing_ref[8:]) |
325 | - |
326 | - if compatibility is None: |
327 | - compatibility = ["ubuntu/2", "pro/2", "kariya/2"] |
328 | - logger.info(" Ubuntu-Compatible-Signing implied as {}".format(signing_compat(compatibility))) |
329 | - |
330 | - compatible = None |
331 | - for stream_level in compatibility: |
332 | - compatible_descriptor = signing_config.lookup_stream(stream_level) |
333 | - if signing_descriptor.stream != compatible_descriptor.stream: |
334 | - continue |
335 | - compatible = signing_descriptor.level == compatible_descriptor.level |
336 | - break |
337 | + compatibility = line.split()[1:] |
338 | + ucs_detail = [(compatibility, "source")] |
339 | + |
340 | + # If there are builds available check they are matching compatibility. |
341 | + compatibility_match = True |
342 | + for build in package_spph.getBuilds(): |
343 | + # If we managed to find a changes file then we can extract the list. |
344 | + changes_url = build.changesfile_url |
345 | + if changes_url is None: |
346 | + logger.warn("Ubuntu-Compatible-Signing - {} build not available".format(build.arch_tag)) |
347 | + continue |
348 | + changes_url = build.changesfile_url.replace('https://launchpad.net/', 'https://api.launchpad.net/devel/') |
349 | + changes = lp._browser.get(changes_url) |
350 | + build_compat = None |
351 | + for line in changes.decode('utf-8').rstrip().split('\n'): |
352 | + if line.startswith("Ubuntu-Compatible-Signing:"): |
353 | + build_compat = line.split()[1:] |
354 | + if compatibility != build_compat: |
355 | + compatibility_match = False |
356 | + ucs_detail.append((build_compat, build.arch_tag)) |
357 | + |
358 | + # Report compatibility error. |
359 | + logger.info(" Ubuntu-Compatible-Signing:") |
360 | + if not compatibility_match: |
361 | + for detail_compatibility, detail_thing in ucs_detail: |
362 | + logger.info(" {} {}".format(detail_thing, signing_compat(detail_compatibility))) |
363 | + raise ReviewError("compatibility missmatch".format(ks_package.type or "main")) |
364 | + logger.info(" all {}".format(signing_compat(compatibility))) |
365 | + |
366 | + if compatibility is None: |
367 | + compatibility = ["ubuntu/2", "pro/2", "kariya/2"] |
368 | + logger.info(" Ubuntu-Compatible-Signing implied as {}".format(signing_compat(compatibility))) |
369 | + |
370 | + compatible = None |
371 | + for stream_level in compatibility: |
372 | + compatible_descriptor = signing_config.lookup_stream(stream_level) |
373 | + if signing_descriptor.stream != compatible_descriptor.stream: |
374 | + continue |
375 | + compatible = signing_descriptor.level == compatible_descriptor.level |
376 | + break |
377 | + |
378 | + # XXX: this really permits us to use the wrong keys, it would be better |
379 | + # if everything used like ibm-gt/1 but ... |
380 | + compatibility.append("*/1") |
381 | + #print("APW", signing_descriptor.level, type(signing_descriptor.level)) |
382 | + if compatible is None or signing_descriptor.level == 1: |
383 | + compatible = 1 |
384 | + |
385 | + #print(compatibility, signing_descriptor.stream, signing_descriptor.level, compatible) |
386 | + if not compatible: |
387 | + raise ReviewError("{}: signing-level {}/{} not compatible with package {}".format(args.tracker, signing_descriptor.stream, signing_descriptor.level, " ".join(compatibility))) |
388 | + logger.info(" key {}:".format(self.signing_ref[8:])) |
389 | + logger.info(" selected via {}".format(self.signing_via)) |
390 | + logger.info(" compatible with package {}".format(" ".join(compatibility))) |
391 | |
392 | - # XXX: this really permits us to use the wrong keys, it would be better |
393 | - # if everything used like ibm-gt/1 but ... |
394 | - compatibility.append("*/1") |
395 | - #print("APW", signing_descriptor.level, type(signing_descriptor.level)) |
396 | - if compatible is None or signing_descriptor.level == 1: |
397 | - compatible = 1 |
398 | + else: |
399 | + signing_reference = self.signing_ref |
400 | |
401 | - #print(compatibility, signing_descriptor.stream, signing_descriptor.level, compatible) |
402 | - if not compatible: |
403 | - raise ReviewError("{}: signing-level {}/{} not compatible with package {}".format(args.tracker, signing_descriptor.stream, signing_descriptor.level, " ".join(compatibility))) |
404 | - logger.info(" key {}/{} deemed compatible with package {}".format(signing_descriptor.stream, signing_descriptor.level, " ".join(compatibility))) |
405 | + logger.info(" signing-ppa {}".format(signing_reference)) |
406 | |
407 | - elif self.signing_present: |
408 | - logger.info("Signing Review:") |
409 | - logger.info(" using signing PPA {}".format(self.signing_ref)) |
410 | + some = False |
411 | + ptypes = set() |
412 | + for package in self.package_group.packages: |
413 | + ptypes.add(package.type or "main") |
414 | + for package in self.package_group.packages: |
415 | + ptype = package.type or "main" |
416 | + signing_from = self.ks_source.lookup_package(type=ptype).signing_from |
417 | + if signing_from is None: |
418 | + continue |
419 | + if not some: |
420 | + some = True |
421 | + logger.info(" pairings:") |
422 | + sf_ptype = signing_from.type or "main" |
423 | + logger.info(" {} with {}".format(ptype, sf_ptype)) |
424 | + if sf_ptype not in ptypes: |
425 | + raise ReviewError("{}: {} in request without {}, request inconsistent ...".format(tracker_id, ptype, sf_ptype)) |
426 | |
427 | def set_routing(self, args, ks_routing, from_route_name, from_route_entry): |
428 | if self.signing_present: |
429 | signing_routes = ks_routing.lookup_route('signing') |
430 | if len(signing_routes) > 0: |
431 | signing_route = signing_routes[0] |
432 | - self.signing_reference_suite(signing_route.raw_reference, signing_route.suite) |
433 | + self.signing_reference_suite(signing_route.raw_reference, signing_route.suite, via="kernel-series") |
434 | |
435 | if args.to_route is not None: |
436 | to_route_name, to_route_entry = self.route_split(args.to_route_name) |
437 | @@ -811,22 +941,6 @@ class ReviewRequestKernelTracker(ReviewRequestKernel): |
438 | if tb_promote is None: |
439 | raise ReviewError("{}: {} promotion data not present".format(tracker_id, task)) |
440 | |
441 | - # Sanity check ... |
442 | - # XXX: this should be done in validate based on the package names and types, so that |
443 | - # it applied equally to handles and could then be extended to Packages. |
444 | - some = False |
445 | - for ptype in tb_promote: |
446 | - signing_from = ks_source.lookup_package(type=ptype).signing_from |
447 | - if signing_from is None: |
448 | - continue |
449 | - if not some: |
450 | - some = True |
451 | - logger.info(" pairings:") |
452 | - sf_ptype = signing_from.type or "main" |
453 | - logger.info(" {} with {}".format(ptype, sf_ptype)) |
454 | - if sf_ptype not in tb_promote: |
455 | - raise ReviewError("{}: {} in request without {}, request inconsistent ...".format(tracker_id, ptype, sf_ptype)) |
456 | - |
457 | packages = PackageGroup() |
458 | for package_type in tb_promote: |
459 | package_name = tb_packages.get(package_type) |
460 | @@ -859,6 +973,7 @@ class ReviewRequestKernelTracker(ReviewRequestKernel): |
461 | generate=ks_package.signing_to is not None, |
462 | signing=ks_package.signing_from is not None, |
463 | main=package_type == "main", |
464 | + type=package_type, |
465 | order=self.ks_package_order(ks_package), |
466 | ) |
467 | ) |
468 | @@ -953,6 +1068,7 @@ class ReviewRequestKernelHandle(ReviewRequestKernel): |
469 | generate=ks_package.signing_to is not None, |
470 | signing=ks_package.signing_from is not None, |
471 | main=package_type == "main", |
472 | + type=package_type, |
473 | order=self.ks_package_order(ks_package), |
474 | ) |
475 | ) |
476 | @@ -1115,7 +1231,7 @@ class ReviewRequestPackages(ReviewRequest): |
477 | logger.info(" key is not driver selected key {}".format(default_key)) |
478 | |
479 | signing_ref = "signing:" + key |
480 | - self.signing_reference_suite(signing_ref, Package.series_pocket2suite(series_name, "Release")) |
481 | + self.signing_reference_suite(signing_ref, Package.series_pocket2suite(series_name, "Release"), via=key_source) |
482 | |
483 | proposed_ref, proposed_suite = args.to_route, args.to_suite or series_name |
484 | if proposed_ref is None: |