Merge ~vorlon/ubuntu-dev-tools:ubuntu-build-revamp into ubuntu-dev-tools: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)
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Gianfranco Costamagna Approve
Review via email: mp+462101@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) :
review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

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.

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
1diff --git a/ubuntu-build b/ubuntu-build
2index 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+
416diff --git a/ubuntutools/lp/lpapicache.py b/ubuntutools/lp/lpapicache.py
417index 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 """

Subscribers

People subscribed via source and target branches