Merge ~vorlon/ubuntu-dev-tools:ubuntu-build-revamp into ubuntu-dev-tools:main
- Git
- lp:~vorlon/ubuntu-dev-tools
- ubuntu-build-revamp
- Merge into main
Proposed by
Steve Langasek
Status: | Merged |
---|---|
Approved by: | Gianfranco Costamagna |
Approved revision: | c92fa6502f0d9dc2485e5d93a66ae0c7ea91c5ed |
Merged at revision: | 0ec53180f23120ec4a86b544ca3e4d763ee82c15 |
Proposed branch: | ~vorlon/ubuntu-dev-tools:ubuntu-build-revamp |
Merge into: | ubuntu-dev-tools:main |
Diff against target: |
471 lines (+211/-122) 2 files modified
ubuntu-build (+211/-77) ubuntutools/lp/lpapicache.py (+0/-45) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Gianfranco Costamagna | Approve | ||
Review via email: mp+462101@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) : | # |
review:
Approve
Revision history for this message
Steve Langasek (vorlon) : | # |
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : | # |
Going in a better direction indeed!
review:
Approve
Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) wrote : | # |
For me it worked just nicely, really faster than before.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/ubuntu-build b/ubuntu-build |
2 | index acbf140..88ec52f 100755 |
3 | --- a/ubuntu-build |
4 | +++ b/ubuntu-build |
5 | @@ -2,16 +2,16 @@ |
6 | # |
7 | # ubuntu-build - command line interface for Launchpad buildd operations. |
8 | # |
9 | -# Copyright (C) 2007 Canonical Ltd. |
10 | +# Copyright (C) 2007-2024 Canonical Ltd. |
11 | # Authors: |
12 | # - Martin Pitt <martin.pitt@canonical.com> |
13 | # - Jonathan Davies <jpds@ubuntu.com> |
14 | # - Michael Bienia <geser@ubuntu.com> |
15 | +# - Steve Langasek <steve.langasek@canonical.com> |
16 | # |
17 | # This program is free software: you can redistribute it and/or modify |
18 | # it under the terms of the GNU General Public License as published by |
19 | -# the Free Software Foundation, either version 3 of the License, or |
20 | -# (at your option) any later version. |
21 | +# the Free Software Foundation, version 3 of the License. |
22 | # |
23 | # This program is distributed in the hope that it will be useful, |
24 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 | @@ -29,19 +29,63 @@ import argparse |
26 | import sys |
27 | |
28 | from launchpadlib.credentials import TokenAuthorizationException |
29 | +from launchpadlib.launchpad import Launchpad |
30 | +import lazr.restfulclient.errors |
31 | |
32 | from ubuntutools import getLogger |
33 | -from ubuntutools.lp.lpapicache import Distribution, Launchpad, PersonTeam |
34 | -from ubuntutools.lp.udtexceptions import ( |
35 | - PackageNotFoundException, |
36 | - PocketDoesNotExistError, |
37 | - SeriesNotFoundException, |
38 | -) |
39 | +from ubuntutools.lp.udtexceptions import PocketDoesNotExistError |
40 | from ubuntutools.misc import split_release_pocket |
41 | |
42 | Logger = getLogger() |
43 | |
44 | |
45 | +def getBuildStates(pkg, archs): |
46 | + res = [] |
47 | + |
48 | + for build in pkg.getBuilds(): |
49 | + if build.arch_tag in archs: |
50 | + res.append(f" {build.arch_tag}: {build.buildstate}") |
51 | + msg = "\n".join(res) |
52 | + return f"Build state(s) for '{pkg.source_package_name}':\n{msg}" |
53 | + |
54 | +def rescoreBuilds(pkg, archs, score): |
55 | + res = [] |
56 | + |
57 | + for build in pkg.getBuilds(): |
58 | + arch = build.arch_tag |
59 | + if arch in archs: |
60 | + if not build.can_be_rescored: |
61 | + continue |
62 | + try: |
63 | + build.rescore(score=score) |
64 | + res.append(f" {arch}: done") |
65 | + except lazr.restfulclient.errors.Unauthorized: |
66 | + Logger.error( |
67 | + "You don't have the permissions to rescore builds. Ignoring your rescore request." |
68 | + ) |
69 | + return None |
70 | + except lazr.restfulclient.errors.BadRequest: |
71 | + Logger.info("Cannot rescore build of %s on %s.", |
72 | + build.source_package_name, arch) |
73 | + res.append(f" {arch}: failed") |
74 | + |
75 | + msg = "\n".join(res) |
76 | + return f"Rescoring builds of '{pkg.source_package_name}' to {score}:\n{msg}" |
77 | + |
78 | +def retryBuilds(pkg, archs): |
79 | + res = [] |
80 | + for build in pkg.getBuilds(): |
81 | + arch = build.arch_tag |
82 | + if arch in archs: |
83 | + try: |
84 | + build.retry() |
85 | + res.append(f" {arch}: done") |
86 | + except lazr.restfulclient.errors.BadRequest: |
87 | + res.append(f" {arch}: failed") |
88 | + msg = "\n".join(res) |
89 | + return f"Retrying builds of '{pkg.source_package_name}':\n{msg}" |
90 | + |
91 | + |
92 | def main(): |
93 | # Usage. |
94 | usage = "%(prog)s <srcpackage> <release> <operation>\n\n" |
95 | @@ -65,12 +109,7 @@ def main(): |
96 | # Prepare our option parser. |
97 | parser = argparse.ArgumentParser(usage=usage) |
98 | |
99 | - # Retry options |
100 | - retry_rescore_options = parser.add_argument_group( |
101 | - "Retry and rescore options", |
102 | - "These options may only be used with the 'retry' and 'rescore' operations.", |
103 | - ) |
104 | - retry_rescore_options.add_argument( |
105 | + parser.add_argument( |
106 | "-a", |
107 | "--arch", |
108 | action="append", |
109 | @@ -79,6 +118,9 @@ def main(): |
110 | f"include: {', '.join(valid_archs)}.", |
111 | ) |
112 | |
113 | + parser.add_argument("-A", "--archive", help="operate on ARCHIVE", |
114 | + default="ubuntu") |
115 | + |
116 | # Batch processing options |
117 | batch_options = parser.add_argument_group( |
118 | "Batch processing", |
119 | @@ -106,25 +148,35 @@ def main(): |
120 | help="Rescore builds to <priority>.", |
121 | ) |
122 | batch_options.add_argument( |
123 | - "--arch2", |
124 | - action="append", |
125 | - dest="architecture", |
126 | - help=f"Affect only 'architecture' (can be used several times)." |
127 | - f" Valid architectures are: {', '.join(valid_archs)}.", |
128 | + "--state", action="store", dest="state", |
129 | + help="Act on builds that are in the specified state", |
130 | ) |
131 | - parser.add_argument("packages", metavar="package", nargs="+", help=argparse.SUPPRESS) |
132 | + |
133 | + parser.add_argument("packages", metavar="package", nargs="*", help=argparse.SUPPRESS) |
134 | |
135 | # Parse our options. |
136 | args = parser.parse_args() |
137 | |
138 | - try: |
139 | - # Will fail here if we have no credentials, bail out |
140 | - Launchpad.login() |
141 | - except TokenAuthorizationException: |
142 | - sys.exit(1) |
143 | - me = PersonTeam.me |
144 | + launchpad = Launchpad.login_with("ubuntu-dev-tools", "production", |
145 | + version="devel") |
146 | + me = launchpad.me |
147 | |
148 | - if not args.batch: |
149 | + ubuntu = launchpad.distributions['ubuntu'] |
150 | + |
151 | + if args.batch: |
152 | + release = args.series |
153 | + if not release: |
154 | + # ppas don't have a proposed pocket so just use the release pocket; |
155 | + # but for the main archive we default to -proposed |
156 | + release = ubuntu.getDevelopmentSeries()[0].name |
157 | + if args.archive == 'ubuntu': |
158 | + release = release + "-proposed" |
159 | + try: |
160 | + (release, pocket) = split_release_pocket(release) |
161 | + except PocketDoesNotExistError as error: |
162 | + Logger.error(error) |
163 | + sys.exit(1) |
164 | + else: |
165 | # Check we have the correct number of arguments. |
166 | if len(args.packages) < 3: |
167 | parser.error("Incorrect number of arguments.") |
168 | @@ -137,6 +189,14 @@ def main(): |
169 | parser.print_help() |
170 | sys.exit(1) |
171 | |
172 | + archive = launchpad.archives.getByReference(reference=args.archive) |
173 | + try: |
174 | + distroseries = ubuntu.getSeries(name_or_version=release) |
175 | + except lazr.restfulclient.errors.NotFound as error: |
176 | + Logger.error(error) |
177 | + sys.exit(1) |
178 | + |
179 | + if not args.batch: |
180 | # Check our operation. |
181 | if operation not in ("rescore", "retry", "status"): |
182 | Logger.error("Invalid operation: %s.", operation) |
183 | @@ -160,36 +220,37 @@ def main(): |
184 | Logger.error(error) |
185 | sys.exit(1) |
186 | |
187 | - ubuntu_archive = Distribution("ubuntu").getArchive() |
188 | # Get list of published sources for package in question. |
189 | try: |
190 | - sources = ubuntu_archive.getSourcePackage(package, release, pocket) |
191 | - distroseries = Distribution("ubuntu").getSeries(release) |
192 | - except (SeriesNotFoundException, PackageNotFoundException) as error: |
193 | - Logger.error(error) |
194 | + sources = archive.getPublishedSources( |
195 | + distro_series=distroseries, |
196 | + exact_match=True, |
197 | + pocket=pocket, |
198 | + source_name=package, |
199 | + status='Published')[0] |
200 | + except IndexError as error: |
201 | + Logger.error("No publication found for package %s", package) |
202 | sys.exit(1) |
203 | # Get list of builds for that package. |
204 | builds = sources.getBuilds() |
205 | |
206 | # Find out the version and component in given release. |
207 | - version = sources.getVersion() |
208 | - component = sources.getComponent() |
209 | + version = sources.source_package_version |
210 | + component = sources.component_name |
211 | |
212 | # Operations that are remaining may only be done by Ubuntu developers |
213 | # (retry) or buildd admins (rescore). Check if the proper permissions |
214 | # are in place. |
215 | - if operation == "rescore": |
216 | - necessary_privs = me.isLpTeamMember("launchpad-buildd-admins") |
217 | if operation == "retry": |
218 | - necessary_privs = me.canUploadPackage( |
219 | - ubuntu_archive, |
220 | - distroseries, |
221 | - sources.getPackageName(), |
222 | - sources.getComponent(), |
223 | - pocket=pocket, |
224 | + necessary_privs = archive.checkUpload( |
225 | + component=sources.getComponent(), |
226 | + distroseries=distroseries, |
227 | + person=launchpad.me, |
228 | + pocket=pocket, |
229 | + sourcepackagename=sources.getPackageName(), |
230 | ) |
231 | |
232 | - if operation in ("rescore", "retry") and not necessary_privs: |
233 | + if operation == "retry" and not necessary_privs: |
234 | Logger.error( |
235 | "You cannot perform the %s operation on a %s package as you" |
236 | " do not have the permissions to do this action.", |
237 | @@ -223,7 +284,13 @@ def main(): |
238 | # FIXME: make priority an option |
239 | priority = 5000 |
240 | Logger.info("Rescoring build %s to %d...", build.arch_tag, priority) |
241 | - build.rescore(score=priority) |
242 | + try: |
243 | + build.rescore(score=priority) |
244 | + except lazr.restfulclient.errors.Unauthorized: |
245 | + Logger.error( |
246 | + "You don't have the permissions to rescore builds. Ignoring your rescore request." |
247 | + ) |
248 | + break |
249 | else: |
250 | Logger.info("Cannot rescore build on %s.", build.arch_tag) |
251 | if operation == "retry": |
252 | @@ -252,64 +319,131 @@ def main(): |
253 | # filter out duplicate and invalid architectures |
254 | archs.intersection_update(valid_archs) |
255 | |
256 | - release = args.series |
257 | - if not release: |
258 | - release = Distribution("ubuntu").getDevelopmentSeries().name + "-proposed" |
259 | - try: |
260 | - (release, pocket) = split_release_pocket(release) |
261 | - except PocketDoesNotExistError as error: |
262 | - Logger.error(error) |
263 | - sys.exit(1) |
264 | + if not args.packages: |
265 | + retry_count = 0 |
266 | + can_rescore = True |
267 | + |
268 | + if not args.state: |
269 | + if args.retry: |
270 | + args.state='Failed to build' |
271 | + elif args.priority: |
272 | + args.state='Needs building' |
273 | + # there is no equivalent to series.getBuildRecords() for a ppa. |
274 | + # however, we don't want to have to traverse all build records for |
275 | + # all series when working on the main archive, so we use |
276 | + # series.getBuildRecords() for ubuntu and handle ppas separately |
277 | + series = ubuntu.getSeries(name_or_version=release) |
278 | + if args.archive == 'ubuntu': |
279 | + builds = series.getBuildRecords(build_state=args.state, |
280 | + pocket=pocket) |
281 | + else: |
282 | + builds = [] |
283 | + for build in archive.getBuildRecords(build_state=args.state, |
284 | + pocket=pocket): |
285 | + if not build.current_source_publication: |
286 | + continue |
287 | + if build.current_source_publication.distro_series==series: |
288 | + builds.append(build) |
289 | + for build in builds: |
290 | + if build.arch_tag not in archs: |
291 | + continue |
292 | + if not build.current_source_publication: |
293 | + continue |
294 | + # fixme: refactor |
295 | + # Check permissions (part 2): check upload permissions for the |
296 | + # source package |
297 | + can_retry = args.retry and archive.checkUpload( |
298 | + component=build.current_source_publication.component_name, |
299 | + distroseries=series, |
300 | + person=launchpad.me, |
301 | + pocket=pocket, |
302 | + sourcepackagename=build.source_package_name, |
303 | + ) |
304 | + if args.retry and not can_retry: |
305 | + Logger.error( |
306 | + "You don't have the permissions to retry the " |
307 | + "build of '%s', skipping.", |
308 | + build.source_package_name |
309 | + ) |
310 | + continue |
311 | + Logger.info( |
312 | + "The source version for '%s' in '%s' (%s) is: %s", |
313 | + build.source_package_name, |
314 | + release, |
315 | + pocket, |
316 | + build.source_package_version |
317 | + ) |
318 | |
319 | - ubuntu_archive = Distribution("ubuntu").getArchive() |
320 | - try: |
321 | - distroseries = Distribution("ubuntu").getSeries(release) |
322 | - except SeriesNotFoundException as error: |
323 | - Logger.error(error) |
324 | - sys.exit(1) |
325 | + if args.retry: |
326 | + Logger.info("Retrying build of %s on %s...", |
327 | + build.source_package_name, build.arch_tag) |
328 | + retry_count += 1 |
329 | + build.retry() |
330 | + |
331 | + if args.priority and can_rescore: |
332 | + if build.can_be_rescored: |
333 | + try: |
334 | + build.rescore(score=args.priority) |
335 | + except lazr.restfulclient.errors.Unauthorized: |
336 | + Logger.error( |
337 | + "You don't have the permissions to rescore builds. Ignoring your rescore request." |
338 | + ) |
339 | + can_rescore = False |
340 | + except lazr.restfulclient.errors.BadRequest: |
341 | + Logger.info("Cannot rescore build of %s on %s.", |
342 | + build.source_package_name, build.arch_tag) |
343 | + |
344 | + Logger.info("") |
345 | + if args.retry: |
346 | + Logger.info("%d package builds retried", retry_count) |
347 | + sys.exit(0) |
348 | |
349 | - # Check permisions (part 1): Rescoring can only be done by buildd admins |
350 | - can_rescore = args.priority and me.isLpTeamMember("launchpad-buildd-admins") |
351 | - if args.priority and not can_rescore: |
352 | - Logger.error( |
353 | - "You don't have the permissions to rescore builds. Ignoring your rescore request." |
354 | - ) |
355 | |
356 | for pkg in args.packages: |
357 | try: |
358 | - pkg = ubuntu_archive.getSourcePackage(pkg, release, pocket) |
359 | - except PackageNotFoundException as error: |
360 | - Logger.error(error) |
361 | + pkg = archive.getPublishedSources( |
362 | + distro_series=distroseries, |
363 | + exact_match=True, |
364 | + pocket=pocket, |
365 | + source_name=pkg, |
366 | + status='Published')[0] |
367 | + except IndexError as error: |
368 | + Logger.error("No publication found for package %s", pkg) |
369 | continue |
370 | |
371 | # Check permissions (part 2): check upload permissions for the source |
372 | # package |
373 | - can_retry = args.retry and me.canUploadPackage( |
374 | - ubuntu_archive, distroseries, pkg.getPackageName(), pkg.getComponent() |
375 | + can_retry = args.retry and archive.checkUpload( |
376 | + component=pkg.component_name, |
377 | + distroseries=distroseries, |
378 | + person=launchpad.me, |
379 | + pocket=pocket, |
380 | + sourcepackagename=pkg.source_package_name, |
381 | ) |
382 | if args.retry and not can_retry: |
383 | Logger.error( |
384 | "You don't have the permissions to retry the " |
385 | "build of '%s'. Ignoring your request.", |
386 | - pkg.getPackageName(), |
387 | + pkg.source_package_name, |
388 | ) |
389 | |
390 | Logger.info( |
391 | "The source version for '%s' in '%s' (%s) is: %s", |
392 | - pkg.getPackageName(), |
393 | + pkg.source_package_name, |
394 | release, |
395 | pocket, |
396 | - pkg.getVersion(), |
397 | + pkg.source_package_version, |
398 | ) |
399 | |
400 | - Logger.info(pkg.getBuildStates(archs)) |
401 | + Logger.info(getBuildStates(pkg, archs)) |
402 | if can_retry: |
403 | - Logger.info(pkg.retryBuilds(archs)) |
404 | - if args.priority and can_rescore: |
405 | - Logger.info(pkg.rescoreBuilds(archs, args.priority)) |
406 | + Logger.info(retryBuilds(pkg, archs)) |
407 | + if args.priority: |
408 | + Logger.info(rescoreBuilds(pkg, archs, args.priority)) |
409 | |
410 | Logger.info("") |
411 | |
412 | |
413 | if __name__ == "__main__": |
414 | main() |
415 | + |
416 | diff --git a/ubuntutools/lp/lpapicache.py b/ubuntutools/lp/lpapicache.py |
417 | index 589b332..1404809 100644 |
418 | --- a/ubuntutools/lp/lpapicache.py |
419 | +++ b/ubuntutools/lp/lpapicache.py |
420 | @@ -1097,51 +1097,6 @@ class SourcePackagePublishingHistory(BaseWrapper): |
421 | for build in builds: |
422 | self._builds[build.arch_tag] = Build(build) |
423 | |
424 | - def getBuildStates(self, archs): |
425 | - res = [] |
426 | - |
427 | - if not self._builds: |
428 | - self._fetch_builds() |
429 | - |
430 | - for arch in archs: |
431 | - build = self._builds.get(arch) |
432 | - if build: |
433 | - res.append(f" {build}") |
434 | - msg = "\n".join(res) |
435 | - return f"Build state(s) for '{self.getPackageName()}':\n{msg}" |
436 | - |
437 | - def rescoreBuilds(self, archs, score): |
438 | - res = [] |
439 | - |
440 | - if not self._builds: |
441 | - self._fetch_builds() |
442 | - |
443 | - for arch in archs: |
444 | - build = self._builds.get(arch) |
445 | - if build: |
446 | - if build.rescore(score): |
447 | - res.append(f" {arch}: done") |
448 | - else: |
449 | - res.append(f" {arch}: failed") |
450 | - msg = "\n".join(res) |
451 | - return f"Rescoring builds of '{self.getPackageName()}' to {score}:\n{msg}" |
452 | - |
453 | - def retryBuilds(self, archs): |
454 | - res = [] |
455 | - |
456 | - if not self._builds: |
457 | - self._fetch_builds() |
458 | - |
459 | - for arch in archs: |
460 | - build = self._builds.get(arch) |
461 | - if build: |
462 | - if build.retry(): |
463 | - res.append(f" {arch}: done") |
464 | - else: |
465 | - res.append(f" {arch}: failed") |
466 | - msg = "\n".join(res) |
467 | - return f"Retrying builds of '{self.getPackageName()}':\n{msg}" |
468 | - |
469 | |
470 | class BinaryPackagePublishingHistory(BaseWrapper): |
471 | """ |
The script still feels a bit poorly factored to me somehow (like it does a lot of slow look-before- you-leap checking?) but also this change is an improvement I suppose.