Merge lp:~broder/ubuntu-dev-tools/backportpackage into lp:~ubuntu-dev/ubuntu-dev-tools/trunk

Proposed by Evan Broder
Status: Superseded
Proposed branch: lp:~broder/ubuntu-dev-tools/backportpackage
Merge into: lp:~ubuntu-dev/ubuntu-dev-tools/trunk
Diff against target: 1037 lines (+531/-147)
8 files modified
backportpackage (+246/-0)
debian/changelog (+5/-1)
doc/backportpackage.1 (+74/-0)
doc/sponsor-patch.1 (+8/-3)
setup.py (+1/-0)
sponsor-patch (+61/-143)
ubuntutools/builder.py (+81/-0)
ubuntutools/logger.py (+55/-0)
To merge this branch: bzr merge lp:~broder/ubuntu-dev-tools/backportpackage
Reviewer Review Type Date Requested Status
Evan Broder (community) Needs Fixing
Stefano Rivera Approve
Benjamin Drung Pending
Review via email: mp+43481@code.launchpad.net

This proposal supersedes a proposal from 2010-12-12.

This proposal has been superseded by a proposal from 2010-12-13.

Description of the change

At the Ubuntu Backports BOF at UDS-N (https://blueprints.edge.launchpad.net/ubuntu/+spec/ubuntutheproject-backports-n-bof), we discussed creating tools to make it easier to test backports. ubuntu-dev-tools seems like the logical place for it to live.

backportpackage is sort of vaguely analogous to syncpackage for backports. Note that while it should have all of the correct semantics for actually doing a backport to the Ubuntu archive, those should continue to be handled by the Archive Admins. backportpackage is really intended for uploading backports to a PPA.

To post a comment you must log in.
Revision history for this message
Benjamin Drung (bdrung) wrote : Posted in a previous version of this proposal

This script belongs to this package.

You should set DEB_VENDOR=Ubuntu for dpkg-source.

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Should this be installed by setup.py? (it isn't, yet it appears in the changelog).

Note that on Debian users can't use ppa:X syntax with dput, due to http://bugs.debian.org/505173. I don't know if there's anything we can do about that here except defaulting to the release pocket unless uploading to 'ubuntu'.

{syncpackage, sponsor-patch} uses the current directory, ack-sync (although it isn't installed) uses /tmp/ack-sync, backportpackage uses a temporary directory. sponsor-patch prints a file:/// URI, others print local paths. I wish there were some consistency.

Revision history for this message
Evan Broder (broder) wrote : Posted in a previous version of this proposal

Heh. I...guess I sort of expected the script to get magically installed. Clever plan, that one.

In terms of the directory to use, I'm happy to change backportpackage to use the current directory, but I honestly kind of hate it when scripts go and drop stuff all over my cwd. Let me know what your preferences are as package maintainers.

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

I'm with you on the current directory thing. But I'm also hesitant about adding a 3rd behavior.

I guess that as this generates something the user isn't expecting to work with, /tmp is correct. But then I think we should give mkdtemp a 'backportpackage' prefix (so it can be easily tab-completed) and document the fact that it'll build in /tmp in the manpage.

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

LGTM, although still with reservations about defaulting to the -backports pocket (which makes the tool hard to use on Debian, you have to say you are going to upload to Debian, then dput to your ppa in another terminal, while it's asking for confirmation)

review: Approve
Revision history for this message
Evan Broder (broder) wrote : Posted in a previous version of this proposal

I think you're reading the logic backwards. backportpackage defaults to the release pocket *unless* the upload target is "ubuntu" (i.e. if you were doing a real backport).

Last time I checked, PPAs don't support uploading to anything but the release pocket, so the intent is that if you upload to a PPA, it goes to the release pocket, and if you upload to the archive proper, it goes to the -backports pocket.

Revision history for this message
Benjamin Drung (bdrung) wrote : Posted in a previous version of this proposal

sponsor-patch prints a file:/// URI to make them click-able in gnome-terminal.

The default workdir of sponsor-patch can be changed to point to temporary directory instead of cwd.

Revision history for this message
Benjamin Drung (bdrung) wrote : Posted in a previous version of this proposal

Use subprocess.call instead of subprocess.check_call and print a proper error message if the command fails.

How do you ensure that the orig source tarball is available in the upload destination?

review: Needs Fixing
Revision history for this message
Evan Broder (broder) wrote : Posted in a previous version of this proposal

Ok, file:/// justification makes sense. I'll updated backportpackage to use that.

I'm also not currently checking to see if the orig tarball is available. Fixing by passing -sa to debuild -S.

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Sorry yes, I was commenting from memory, always a bad idea. The only check based on "ppa:" is to add ~ppa1. That's fine.

New issue: Your use subprocess.call and check_call. You use it correctly for pull-lp-source.
For dch, you should probably check "== 0", instead of "not" because returncode can be negative if dch was killed.
For debuild, remember that check_call will throw, not return, on error.

review: Needs Fixing
Revision history for this message
Stefano Rivera (stefanor) :
review: Approve
Revision history for this message
Evan Broder (broder) wrote :

Actually, please hold off on merging this for the moment. I'd like to adjust the interface to accept multiple destination releases and do a backport to each one.

review: Needs Fixing
Revision history for this message
Benjamin Drung (bdrung) wrote :

"Something went wrong updating the package changelog" is not very precise. You could tell that the "dch" command fail and tell the exit code.

834. By Evan Broder

backportpackage: Support backporting to multiple destination releases by using (required) options instead of arguments

835. By Evan Broder

backportpackage: Add a --version argument

--version lets you specify either the version of the source package to
fetch (in the absence of a source release option) or the version to
verify against the specified source release.

836. By Evan Broder

backportpackage: Factor out subprocess.call error handling into a helper

837. By Evan Broder

backportpackage: Wrap source to 80-column lines

838. By Evan Broder

backportpackage: Fix slightly awkward text

839. By Evan Broder

backportpackage: Refactor code some so main structure is clearer

840. By Evan Broder

backportpackage: Make the source package an argument instead of an option

841. By Evan Broder

backportpackage: Update the manpage for the new UI

842. By Evan Broder

sponsor-patch, backportpackage:
Factor out logging code from sponsor-patch and share it with backportpackage

843. By Evan Broder

sponsor-patch: Separate multi-builder support into a Python module.

Rename SPONSOR_PATCH_BUILDER environment variable to
UBUNTUTOOLS_BUILDER (but still support SPONSOR_PATCH_BUILDER)

844. By Evan Broder

backportpackage: Add a --build option. (And a --builder option)

845. By Evan Broder

backportpackage: Minor cleanup identified by pylint

846. By Evan Broder

ubuntutools.builder: Small fixup spotted by pylint

847. By Evan Broder

backportpackage: Unquote URLs we get back from LP

848. By Evan Broder

backportpackage: Abort if build fails

849. By Evan Broder

backportpackage: Error if neither -b nor -u is specified

850. By Evan Broder

backportpackage: --source/--destination instead of --from/--to

851. By Evan Broder

backportpackage: Eliminate global variables

852. By Evan Broder

backportpackage: Pass around options instead of options container

853. By Evan Broder

backportpackage: If no dest release is specified, default to the current release

854. By Evan Broder

doc/backportpackage.1: Add an EXAMPLES section

855. By Evan Broder

backportpackage: Allow specifying a working directory

856. By Evan Broder

backportpackage: Accept a URL or path to a .dsc file as an alternative to a source package name

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'backportpackage'
2--- backportpackage 1970-01-01 00:00:00 +0000
3+++ backportpackage 2010-12-13 04:23:09 +0000
4@@ -0,0 +1,246 @@
5+#!/usr/bin/python
6+# -*- coding: utf-8 -*-
7+# ##################################################################
8+#
9+# This program is free software; you can redistribute it and/or
10+# modify it under the terms of the GNU General Public License
11+# as published by the Free Software Foundation; version 2.
12+#
13+# This program is distributed in the hope that it will be useful,
14+# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+# GNU General Public License for more details.
17+#
18+# See file /usr/share/common-licenses/GPL-2 for more details.
19+#
20+# ##################################################################
21+
22+import optparse
23+import os
24+import shutil
25+import subprocess
26+import sys
27+import tempfile
28+
29+from debian.deb822 import Dsc
30+import launchpadlib.launchpad
31+
32+from ubuntutools.builder import getBuilder
33+from ubuntutools.logger import Logger
34+
35+devnull = open('/dev/null', 'r+')
36+lp = None
37+
38+def error(msg):
39+ Logger.error(msg)
40+ sys.exit(1)
41+
42+def check_call(cmd, *args, **kwargs):
43+ Logger.command(cmd)
44+ ret = subprocess.call(cmd, *args, **kwargs)
45+ if ret != 0:
46+ error('%s returned %d' % (cmd, ret))
47+
48+def parse(args):
49+ usage = 'Usage: %prog [options] <source package>'
50+ p = optparse.OptionParser(usage)
51+ p.add_option('-t', '--to',
52+ dest='dest_releases',
53+ default=[],
54+ action='append',
55+ help='Backport to DEST release (required)',
56+ metavar='DEST')
57+ p.add_option('-f', '--from',
58+ dest='source_release',
59+ default=None,
60+ help='Backport from SOURCE release (default: devel release)',
61+ metavar='SOURCE')
62+ p.add_option('-b', '--build',
63+ dest='build',
64+ default=False,
65+ action='store_true',
66+ help='Build the package before uploading (default: %default)')
67+ p.add_option('-B', '--builder',
68+ dest='builder',
69+ default=None,
70+ help='Specify the package builder (default: pbuilder)',
71+ metavar='BUILDER')
72+ p.add_option('-u', '--upload',
73+ dest='upload',
74+ help='Specify an upload destination',
75+ metavar='UPLOAD')
76+ p.add_option('-v', '--version',
77+ dest='version',
78+ default=None,
79+ help='Package version to backport (or verify)',
80+ metavar='VERSION')
81+ p.add_option('-l', '--launchpad',
82+ dest='launchpad',
83+ default='production',
84+ help='Launchpad instance to connect to (default: %default)',
85+ metavar='INSTANCE')
86+
87+ opts, args = p.parse_args(args)
88+ if len(args) != 1:
89+ p.error('You must specify a source package')
90+ if not opts.dest_releases:
91+ p.error('You must specify at least one destination release')
92+
93+ return opts, args
94+
95+def find_release_package(package, opts):
96+ ubuntu = lp.distributions['ubuntu']
97+ archive = ubuntu.main_archive
98+ series = ubuntu.getSeries(name_or_version=opts.source_release)
99+ status = 'Published'
100+ for pocket in ('Updates', 'Security', 'Release'):
101+ try:
102+ srcpkg = archive.getPublishedSources(source_name=package,
103+ distro_series=series,
104+ pocket=pocket,
105+ status=status,
106+ exact_match=True)[0]
107+ break
108+ except IndexError:
109+ continue
110+ else:
111+ error('Unable to find package %s in release %s' %
112+ (package, opts.source_release))
113+
114+ if opts.version and opts.version != srcpkg.source_package_version:
115+ error('Requested backport of version %s but %s is at version %s' %
116+ (opts.version, package, srcpkg.source_package_version))
117+
118+ return srcpkg
119+
120+def find_version_package(package, opts):
121+ ubuntu = lp.distributions['ubuntu']
122+ archive = ubuntu.main_archive
123+ try:
124+ # Might get more than one (i.e. same version in multiple
125+ # releases), but they should all be identical
126+ return archive.getPublishedSources(source_name=package,
127+ version=opts.version)[0]
128+ except IndexError:
129+ error('Package %s was never published with version %s in Ubuntu' %
130+ (package, opts.version))
131+
132+def fetch_package(workdir, package, opts):
133+ # Returns the path to the .dsc file that was fetched
134+
135+ if not opts.source_release and not opts.version:
136+ opts.source_release = lp.distributions['ubuntu'].current_series.name
137+
138+ # If source_release is specified, then version is just for
139+ # verification
140+ if opts.source_release:
141+ srcpkg = find_release_package(package, opts)
142+ else:
143+ srcpkg = find_version_package(package, opts)
144+
145+ for f in srcpkg.sourceFileUrls():
146+ if f.endswith('.dsc'):
147+ check_call(['dget',
148+ '--download-only',
149+ '--allow-unauthenticated',
150+ f],
151+ cwd=workdir)
152+ return os.path.join(workdir, os.path.basename(f))
153+ else:
154+ error('Package %s contains no .dsc file' % package)
155+
156+def get_backport_version(version, upload, release):
157+ v = version + ('~%s1' % release)
158+ if upload and upload.startswith('ppa:'):
159+ v += '~ppa1'
160+ return v
161+
162+def get_backport_dist(upload, release):
163+ if not upload or upload == 'ubuntu':
164+ return '%s-backports' % release
165+ else:
166+ return release
167+
168+def do_build(workdir, package, release, bp_version, opts):
169+ builder = getBuilder(opts.builder)
170+ if not builder:
171+ return
172+
173+ builder.build(os.path.join(workdir,
174+ '%s_%s.dsc' % (package, bp_version)),
175+ release,
176+ workdir)
177+
178+def do_upload(workdir, package, bp_version, opts):
179+ prompt = 'Do you want to upload this to %s? [Y/n]' % opts.upload
180+ while True:
181+ answer = raw_input(prompt).strip().lower()
182+ if answer in ('', 'y', 'yes'):
183+ break
184+ elif answer in ('n', 'no'):
185+ return
186+
187+ check_call(['dput',
188+ opts.upload,
189+ '%s_%s_source.changes' %
190+ (package, bp_version)],
191+ cwd=workdir)
192+
193+
194+def do_backport(workdir, package, dscfile, release, opts):
195+ dsc = Dsc(open(os.path.join(workdir, dscfile)))
196+ v = dsc['Version']
197+
198+ check_call(['dpkg-source',
199+ '-x',
200+ dscfile,
201+ package],
202+ cwd=workdir)
203+ srcdir = os.path.join(workdir, package)
204+
205+ bp_version = get_backport_version(v, opts.upload, release)
206+ bp_dist = get_backport_dist(opts.upload, release)
207+
208+ check_call(['dch',
209+ '--force-bad-version',
210+ '--preserve',
211+ '--newversion', bp_version,
212+ '--distribution', bp_dist,
213+ 'No-change backport to %s' % release],
214+ cwd=srcdir)
215+ check_call(['debuild', '-S', '-sa'],
216+ cwd=srcdir)
217+
218+ if ':' in bp_version:
219+ bp_version = bp_version[bp_version.find(':')+1:]
220+
221+ print 'Please check the package in file://%s carefully' % workdir
222+ if opts.build:
223+ do_build(workdir, package, release, bp_version, opts)
224+ if opts.upload:
225+ do_upload(workdir, package, bp_version, opts)
226+
227+ shutil.rmtree(srcdir)
228+
229+def main(args):
230+ global lp
231+
232+ os.environ['DEB_VENDOR'] = 'Ubuntu'
233+
234+ opts, (package,) = parse(args[1:])
235+
236+ script_name = os.path.basename(sys.argv[0])
237+ lp = launchpadlib.launchpad.Launchpad.login_anonymously(script_name,
238+ opts.launchpad)
239+
240+ workdir = tempfile.mkdtemp(prefix='backportpackage-')
241+ try:
242+ dscfile = fetch_package(workdir, package, opts)
243+
244+ for release in opts.dest_releases:
245+ do_backport(workdir, package, dscfile, release, opts)
246+ finally:
247+ shutil.rmtree(workdir)
248+
249+if __name__ == '__main__':
250+ sys.exit(main(sys.argv))
251
252=== modified file 'debian/changelog'
253--- debian/changelog 2010-12-11 10:35:18 +0000
254+++ debian/changelog 2010-12-13 04:23:09 +0000
255@@ -1,10 +1,14 @@
256 ubuntu-dev-tools (0.108) UNRELEASED; urgency=low
257
258+ [ Stefano Rivera ]
259 * lp-shell, import-bug-from-debian:
260 Use the 'production' LP instance instead of 'edge' (which is going away).
261 * pbuilder-dist: Fix typo in local archive support, introduced in 0.107.
262
263- -- Stefano Rivera <stefanor@ubuntu.com> Sat, 11 Dec 2010 12:33:37 +0200
264+ [ Evan Broder ]
265+ * backportpackage: new script for testing backport requests in a PPA.
266+
267+ -- Evan Broder <evan@ebroder.net> Sat, 11 Dec 2010 14:11:54 -0800
268
269 ubuntu-dev-tools (0.107) experimental; urgency=low
270
271
272=== added file 'doc/backportpackage.1'
273--- doc/backportpackage.1 1970-01-01 00:00:00 +0000
274+++ doc/backportpackage.1 2010-12-13 04:23:09 +0000
275@@ -0,0 +1,74 @@
276+.TH BACKPORTPACKAGE "1" "December 2010" "ubuntu-dev-tools"
277+.SH NAME
278+backportpackage \- helper to test package backports
279+.SH SYNOPSIS
280+.TP
281+.B backportpackage \fR[\fIadditional options\fR]
282+\-\-to <\fIdest release\fR>
283+.br
284+\-\-upload <\fIupload target\fR>
285+.br
286+<\fIsource package\fR>
287+.PP
288+.B backportpackage \-h
289+.SH OPTIONS
290+.TP
291+.B \-t \fIDEST\fR, \-\-to=\fIDEST\fR
292+\fBRequired\fR. Backport the package to the specified Ubuntu
293+release. This option may be specified multiple times, but must be
294+specified at least once.
295+.TP
296+.B \-f \fISOURCE\fR, \-\-from=\fISOURCE\fR
297+Backport the package from the specified Ubuntu release. If neither
298+this option nor \fB\-\-version\fR are specified, then
299+\fBbackportpackage\fR defaults to the current Ubuntu development
300+release.
301+.TP
302+.B \-b, \-\-build
303+Build the package with the specified builder before uploading. Note
304+for \fBpbuilder\fR(8) users: This assumes the common configuration,
305+where the \fBDIST\fR environment is read by \fBpbuilderrc\fR(5) to
306+select the correct base image.
307+.TP
308+.B \-B \fIBUILDER\fR, \fB\-\-builder\fR=\fIBUILDER
309+Use the specified builder to build the package. Supported are
310+\fBpbuilder\fR(8) and \fBsbuild\fR(1). This overrides
311+\fBUBUNTUTOOLS_BUILDER\fR. The default is \fBpbuilder\fR(8).
312+.TP
313+.B \-u \fIUPLOAD\fR, \-\-upload=\fIUPLOAD\fR
314+Upload to \fIUPLOAD\fR with \fBdput\fR(1) (after confirmation).
315+.TP
316+.B \-v \fIVERSION\fR, \-\-version=\fIVERSION\fR
317+If the \fB\-\-from\fR option is specified, then \fBbackportpackage\fR
318+verifies that the current version of \fIsource package\fR in
319+\fISOURCE\fR is the same as \fIVERSION\fR. Otherwise,
320+\fBbackportpackage\fR finds version \fIVERSION\fR of \fIsource
321+package\fR, regardless of the release in which it was published (or if
322+that version is still current).
323+.TP
324+.B \-l \fIINSTANCE\fR, \-\-launchpad=\fIINSTANCE\fR
325+Use the specified instance of Launchpad (e.g. "staging"), instead of
326+the default of "production".
327+.SH DESCRIPTION
328+\fBbackportpackage\fR fetches a package from one Ubuntu release and
329+creates a no-change backport of that package to a previous release,
330+optionally doing a test build of the package and/or uploading the
331+resulting backport for testing.
332+.PP
333+The backported package is fetched and built in a temporary directory
334+in \fB/tmp\fR, which is removed once the script finishes running.
335+.PP
336+\fBbackportpackage\fR is only recommended for testing backports in a
337+PPA, not uploading backports to the Ubuntu archive.
338+.SH ENVIRONMENT
339+.TP
340+.B UBUNTUTOOLS_BUILDER
341+The default builder for Ubuntu development tools that support it
342+(including \fBbackportpackage\fR. Supported are \fBpbuilder\fR(8) and
343+\fBsbuild\fR(1). If unset and not provided on the command line,
344+\fBpbuilder\fR(8) is used.
345+.SH AUTHOR
346+\fBbackportpackage\fR and this manpage were written by Evan Broder
347+<evan@ebroder.net>
348+.PP
349+Both are released under GNU General Public License, version 2.
350
351=== modified file 'doc/sponsor-patch.1'
352--- doc/sponsor-patch.1 2010-11-26 18:34:31 +0000
353+++ doc/sponsor-patch.1 2010-12-13 04:23:09 +0000
354@@ -61,7 +61,7 @@
355 .B \-B \fIBUILDER\fR, \fB\-\-builder\fR=\fIBUILDER
356 Use the specify builder to build the package.
357 Supported are \fBpbuilder\fR(8) and \fBsbuild\fR(1).
358-This overrides \fBSPONSOR_PATCH_BUILDER\fR.
359+This overrides \fBUBUNTUTOOLS_BUILDER\fR and \fBSPONSOR_PATCH_BUILDER\fR.
360 The default is \fBpbuilder\fR(8).
361 .TP
362 .BR \-e ", " \-\-edit
363@@ -90,10 +90,15 @@
364 .SH ENVIRONMENT
365
366 .TP
367+.B UBUNTUTOOLS_BUILDER
368+The default builder for Ubuntu development tools that support it (including \fBsponsor\-patch\fR.
369+Supported are \fBpbuilder\fR(8) and \fBsbuild\fR(1).
370+If unset and not provided on the command line, \fBpbuilder\fR(8) is used.
371+
372+.TP
373 .B SPONSOR_PATCH_BUILDER
374 The default builder for \fBsponsor\-patch\fR.
375-Supported are \fBpbuilder\fR(8) and \fBsbuild\fR(1).
376-If unset and not provided on the command line, \fBpbuilder\fR(8) is used.
377+If specified, this overrides \fBUBUNTUTOOLS_BUILDER\fR.
378
379 .TP
380 .B SPONSOR_PATCH_WORKDIR
381
382=== modified file 'setup.py'
383--- setup.py 2010-09-19 22:13:30 +0000
384+++ setup.py 2010-12-13 04:23:09 +0000
385@@ -16,6 +16,7 @@
386 setup(name='ubuntu-dev-tools',
387 version=version,
388 scripts=['404main',
389+ 'backportpackage',
390 'check-symbols',
391 'dch-repeat',
392 'dgetlp',
393
394=== modified file 'sponsor-patch'
395--- sponsor-patch 2010-11-26 18:34:31 +0000
396+++ sponsor-patch 2010-12-13 04:23:09 +0000
397@@ -28,7 +28,9 @@
398 import debian.debian_support
399 import launchpadlib.launchpad
400
401+from ubuntutools.builder import getBuilder
402 import ubuntutools.update_maintainer
403+from ubuntutools.logger import Logger
404
405 USER_ABORT = 2
406
407@@ -57,7 +59,7 @@
408 dsc_file = None
409 for url in source_files:
410 filename = urllib.unquote(os.path.basename(url))
411- Print.info("Downloading %s..." % (filename))
412+ Logger.info("Downloading %s..." % (filename))
413 urllib.urlretrieve(url, filename)
414 if url.endswith(".dsc"):
415 dsc_file = filename
416@@ -148,50 +150,6 @@
417 return self.project == "ubuntu"
418
419
420-class Builder(object):
421- def __init__(self, name):
422- self.name = name
423- cmd = ["dpkg-architecture", "-qDEB_BUILD_ARCH_CPU"]
424- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
425- self.architecture = process.communicate()[0].strip()
426-
427- def get_architecture(self):
428- return self.architecture
429-
430- def get_name(self):
431- return self.name
432-
433-
434-class Pbuilder(Builder):
435- def __init__(self):
436- Builder.__init__(self, "pbuilder")
437-
438- def build(self, dsc_file, dist, result_directory):
439- # TODO: Do not rely on a specific pbuilder configuration.
440- cmd = ["sudo", "-E", "DIST=" + dist, "pbuilder", "--build",
441- "--distribution", dist, "--architecture", self.architecture,
442- "--buildresult", result_directory, dsc_file]
443- Print.command(cmd)
444- return subprocess.call(cmd)
445-
446-
447-class Sbuild(Builder):
448- def __init__(self):
449- Builder.__init__(self, "sbuild")
450-
451- def build(self, dsc_file, dist, result_directory):
452- workdir = os.getcwd()
453- Print.command(["cd", result_directory])
454- os.chdir(result_directory)
455- cmd = ["sbuild", "--arch-all", "--dist=" + dist,
456- "--arch=" + self.architecture, dsc_file]
457- Print.command(cmd)
458- result = subprocess.call(cmd)
459- Print.command(["cd", workdir])
460- os.chdir(workdir)
461- return result
462-
463-
464 class Patch(object):
465 def __init__(self, patch_file):
466 self.patch_file = patch_file
467@@ -220,41 +178,6 @@
468 self.changed_files)) > 0
469
470
471-class Print(object):
472- script_name = os.path.basename(sys.argv[0])
473- verbose = False
474-
475- @classmethod
476- def command(cls, cmd):
477- if cls.verbose:
478- for i in xrange(len(cmd)):
479- if cmd[i].find(" ") >= 0:
480- cmd[i] = '"' + cmd[i] + '"'
481- print "%s: I: %s" % (script_name, " ".join(cmd))
482-
483- @classmethod
484- def debug(cls, message):
485- if cls.verbose:
486- print "%s: D: %s" % (script_name, message)
487-
488- @classmethod
489- def error(cls, message):
490- print >> sys.stderr, "%s: Error: %s" % (script_name, message)
491-
492- @classmethod
493- def info(cls, message):
494- if cls.verbose:
495- print "%s: I: %s" % (script_name, message)
496-
497- @classmethod
498- def normal(cls, message):
499- print "%s: %s" % (script_name, message)
500-
501- @classmethod
502- def set_verbosity(cls, verbose):
503- cls.verbose = verbose
504-
505-
506 def get_source_package_name(bug_task):
507 package = None
508 if bug_task.bug_target_name != "ubuntu":
509@@ -334,7 +257,7 @@
510 def edit_source():
511 # Spawn shell to allow modifications
512 cmd = [get_user_shell()]
513- Print.command(cmd)
514+ Logger.command(cmd)
515 print """An interactive shell was launched in
516 file://%s
517 Edit your files. When you are done, exit the shell. If you wish to abort the
518@@ -342,7 +265,7 @@
519 """ % (os.getcwd()),
520 returncode = subprocess.call(cmd)
521 if returncode != 0:
522- Print.error("Shell exited with exit value %i." % (returncode))
523+ Logger.error("Shell exited with exit value %i." % (returncode))
524 sys.exit(1)
525
526 def get_fixed_lauchpad_bugs(changes_file):
527@@ -380,10 +303,10 @@
528 linked_branches = map(lambda b: b.branch, bug.linked_branches)
529 if len(attached_patches) == 0 and len(linked_branches) == 0:
530 if len(bug.attachments) == 0:
531- Print.error("No attachment and no linked branch found on bug #%i." \
532+ Logger.error("No attachment and no linked branch found on bug #%i." \
533 % (bug.id))
534 else:
535- Print.error(("No attached patch and no linked branch found. Go " \
536+ Logger.error(("No attached patch and no linked branch found. Go " \
537 "to https://launchpad.net/bugs/%i and mark an " \
538 "attachment as patch.") % (bug.id))
539 sys.exit(1)
540@@ -393,13 +316,13 @@
541 branch = linked_branches[0].bzr_identity
542 else:
543 if len(attached_patches) == 0:
544- Print.normal("https://launchpad.net/bugs/%i has %i branches " \
545+ Logger.normal("https://launchpad.net/bugs/%i has %i branches " \
546 "linked:" % (bug.id, len(linked_branches)))
547 elif len(linked_branches) == 0:
548- Print.normal("https://launchpad.net/bugs/%i has %i patches" \
549+ Logger.normal("https://launchpad.net/bugs/%i has %i patches" \
550 " attached:" % (bug.id, len(attached_patches)))
551 else:
552- Print.normal("https://launchpad.net/bugs/%i has %i branch(es)" \
553+ Logger.normal("https://launchpad.net/bugs/%i has %i branch(es)" \
554 " linked and %i patch(es) attached:" % \
555 (bug.id, len(linked_branches), len(attached_patches)))
556 i = 0
557@@ -421,11 +344,11 @@
558 patch_filename = re.sub(" ", "_", patch.title)
559 if not reduce(lambda r, x: r or patch.title.endswith(x),
560 (".debdiff", ".diff", ".patch"), False):
561- Print.info("Patch %s does not have a proper file extension." % \
562+ Logger.info("Patch %s does not have a proper file extension." % \
563 (patch.title))
564 patch_filename += ".patch"
565
566- Print.info("Downloading %s." % (patch_filename))
567+ Logger.info("Downloading %s." % (patch_filename))
568 patch_file = open(patch_filename, "w")
569 patch_file.write(patch.data.open().read())
570 patch_file.close()
571@@ -436,18 +359,18 @@
572 if os.path.isdir(dir_name):
573 shutil.rmtree(dir_name)
574 cmd = ["bzr", "branch", branch]
575- Print.command(cmd)
576+ Logger.command(cmd)
577 if subprocess.call(cmd) != 0:
578- Print.error("Failed to download branch %s." % (branch))
579+ Logger.error("Failed to download branch %s." % (branch))
580 sys.exit(1)
581 return dir_name
582
583 def merge_branch(branch):
584 edit = False
585 cmd = ["bzr", "merge", branch]
586- Print.command(cmd)
587+ Logger.command(cmd)
588 if subprocess.call(cmd) != 0:
589- Print.error("Failed to merge branch %s." % (branch))
590+ Logger.error("Failed to merge branch %s." % (branch))
591 ask_for_manual_fixing()
592 edit = True
593 return edit
594@@ -456,9 +379,9 @@
595 cmd = ["dpkg-source", "--no-preparation", "-x", dsc_file]
596 if not verbose:
597 cmd.insert(1, "-q")
598- Print.command(cmd)
599+ Logger.command(cmd)
600 if subprocess.call(cmd) != 0:
601- Print.error("Extraction of %s failed." % (os.path.basename(dsc_file)))
602+ Logger.error("Extraction of %s failed." % (os.path.basename(dsc_file)))
603 sys.exit(1)
604
605 def apply_patch(task, patch):
606@@ -466,9 +389,9 @@
607 if patch.is_debdiff():
608 cmd = ["patch", "--merge", "--force", "-p",
609 str(patch.get_strip_level()), "-i", patch.full_path]
610- Print.command(cmd)
611+ Logger.command(cmd)
612 if subprocess.call(cmd) != 0:
613- Print.error("Failed to apply debdiff %s to %s %s." % \
614+ Logger.error("Failed to apply debdiff %s to %s %s." % \
615 (patch.get_name(), task.package, task.get_version()))
616 if not edit:
617 ask_for_manual_fixing()
618@@ -477,9 +400,9 @@
619 # FIXME: edit-patch needs a non-interactive mode
620 # https://launchpad.net/bugs/612566
621 cmd = ["edit-patch", patch.full_path]
622- Print.command(cmd)
623+ Logger.command(cmd)
624 if subprocess.call(cmd) != 0:
625- Print.error("Failed to apply diff %s to %s %s." % \
626+ Logger.error("Failed to apply diff %s to %s %s." % \
627 (patch.get_name(), task.package, task.get_version()))
628 if not edit:
629 ask_for_manual_fixing()
630@@ -493,11 +416,11 @@
631 try:
632 os.makedirs(workdir)
633 except os.error, error:
634- Print.error("Failed to create the working directory %s [Errno " \
635+ Logger.error("Failed to create the working directory %s [Errno " \
636 "%i]: %s." % (workdir, error.errno, error.strerror))
637 sys.exit(1)
638 if workdir != os.getcwd():
639- Print.command(["cd", workdir])
640+ Logger.command(["cd", workdir])
641 os.chdir(workdir)
642
643 script_name = os.path.basename(sys.argv[0])
644@@ -510,13 +433,13 @@
645 bug_tasks = map(lambda x: BugTask(x, launchpad), bug.bug_tasks)
646 ubuntu_tasks = filter(lambda x: x.is_ubuntu_task(), bug_tasks)
647 if len(ubuntu_tasks) == 0:
648- Print.error("No Ubuntu bug task found on bug #%i." % (bug_number))
649+ Logger.error("No Ubuntu bug task found on bug #%i." % (bug_number))
650 sys.exit(1)
651 elif len(ubuntu_tasks) == 1:
652 task = ubuntu_tasks[0]
653 if len(ubuntu_tasks) > 1:
654 if verbose:
655- Print.info("%i Ubuntu tasks exist for bug #%i." % \
656+ Logger.info("%i Ubuntu tasks exist for bug #%i." % \
657 (len(ubuntu_tasks), bug_number))
658 for task in ubuntu_tasks:
659 print task.get_short_info()
660@@ -524,7 +447,7 @@
661 if len(open_ubuntu_tasks) == 1:
662 task = open_ubuntu_tasks[0]
663 else:
664- Print.normal("https://launchpad.net/bugs/%i has %i Ubuntu tasks:" \
665+ Logger.normal("https://launchpad.net/bugs/%i has %i Ubuntu tasks:" \
666 % (bug_number, len(ubuntu_tasks)))
667 for i in xrange(len(ubuntu_tasks)):
668 print "%i) %s" % (i + 1,
669@@ -532,7 +455,7 @@
670 selected = input_number("To which Ubuntu tasks do the patch belong",
671 1, len(ubuntu_tasks))
672 task = ubuntu_tasks[selected - 1]
673- Print.info("Selected Ubuntu task: %s" % (task.get_short_info()))
674+ Logger.info("Selected Ubuntu task: %s" % (task.get_short_info()))
675
676 dsc_file = task.download_source()
677 assert os.path.isfile(dsc_file), "%s does not exist." % (dsc_file)
678@@ -540,15 +463,15 @@
679 if patch:
680 patch = download_patch(patch)
681
682- Print.info("Ubuntu package: %s" % (task.package))
683+ Logger.info("Ubuntu package: %s" % (task.package))
684 if task.is_merge():
685- Print.info("The task is a merge request.")
686+ Logger.info("The task is a merge request.")
687
688 extract_source(dsc_file, verbose)
689
690 # change directory
691 directory = task.package + '-' + task.get_version().upstream_version
692- Print.command(["cd", directory])
693+ Logger.command(["cd", directory])
694 os.chdir(directory)
695
696 edit |= apply_patch(task, patch)
697@@ -556,7 +479,7 @@
698 branch_dir = download_branch(task.get_branch_link())
699
700 # change directory
701- Print.command(["cd", branch_dir])
702+ Logger.command(["cd", branch_dir])
703 os.chdir(branch_dir)
704
705 edit |= merge_branch(branch)
706@@ -568,9 +491,9 @@
707 edit = True
708
709 # update the Maintainer field
710- Print.command(["update-maintainer"])
711+ Logger.command(["update-maintainer"])
712 if ubuntutools.update_maintainer.update_maintainer(verbose) != 0:
713- Print.error("update-maintainer script failed.")
714+ Logger.error("update-maintainer script failed.")
715 sys.exit(1)
716
717 # Get new version of package
718@@ -578,7 +501,7 @@
719 try:
720 new_version = changelog.get_version()
721 except IndexError:
722- Print.error("Debian package version could not be determined. " \
723+ Logger.error("Debian package version could not be determined. " \
724 "debian/changelog is probably malformed.")
725 ask_for_manual_fixing()
726 continue
727@@ -586,15 +509,15 @@
728 # Check if version of the new package is greater than the version in
729 # the archive.
730 if new_version <= task.get_version():
731- Print.error("The version %s is not greater than the already " \
732+ Logger.error("The version %s is not greater than the already " \
733 "available %s." % (new_version, task.get_version()))
734 ask_for_manual_fixing()
735 continue
736
737 cmd = ["dch", "--maintmaint", "--edit", ""]
738- Print.command(cmd)
739+ Logger.command(cmd)
740 if subprocess.call(cmd) != 0:
741- Print.info("Failed to update timestamp in debian/changelog.")
742+ Logger.info("Failed to update timestamp in debian/changelog.")
743
744 # Build source package
745 if patch:
746@@ -615,9 +538,9 @@
747 env = os.environ
748 if upload == 'ubuntu':
749 env['DEB_VENDOR'] = 'Ubuntu'
750- Print.command(cmd)
751+ Logger.command(cmd)
752 if subprocess.call(cmd, env=env) != 0:
753- Print.error("Failed to build source tarball.")
754+ Logger.error("Failed to build source tarball.")
755 # TODO: Add a "retry" option
756 ask_for_manual_fixing()
757 continue
758@@ -634,7 +557,7 @@
759 debdiff_filename = os.path.join(workdir, debdiff_name)
760 if not verbose:
761 cmd.insert(1, "-q")
762- Print.command(cmd + [">", debdiff_filename])
763+ Logger.command(cmd + [">", debdiff_filename])
764 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
765 debdiff = process.communicate()[0]
766
767@@ -646,7 +569,7 @@
768 # Make sure that the Launchpad bug will be closed
769 changes_file = new_dsc_file[:-4] + "_source.changes"
770 if bug_number not in get_fixed_lauchpad_bugs(changes_file):
771- Print.error("Launchpad bug #%i is not closed by new version." % \
772+ Logger.error("Launchpad bug #%i is not closed by new version." % \
773 (bug_number))
774 ask_for_manual_fixing()
775 continue
776@@ -660,7 +583,7 @@
777 allowed = map(lambda s: s + "-proposed", supported_series) + \
778 [devel_series]
779 if changelog.distributions not in allowed:
780- Print.error("%s is not an allowed series. It needs to be one " \
781+ Logger.error("%s is not an allowed series. It needs to be one " \
782 "of %s." % (changelog.distributions,
783 ", ".join(allowed)))
784 ask_for_manual_fixing()
785@@ -668,7 +591,7 @@
786 elif upload and upload.startwith("ppa/"):
787 allowed = supported_series + [devel_series]
788 if changelog.distributions not in allowed:
789- Print.error("%s is not an allowed series. It needs to be one " \
790+ Logger.error("%s is not an allowed series. It needs to be one " \
791 "of %s." % (changelog.distributions,
792 ", ".join(allowed)))
793 ask_for_manual_fixing()
794@@ -683,7 +606,7 @@
795 dist = re.sub("-.*$", "", changelog.distributions)
796 result = builder.build(new_dsc_file, dist, buildresult)
797 if result != 0:
798- Print.error("Failed to build %s from source with %s." % \
799+ Logger.error("Failed to build %s from source with %s." % \
800 (os.path.basename(new_dsc_file),
801 builder.get_name()))
802 # TODO: Add "retry" and "update" option
803@@ -699,7 +622,7 @@
804 cmd = ["lintian", "-IE", "--pedantic", "-q", build_changes]
805 lintian_filename = os.path.join(workdir,
806 task.package + "_" + strip_epoch(new_version) + ".lintian")
807- Print.command(cmd + [">", lintian_filename])
808+ Logger.command(cmd + [">", lintian_filename])
809 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
810 report = process.communicate()[0]
811
812@@ -727,26 +650,26 @@
813 print "Abort."
814 sys.exit(USER_ABORT)
815 cmd = ["dput", "--force", upload, changes_file]
816- Print.command(cmd)
817+ Logger.command(cmd)
818 if subprocess.call(cmd) != 0:
819- Print.error("Upload of %s to %s failed." % \
820+ Logger.error("Upload of %s to %s failed." % \
821 (os.path.basename(changes_file), upload))
822 sys.exit(1)
823 if branch:
824 cmd = ['debcommit']
825- Print.command(cmd)
826+ Logger.command(cmd)
827 if subprocess.call(cmd) != 0:
828- Print.error('Bzr commit failed.')
829+ Logger.error('Bzr commit failed.')
830 sys.exit(1)
831 cmd = ['bzr', 'mark-uploaded']
832- Print.command(cmd)
833+ Logger.command(cmd)
834 if subprocess.call(cmd) != 0:
835- Print.error('Bzr tagging failed.')
836+ Logger.error('Bzr tagging failed.')
837 sys.exit(1)
838 cmd = ['bzr', 'push', ':parent']
839- Print.command(cmd)
840+ Logger.command(cmd)
841 if subprocess.call(cmd) != 0:
842- Print.error('Bzr push failed.')
843+ Logger.error('Bzr push failed.')
844 sys.exit(1)
845
846 # Leave while loop if everything worked
847@@ -766,7 +689,7 @@
848 if "SPONSOR_PATCH_BUILDER" in os.environ:
849 default_builder = os.environ["SPONSOR_PATCH_BUILDER"]
850 else:
851- default_builder = "pbuilder"
852+ default_builder = None
853
854 parser.add_option("-b", "--build", dest="build",
855 help="Build the package with the specified builder.",
856@@ -790,29 +713,24 @@
857 help="Specify a working directory.")
858
859 (options, args) = parser.parse_args()
860- Print.set_verbosity(options.verbose)
861+ Logger.set_verbosity(options.verbose)
862
863 if len(args) == 0:
864- Print.error("No bug number specified.")
865+ Logger.error("No bug number specified.")
866 sys.exit(1)
867 elif len(args) > 1:
868- Print.error("Multiple bug numbers specified: %s" % (", ".join(args)))
869+ Logger.error("Multiple bug numbers specified: %s" % (", ".join(args)))
870 sys.exit(1)
871
872 bug_number = args[0]
873 if bug_number.isdigit():
874 bug_number = int(bug_number)
875 else:
876- Print.error("Invalid bug number specified: %s" % (bug_number))
877+ Logger.error("Invalid bug number specified: %s" % (bug_number))
878 sys.exit(1)
879
880- if options.builder == "pbuilder":
881- builder = Pbuilder()
882- elif options.builder == "sbuild":
883- builder = Sbuild()
884- else:
885- Print.error("Unsupported builder specified: %s. Only pbuilder and "
886- "sbuild are supported." % (options.builder))
887+ builder = getBuilder(options.builder)
888+ if not builder:
889 sys.exit(1)
890
891 if options.sponsoring:
892
893=== added file 'ubuntutools/builder.py'
894--- ubuntutools/builder.py 1970-01-01 00:00:00 +0000
895+++ ubuntutools/builder.py 2010-12-13 04:23:09 +0000
896@@ -0,0 +1,81 @@
897+#
898+# builder.py - Helper classes for building packages
899+#
900+# Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com>
901+# Copyright (C) 2010, Evan Broder <evan@ebroder.net>
902+#
903+# Permission to use, copy, modify, and/or distribute this software
904+# for any purpose with or without fee is hereby granted, provided
905+# that the above copyright notice and this permission notice appear
906+# in all copies.
907+#
908+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
909+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
910+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
911+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
912+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
913+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
914+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
915+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
916+#
917+
918+import os
919+import subprocess
920+
921+from ubuntutools.logger import Logger
922+
923+class Builder(object):
924+ def __init__(self, name):
925+ self.name = name
926+ cmd = ["dpkg-architecture", "-qDEB_BUILD_ARCH_CPU"]
927+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
928+ self.architecture = process.communicate()[0].strip()
929+
930+ def get_architecture(self):
931+ return self.architecture
932+
933+ def get_name(self):
934+ return self.name
935+
936+
937+class Pbuilder(Builder):
938+ def __init__(self):
939+ Builder.__init__(self, "pbuilder")
940+
941+ def build(self, dsc_file, dist, result_directory):
942+ # TODO: Do not rely on a specific pbuilder configuration.
943+ cmd = ["sudo", "-E", "DIST=" + dist, "pbuilder", "--build",
944+ "--distribution", dist, "--architecture", self.architecture,
945+ "--buildresult", result_directory, dsc_file]
946+ Logger.command(cmd)
947+ return subprocess.call(cmd)
948+
949+
950+class Sbuild(Builder):
951+ def __init__(self):
952+ Builder.__init__(self, "sbuild")
953+
954+ def build(self, dsc_file, dist, result_directory):
955+ workdir = os.getcwd()
956+ Logger.command(["cd", result_directory])
957+ os.chdir(result_directory)
958+ cmd = ["sbuild", "--arch-all", "--dist=" + dist,
959+ "--arch=" + self.architecture, dsc_file]
960+ Logger.command(cmd)
961+ result = subprocess.call(cmd)
962+ Logger.command(["cd", workdir])
963+ os.chdir(workdir)
964+ return result
965+
966+
967+def getBuilder(builder=None):
968+ if not builder:
969+ builder = os.environ.get('UBUNTUTOOLS_BUILDER', 'pbuilder')
970+
971+ if builder == 'pbuilder':
972+ return Pbuilder()
973+ elif builder == 'sbuild':
974+ return Sbuild()
975+
976+ Logger.error("Unsupported builder specified: %s. Only pbuilder and "
977+ "sbuild are supported." % builder)
978
979=== added file 'ubuntutools/logger.py'
980--- ubuntutools/logger.py 1970-01-01 00:00:00 +0000
981+++ ubuntutools/logger.py 2010-12-13 04:23:09 +0000
982@@ -0,0 +1,55 @@
983+#
984+# logger.py - A simple logging helper class
985+#
986+# Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com>
987+#
988+# Permission to use, copy, modify, and/or distribute this software
989+# for any purpose with or without fee is hereby granted, provided
990+# that the above copyright notice and this permission notice appear
991+# in all copies.
992+#
993+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
994+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
995+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
996+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
997+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
998+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
999+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
1000+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1001+
1002+import os
1003+import sys
1004+
1005+class Logger(object):
1006+ script_name = os.path.basename(sys.argv[0])
1007+ verbose = False
1008+
1009+ @classmethod
1010+ def command(cls, cmd):
1011+ if cls.verbose:
1012+ for i in xrange(len(cmd)):
1013+ if cmd[i].find(" ") >= 0:
1014+ cmd[i] = '"' + cmd[i] + '"'
1015+ print "%s: I: %s" % (cls.script_name, " ".join(cmd))
1016+
1017+ @classmethod
1018+ def debug(cls, message):
1019+ if cls.verbose:
1020+ print "%s: D: %s" % (cls.script_name, message)
1021+
1022+ @classmethod
1023+ def error(cls, message):
1024+ print >> sys.stderr, "%s: Error: %s" % (cls.script_name, message)
1025+
1026+ @classmethod
1027+ def info(cls, message):
1028+ if cls.verbose:
1029+ print "%s: I: %s" % (cls.script_name, message)
1030+
1031+ @classmethod
1032+ def normal(cls, message):
1033+ print "%s: %s" % (cls.script_name, message)
1034+
1035+ @classmethod
1036+ def set_verbosity(cls, verbose):
1037+ cls.verbose = verbose

Subscribers

People subscribed via source and target branches

to status/vote changes: