From 532e055b7c0b45f32e2f695bda0bee429e8d4951 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 7 Nov 2011 23:20:37 +0200 Subject: [PATCH 01/38] Requestbackport script --- requestbackport | 150 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100755 requestbackport diff --git a/requestbackport b/requestbackport new file mode 100755 index 0000000..c06607e --- /dev/null +++ b/requestbackport @@ -0,0 +1,150 @@ +#!/usr/bin/python +# +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import optparse +import sys + +from distro_info import UbuntuDistroInfo + +from ubuntutools.lp.lpapicache import Launchpad, Archive, Distribution +from ubuntutools.lp.udtexceptions import PackageNotFoundException +from ubuntutools.config import UDTConfig +from ubuntutools.requestsync.common import edit_report +from ubuntutools.question import YesNoQuestion + +class DestinationException(Exception): + pass + +def determine_destinations(source, destination): + ubuntu_info = UbuntuDistroInfo() + if destination is None: + destination = ubuntu_info.stable() + + if source not in ubuntu_info.all: + raise DestinationException("Source release %s does not exist" % source) + if destination not in ubuntu_info.all: + raise DestinationException("Destination release %s does not exist" + % destination) + if destination not in ubuntu_info.supported(): + raise DestinationException("Destination release %s is not supported" + % destination) + + found = False + destinations = [] + support_gap = False + for release in ubuntu_info.all: + if release == destination: + found = True + if release == source: + break + if found: + if support_gap and not ubuntu_info.is_lts(release): + continue + if release not in ubuntu_info.supported(): + support_gap = True + continue + destinations.append(release) + + assert found + assert len(destinations) > 0 + + return destinations + + +def main(): + parser = optparse.OptionParser('%progname [options] package') + parser.add_option('-d', '--destination', metavar='DEST', + help='Backport to DEST release and necessary ' + 'intermediate releases ' + '(default: current stable release)') + parser.add_option('-s', '--source', metavar='SOURCE', + help='Backport from SOURCE release ' + '(default: current devel release)') + parser.add_option('-l', '--lpinstance', metavar='INSTANCE', default=None, + help='Launchpad instance to connect to ' + '(default: production).') + parser.add_option('--no-conf', action='store_true', + dest='no_conf', default=False, + help="Don't read config files or environment variables") + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("One (and only one) package must be specified") + package = args[0] + + config = UDTConfig(options.no_conf) + + if options.lpinstance is None: + options.lpinstance = config.get_value('LPINSTANCE') + Launchpad.login(options.lpinstance) + + if options.source is None: + options.source = Distribution('ubuntu').getDevelopmentSeries().name + + try: + destinations = determine_destinations(options.source, + options.destination) + except DestinationException, e: + print str(e) + sys.exit(1) + + request_backport(package, options.source, destinations) + + +def request_backport(package, source, destinations): + archive = Distribution('ubuntu').getArchive() + try: + package_spph = archive.getSourcePackage(package, source) + except PackageNotFoundException, e: + print str(e) + sys.exit(1) + + subst = { + 'package': package_spph.getPackageName(), + 'version': package_spph.getVersion(), + 'component': package_spph.getComponent(), + 'source': source, + 'destinations': ', '.join(destinations), + } + subject = ("Please backport %(package)s %(version)s (%(component)s) " + "from %(source)s" % subst) + body = ("Please backport %(package)s %(version)s (%(component)s) " + "from %(source)s to %(destinations)s.\n\n" + "Reason for the backport:\n" + "<<< Enter your reasoning here >>>\n\n" + "Testing performed:\n" + "<<< Mention any build & install tests you've done >>>\n" + "<<< List the reverse dependencies that you've tested >>>\n" + % subst) + + subject, body = edit_report(subject, body, changes_required=True) + + print ('The final report is:\nSummary: %s\nDescription:\n%s\n' + % (subject, body)) + if YesNoQuestion().ask("Request this backport", "yes") == "no": + sys.exit(1) + + targets = [Launchpad.projects['%s-backports' % destination] + for destination in destinations] + bug = Launchpad.bugs.createBug(title=subject, description=body, + target=targets[0]) + for target in targets[1:]: + bug.addTask(target=target) + + print "Backport request filed as %s" % bug.web_link + +if __name__ == '__main__': + main() From 6be22ce24b6ffe736f4944d673135f383e485841 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 7 Nov 2011 23:35:14 +0200 Subject: [PATCH 02/38] Tidy up --- requestbackport | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requestbackport b/requestbackport index c06607e..2ab1246 100755 --- a/requestbackport +++ b/requestbackport @@ -19,15 +19,17 @@ import sys from distro_info import UbuntuDistroInfo -from ubuntutools.lp.lpapicache import Launchpad, Archive, Distribution +from ubuntutools.lp.lpapicache import Launchpad, Distribution from ubuntutools.lp.udtexceptions import PackageNotFoundException from ubuntutools.config import UDTConfig from ubuntutools.requestsync.common import edit_report from ubuntutools.question import YesNoQuestion + class DestinationException(Exception): pass + def determine_destinations(source, destination): ubuntu_info = UbuntuDistroInfo() if destination is None: From f712a32238700bb45c64c2adf8193fad5a118038 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 8 Nov 2011 00:03:45 +0200 Subject: [PATCH 03/38] Reset support_gap at LTSs --- requestbackport | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/requestbackport b/requestbackport index 2ab1246..179214b 100755 --- a/requestbackport +++ b/requestbackport @@ -53,8 +53,11 @@ def determine_destinations(source, destination): if release == source: break if found: - if support_gap and not ubuntu_info.is_lts(release): - continue + if support_gap: + if ubuntu_info.is_lts(release): + support_gap = False + else: + continue if release not in ubuntu_info.supported(): support_gap = True continue From fed4e1718879105d0cc6bdf51e07b3e7957278c5 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Wed, 9 Nov 2011 23:44:22 +0200 Subject: [PATCH 04/38] Rdepends client tool --- doc/reverse-build-depends.1 | 172 ------------------- reverse-build-depends | 271 ------------------------------ reverse-depends | 65 +++++++ ubuntutools/rdepends.py | 45 +++++ ubuntutools/test/test_rdepends.py | 98 +++++++++++ 5 files changed, 208 insertions(+), 443 deletions(-) delete mode 100644 doc/reverse-build-depends.1 delete mode 100755 reverse-build-depends create mode 100755 reverse-depends create mode 100644 ubuntutools/rdepends.py create mode 100644 ubuntutools/test/test_rdepends.py diff --git a/doc/reverse-build-depends.1 b/doc/reverse-build-depends.1 deleted file mode 100644 index 3fcdc0d..0000000 --- a/doc/reverse-build-depends.1 +++ /dev/null @@ -1,172 +0,0 @@ -.\" Automatically generated by Pod::Man 2.1801 (Pod::Simple 3.05) -.\" -.\" Standard preamble: -.\" ======================================================================== -.de Sp \" Vertical space (when we can't use .PP) -.if t .sp .5v -.if n .sp -.. -.de Vb \" Begin verbatim text -.ft CW -.nf -.ne \\$1 -.. -.de Ve \" End verbatim text -.ft R -.fi -.. -.\" Set up some character translations and predefined strings. \*(-- will -.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left -.\" double quote, and \*(R" will give a right double quote. \*(C+ will -.\" give a nicer C++. Capital omega is used to do unbreakable dashes and -.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, -.\" nothing in troff, for use with C<>. -.tr \(*W- -.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' -.ie n \{\ -. ds -- \(*W- -. ds PI pi -. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch -. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch -. ds L" "" -. ds R" "" -. ds C` "" -. ds C' "" -'br\} -.el\{\ -. ds -- \|\(em\| -. ds PI \(*p -. ds L" `` -. ds R" '' -'br\} -.\" -.\" Escape single quotes in literal strings from groff's Unicode transform. -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.\" -.\" If the F register is turned on, we'll generate index entries on stderr for -.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index -.\" entries marked with X<> in POD. Of course, you'll have to process the -.\" output yourself in some meaningful fashion. -.ie \nF \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" -.. -. nr % 0 -. rr F -.\} -.el \{\ -. de IX -.. -.\} -.\" -.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). -.\" Fear. Run. Save yourself. No user-serviceable parts. -. \" fudge factors for nroff and troff -.if n \{\ -. ds #H 0 -. ds #V .8m -. ds #F .3m -. ds #[ \f1 -. ds #] \fP -.\} -.if t \{\ -. ds #H ((1u-(\\\\n(.fu%2u))*.13m) -. ds #V .6m -. ds #F 0 -. ds #[ \& -. ds #] \& -.\} -. \" simple accents for nroff and troff -.if n \{\ -. ds ' \& -. ds ` \& -. ds ^ \& -. ds , \& -. ds ~ ~ -. ds / -.\} -.if t \{\ -. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" -. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' -. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' -. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' -. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' -. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' -.\} -. \" troff and (daisy-wheel) nroff accents -.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' -.ds 8 \h'\*(#H'\(*b\h'-\*(#H' -.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] -.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' -.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' -.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] -.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] -.ds ae a\h'-(\w'a'u*4/10)'e -.ds Ae A\h'-(\w'A'u*4/10)'E -. \" corrections for vroff -.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' -.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' -. \" for low resolution devices (crt and lpr) -.if \n(.H>23 .if \n(.V>19 \ -\{\ -. ds : e -. ds 8 ss -. ds o a -. ds d- d\h'-1'\(ga -. ds D- D\h'-1'\(hy -. ds th \o'bp' -. ds Th \o'LP' -. ds ae ae -. ds Ae AE -.\} -.rm #[ #] #H #V #F C -.\" ======================================================================== -.\" -.IX Title "BUILD-RDEPS 1" -.TH BUILD-RDEPS 1 "2008-08-14" "Debian Utilities" " " -.\" For nroff, turn off justification. Always turn off hyphenation; it makes -.\" way too many mistakes in technical documents. -.if n .ad l -.nh -.SH "NAME" -build\-rdeps \- find packages that depend on a specific package to build (reverse build depends) -.SH "SYNOPSIS" -.IX Header "SYNOPSIS" -\&\fBubuild-rdeps\fR \fIpackage\fR -.SH "DESCRIPTION" -.IX Header "DESCRIPTION" -\&\fBubuild-rdeps\fR searches for all packages that build-depend on the specified package. -.SH "OPTIONS" -.IX Header "OPTIONS" -.IP "\fB\-u\fR \fB\-\-update\fR" 4 -.IX Item "-u --update" -Run apt-get update before searching for build-depends. -.IP "\fB\-s\fR \fB\-\-sudo\fR" 4 -.IX Item "-s --sudo" -Use sudo when running apt-get update. Has no effect if \-u is omitted. -.IP "\fB\-\-distribution\fR" 4 -.IX Item "--distribution" -Select another distribution, which is searched for build-depends. -.IP "\fB\-m\fR \fB\-\-print\-maintainer\fR" 4 -.IX Item "-m --print-maintainer" -Print the value of the maintainer field for each package. -.IP "\fB\-d\fR \fB\-\-debug\fR" 4 -.IX Item "-d --debug" -Run the debug mode -.IP "\fB\-\-help\fR" 4 -.IX Item "--help" -Show the usage information. -.IP "\fB\-\-version\fR" 4 -.IX Item "--version" -Show the version information. -.SH "LICENSE" -.IX Header "LICENSE" -This code is copyright by Patrick Schoenfeld -, all rights reserved. -This program comes with \s-1ABSOLUTELEY\s0 \s-1NO\s0 \s-1WARRANTY\s0. -You are free to redistribute this code under the terms of the -\&\s-1GNU\s0 General Public License, version 2 or later. -.SH "AUTHOR" -.IX Header "AUTHOR" -Patrick Schoenfeld diff --git a/reverse-build-depends b/reverse-build-depends deleted file mode 100755 index 8b66be9..0000000 --- a/reverse-build-depends +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/perl -# Copyright (C) Patrick Schoenfeld -# Copyright (C) 2009 Ryan Kavanagh -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -=head1 NAME - -build-rdeps - find packages that depend on a specific package to build (reverse build depends) - -=head1 SYNOPSIS - -B I - -=head1 DESCRIPTION - -B searches for all packages that build-depend on the specified package. - -=head1 OPTIONS - -=over 4 - -=item B<-u> B<--update> - -Run apt-get update before searching for build-depends. - -=item B<-s> B<--sudo> - -Use sudo when running apt-get update. Has no effect if -u is omitted. - -=item B<--distribution> - -Select another distribution, which is searched for build-depends. - -=item B<-m> B<--print-maintainer> - -Print the value of the maintainer field for each package. - -=item B<-d> B<--debug> - -Run the debug mode - -=item B<--help> - -Show the usage information. - -=item B<--version> - -Show the version information. - -=back - -=cut - -use warnings; -use strict; -use File::Basename; -use File::Find; -use Getopt::Long; -use Pod::Usage; -use Data::Dumper; -my $progname = basename($0); -my $version = '1.0'; -my $default_distribution = `ubuntu-distro-info --devel`; -chomp($default_distribution); -my $dctrl = "/usr/bin/grep-dctrl"; -my $sources_path = "/var/lib/apt/lists/"; -my $source_pattern = ".*_dists_${default_distribution}_.*Sources\$"; -my @source_files; -my $sources_count=0; -my $opt_debug; -my $opt_update; -my $opt_sudo; -my $opt_maintainer; -my $opt_mainonly; -my $opt_distribution; - -if (!(-x $dctrl)) { - die "$progname: Fatal error. grep-dctrl is not available.\nPlease install the 'dctrl-tools' package.\n"; -} - -sub version { - print <<"EOT"; -This is $progname $version, from the Debian devscripts package, v. ###VERSION### -This code is copyright by Patrick Schoenfeld, all rights reserved. -It comes with ABSOLUTELY NO WARRANTY. You are free to redistribute this code -under the terms of the GNU General Public License, version 2 or later. -EOT -exit (0); -} - -sub usage { - print <<"EOT"; -usage: $progname packagename - $progname --help - $progname --version - -Searches for all packages that build-depend on the specified package. - -Options: - -u, --update Run apt-get update before searching for build-depends. - (needs root privileges) - -s, --sudo Use sudo when running apt-get update - (has no effect when -u is omitted) - -d, --debug Enable the debug mode - -m, --print-maintainer Print the maintainer information (experimental) - --distribution distribution Select a distribution to search for build-depends - (Default: $default_distribution) - --only-main Ignore universe and multiverse - -EOT -version; -} - -sub findsources { - if (/$source_pattern/ and $sources_count <= 3) { - unless ($opt_mainonly and /(universe|multiverse)/) { - push(@source_files, $_); - $sources_count+=1; - print STDERR "DEBUG: Added source file: $_ (#$sources_count)\n" if ($opt_debug); - } - } -} - -sub findreversebuilddeps { - my ($package, $source_file) = @_; - my %packages; - my $depending_package; - my $count=0; - my $maintainer_info=''; - - open(PACKAGES, "$dctrl -F Build-Depends,Build-Depends-Indep $package -s Package,Build-Depends,Build-Depends-Indep,Maintainer $source_file|"); - - while() { - chomp; - print STDERR "$_\n" if ($opt_debug); - if (/Package: (.*)$/) { - $depending_package = $1; - $packages{$depending_package}->{'Build-Depends'} = 0; - } - - if (/Maintainer: (.*)$/) { - if ($depending_package) { - $packages{$depending_package}->{'Maintainer'} = $1; - } - } - - if (/Build-Depends: (.*)$/ or /Build-Depends-Indep: (.*)$/) { - if ($depending_package) { - print STDERR "$1\n" if ($opt_debug); - if ($1 =~ /^(.*\s)?$package([\s,]|$)/) { - $packages{$depending_package}->{'Build-Depends'} = 1; - } - } - - } - } - - while($depending_package = each(%packages)) { - if ($packages{$depending_package}->{'Build-Depends'} != 1) { - print STDERR "Ignoring package $depending_package because its not really build depending on $package.\n" if ($opt_debug); - next; - } - if ($opt_maintainer) { - $maintainer_info = "($packages{$depending_package}->{'Maintainer'})"; - } - - $count+=1; - print "$depending_package $maintainer_info \n"; - - } - - if ($count == 0) { - print "No reverse build-depends found for $package.\n\n" - } - else { - print "\nFound a total of $count reverse build-depend(s) for $package.\n\n"; - } -} - -if ($#ARGV < 0) { usage; exit(0); } - - -Getopt::Long::Configure('bundling'); -GetOptions( - "u|update" => \$opt_update, - "s|sudo" => \$opt_sudo, - "m|print-maintainer" => \$opt_maintainer, - "distribution=s" => \$opt_distribution, - "only-main" => \$opt_mainonly, - "d|debug" => \$opt_debug, - "h|help" => sub { usage; }, - "v|version" => sub { version; } -); - -my $package = shift; - -if (!$package) { - die "$progname: missing argument. expecting packagename\n"; -} - -print STDERR "DEBUG: Package => $package\n" if ($opt_debug); - -if ($opt_update) { - print STDERR "DEBUG: Updating apt-cache before search\n" if ($opt_debug); - my @cmd; - if ($opt_sudo) { - print STDERR "DEBUG: Using sudo to become root\n" if ($opt_debug); - push(@cmd, 'sudo'); - } - push(@cmd, 'apt-get', 'update'); - system @cmd; -} - -if ($opt_distribution) { - print STDERR "DEBUG: Setting distribution to $opt_distribution" if ($opt_debug); - $source_pattern = ".*_dists_" . $opt_distribution . "_.*Sources\$"; -} - -# Find sources files -find(\&findsources, $sources_path); - -if (($#source_files+1) <= 0) { - die "$progname: unable to find sources files.\nDid you forget to run apt-get update (or add --update to this command)?"; -} - -foreach my $source_file (@source_files) { - if ($source_file =~ /main/) { - print "Reverse Build-depends in main:\n"; - print "------------------------------\n\n"; - findreversebuilddeps($package, "$sources_path/$source_file"); - } - - if ($source_file =~ /universe/) { - print "Reverse Build-depends in universe:\n"; - print "---------------------------------\n\n"; - findreversebuilddeps($package, "$sources_path/$source_file"); - } - - if ($source_file =~ /multiverse/) { - print "Reverse Build-depends in multiverse:\n"; - print "----------------------------------\n\n"; - findreversebuilddeps($package, "$sources_path/$source_file"); - } -} - -=head1 LICENSE - -This code is copyright by Patrick Schoenfeld -, all rights reserved. -This program comes with ABSOLUTELEY NO WARRANTY. -You are free to redistribute this code under the terms of the -GNU General Public License, version 2 or later. - -=head1 AUTHOR - -Patrick Schoenfeld - -=cut diff --git a/reverse-depends b/reverse-depends new file mode 100755 index 0000000..a2ffd61 --- /dev/null +++ b/reverse-depends @@ -0,0 +1,65 @@ +#!/usr/bin/python +# +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import optparse + +from distro_info import UbuntuDistroInfo + +from ubuntutools.rdepends import rdepends + +def main(): + parser = optparse.OptionParser('%progname [options] package') + parser.add_option('-r', '--release', metavar='RELEASE', + default=UbuntuDistroInfo().devel(), + help='Query dependencies in RELEASE. Default: devel') + parser.add_option('-R', '--without-recommends', + action='store_false', dest='recommends', default=True, + help='Only examine Depends relationships, ' + 'not Recommends.') + parser.add_option('-s', '--with-suggests', + action='store_true', dest='suggests', default=False, + help='Also consider Suggests relationships.') + parser.add_option('-b', '--build-depends', + action='store_const', dest='arch', const='source', + help='Query build dependencies (synonym for ' + '--arch=source') + parser.add_option('-a', '--arch', metavar='ARCH', default='i386', + help='Query dependencies in ARCH' + '(default: i386)') + parser.add_option('-u', '--service-url', metavar='URL', + dest='server', default=None, + help='Reverse Depedencies webservice URL' + '(default: UbuntuWire)') + + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("One (and only one) package must be specified") + package = args[0] + + opts = { + 'recommends': options.recommends, + 'suggests': options.suggests, + } + if options.server is not None: + opts['server'] = options.server + + result = rdepends(package, options.release, options.arch, **opts) + result.sort() + print u'\n'.join(result) + +if __name__ == '__main__': + main() diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py new file mode 100644 index 0000000..49e7f38 --- /dev/null +++ b/ubuntutools/rdepends.py @@ -0,0 +1,45 @@ +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import json +import os +import urllib2 + + +def rdepends(package, release, arch, recommends=True, suggests=False, + server='http://qa.ubuntuwire.org/rdepends'): + """Look up a packages reverse-dependencies on the Ubuntuwire + Reverse- webservice + """ + url = os.path.join(server, 'v1', release, arch, package) + try: + data = json.load(urllib2.urlopen(url)) + except urllib2.HTTPError, e: + if e.code == 404: + return [] + raise + + if arch == 'source': + fields = ('Build-Depends', 'Build-Depends-Indep') + else: + fields = ['Depends'] + if recommends: + fields.append('Recommends') + if suggests: + fields.append('Suggests') + + result = set() + for field in fields: + result.update(data.get(field, [])) + return list(result) diff --git a/ubuntutools/test/test_rdepends.py b/ubuntutools/test/test_rdepends.py new file mode 100644 index 0000000..52d09b5 --- /dev/null +++ b/ubuntutools/test/test_rdepends.py @@ -0,0 +1,98 @@ +# test_rdepends.py - Test suite for ubuntutools.rdepends +# +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +import BaseHTTPServer +import SocketServer +import re +import threading + +from ubuntutools.rdepends import rdepends +from ubuntutools.test import unittest + +responses = { + '/v1/precise/source/python-beautifulsoup': + """{"Build-Depends-Indep": ["episoder"], + "Build-Depends": ["pyth", "wikkid", "calibre", "ibid", + "linaro-image-tools"]}""", + '/v1/precise/i386/python-beautifulsoup': + """{"Suggests": ["python-formalchemy", "python-pysolr", "chm2pdf", + "foxtrotgps", "python-html5lib"], + "Depends": ["python-btsutils", "python-pyth", "nagstamon", + "python-deliciousapi", "ibid", "python-freevo", "anki", + "archmage", "calibre", "creepy", "w3af-console", + "screenlets", "episoder", "uicilibris", + "totem-plugins-extra", "python-nodebox-web", "wikkid", + "python-bzutils", "wxbanker", "linaro-image-tools"], + "Recommends": ["sugar-read-activity-0.84", "webcheck", + "sugar-read-activity-0.86", "python-webtest", + "planet-venus"]}""", +} + +class FakeServer(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + if self.path in responses: + self.send_response(200) + self.end_headers() + self.wfile.write(responses[self.path]) + else: + self.send_response(404) + self.end_headers() + +class RdependsTestCase(unittest.TestCase): + def setUp(self): + self.fake_server = SocketServer.TCPServer(("127.0.0.1", 0), FakeServer) + self.fake_server.server_activate() + self.server_url = ('http://127.0.0.1:%i' + % self.fake_server.server_address[1]) + + self.server_thread = threading.Thread( + target=self.fake_server.serve_forever) + self.server_thread.start() + + def tearDown(self): + self.fake_server.shutdown() + + def test_source(self): + result = rdepends('python-beautifulsoup', 'precise', 'source', + server=self.server_url) + self.assertIn('ibid', result) + self.assertIn('episoder', result) + + def test_depends(self): + result = rdepends('python-beautifulsoup', 'precise', 'i386', + recommends=False, server=self.server_url) + self.assertIn('ibid', result) + self.assertNotIn('webcheck', result) + self.assertNotIn('chm2pdf', result) + + def test_recommends(self): + result = rdepends('python-beautifulsoup', 'precise', 'i386', + server=self.server_url) + self.assertIn('ibid', result) + self.assertIn('webcheck', result) + self.assertNotIn('chm2pdf', result) + + def test_suggests(self): + result = rdepends('python-beautifulsoup', 'precise', 'i386', + suggests=True, server=self.server_url) + self.assertIn('ibid', result) + self.assertIn('webcheck', result) + self.assertIn('chm2pdf', result) + + def test_empty(self): + result = rdepends('ibid', 'precise', 'source', + suggests=True, server=self.server_url) + self.assertEqual(result, []) From c44f53e185c92b295969fb770c21605fc3f6ed9e Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Thu, 10 Nov 2011 02:05:43 +0200 Subject: [PATCH 05/38] Move the API boundry of ubuntutools.rdepends --- reverse-depends | 25 +++++--- ubuntutools/rdepends.py | 20 +------ ubuntutools/test/test_rdepends.py | 98 ------------------------------- 3 files changed, 21 insertions(+), 122 deletions(-) delete mode 100644 ubuntutools/test/test_rdepends.py diff --git a/reverse-depends b/reverse-depends index a2ffd61..ddd70e2 100755 --- a/reverse-depends +++ b/reverse-depends @@ -20,6 +20,7 @@ from distro_info import UbuntuDistroInfo from ubuntutools.rdepends import rdepends + def main(): parser = optparse.OptionParser('%progname [options] package') parser.add_option('-r', '--release', metavar='RELEASE', @@ -50,16 +51,26 @@ def main(): parser.error("One (and only one) package must be specified") package = args[0] - opts = { - 'recommends': options.recommends, - 'suggests': options.suggests, - } + opts = {} if options.server is not None: opts['server'] = options.server - result = rdepends(package, options.release, options.arch, **opts) - result.sort() - print u'\n'.join(result) + data = rdepends(package, options.release, options.arch, **opts) + + if options.arch == 'source': + fields = ('Build-Depends', 'Build-Depends-Indep') + else: + fields = ['Depends'] + if options.recommends: + fields.append('Recommends') + if options.suggests: + fields.append('Suggests') + + result = set() + for field in fields: + result.update(data.get(field, [])) + + print u'\n'.join(sorted(list(result))) if __name__ == '__main__': main() diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index 49e7f38..7679f1e 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -17,29 +17,15 @@ import os import urllib2 -def rdepends(package, release, arch, recommends=True, suggests=False, +def rdepends(package, release, arch, server='http://qa.ubuntuwire.org/rdepends'): """Look up a packages reverse-dependencies on the Ubuntuwire Reverse- webservice """ url = os.path.join(server, 'v1', release, arch, package) try: - data = json.load(urllib2.urlopen(url)) + return json.load(urllib2.urlopen(url)) except urllib2.HTTPError, e: if e.code == 404: - return [] + return {} raise - - if arch == 'source': - fields = ('Build-Depends', 'Build-Depends-Indep') - else: - fields = ['Depends'] - if recommends: - fields.append('Recommends') - if suggests: - fields.append('Suggests') - - result = set() - for field in fields: - result.update(data.get(field, [])) - return list(result) diff --git a/ubuntutools/test/test_rdepends.py b/ubuntutools/test/test_rdepends.py deleted file mode 100644 index 52d09b5..0000000 --- a/ubuntutools/test/test_rdepends.py +++ /dev/null @@ -1,98 +0,0 @@ -# test_rdepends.py - Test suite for ubuntutools.rdepends -# -# Copyright (C) 2011, Stefano Rivera -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -import BaseHTTPServer -import SocketServer -import re -import threading - -from ubuntutools.rdepends import rdepends -from ubuntutools.test import unittest - -responses = { - '/v1/precise/source/python-beautifulsoup': - """{"Build-Depends-Indep": ["episoder"], - "Build-Depends": ["pyth", "wikkid", "calibre", "ibid", - "linaro-image-tools"]}""", - '/v1/precise/i386/python-beautifulsoup': - """{"Suggests": ["python-formalchemy", "python-pysolr", "chm2pdf", - "foxtrotgps", "python-html5lib"], - "Depends": ["python-btsutils", "python-pyth", "nagstamon", - "python-deliciousapi", "ibid", "python-freevo", "anki", - "archmage", "calibre", "creepy", "w3af-console", - "screenlets", "episoder", "uicilibris", - "totem-plugins-extra", "python-nodebox-web", "wikkid", - "python-bzutils", "wxbanker", "linaro-image-tools"], - "Recommends": ["sugar-read-activity-0.84", "webcheck", - "sugar-read-activity-0.86", "python-webtest", - "planet-venus"]}""", -} - -class FakeServer(BaseHTTPServer.BaseHTTPRequestHandler): - def do_GET(self): - if self.path in responses: - self.send_response(200) - self.end_headers() - self.wfile.write(responses[self.path]) - else: - self.send_response(404) - self.end_headers() - -class RdependsTestCase(unittest.TestCase): - def setUp(self): - self.fake_server = SocketServer.TCPServer(("127.0.0.1", 0), FakeServer) - self.fake_server.server_activate() - self.server_url = ('http://127.0.0.1:%i' - % self.fake_server.server_address[1]) - - self.server_thread = threading.Thread( - target=self.fake_server.serve_forever) - self.server_thread.start() - - def tearDown(self): - self.fake_server.shutdown() - - def test_source(self): - result = rdepends('python-beautifulsoup', 'precise', 'source', - server=self.server_url) - self.assertIn('ibid', result) - self.assertIn('episoder', result) - - def test_depends(self): - result = rdepends('python-beautifulsoup', 'precise', 'i386', - recommends=False, server=self.server_url) - self.assertIn('ibid', result) - self.assertNotIn('webcheck', result) - self.assertNotIn('chm2pdf', result) - - def test_recommends(self): - result = rdepends('python-beautifulsoup', 'precise', 'i386', - server=self.server_url) - self.assertIn('ibid', result) - self.assertIn('webcheck', result) - self.assertNotIn('chm2pdf', result) - - def test_suggests(self): - result = rdepends('python-beautifulsoup', 'precise', 'i386', - suggests=True, server=self.server_url) - self.assertIn('ibid', result) - self.assertIn('webcheck', result) - self.assertIn('chm2pdf', result) - - def test_empty(self): - result = rdepends('ibid', 'precise', 'source', - suggests=True, server=self.server_url) - self.assertEqual(result, []) From ab02df8cd17bd1cfd0501f91da0b270c680a77d8 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 16:31:10 +0200 Subject: [PATCH 06/38] Update to newer webservice output. Verbose listing. --- reverse-depends | 82 +++++++++++++++++++++++++++++++++++------ ubuntutools/rdepends.py | 4 +- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/reverse-depends b/reverse-depends index ddd70e2..13d4cd8 100755 --- a/reverse-depends +++ b/reverse-depends @@ -18,7 +18,7 @@ import optparse from distro_info import UbuntuDistroInfo -from ubuntutools.rdepends import rdepends +from ubuntutools.rdepends import query_rdepends def main(): @@ -28,7 +28,7 @@ def main(): help='Query dependencies in RELEASE. Default: devel') parser.add_option('-R', '--without-recommends', action='store_false', dest='recommends', default=True, - help='Only examine Depends relationships, ' + help='Only consider Depends relationships, ' 'not Recommends.') parser.add_option('-s', '--with-suggests', action='store_true', dest='suggests', default=False, @@ -37,9 +37,16 @@ def main(): action='store_const', dest='arch', const='source', help='Query build dependencies (synonym for ' '--arch=source') - parser.add_option('-a', '--arch', metavar='ARCH', default='i386', - help='Query dependencies in ARCH' - '(default: i386)') + parser.add_option('-a', '--arch', metavar='ARCH', default='any', + help='Query dependencies in ARCH ' + '(default: any)') + parser.add_option('-c', '--component', metavar='COMPONENT', + action='append', + help='Only consider reverse-dependencies in COMPONENT. ' + 'Can be specified multiple times. (default: all)') + parser.add_option('-l', '--list', + action='store_true', default=False, + help='Display a simple, machine-readable list') parser.add_option('-u', '--service-url', metavar='URL', dest='server', default=None, help='Reverse Depedencies webservice URL' @@ -55,22 +62,73 @@ def main(): if options.server is not None: opts['server'] = options.server - data = rdepends(package, options.release, options.arch, **opts) + data = query_rdepends(package, options.release, options.arch, **opts) if options.arch == 'source': - fields = ('Build-Depends', 'Build-Depends-Indep') + fields = ('Reverse-Build-Depends', 'Reverse-Build-Depends-Indep') else: - fields = ['Depends'] + fields = ['Reverse-Depends'] if options.recommends: - fields.append('Recommends') + fields.append('Reverse-Recommends') if options.suggests: - fields.append('Suggests') + fields.append('Reverse-Suggests') + for field in data.keys(): + if field not in fields: + del data[field] + + if options.component: + for field, rdeps in data.items(): + filtered = [rdep for rdep in rdeps + if rdep['Component'] in options.component] + if not filtered: + del data[field] + else: + data[field] = filtered + + if options.list: + display_consise(data) + else: + display_verbose(data) + + +def display_verbose(data): + if not data: + print "No reverse dependencies found" + return + + all_archs = set() + # This isn't accurate, but we make up for it by displaying what we found + for rdeps in data.itervalues(): + for rdep in rdeps: + if 'Architectures' in rdep: + all_archs.update(rdep['Architectures']) + + for field, rdeps in data.iteritems(): + print field + print '=' * len(field) + for rdep in rdeps: + if all_archs and set(rdep['Architectures']) != all_archs: + print '* %s [%s]' % (rdep['Package'], + ', '.join(sorted(rdep['Architectures']))) + else: + print '* %s' % rdep['Package'] + print + + if all_archs: + print ("Packages without architectures listed are " + "reverse-dependencies in: %s" + % ', '.join(sorted(list(all_archs)))) + + +def display_consise(data): result = set() - for field in fields: - result.update(data.get(field, [])) + for rdeps in data.itervalues(): + for rdep in rdeps: + result.add(rdep['Package']) print u'\n'.join(sorted(list(result))) + if __name__ == '__main__': main() diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index 7679f1e..ab999b5 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -17,8 +17,8 @@ import os import urllib2 -def rdepends(package, release, arch, - server='http://qa.ubuntuwire.org/rdepends'): +def query_rdepends(package, release, arch, + server='http://qa.ubuntuwire.org/rdepends'): """Look up a packages reverse-dependencies on the Ubuntuwire Reverse- webservice """ From 1948b455bea9052f718e6643246e557d793a6bc9 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 16:42:10 +0200 Subject: [PATCH 07/38] Don't try and look for reverse-Recommends on source packages --- reverse-depends | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/reverse-depends b/reverse-depends index 13d4cd8..fe26f07 100755 --- a/reverse-depends +++ b/reverse-depends @@ -65,13 +65,13 @@ def main(): data = query_rdepends(package, options.release, options.arch, **opts) if options.arch == 'source': - fields = ('Reverse-Build-Depends', 'Reverse-Build-Depends-Indep') + fields = ['Reverse-Build-Depends', 'Reverse-Build-Depends-Indep'] else: fields = ['Reverse-Depends'] - if options.recommends: - fields.append('Reverse-Recommends') - if options.suggests: - fields.append('Reverse-Suggests') + if options.recommends: + fields.append('Reverse-Recommends') + if options.suggests: + fields.append('Reverse-Suggests') for field in data.keys(): if field not in fields: From 8f914adf7ad2c53ea55f078159f29d42bb4b91c0 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 19:11:27 +0200 Subject: [PATCH 08/38] We don't 404 for empty responses any more, that was silly --- ubuntutools/rdepends.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index ab999b5..6b957d1 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -23,9 +23,4 @@ def query_rdepends(package, release, arch, Reverse- webservice """ url = os.path.join(server, 'v1', release, arch, package) - try: - return json.load(urllib2.urlopen(url)) - except urllib2.HTTPError, e: - if e.code == 404: - return {} - raise + return json.load(urllib2.urlopen(url)) From d0149b00d7228df1192a84f7c093f1f1ed3ba967 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 23:13:01 +0200 Subject: [PATCH 09/38] Catch 404s (preliminary support) --- reverse-depends | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/reverse-depends b/reverse-depends index fe26f07..88aa7df 100755 --- a/reverse-depends +++ b/reverse-depends @@ -15,7 +15,10 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import optparse +import urllib2 +import sys +from devscripts.logger import Logger from distro_info import UbuntuDistroInfo from ubuntutools.rdepends import query_rdepends @@ -62,7 +65,13 @@ def main(): if options.server is not None: opts['server'] = options.server - data = query_rdepends(package, options.release, options.arch, **opts) + try: + data = query_rdepends(package, options.release, options.arch, **opts) + except urllib2.HTTPError, e: + if e.code == 404: + # TODO: Get a real error message through the HTTP response's status + Logger.error("Unable to determine rdepends") + sys.exit(1) if options.arch == 'source': fields = ['Reverse-Build-Depends', 'Reverse-Build-Depends-Indep'] From 3d2127478d51688cbfdc5201fddd8b28673ae458 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 23:21:42 +0200 Subject: [PATCH 10/38] Make sure source packages are specified with src: --- ubuntutools/rdepends.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index 6b957d1..42a01c7 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -22,5 +22,7 @@ def query_rdepends(package, release, arch, """Look up a packages reverse-dependencies on the Ubuntuwire Reverse- webservice """ + if arch == 'source' and not package.startswith('src:'): + package = 'src:' + package url = os.path.join(server, 'v1', release, arch, package) return json.load(urllib2.urlopen(url)) From c5201e1847502a36ac48725b0d52f41e610716fd Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 23:32:20 +0200 Subject: [PATCH 11/38] Sort rdepends --- reverse-depends | 1 + 1 file changed, 1 insertion(+) diff --git a/reverse-depends b/reverse-depends index 88aa7df..23edd28 100755 --- a/reverse-depends +++ b/reverse-depends @@ -116,6 +116,7 @@ def display_verbose(data): for field, rdeps in data.iteritems(): print field print '=' * len(field) + rdeps.sort(key=lambda x: x['Package']) for rdep in rdeps: if all_archs and set(rdep['Architectures']) != all_archs: print '* %s [%s]' % (rdep['Package'], From bb4f0d4295fda87e2c5fa7b95b2b66b33f9ad2c4 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 23:38:01 +0200 Subject: [PATCH 12/38] Pass through error messages nicely --- reverse-depends | 11 ++++------- ubuntutools/rdepends.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/reverse-depends b/reverse-depends index 23edd28..2a16774 100755 --- a/reverse-depends +++ b/reverse-depends @@ -15,13 +15,12 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import optparse -import urllib2 import sys from devscripts.logger import Logger from distro_info import UbuntuDistroInfo -from ubuntutools.rdepends import query_rdepends +from ubuntutools.rdepends import query_rdepends, RDependsException def main(): @@ -67,11 +66,9 @@ def main(): try: data = query_rdepends(package, options.release, options.arch, **opts) - except urllib2.HTTPError, e: - if e.code == 404: - # TODO: Get a real error message through the HTTP response's status - Logger.error("Unable to determine rdepends") - sys.exit(1) + except RDependsException, e: + Logger.error(str(e)) + sys.exit(1) if options.arch == 'source': fields = ['Reverse-Build-Depends', 'Reverse-Build-Depends-Indep'] diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index 42a01c7..9a07321 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -17,6 +17,10 @@ import os import urllib2 +class RDependsException(Exception): + pass + + def query_rdepends(package, release, arch, server='http://qa.ubuntuwire.org/rdepends'): """Look up a packages reverse-dependencies on the Ubuntuwire @@ -24,5 +28,10 @@ def query_rdepends(package, release, arch, """ if arch == 'source' and not package.startswith('src:'): package = 'src:' + package + url = os.path.join(server, 'v1', release, arch, package) - return json.load(urllib2.urlopen(url)) + + try: + return json.load(urllib2.urlopen(url)) + except urllib2.HTTPError, e: + raise RDependsException(e.read().strip()) From 7466d8438bcddcd22e4d9ca731018fa448ffc602 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 23:40:30 +0200 Subject: [PATCH 13/38] r1211 was just crazy --- ubuntutools/rdepends.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index 9a07321..cc1a787 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -26,8 +26,6 @@ def query_rdepends(package, release, arch, """Look up a packages reverse-dependencies on the Ubuntuwire Reverse- webservice """ - if arch == 'source' and not package.startswith('src:'): - package = 'src:' + package url = os.path.join(server, 'v1', release, arch, package) From fae3d8db540a7683f6592c42d73f6b1ac0dd90e0 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Fri, 11 Nov 2011 23:47:14 +0200 Subject: [PATCH 14/38] Description --- reverse-depends | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/reverse-depends b/reverse-depends index 2a16774..8e14dfe 100755 --- a/reverse-depends +++ b/reverse-depends @@ -24,7 +24,11 @@ from ubuntutools.rdepends import query_rdepends, RDependsException def main(): - parser = optparse.OptionParser('%progname [options] package') + parser = optparse.OptionParser('%progname [options] package', + description="List reverse-dependancies of package. " + "If the package name is prefixed with src: then the " + "reverse-depndencies of all the binary packages that " + "the specified source package builds will be listed.") parser.add_option('-r', '--release', metavar='RELEASE', default=UbuntuDistroInfo().devel(), help='Query dependencies in RELEASE. Default: devel') From 6ac3640c95626f000c338e82e7fff331d9379a14 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 00:00:44 +0200 Subject: [PATCH 15/38] Display the binary package depended on, when searching by source package --- reverse-depends | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/reverse-depends b/reverse-depends index 8e14dfe..cb2bcb8 100755 --- a/reverse-depends +++ b/reverse-depends @@ -119,11 +119,14 @@ def display_verbose(data): print '=' * len(field) rdeps.sort(key=lambda x: x['Package']) for rdep in rdeps: + line = '* %s' % rdep['Package'] if all_archs and set(rdep['Architectures']) != all_archs: - print '* %s [%s]' % (rdep['Package'], - ', '.join(sorted(rdep['Architectures']))) - else: - print '* %s' % rdep['Package'] + line += ' [%s]' % ' '.join(sorted(rdep['Architectures'])) + if 'Dependency' in rdep: + if len(line) < 30: + line += ' ' * (30 - len(line)) + line += ' (for %s)' % rdep['Dependency'] + print line print if all_archs: From 6155633e855a2f6b329a3858979fb8cb59d296f9 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 00:46:48 +0200 Subject: [PATCH 16/38] Display reverse depends in requestbackport --- requestbackport | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/requestbackport b/requestbackport index 179214b..7141114 100755 --- a/requestbackport +++ b/requestbackport @@ -14,6 +14,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import collections import optparse import sys @@ -22,6 +23,7 @@ from distro_info import UbuntuDistroInfo from ubuntutools.lp.lpapicache import Launchpad, Distribution from ubuntutools.lp.udtexceptions import PackageNotFoundException from ubuntutools.config import UDTConfig +from ubuntutools.rdepends import query_rdepends from ubuntutools.requestsync.common import edit_report from ubuntutools.question import YesNoQuestion @@ -109,6 +111,32 @@ def main(): request_backport(package, options.source, destinations) +def find_rdepends(package, release, releases): + published_binaries = set() + for bpph in package._lpobject.getPublishedBinaries(): + published_binaries.add(bpph.binary_package_name) + + intermediate = collections.defaultdict(list) + for arch in ('any', 'source'): + raw_rdeps = query_rdepends('src:' + package.getPackageName(), + release, 'any') + for relationship, rdeps in raw_rdeps.iteritems(): + for rdep in rdeps: + if rdep['Package'] in published_binaries: + continue + intermediate[rdep['Dependency']].append((rdep['Package'], + relationship)) + + output = [] + for binpkg, rdeps in intermediate.iteritems(): + output += ['', binpkg, '=' * len(binpkg)] + for pkg, relationship in rdeps: + output += ['* %s (%s)' % (pkg, relationship)] + output += [' [ ] %s' % release for release in releases] + + return '\n'.join(output) + + def request_backport(package, source, destinations): archive = Distribution('ubuntu').getArchive() try: @@ -121,6 +149,7 @@ def request_backport(package, source, destinations): 'package': package_spph.getPackageName(), 'version': package_spph.getVersion(), 'component': package_spph.getComponent(), + 'rdepends': find_rdepends(package_spph, source, destinations), 'source': source, 'destinations': ', '.join(destinations), } @@ -131,8 +160,19 @@ def request_backport(package, source, destinations): "Reason for the backport:\n" "<<< Enter your reasoning here >>>\n\n" "Testing performed:\n" - "<<< Mention any build & install tests you've done >>>\n" - "<<< List the reverse dependencies that you've tested >>>\n" + "<<< Mention any build & install tests you've done >>>\n\n" + "Reverse dependencies:\n" + "The following reverse-dependencies need to be tested against the " + "new version of %(package)s. " + "For reverse-build-dependencies, please test that the package " + "still builds against the new %(package)s. " + "For reverse-dependencies, please test that the version of the " + "package currently in the release still works with the new " + "libgdata installed. " + "Mark off items in the checklist [X] as you test them, " + "but please leave the checklist so that backporters can quickly " + "evaluate the state of testing.\n" + "%(rdepends)s\n" % subst) subject, body = edit_report(subject, body, changes_required=True) From d125c47081c421ad3602b61ed57a6f663141b430 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 01:04:11 +0200 Subject: [PATCH 17/38] Iterate over all destination releases --- requestbackport | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/requestbackport b/requestbackport index 7141114..dc6b91e 100755 --- a/requestbackport +++ b/requestbackport @@ -23,7 +23,7 @@ from distro_info import UbuntuDistroInfo from ubuntutools.lp.lpapicache import Launchpad, Distribution from ubuntutools.lp.udtexceptions import PackageNotFoundException from ubuntutools.config import UDTConfig -from ubuntutools.rdepends import query_rdepends +from ubuntutools.rdepends import query_rdepends, RDependsException from ubuntutools.requestsync.common import edit_report from ubuntutools.question import YesNoQuestion @@ -111,28 +111,35 @@ def main(): request_backport(package, options.source, destinations) -def find_rdepends(package, release, releases): +def find_rdepends(package, releases): published_binaries = set() for bpph in package._lpobject.getPublishedBinaries(): published_binaries.add(bpph.binary_package_name) - intermediate = collections.defaultdict(list) + intermediate = dict((binpkg, collections.defaultdict(list)) + for binpkg in published_binaries) for arch in ('any', 'source'): - raw_rdeps = query_rdepends('src:' + package.getPackageName(), - release, 'any') - for relationship, rdeps in raw_rdeps.iteritems(): - for rdep in rdeps: - if rdep['Package'] in published_binaries: - continue - intermediate[rdep['Dependency']].append((rdep['Package'], - relationship)) + for release in releases: + try: + raw_rdeps = query_rdepends('src:' + package.getPackageName(), + release, arch) + except RDependsException: + # Not published? TODO: Check + continue + for relationship, rdeps in raw_rdeps.iteritems(): + for rdep in rdeps: + if rdep['Package'] in published_binaries: + continue + intermediate[rdep['Dependency']][rdep['Package']] \ + .append((release, relationship)) output = [] for binpkg, rdeps in intermediate.iteritems(): output += ['', binpkg, '=' * len(binpkg)] - for pkg, relationship in rdeps: - output += ['* %s (%s)' % (pkg, relationship)] - output += [' [ ] %s' % release for release in releases] + for pkg, appearences in rdeps.iteritems(): + output += ['* %s' % pkg] + for release, relationship in appearences: + output += [' [ ] %s (%s)' % (release, relationship)] return '\n'.join(output) @@ -149,7 +156,7 @@ def request_backport(package, source, destinations): 'package': package_spph.getPackageName(), 'version': package_spph.getVersion(), 'component': package_spph.getComponent(), - 'rdepends': find_rdepends(package_spph, source, destinations), + 'rdepends': find_rdepends(package_spph, destinations), 'source': source, 'destinations': ', '.join(destinations), } From 222ab46cff1ebda8e029064bd8fe09fe03788d96 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 01:20:47 +0200 Subject: [PATCH 18/38] Use a seeded defaultdict for the intermediate data structure, in case of binary package name differences between releases --- requestbackport | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/requestbackport b/requestbackport index dc6b91e..9e17562 100755 --- a/requestbackport +++ b/requestbackport @@ -14,7 +14,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -import collections +from collections import defaultdict import optparse import sys @@ -116,8 +116,12 @@ def find_rdepends(package, releases): for bpph in package._lpobject.getPublishedBinaries(): published_binaries.add(bpph.binary_package_name) - intermediate = dict((binpkg, collections.defaultdict(list)) - for binpkg in published_binaries) + intermediate = defaultdict(lambda: defaultdict(list)) + + # We want to display every pubilshed binary, even if it has no rdepends + for binpkg in published_binaries: + intermediate[binpkg] + for arch in ('any', 'source'): for release in releases: try: From 840421c7c693635fcb71249c0b6c537135068751 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 01:54:04 +0200 Subject: [PATCH 19/38] Look up the reverse dependencies in the target releases, for each binary package build by this source package in the source release --- requestbackport | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/requestbackport b/requestbackport index 9e17562..2b022d5 100755 --- a/requestbackport +++ b/requestbackport @@ -124,18 +124,18 @@ def find_rdepends(package, releases): for arch in ('any', 'source'): for release in releases: - try: - raw_rdeps = query_rdepends('src:' + package.getPackageName(), - release, arch) - except RDependsException: - # Not published? TODO: Check - continue - for relationship, rdeps in raw_rdeps.iteritems(): - for rdep in rdeps: - if rdep['Package'] in published_binaries: - continue - intermediate[rdep['Dependency']][rdep['Package']] \ - .append((release, relationship)) + for binpkg in published_binaries: + try: + raw_rdeps = query_rdepends(binpkg, release, arch) + except RDependsException: + # Not published? TODO: Check + continue + for relationship, rdeps in raw_rdeps.iteritems(): + for rdep in rdeps: + if rdep['Package'] in published_binaries: + continue + intermediate[binpkg][rdep['Package']] \ + .append((release, relationship)) output = [] for binpkg, rdeps in intermediate.iteritems(): From 84e7c2ccabb4271ce8bcb2b5329701d5b1981585 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 10:42:47 +0200 Subject: [PATCH 20/38] devscripts.logger --- requestbackport | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/requestbackport b/requestbackport index 2b022d5..9daddbf 100755 --- a/requestbackport +++ b/requestbackport @@ -18,6 +18,7 @@ from collections import defaultdict import optparse import sys +from devscripts.logger import Logger from distro_info import UbuntuDistroInfo from ubuntutools.lp.lpapicache import Launchpad, Distribution @@ -105,7 +106,7 @@ def main(): destinations = determine_destinations(options.source, options.destination) except DestinationException, e: - print str(e) + Logger.error(str(e)) sys.exit(1) request_backport(package, options.source, destinations) @@ -153,7 +154,7 @@ def request_backport(package, source, destinations): try: package_spph = archive.getSourcePackage(package, source) except PackageNotFoundException, e: - print str(e) + Logger.error(str(e)) sys.exit(1) subst = { @@ -188,8 +189,8 @@ def request_backport(package, source, destinations): subject, body = edit_report(subject, body, changes_required=True) - print ('The final report is:\nSummary: %s\nDescription:\n%s\n' - % (subject, body)) + Logger.normal('The final report is:\nSummary: %s\nDescription:\n%s\n', + subject, body) if YesNoQuestion().ask("Request this backport", "yes") == "no": sys.exit(1) @@ -200,7 +201,7 @@ def request_backport(package, source, destinations): for target in targets[1:]: bug.addTask(target=target) - print "Backport request filed as %s" % bug.web_link + Logger.normal("Backport request filed as %s", bug.web_link) if __name__ == '__main__': main() From 20cb64e5571c1767f3928e362c07fa7260001096 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 10:57:16 +0200 Subject: [PATCH 21/38] If the user names a binary package, determine the source package --- requestbackport | 108 +++++++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/requestbackport b/requestbackport index 9daddbf..83762c9 100755 --- a/requestbackport +++ b/requestbackport @@ -18,6 +18,7 @@ from collections import defaultdict import optparse import sys +import apt from devscripts.logger import Logger from distro_info import UbuntuDistroInfo @@ -72,46 +73,6 @@ def determine_destinations(source, destination): return destinations -def main(): - parser = optparse.OptionParser('%progname [options] package') - parser.add_option('-d', '--destination', metavar='DEST', - help='Backport to DEST release and necessary ' - 'intermediate releases ' - '(default: current stable release)') - parser.add_option('-s', '--source', metavar='SOURCE', - help='Backport from SOURCE release ' - '(default: current devel release)') - parser.add_option('-l', '--lpinstance', metavar='INSTANCE', default=None, - help='Launchpad instance to connect to ' - '(default: production).') - parser.add_option('--no-conf', action='store_true', - dest='no_conf', default=False, - help="Don't read config files or environment variables") - options, args = parser.parse_args() - - if len(args) != 1: - parser.error("One (and only one) package must be specified") - package = args[0] - - config = UDTConfig(options.no_conf) - - if options.lpinstance is None: - options.lpinstance = config.get_value('LPINSTANCE') - Launchpad.login(options.lpinstance) - - if options.source is None: - options.source = Distribution('ubuntu').getDevelopmentSeries().name - - try: - destinations = determine_destinations(options.source, - options.destination) - except DestinationException, e: - Logger.error(str(e)) - sys.exit(1) - - request_backport(package, options.source, destinations) - - def find_rdepends(package, releases): published_binaries = set() for bpph in package._lpobject.getPublishedBinaries(): @@ -149,14 +110,27 @@ def find_rdepends(package, releases): return '\n'.join(output) -def request_backport(package, source, destinations): +def locate_package(package, distribution): archive = Distribution('ubuntu').getArchive() - try: - package_spph = archive.getSourcePackage(package, source) - except PackageNotFoundException, e: - Logger.error(str(e)) - sys.exit(1) + for pass_ in ('source', 'binary'): + try: + package_spph = archive.getSourcePackage(package, distribution) + return package_spph + except PackageNotFoundException, e: + if pass_ == 'binary': + Logger.error(str(e)) + sys.exit(1) + try: + apt_pkg = apt.Cache()[package] + except KeyError: + continue + package = apt_pkg.candidate.source_name + Logger.normal("Binary package specified, considering its source " + "package instead: %s", package) + + +def request_backport(package_spph, source, destinations): subst = { 'package': package_spph.getPackageName(), 'version': package_spph.getVersion(), @@ -203,5 +177,47 @@ def request_backport(package, source, destinations): Logger.normal("Backport request filed as %s", bug.web_link) + +def main(): + parser = optparse.OptionParser('%progname [options] package') + parser.add_option('-d', '--destination', metavar='DEST', + help='Backport to DEST release and necessary ' + 'intermediate releases ' + '(default: current stable release)') + parser.add_option('-s', '--source', metavar='SOURCE', + help='Backport from SOURCE release ' + '(default: current devel release)') + parser.add_option('-l', '--lpinstance', metavar='INSTANCE', default=None, + help='Launchpad instance to connect to ' + '(default: production).') + parser.add_option('--no-conf', action='store_true', + dest='no_conf', default=False, + help="Don't read config files or environment variables") + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("One (and only one) package must be specified") + package = args[0] + + config = UDTConfig(options.no_conf) + + if options.lpinstance is None: + options.lpinstance = config.get_value('LPINSTANCE') + Launchpad.login(options.lpinstance) + + if options.source is None: + options.source = Distribution('ubuntu').getDevelopmentSeries().name + + try: + destinations = determine_destinations(options.source, + options.destination) + except DestinationException, e: + Logger.error(str(e)) + sys.exit(1) + + package_spph = locate_package(package, options.source) + request_backport(package_spph, options.source, destinations) + + if __name__ == '__main__': main() From f6344a01c5d355eda89416d2bc6f72e93ba90055 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 11:22:55 +0200 Subject: [PATCH 22/38] Add bir testing --- requestbackport | 52 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/requestbackport b/requestbackport index 83762c9..822c09f 100755 --- a/requestbackport +++ b/requestbackport @@ -101,7 +101,7 @@ def find_rdepends(package, releases): output = [] for binpkg, rdeps in intermediate.iteritems(): - output += ['', binpkg, '=' * len(binpkg)] + output += ['', binpkg, '-' * len(binpkg)] for pkg, appearences in rdeps.iteritems(): output += ['* %s' % pkg] for release, relationship in appearences: @@ -131,33 +131,57 @@ def locate_package(package, distribution): def request_backport(package_spph, source, destinations): + testing = [] + testing += ["You can test-build the backport in your PPA with " + "backportpackage:"] + lp_user = Launchpad.me.name + testing += ["$ backportpackage -u ppa:%s/ppa -s %s -d %s" + % (lp_user, source, dest) + for dest in destinations] + testing += [""] + testing += ["* Package builds without modification on"] + testing += ["[ ] %s" % dest for dest in destinations] + testing += ["* Package installs and removes cleanly on"] + testing += ["[ ] %s" % dest for dest in destinations] + subst = { 'package': package_spph.getPackageName(), 'version': package_spph.getVersion(), 'component': package_spph.getComponent(), - 'rdepends': find_rdepends(package_spph, destinations), 'source': source, 'destinations': ', '.join(destinations), + 'testing': '\n'.join(testing), + 'rdepends': find_rdepends(package_spph, destinations), } subject = ("Please backport %(package)s %(version)s (%(component)s) " "from %(source)s" % subst) body = ("Please backport %(package)s %(version)s (%(component)s) " - "from %(source)s to %(destinations)s.\n\n" + "from %(source)s to %(destinations)s.\n" + "\n" "Reason for the backport:\n" - "<<< Enter your reasoning here >>>\n\n" - "Testing performed:\n" - "<<< Mention any build & install tests you've done >>>\n\n" - "Reverse dependencies:\n" - "The following reverse-dependencies need to be tested against the " - "new version of %(package)s. " - "For reverse-build-dependencies, please test that the package " - "still builds against the new %(package)s. " - "For reverse-dependencies, please test that the version of the " - "package currently in the release still works with the new " - "libgdata installed. " + "========================\n" + "<<< Enter your reasoning here >>>\n" + "\n" + "Testing:\n" + "========\n" "Mark off items in the checklist [X] as you test them, " "but please leave the checklist so that backporters can quickly " "evaluate the state of testing.\n" + "\n" + "%(testing)s\n" + "\n" + "Reverse dependencies:\n" + "=====================\n" + "The following reverse-dependencies need to be tested against the " + "new version of %(package)s. " + "For reverse-build-dependencies (-Indep), please test that the " + "package still builds against the new %(package)s. " + "For reverse-dependencies, please test that the version of the " + "package currently in the release still works with the new " + "%(package)s installed. " + "Reverse- Recommends, Suggests, and Enhances don't need to be " + "tested, and are listed for completeness-sake." + "\n" "%(rdepends)s\n" % subst) From 885b241709ea3a7004e941a8d200fa64e7b56fdf Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 12:53:32 +0200 Subject: [PATCH 23/38] Spelling and help formatting --- reverse-depends | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/reverse-depends b/reverse-depends index cb2bcb8..704414b 100755 --- a/reverse-depends +++ b/reverse-depends @@ -25,9 +25,9 @@ from ubuntutools.rdepends import query_rdepends, RDependsException def main(): parser = optparse.OptionParser('%progname [options] package', - description="List reverse-dependancies of package. " + description="List reverse-dependencies of package. " "If the package name is prefixed with src: then the " - "reverse-depndencies of all the binary packages that " + "reverse-dependencies of all the binary packages that " "the specified source package builds will be listed.") parser.add_option('-r', '--release', metavar='RELEASE', default=UbuntuDistroInfo().devel(), @@ -35,28 +35,28 @@ def main(): parser.add_option('-R', '--without-recommends', action='store_false', dest='recommends', default=True, help='Only consider Depends relationships, ' - 'not Recommends.') + 'not Recommends') parser.add_option('-s', '--with-suggests', action='store_true', dest='suggests', default=False, - help='Also consider Suggests relationships.') + help='Also consider Suggests relationships') parser.add_option('-b', '--build-depends', action='store_const', dest='arch', const='source', help='Query build dependencies (synonym for ' - '--arch=source') + '--arch=source)') parser.add_option('-a', '--arch', metavar='ARCH', default='any', - help='Query dependencies in ARCH ' - '(default: any)') + help='Query dependencies in ARCH. ' + 'Default: any') parser.add_option('-c', '--component', metavar='COMPONENT', action='append', help='Only consider reverse-dependencies in COMPONENT. ' - 'Can be specified multiple times. (default: all)') + 'Can be specified multiple times. Default: all') parser.add_option('-l', '--list', action='store_true', default=False, help='Display a simple, machine-readable list') parser.add_option('-u', '--service-url', metavar='URL', dest='server', default=None, - help='Reverse Depedencies webservice URL' - '(default: UbuntuWire)') + help='Reverse Dependencies webservice URL. ' + 'Default: UbuntuWire') options, args = parser.parse_args() From 8aa54e7cff7f9a7de257a12cec39e2bf7f5516db Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 13:00:20 +0200 Subject: [PATCH 24/38] Man page for reverse-depends --- doc/reverse-depends.1 | 81 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 doc/reverse-depends.1 diff --git a/doc/reverse-depends.1 b/doc/reverse-depends.1 new file mode 100644 index 0000000..9b405b1 --- /dev/null +++ b/doc/reverse-depends.1 @@ -0,0 +1,81 @@ +.\" Copyright (C) 2011, Stefano Rivera +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +.\" PERFORMANCE OF THIS SOFTWARE. +.TH reverse\-depends 1 "November 2011" ubuntu\-dev\-tools + +.SH NAME +reverse\-depends \- List the reverse\-dependencies (or +build\-dependencies) of a package + +.SH SYNOPSIS +.B reverse\-depends \fR[\fIoptions\fR] \fIpackage + +.SH DESCRIPTION +List reverse\-dependencies (or build\-dependencies) of \fIpackage\fR. +If the package name is prefixed with \fBsrc:\fR then the +reverse\-dependencies of all the binary packages that the specified +source package builds will be listed. + +.SH OPTIONS +.TP +\fB\-r\fR \fIRELEASE\fR, \fB\-\-release\fR=\fIRELEASE\fR +Query dependencies in \fIRELEASE\fR. +Default: current development release. +.TP +\fB\-R\fR, \fB\-\-without\-recommends\fR +Only consider Depends relationships, not Recommends. +.TP +\fB\-s\fR, \fB\-\-with\-suggests\fR +Also consider Suggests relationships. +.TP +\fB\-b\fR, \fB\-\-build\-depends\fR +Query build dependencies. +Synonym for \fB\-\-arch\fR=\fIsource\fR. +.TP +\fB\-a\fR \fIARCH\fR, \fB\-\-arch\fR=\fIARCH\fR +Query dependencies in \fIARCH\fR. +Besides valid architecture names, the special values \fBany\fR and +\fBsource\fR may be used. +\fBany\fR displays all reverse dependencies, the union across all +architecture. +\fBsource\fR displays build dependencies. +Default: \fBany\fR. +.TP +\fB\-c\fR \fICOMPONENT\fR, \fB\-\-component\fR=\fICOMPONENT\fR +Only consider reverse\-dependencies in \fICOMPONENT\fR. Can +be specified multiple times. +Default: all components. +.TP +\fB\-l\fR, \fB\-\-list\fR +Display a simple, machine\-readable list. +.TP +\fB\-u\fR \fIURL\fR, \fB\-\-service\-url\fR=\fIURL\fR +Reverse Dependencies web\-service \fIURL\fR. +Default: UbuntuWire's service at +\fBhttp://qa.ubuntuwire.org/rdepends/\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display a help message and exit + +.SH EXAMPLES +All reverse dependencies of source package bash: +.IP +.nf +.B reverse\-depends src:bash +.fi + +.SH AUTHORS +\fBreverse\-depends\fR and this manpage were written by Stefano Rivera +. +.PP +Both are released under the terms of the ISC License. From 9bcda8d239fb880aa6a9af5a3b641105fc1313c0 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 13:08:42 +0200 Subject: [PATCH 25/38] Man page for requestbackport --- doc/requestbackport.1 | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 doc/requestbackport.1 diff --git a/doc/requestbackport.1 b/doc/requestbackport.1 new file mode 100644 index 0000000..5ee9eba --- /dev/null +++ b/doc/requestbackport.1 @@ -0,0 +1,56 @@ +.\" Copyright (C) 2011, Stefano Rivera +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +.\" PERFORMANCE OF THIS SOFTWARE. +.TH requestbackport 1 "November 2011" ubuntu\-dev\-tools + +.SH NAME +requestbackport \- File a backport request bug + +.SH SYNOPSIS +.B requestbackport \fR[\fIoptions\fR] \fIpackage\fR + +.SH DESCRIPTION +Determine the intermediate releases that \fIpackage\fR needs to be +backported to, list all reverse\-dependencies, and file the backporting +request. +\fBrequestbackport\fR will include a testing checklist in the bug. + +.SH OPTIONS +.TP +\fB\-d\fR \fIDEST\fR, \fB\-\-destination\fR=\fIDEST\fR +Backport to \fIDEST\fR release and necessary intermediate +releases. Default: current stable release. +.TP +\fB\-s\fR \fISOURCE\fR, \fB\-\-source\fR=\fISOURCE\fR +Backport from \fISOURCE\fR release. +Default: current development release. +.TP +\fB\-l\fR \fIINSTANCE\fR, \fB\-\-lpinstance\fR=\fIINSTANCE\fR +Launchpad instance to connect to. +Default: \fBproduction\fR. +.TP +\fB\-\-no\-conf\fR +Don't read config files or environment variables +.TP +\fB\-h\fR, \fB\-\-help\fR +Display a help message and exit. + +.SH SEE ALSO +.BR backportpackage (1), +.BR reverse\-depends (1). + +.SH AUTHORS +\fBreverse\-depends\fR and this manpage were written by Stefano Rivera +. +.PP +Both are released under the terms of the ISC License. From 6e4c276d1fa5defe1000b1ce673dbebbf817a6be Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 13:12:15 +0200 Subject: [PATCH 26/38] Update metadata --- debian/NEWS | 7 +++++++ debian/changelog | 11 +++++++++++ debian/control | 5 +++-- debian/copyright | 8 +++++--- requestbackport | 2 +- reverse-depends | 2 +- setup.py | 3 ++- ubuntutools/rdepends.py | 2 +- 8 files changed, 31 insertions(+), 9 deletions(-) diff --git a/debian/NEWS b/debian/NEWS index 1915691..d096a17 100644 --- a/debian/NEWS +++ b/debian/NEWS @@ -1,3 +1,10 @@ +ubuntu-dev-tools (0.135) unstable; urgency=low + + reverse-build-depends was removed from ubuntu-dev-tools. reverse-depends -b + is equivalent. + + -- Stefano Rivera Sat, 12 Nov 2011 13:11:21 +0200 + ubuntu-dev-tools (0.131) unstable; urgency=low get-build-deps was removed from ubuntu-dev-tools. The newer mk-build-deps in diff --git a/debian/changelog b/debian/changelog index e4cb30c..1dc687a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +ubuntu-dev-tools (0.135) UNRELEASED; urgency=low + + * New scripts: + - reverse-depends: Replaces reverse-build-depends. Uses an UbuntuWire + webservice for determining all reverse(-build)-dependencies for a + package. + - requestbackport: Files a backport request bug report, including a full + testing checklist. + + -- Stefano Rivera Sat, 12 Nov 2011 13:09:05 +0200 + ubuntu-dev-tools (0.134) unstable; urgency=low [ Stefano Rivera ] diff --git a/debian/control b/debian/control index bb91a4c..d75670c 100644 --- a/debian/control +++ b/debian/control @@ -93,9 +93,10 @@ Description: useful tools for Ubuntu developers Debian of a package. - pull-lp-source - downloads lastest source package from Launchpad. - pull-revu-source - downloads the latest source package from REVU + - requestbackport - file a backporting request. - requestsync - files a sync request with Debian changelog and rationale. - - reverse-build-depends - find the reverse build dependencies that a package - has. + - reverse-depends - find the reverse dependencies (or build dependencies) of + a package. - setup-packaging-environment - assistant to get an Ubuntu installation ready for packaging work. - sponsor-patch - Downloads a patch from a Launchpad bug, patches the source diff --git a/debian/copyright b/debian/copyright index 15f3b53..55a4885 100644 --- a/debian/copyright +++ b/debian/copyright @@ -47,18 +47,15 @@ Files: 404main doc/import-bug-from-debian.1 doc/pbuilder-dist-simple.1 doc/pbuilder-dist.1 - doc/reverse-build-depends.1 doc/submittodebian.1 import-bug-from-debian pbuilder-dist pbuilder-dist-simple - reverse-build-depends submittodebian Copyright: 2007-2010, Canonical Ltd. 2009, James Westby 2008, Jamin W. Collins 2008, Jordan Mantha - 2008-2009, Patrick Schoenfeld 2006-2007, Pete Savage 2009, Ryan Kavanagh 2007, Siegfried-Angel Gevatter Pujals @@ -152,17 +149,22 @@ License: GPL-3+ Files: doc/pull-debian-debdiff.1 doc/pull-debian-source.1 + doc/requestbackport.1 + doc/reverse-depends.1 doc/sponsor-patch.1 doc/ubuntu-dev-tools.5 doc/update-maintainer.1 pull-debian-debdiff pull-debian-source + requestbackport + reverse-depends sponsor-patch test-data/* ubuntutools/archive.py ubuntutools/builder.py ubuntutools/config.py ubuntutools/question.py + ubuntutools/rdepends.py ubuntutools/sponsor_patch/* ubuntutools/test/* ubuntutools/update_maintainer.py diff --git a/requestbackport b/requestbackport index 822c09f..9e8cf4e 100755 --- a/requestbackport +++ b/requestbackport @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright (C) 2011, Stefano Rivera +# Copyright (C) 2011, Stefano Rivera # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above diff --git a/reverse-depends b/reverse-depends index 704414b..983b7f5 100755 --- a/reverse-depends +++ b/reverse-depends @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright (C) 2011, Stefano Rivera +# Copyright (C) 2011, Stefano Rivera # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above diff --git a/setup.py b/setup.py index 2439c76..f6a83b4 100755 --- a/setup.py +++ b/setup.py @@ -33,8 +33,9 @@ scripts = ['404main', 'pull-debian-source', 'pull-lp-source', 'pull-revu-source', + 'requestbackport', 'requestsync', - 'reverse-build-depends', + 'reverse-depends', 'setup-packaging-environment', 'sponsor-patch', 'submittodebian', diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py index cc1a787..f9d72de 100644 --- a/ubuntutools/rdepends.py +++ b/ubuntutools/rdepends.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011, Stefano Rivera +# Copyright (C) 2011, Stefano Rivera # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above From 45db7b738e25f29f39ab62c699d87bb794e5edce Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 12 Nov 2011 23:39:49 +0200 Subject: [PATCH 27/38] What do you know, there was a bug... --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 1dc687a..9080181 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,7 +3,7 @@ ubuntu-dev-tools (0.135) UNRELEASED; urgency=low * New scripts: - reverse-depends: Replaces reverse-build-depends. Uses an UbuntuWire webservice for determining all reverse(-build)-dependencies for a - package. + package. (LP: #696373) - requestbackport: Files a backport request bug report, including a full testing checklist. From c4c758cc177b3727f4d89eeed24505f16825a681 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 02:00:06 +0200 Subject: [PATCH 28/38] Provide installs & removes cleanly checkboxes for each binary package --- requestbackport | 23 +++++++++++++---------- ubuntutools/question.py | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/requestbackport b/requestbackport index 9e8cf4e..6b3c294 100755 --- a/requestbackport +++ b/requestbackport @@ -73,11 +73,7 @@ def determine_destinations(source, destination): return destinations -def find_rdepends(package, releases): - published_binaries = set() - for bpph in package._lpobject.getPublishedBinaries(): - published_binaries.add(bpph.binary_package_name) - +def find_rdepends(package, releases, published_binaries): intermediate = defaultdict(lambda: defaultdict(list)) # We want to display every pubilshed binary, even if it has no rdepends @@ -131,6 +127,11 @@ def locate_package(package, distribution): def request_backport(package_spph, source, destinations): + + published_binaries = set() + for bpph in package_spph._lpobject.getPublishedBinaries(): + published_binaries.add(bpph.binary_package_name) + testing = [] testing += ["You can test-build the backport in your PPA with " "backportpackage:"] @@ -139,10 +140,11 @@ def request_backport(package_spph, source, destinations): % (lp_user, source, dest) for dest in destinations] testing += [""] - testing += ["* Package builds without modification on"] - testing += ["[ ] %s" % dest for dest in destinations] - testing += ["* Package installs and removes cleanly on"] - testing += ["[ ] %s" % dest for dest in destinations] + for dest in destinations: + testing += ['* %s:' % dest] + testing += ["[ ] Package builds without modification"] + testing += ["[ ] %s installs and removes cleanly" % binary + for binary in published_binaries] subst = { 'package': package_spph.getPackageName(), @@ -151,7 +153,8 @@ def request_backport(package_spph, source, destinations): 'source': source, 'destinations': ', '.join(destinations), 'testing': '\n'.join(testing), - 'rdepends': find_rdepends(package_spph, destinations), + 'rdepends': find_rdepends(package_spph, destinations, + published_binaries), } subject = ("Please backport %(package)s %(version)s (%(component)s) " "from %(source)s" % subst) diff --git a/ubuntutools/question.py b/ubuntutools/question.py index 2f547a2..522b236 100644 --- a/ubuntutools/question.py +++ b/ubuntutools/question.py @@ -15,6 +15,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + class Question(object): def __init__(self, options, show_help=True): assert len(options) >= 2 From ce61a43a133df60dffec0c8163037bc5d30d0e51 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 02:06:52 +0200 Subject: [PATCH 29/38] Installs and runs --- requestbackport | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requestbackport b/requestbackport index 6b3c294..7e056ef 100755 --- a/requestbackport +++ b/requestbackport @@ -143,7 +143,7 @@ def request_backport(package_spph, source, destinations): for dest in destinations: testing += ['* %s:' % dest] testing += ["[ ] Package builds without modification"] - testing += ["[ ] %s installs and removes cleanly" % binary + testing += ["[ ] %s installs cleanly and runs" % binary for binary in published_binaries] subst = { From ad463fb83141667835cfbf0a3321adead180854c Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 02:25:42 +0200 Subject: [PATCH 30/38] Only display reverse dependency section when there are any --- requestbackport | 73 +++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/requestbackport b/requestbackport index 7e056ef..e961fc1 100755 --- a/requestbackport +++ b/requestbackport @@ -103,7 +103,25 @@ def find_rdepends(package, releases, published_binaries): for release, relationship in appearences: output += [' [ ] %s (%s)' % (release, relationship)] - return '\n'.join(output) + found_any = sum(len(rdeps) for rdeps in intermediate.itervalues()) + if found_any: + output = [ + "Reverse dependencies:", + "=====================", + "The following reverse-dependencies need to be tested against the " + "new version of %(package)s. " + "For reverse-build-dependencies (-Indep), please test that the " + "package still builds against the new %(package)s. " + "For reverse-dependencies, please test that the version of the " + "package currently in the release still works with the new " + "%(package)s installed. " + "Reverse- Recommends, Suggests, and Enhances don't need to be " + "tested, and are listed for completeness-sake." + ] + output + else: + output = ["No reverse dependencies"] + + return output def locate_package(package, distribution): @@ -152,41 +170,30 @@ def request_backport(package_spph, source, destinations): 'component': package_spph.getComponent(), 'source': source, 'destinations': ', '.join(destinations), - 'testing': '\n'.join(testing), - 'rdepends': find_rdepends(package_spph, destinations, - published_binaries), } subject = ("Please backport %(package)s %(version)s (%(component)s) " "from %(source)s" % subst) - body = ("Please backport %(package)s %(version)s (%(component)s) " - "from %(source)s to %(destinations)s.\n" - "\n" - "Reason for the backport:\n" - "========================\n" - "<<< Enter your reasoning here >>>\n" - "\n" - "Testing:\n" - "========\n" - "Mark off items in the checklist [X] as you test them, " - "but please leave the checklist so that backporters can quickly " - "evaluate the state of testing.\n" - "\n" - "%(testing)s\n" - "\n" - "Reverse dependencies:\n" - "=====================\n" - "The following reverse-dependencies need to be tested against the " - "new version of %(package)s. " - "For reverse-build-dependencies (-Indep), please test that the " - "package still builds against the new %(package)s. " - "For reverse-dependencies, please test that the version of the " - "package currently in the release still works with the new " - "%(package)s installed. " - "Reverse- Recommends, Suggests, and Enhances don't need to be " - "tested, and are listed for completeness-sake." - "\n" - "%(rdepends)s\n" - % subst) + body = ('\n'.join( + [ + "Please backport %(package)s %(version)s (%(component)s) " + "from %(source)s to %(destinations)s.", + "", + "Reason for the backport:", + "========================", + "<<< Enter your reasoning here >>>", + "", + "Testing:", + "========", + "Mark off items in the checklist [X] as you test them, " + "but please leave the checklist so that backporters can quickly " + "evaluate the state of testing.", + "" + ] + + testing + + [""] + + find_rdepends(package_spph, destinations, published_binaries) + + [""] + ) % subst) subject, body = edit_report(subject, body, changes_required=True) From f06bc5b3751b79792c08e6b18be7d362e84c7695 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 20:14:55 +0200 Subject: [PATCH 31/38] Add generic file-editing support to ubuntutools.question --- ubuntutools/question.py | 112 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/ubuntutools/question.py b/ubuntutools/question.py index 522b236..124f66e 100644 --- a/ubuntutools/question.py +++ b/ubuntutools/question.py @@ -1,7 +1,8 @@ # # question.py - Helper class for asking questions # -# Copyright (C) 2010, Benjamin Drung +# Copyright (C) 2010, Benjamin Drung , +# 2011, Stefano Rivera # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -15,6 +16,13 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import tempfile +import os +import re +import sys + +import ubuntutools.subprocess + class Question(object): def __init__(self, options, show_help=True): @@ -86,3 +94,105 @@ def input_number(question, min_number, max_number, default=None): print "Please input a number." assert type(selected) == int return selected + + +def confirmation_prompt(message=None, action=None): + '''Display message, or a stock message including action, and wait for the + user to press Enter + ''' + if message is None: + if action is None: + action = 'continue' + message = 'Press [Enter] to %s. Press [Ctrl-C] to abort now.' % action + try: + raw_input(message) + except KeyboardInterrupt: + print '\nAborting as requested.' + sys.exit(1) + + +class EditFile(object): + def __init__(self, filename, description, placeholders=None): + self.filename = filename + self.description = description + if placeholders is None: + placeholders = (re.compile(r'^<<<.*>>>$', re.UNICODE),) + self.placeholders = placeholders + + def optional_edit(self): + '''Prompt the user to decide if the file needs editing''' + print "Currently the %s looks like:" % self.description + with open(self.filename, 'r') as f: + print f.read() + if YesNoQuestion().ask("Edit", "no") == "yes": + self.edit() + + def edit(self): + done = False + while not done: + old_mtime = os.stat(self.filename).st_mtime + ubuntutools.subprocess.check_call(['sensible-editor', + self.filename]) + modified = old_mtime != os.stat(self.filename).st_mtime + placeholders_present = False + if self.placeholders: + with open(self.filename, 'r') as f: + for line in f: + for placeholder in self.placeholders: + if placeholder.search(line.strip()): + placeholders_present = True + + if placeholders_present: + print ("Placeholders still present in the %s. " + "Please replace them with useful information." + % self.description) + confirmation_prompt('edit again') + elif not modified: + print "The %s was not modified" % self.description + if YesNoQuestion().ask("Edit again", "yes") == "no": + done = True + elif self.check_edit(): + done = True + + def check_edit(self): + '''Override this to implement extra checks on the edited report. + Should return False if another round of editing is needed, + and should prompt the user to confirm that, if necessary. + ''' + return True + + +class EditBugReport(EditFile): + split_re = re.compile(r'^Summary.*?:\s+(.*)\s+' + r'Description:\s+(.*)$', + re.DOTALL | re.UNICODE) + + def __init__(self, subject, body, placeholders=None): + tmpfile = tempfile.NamedTemporaryFile(prefix=sys.argv[0] + '_', + suffix='.txt', + delete=False) + tmpfile.write((u'Summary (one line):\n%s\n\nDescription:\n%s' + % (subject, body)).encode('utf-8')) + tmpfile.close() + super(EditBugReport, self).__init__(tmpfile.name, 'bug report', + placeholders) + + def check_edit(self): + with open(self.filename, 'r') as f: + report = f.read().decode('utf-8') + + if self.split_re.match(report) is None: + print ("The %s doesn't start with 'Summary:' and 'Description:' " + "blocks" % self.description) + confirmation_prompt('edit again') + return False + return True + + def get_report(self): + with open(self.filename, 'r') as f: + report = f.read().decode('utf-8') + + m = self.split_re.match(report) + report = (m.group(1), m.group(2)) + os.unlink(self.filename) + return report From d4fbed617f2a1921bdebdf488408cdfbace3cd37 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 20:15:19 +0200 Subject: [PATCH 32/38] Use EditBugReport instead of requestsync's edit_report. Drop that. --- requestbackport | 7 +-- requestsync | 19 ++++---- ubuntutools/requestsync/common.py | 77 ------------------------------- ubuntutools/requestsync/lp.py | 9 ++-- ubuntutools/requestsync/mail.py | 25 ++++------ 5 files changed, 29 insertions(+), 108 deletions(-) diff --git a/requestbackport b/requestbackport index e961fc1..40c8245 100755 --- a/requestbackport +++ b/requestbackport @@ -26,8 +26,7 @@ from ubuntutools.lp.lpapicache import Launchpad, Distribution from ubuntutools.lp.udtexceptions import PackageNotFoundException from ubuntutools.config import UDTConfig from ubuntutools.rdepends import query_rdepends, RDependsException -from ubuntutools.requestsync.common import edit_report -from ubuntutools.question import YesNoQuestion +from ubuntutools.question import YesNoQuestion, EditBugReport class DestinationException(Exception): @@ -195,7 +194,9 @@ def request_backport(package_spph, source, destinations): + [""] ) % subst) - subject, body = edit_report(subject, body, changes_required=True) + editor = EditBugReport(subject, body) + editor.edit() + subject, body = editor.get_report() Logger.normal('The final report is:\nSummary: %s\nDescription:\n%s\n', subject, body) diff --git a/requestsync b/requestsync index 752998f..22aefc3 100755 --- a/requestsync +++ b/requestsync @@ -36,8 +36,8 @@ from distro_info import UbuntuDistroInfo from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.lp import udtexceptions from ubuntutools.misc import require_utf8 -from ubuntutools.requestsync.common import (edit_report, get_debian_changelog, - raw_input_exit_on_ctrlc) +from ubuntutools.question import confirmation_prompt, EditBugReport +from ubuntutools.requestsync.common import get_debian_changelog # # entry point @@ -205,8 +205,7 @@ def main(): print ("'%s' doesn't exist in 'Ubuntu %s'.\n" "Do you want to sync a new package?" % (srcpkg, release)) - raw_input_exit_on_ctrlc('Press [Enter] to continue ' - 'or [Ctrl-C] to abort. ') + confirmation_prompt() newsource = True # Get the requested Debian source package @@ -283,8 +282,7 @@ def main(): '>>> ENTER_EXPLANATION_HERE <<<\n\n') if need_interaction: - raw_input_exit_on_ctrlc('Press [Enter] to continue.' - 'Press [Ctrl-C] to abort now. ') + confirmation_prompt() base_version = force_base_version or ubuntu_version @@ -306,8 +304,13 @@ def main(): changelog = "XXX FIXME: add changelog here XXX" report += changelog - (title, report) = edit_report(title, report, - changes_required=need_interaction) + editor = EditBugReport(title, report) + if need_interaction: + editor.edit() + else: + editor.optional_edit() + title, report = editor.get_report() + if 'XXX FIXME' in report: print >> sys.stderr, ("E: changelog boilerplate found in report, " "please manually add changelog when using '-C'") diff --git a/ubuntutools/requestsync/common.py b/ubuntutools/requestsync/common.py index 37f90cb..37511f5 100644 --- a/ubuntutools/requestsync/common.py +++ b/ubuntutools/requestsync/common.py @@ -22,22 +22,10 @@ import os import sys import urllib2 -import re -import tempfile from debian.changelog import Changelog from ubuntutools import subprocess -def raw_input_exit_on_ctrlc(*args, **kwargs): - ''' - A wrapper around raw_input() to exit with a normalized message on Control-C - ''' - try: - return raw_input(*args, **kwargs) - except KeyboardInterrupt: - print '\nAbort requested. No sync request filed.' - sys.exit(1) - def get_changelog(srcpkg, distro): ''' Download and return a parsed changelog for srcpackage, from @@ -83,68 +71,3 @@ def get_debian_changelog(srcpkg, version): break new_entries.append(unicode(block)) return u''.join(new_entries) - -def edit_report(subject, body, changes_required = False): - ''' - Ask if the user wants to edit a report (consisting of subject and body) - in sensible-editor. - - If changes_required is True then the file has to be edited before we - can proceed. - - Returns (new_subject, new_body). - ''' - - editing_finished = False - while not editing_finished: - report = 'Summary (one line):\n%s\n\nDescription:\n%s' % (subject, body) - - if not changes_required: - print 'Currently the report looks as follows:\n%s' % report - while True: - val = raw_input_exit_on_ctrlc('Do you want to edit the report ' - '[y/N]? ') - if val.lower() in ('y', 'yes'): - break - elif val.lower() in ('n', 'no', ''): - editing_finished = True - break - else: - print 'Invalid answer.' - - if not editing_finished: - # Create tempfile and remember mtime - report_file = tempfile.NamedTemporaryFile(prefix='requestsync_') - report_file.write(report.encode('utf-8')) - report_file.flush() - mtime_before = os.stat(report_file.name).st_mtime - - # Launch editor - try: - subprocess.check_call(['sensible-editor', report_file.name]) - except subprocess.CalledProcessError, e: - print >> sys.stderr, ('Error calling sensible-editor: %s\n' - 'Aborting.' % e) - sys.exit(1) - - # Check if the tempfile has been changed - if changes_required: - if mtime_before == os.stat(report_file.name).st_mtime: - print ('The report has not been changed, but you have to ' - 'explain why the Ubuntu changes can be dropped.') - raw_input_exit_on_ctrlc('Press [Enter] to retry or ' - '[Control-C] to abort. ') - else: - changes_required = False - - report_file.seek(0) - report = report_file.read().decode('utf-8') - report_file.close() - - # Undecorate report again - (subject, body) = report.split("\nDescription:\n", 1) - # Remove prefix and whitespace from subject - subject = re.sub('^Summary \(one line\):\s*', '', subject, - 1).strip() - - return (subject, body) diff --git a/ubuntutools/requestsync/lp.py b/ubuntutools/requestsync/lp.py index b6067da..b201ef9 100644 --- a/ubuntutools/requestsync/lp.py +++ b/ubuntutools/requestsync/lp.py @@ -26,9 +26,9 @@ import urllib2 from debian.deb822 import Changes from distro_info import DebianDistroInfo -from ubuntutools.requestsync.common import raw_input_exit_on_ctrlc from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam, DistributionSourcePackage) +from ubuntutools.question import confirmation_prompt def get_debian_srcpkg(name, release): debian = Distribution('debian') @@ -60,7 +60,7 @@ Your sync request shall require an approval by a member of the appropriate sponsorship team, who shall be subscribed to this bug report. This must be done before it can be processed by a member of the Ubuntu Archive team.''' - raw_input_exit_on_ctrlc('If the above is correct please press [Enter] ') + confirmation_prompt() return need_sponsor @@ -88,8 +88,7 @@ def check_existing_reports(srcpkg): 'Please check the above URL to verify this before ' 'continuing.' % (bug.title, bug.web_link)) - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] ' - 'to abort. ') + confirmation_prompt() def get_ubuntu_delta_changelog(srcpkg): ''' @@ -133,7 +132,7 @@ def post_bug(srcpkg, subscribe, status, bugtitle, bugtext): print ('The final report is:\nSummary: %s\nDescription:\n%s\n' % (bugtitle, bugtext)) - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] to abort. ') + confirmation_prompt() if srcpkg: bug_target = DistributionSourcePackage( diff --git a/ubuntutools/requestsync/mail.py b/ubuntutools/requestsync/mail.py index 862feb7..6335ee2 100644 --- a/ubuntutools/requestsync/mail.py +++ b/ubuntutools/requestsync/mail.py @@ -30,8 +30,8 @@ from devscripts.logger import Logger from distro_info import DebianDistroInfo from ubuntutools.archive import rmadison, FakeSPPH -from ubuntutools.requestsync.common import (get_changelog, - raw_input_exit_on_ctrlc) +from ubuntutools.requestsync.common import get_changelog +from ubuntutools.question import confirmation_prompt, YesNoQuestion from ubuntutools import subprocess from ubuntutools.lp.udtexceptions import PackageNotFoundException @@ -71,17 +71,12 @@ def need_sponsorship(name, component, release): component. ''' - while True: - print ("Do you have upload permissions for the '%s' component " - "or the package '%s' in Ubuntu %s?" - % (component, name, release)) - val = raw_input_exit_on_ctrlc("If in doubt answer 'n'. [y/N]? ") - if val.lower() in ('y', 'yes'): - return False - elif val.lower() in ('n', 'no', ''): - return True - else: - print 'Invalid answer' + val = YesNoQuestion().ask("Do you have upload permissions for the " + "'%s' component or the package '%s' in " + "Ubuntu %s?\n" + "If in doubt answer 'n'." + % (component, name, release), 'no') + return val == 'no' def check_existing_reports(srcpkg): ''' @@ -90,7 +85,7 @@ def check_existing_reports(srcpkg): print ('Please check on ' 'https://bugs.launchpad.net/ubuntu/+source/%s/+bugs\n' 'for duplicate sync requests before continuing.' % srcpkg) - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] to abort. ') + confirmation_prompt() def get_ubuntu_delta_changelog(srcpkg): ''' @@ -166,7 +161,7 @@ Content-Type: text/plain; charset=UTF-8 %s''' % (myemailaddr, to, bugtitle, signed_report) print 'The final report is:\n%s' % mail - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] to abort. ') + confirmation_prompt() # connect to the server try: From 054423d016ebbf01dd70b2899a9e6d78d1c5f7ad Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 20:17:50 +0200 Subject: [PATCH 33/38] Use YesNoQuestion in pbuilder-dist --- pbuilder-dist | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/pbuilder-dist b/pbuilder-dist index 8ce0320..3f8e787 100755 --- a/pbuilder-dist +++ b/pbuilder-dist @@ -37,6 +37,8 @@ from distro_info import DebianDistroInfo import ubuntutools.misc from ubuntutools import subprocess +from ubuntutools.question import YesNoQuestion + class PbuilderDist: def __init__(self, builder): @@ -135,9 +137,10 @@ class PbuilderDist: # Debian experimental doesn't have a debootstrap file but # should work nevertheless. if distro not in self._debian_distros: - answer = ask(('Warning: Unknown distribution "%s". Do you ' - 'want to continue [y/N]? ') % distro) - if answer not in ('y', 'Y'): + answer = YesNoQuestion().ask( + 'Warning: Unknown distribution "%s". ' + 'Do you want to continue' % distro, 'no') + if answer == 'yes': sys.exit(0) else: Logger.error('Please install package "debootstrap".') @@ -297,22 +300,6 @@ class PbuilderDist: self.builder, ] + arguments - -def ask(question): - """ ask(question) -> string - - Ask the given question and return the answer. Also catch - KeyboardInterrupt (Ctrl+C) and EOFError (Ctrl+D) exceptions and - immediately return None if one of those is found. - """ - try: - answer = raw_input(question) - except (KeyboardInterrupt, EOFError): - print - answer = None - - return answer - def show_help(exit_code = 0): """ help() -> None From 629fbd14ad97fe931ce162374ade0137e440e354 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 20:53:39 +0200 Subject: [PATCH 34/38] optional_edit was a silly idea --- requestsync | 5 +---- ubuntutools/question.py | 17 ++++++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/requestsync b/requestsync index 22aefc3..ffcc2dd 100755 --- a/requestsync +++ b/requestsync @@ -305,10 +305,7 @@ def main(): report += changelog editor = EditBugReport(title, report) - if need_interaction: - editor.edit() - else: - editor.optional_edit() + editor.edit(optional=not need_interaction) title, report = editor.get_report() if 'XXX FIXME' in report: diff --git a/ubuntutools/question.py b/ubuntutools/question.py index 124f66e..60c8f13 100644 --- a/ubuntutools/question.py +++ b/ubuntutools/question.py @@ -119,15 +119,14 @@ class EditFile(object): placeholders = (re.compile(r'^<<<.*>>>$', re.UNICODE),) self.placeholders = placeholders - def optional_edit(self): - '''Prompt the user to decide if the file needs editing''' - print "Currently the %s looks like:" % self.description - with open(self.filename, 'r') as f: - print f.read() - if YesNoQuestion().ask("Edit", "no") == "yes": - self.edit() + def edit(self, optional=False): + if optional: + print "Currently the %s looks like:" % self.description + with open(self.filename, 'r') as f: + print f.read() + if YesNoQuestion().ask("Edit", "no") == "no": + return - def edit(self): done = False while not done: old_mtime = os.stat(self.filename).st_mtime @@ -146,7 +145,7 @@ class EditFile(object): print ("Placeholders still present in the %s. " "Please replace them with useful information." % self.description) - confirmation_prompt('edit again') + confirmation_prompt(action='edit again') elif not modified: print "The %s was not modified" % self.description if YesNoQuestion().ask("Edit again", "yes") == "no": From 1f91b015ab0174c26a39771156a3f26962be9db1 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 20:54:05 +0200 Subject: [PATCH 35/38] Use subprocess and EditFile in submittodebian --- submittodebian | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/submittodebian b/submittodebian index f697543..6b9b349 100755 --- a/submittodebian +++ b/submittodebian @@ -30,7 +30,8 @@ from tempfile import mkstemp from distro_info import UbuntuDistroInfo from ubuntutools.config import ubu_email -from ubuntutools.question import YesNoQuestion +from ubuntutools.question import YesNoQuestion, EditFile +from ubuntutools.subprocess import call, check_call, Popen, PIPE try: from debian.changelog import Changelog @@ -75,13 +76,12 @@ def gen_debdiff(changelog): oldver = next(changelog_it).version (fd, debdiff) = mkstemp() - os.close(fd) + debdiff_f = os.fdopen(fd, 'w') - if os.system('bzr diff -r tag:%s > /dev/null 2>&1' % oldver) == 256: + devnull = open('/dev/null', 'w') + diff_cmd = ['bzr', 'diff', '-r', 'tag:' + str(oldver)] + if call(diff_cmd, stdout=devnull, stderr=devnull) == 1: print "Extracting bzr diff between %s and %s" % (oldver, newver) - cmd = 'bzr diff -r tag:%s | filterdiff -x "*changelog*" > %s' % \ - (oldver, debdiff) - run_cmd(cmd) else: if oldver.epoch is not None: oldver = str(oldver)[str(oldver).index(":")+1:] @@ -95,9 +95,15 @@ def gen_debdiff(changelog): check_file(newdsc) print "Generating debdiff between %s and %s" % (oldver, newver) - cmd = 'debdiff %s %s | filterdiff -x "*changelog*" > %s' % \ - (olddsc, newdsc, debdiff) - run_cmd(cmd) + diff_cmd = ['debdiff', olddsc, newdsc] + + diff = Popen(diff_cmd, stdout=PIPE) + filterdiff = Popen(['filterdiff', '-x', '*changelog*'], + stdin=diff.stdout, stdout=debdiff_f) + diff.stdout.close() + filterdiff.wait() + debdiff_f.close() + devnull.close() return debdiff @@ -110,22 +116,13 @@ def check_file(fname, critical = True): print u"Couldn't find «%s».\n" % fname sys.exit(1) -def edit_debdiff(debdiff): - cmd = 'sensible-editor %s' % (debdiff) - run_cmd(cmd) - def submit_bugreport(body, debdiff, deb_version, changelog): - cmd = ('reportbug -P "User: ubuntu-devel@lists.ubuntu.com" ' - '-P "Usertags: origin-ubuntu %s ubuntu-patch" -T patch -A %s ' - '-B debian -i %s -V %s %s') % \ - (UbuntuDistroInfo().devel(), debdiff, body, deb_version, - changelog.package) - run_cmd(cmd) - -def run_cmd(cmd): - if os.getenv('DEBUG'): - print "%s\n" % cmd - os.system(cmd) + devel = UbuntuDistroInfo().devel() + cmd = ('reportbug', '-P', 'User: ubuntu-devel@lists.ubuntu.com', + '-P', 'Usertags: origin-ubuntu %s ubuntu-patch' % devel, + '-T', 'patch', '-A', debdiff, '-B', 'debian', '-i', body, + '-V', deb_version, changelog.package) + check_call(cmd) def check_reportbug_config(): fn = os.path.expanduser('~/.reportbugrc') @@ -182,7 +179,10 @@ def main(): fp.close() debdiff = gen_debdiff(changelog) - edit_debdiff(debdiff) + EditFile(debdiff, 'debdiff').edit(optional=True) + EditFile(body, 'bug report', [ + re.compile('.*REPLACE THIS WITH ACTUAL INFORMATION.*') + ]).edit() submit_bugreport(body, debdiff, deb_version, changelog) os.unlink(body) os.unlink(debdiff) From f0a0d62a5b59136dcf38e92370ea5203c82e023c Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 21:04:24 +0200 Subject: [PATCH 36/38] Don't allow boilerplate prompts through in submittodebian and requestsync (LP: #887336) --- debian/changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9080181..8a917f5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -6,6 +6,8 @@ ubuntu-dev-tools (0.135) UNRELEASED; urgency=low package. (LP: #696373) - requestbackport: Files a backport request bug report, including a full testing checklist. + * Don't allow boilerplate prompts through in submittodebian and requestsync + (LP: #887336) -- Stefano Rivera Sat, 12 Nov 2011 13:09:05 +0200 From 3d6d497df34a9b5fed83b818cbed57c963a7dd5f Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 21:09:48 +0200 Subject: [PATCH 37/38] Don't substitute user's username into the backportpackage example command --- requestbackport | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/requestbackport b/requestbackport index 40c8245..455ecd8 100755 --- a/requestbackport +++ b/requestbackport @@ -152,9 +152,8 @@ def request_backport(package_spph, source, destinations): testing = [] testing += ["You can test-build the backport in your PPA with " "backportpackage:"] - lp_user = Launchpad.me.name - testing += ["$ backportpackage -u ppa:%s/ppa -s %s -d %s" - % (lp_user, source, dest) + testing += ["$ backportpackage -u ppa:/ -s %s -d %s" + % (source, dest) for dest in destinations] testing += [""] for dest in destinations: From 0838fc6390d4e26f07bc46e5af10ab81e3b2efd0 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 13 Nov 2011 21:16:15 +0200 Subject: [PATCH 38/38] Actually include the package name in the backportpackage command --- requestbackport | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requestbackport b/requestbackport index 455ecd8..1ad21b0 100755 --- a/requestbackport +++ b/requestbackport @@ -152,8 +152,9 @@ def request_backport(package_spph, source, destinations): testing = [] testing += ["You can test-build the backport in your PPA with " "backportpackage:"] - testing += ["$ backportpackage -u ppa:/ -s %s -d %s" - % (source, dest) + testing += ["$ backportpackage -u ppa:/ " + "-s %s -d %s %s" + % (source, dest, package_spph.getPackageName()) for dest in destinations] testing += [""] for dest in destinations: