516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
1 |
#! /usr/bin/python
|
2 |
||
3 |
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.
|
|
4 |
||
5 |
# This program is free software: you can redistribute it and/or modify
|
|
6 |
# it under the terms of the GNU General Public License as published by
|
|
7 |
# the Free Software Foundation; version 3 of the License.
|
|
8 |
#
|
|
9 |
# This program is distributed in the hope that it will be useful,
|
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
# GNU General Public License for more details.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License
|
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16 |
||
17 |
"""Apply suitable overrides to new kernel binaries, matching previous ones."""
|
|
18 |
||
19 |
from __future__ import print_function |
|
20 |
||
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
21 |
import atexit |
555
by Colin Watson
Simplify using collections.defaultdict. |
22 |
from collections import defaultdict |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
23 |
from contextlib import closing |
1011
by Colin Watson
auto-sync, kernel-overrides, orphaned-sources: stop relying on {Packages,Sources}.bz2; use .gz for now because I'm lazy |
24 |
import gzip |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
25 |
from optparse import OptionParser, Values |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
26 |
import os |
27 |
import shutil |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
28 |
import sys |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
29 |
import tempfile |
30 |
try: |
|
31 |
from urllib.request import urlopen |
|
32 |
except ImportError: |
|
33 |
from urllib2 import urlopen |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
34 |
|
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
35 |
import apt_pkg |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
36 |
from launchpadlib.launchpad import Launchpad |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
37 |
from lazr.restfulclient.errors import ServerError |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
38 |
from ubuntutools.question import YesNoQuestion |
39 |
||
40 |
import lputils |
|
41 |
||
42 |
||
43 |
CONSUMER_KEY = "kernel-overrides" |
|
44 |
||
45 |
||
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
46 |
tempdir = None |
47 |
||
48 |
||
49 |
def ensure_tempdir(): |
|
50 |
global tempdir |
|
51 |
if not tempdir: |
|
52 |
tempdir = tempfile.mkdtemp(prefix='kernel-overrides') |
|
53 |
atexit.register(shutil.rmtree, tempdir) |
|
54 |
||
55 |
||
56 |
class FakeBPPH: |
|
57 |
def __init__(self, pkg, component, das): |
|
58 |
self.binary_package_name = pkg |
|
59 |
self.component_name = component |
|
60 |
self.distro_arch_series = das |
|
61 |
||
62 |
||
63 |
def get_published_binaries(options, source): |
|
64 |
"""If getPublishedBinaries times out, fall back to doing it by hand."""
|
|
65 |
try: |
|
66 |
for binary in source.getPublishedBinaries(): |
|
719.1.1
by William Grant
Teach overrides and deletions to skip debug publications. They follow the corresponding non-debug publication and cannot be modified independently. |
67 |
if not binary.is_debug: |
68 |
yield binary |
|
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
69 |
except ServerError as e: |
70 |
if e.response.status != 503: |
|
71 |
raise
|
|
72 |
print("getPublishedBinaries timed out; fetching Packages instead ...") |
|
73 |
ensure_tempdir() |
|
74 |
for section_name in ("", "debian-installer"): |
|
75 |
for component in ("main", "restricted", "universe", "multiverse"): |
|
76 |
for das in options.old.series.architectures: |
|
77 |
arch = das.architecture_tag |
|
78 |
if arch in ("amd64", "i386"): |
|
79 |
base = "http://archive.ubuntu.com/ubuntu" |
|
80 |
else: |
|
81 |
base = "http://ports.ubuntu.com/ubuntu-ports" |
|
1011
by Colin Watson
auto-sync, kernel-overrides, orphaned-sources: stop relying on {Packages,Sources}.bz2; use .gz for now because I'm lazy |
82 |
url = ("%s/dists/%s/%s%s/binary-%s/Packages.gz" % |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
83 |
(base, options.old.suite, component, |
84 |
"/%s" % section_name if section_name else "", |
|
85 |
arch)) |
|
86 |
path = os.path.join( |
|
87 |
tempdir, "Ubuntu_%s_%s%s_Packages_%s" % |
|
88 |
(options.old.suite, component, |
|
89 |
"_%s" % section_name if section_name else "", arch)) |
|
90 |
with closing(urlopen(url)) as url_file: |
|
1011
by Colin Watson
auto-sync, kernel-overrides, orphaned-sources: stop relying on {Packages,Sources}.bz2; use .gz for now because I'm lazy |
91 |
with open("%s.gz" % path, "wb") as comp_file: |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
92 |
comp_file.write(url_file.read()) |
1011
by Colin Watson
auto-sync, kernel-overrides, orphaned-sources: stop relying on {Packages,Sources}.bz2; use .gz for now because I'm lazy |
93 |
with closing(gzip.GzipFile("%s.gz" % path)) as gz_file: |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
94 |
with open(path, "wb") as out_file: |
1011
by Colin Watson
auto-sync, kernel-overrides, orphaned-sources: stop relying on {Packages,Sources}.bz2; use .gz for now because I'm lazy |
95 |
out_file.write(gz_file.read()) |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
96 |
with open(path) as packages_file: |
97 |
apt_packages = apt_pkg.TagFile(packages_file) |
|
98 |
for section in apt_packages: |
|
99 |
pkg = section["Package"] |
|
928
by Colin Watson
component-mismatches, kernel-overrides: strip any version from Source fields |
100 |
src = section.get("Source", pkg).split(" ", 1)[0] |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
101 |
if src != options.source: |
102 |
continue
|
|
103 |
yield FakeBPPH(pkg, component, das) |
|
104 |
||
105 |
||
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
106 |
def find_current_binaries(options): |
107 |
print("Checking existing binaries in %s ..." % options.old.suite, |
|
108 |
file=sys.stderr) |
|
109 |
sources = options.old.archive.getPublishedSources( |
|
110 |
source_name=options.source, distro_series=options.old.series, |
|
111 |
pocket=options.old.pocket, exact_match=True, status="Published") |
|
112 |
for source in sources: |
|
555
by Colin Watson
Simplify using collections.defaultdict. |
113 |
binaries = defaultdict(dict) |
662
by Colin Watson
kernel-overrides: fall back to fetching Packages if SPPH.getPublishedBinaries() times out |
114 |
for binary in get_published_binaries(options, source): |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
115 |
print(".", end="") |
116 |
sys.stdout.flush() |
|
117 |
arch = binary.distro_arch_series.architecture_tag |
|
118 |
name = binary.binary_package_name |
|
119 |
component = binary.component_name |
|
120 |
if name not in binaries[arch]: |
|
121 |
binaries[arch][name] = component |
|
556
by Colin Watson
Rely on default iterator for mappings iterating over keys. |
122 |
if binaries: |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
123 |
print() |
124 |
return binaries |
|
125 |
print() |
|
621
by Colin Watson
kernel-overrides: fix crash if there are no current binary publications |
126 |
return [] |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
127 |
|
128 |
||
129 |
def find_matching_uploads(options, newabi): |
|
130 |
print("Checking %s uploads to %s ..." % |
|
131 |
(options.queue.lower(), options.suite), file=sys.stderr) |
|
132 |
uploads = options.series.getPackageUploads( |
|
680
by Colin Watson
kernel-overrides: simplify find_matching_uploads now that LP #33700 is fixed |
133 |
name=options.source, exact_match=True, archive=options.archive, |
578
by Colin Watson
kernel-overrides, new-binary-debian-universe: pass archive to DistroSeries.getPackageUploads |
134 |
pocket=options.pocket, status=options.queue) |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
135 |
for upload in uploads: |
136 |
if upload.contains_build: |
|
137 |
# display_name is inaccurate for the theoretical case of an
|
|
138 |
# upload containing multiple builds, but in practice it's close
|
|
139 |
# enough.
|
|
140 |
source = upload.display_name.split(",")[0] |
|
141 |
if source == options.source: |
|
142 |
binaries = upload.getBinaryProperties() |
|
143 |
binaries = [b for b in binaries if "customformat" not in b] |
|
144 |
if [b for b in binaries if newabi in b["version"]]: |
|
145 |
yield upload, binaries |
|
146 |
||
147 |
||
148 |
def equal_except_abi(old, new, abi): |
|
149 |
"""Are OLD and NEW the same package name aside from ABI?"""
|
|
150 |
# Make sure new always contains the ABI.
|
|
151 |
if abi in old: |
|
152 |
old, new = new, old |
|
153 |
if abi not in new: |
|
154 |
return False |
|
155 |
||
156 |
left, _, right = new.partition(abi) |
|
157 |
if not old.startswith(left) or not old.endswith(right): |
|
158 |
return False |
|
159 |
old_abi = old[len(left):] |
|
160 |
if right: |
|
161 |
old_abi = old_abi[:-len(right)] |
|
162 |
return old_abi[0].isdigit() and old_abi[-1].isdigit() |
|
163 |
||
164 |
||
165 |
def apply_kernel_overrides(options, newabi): |
|
166 |
current_binaries = find_current_binaries(options) |
|
167 |
all_changes = [] |
|
168 |
||
169 |
for upload, binaries in find_matching_uploads(options, newabi): |
|
170 |
print("%s/%s (%s):" % |
|
171 |
(upload.package_name, upload.package_version, |
|
172 |
upload.display_arches.split(",")[0])) |
|
173 |
changes = [] |
|
174 |
for binary in binaries: |
|
175 |
if binary["architecture"] not in current_binaries: |
|
176 |
continue
|
|
177 |
current_binaries_arch = current_binaries[binary["architecture"]] |
|
178 |
for name, component in current_binaries_arch.items(): |
|
179 |
if (binary["component"] != component and |
|
677
by Colin Watson
make all scripts pass current stricter pep8(1) in raring |
180 |
equal_except_abi(name, binary["name"], newabi)): |
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
181 |
print("\t%s: %s -> %s" % |
182 |
(binary["name"], binary["component"], component)) |
|
183 |
changes.append( |
|
184 |
{"name": binary["name"], "component": component}) |
|
185 |
if changes: |
|
186 |
all_changes.append((upload, changes)) |
|
187 |
||
188 |
if all_changes: |
|
189 |
if options.dry_run: |
|
190 |
print("Dry run; no changes made.") |
|
191 |
else: |
|
192 |
if not options.confirm_all: |
|
620
by Colin Watson
kernel-overrides: fix --confirm-all handling |
193 |
if YesNoQuestion().ask("Override", "no") == "no": |
194 |
return
|
|
195 |
for upload, changes in all_changes: |
|
196 |
upload.overrideBinaries(changes=changes) |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
197 |
|
198 |
||
199 |
def main(): |
|
200 |
parser = OptionParser(usage="usage: %prog [options] NEW-ABI") |
|
201 |
parser.add_option( |
|
202 |
"-l", "--launchpad", dest="launchpad_instance", default="production") |
|
203 |
parser.add_option( |
|
204 |
"-d", "--distribution", metavar="DISTRO", default="ubuntu", |
|
205 |
help="look in distribution DISTRO") |
|
206 |
parser.add_option( |
|
591
by Colin Watson
kernel-overrides: improve --suite/--old-suite defaults so that the standard case of development kernel uploads can be handled without options |
207 |
"-S", "--suite", metavar="SUITE", |
208 |
help="look in suite SUITE (default: <current series>-proposed)") |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
209 |
parser.add_option( |
210 |
"--old-suite", metavar="SUITE", |
|
591
by Colin Watson
kernel-overrides: improve --suite/--old-suite defaults so that the standard case of development kernel uploads can be handled without options |
211 |
help="look for previous binaries in suite SUITE " |
212 |
"(default: value of --suite without -proposed)") |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
213 |
parser.add_option( |
214 |
"-s", "--source", metavar="SOURCE", default="linux", |
|
215 |
help="operate on source package SOURCE") |
|
216 |
parser.add_option( |
|
217 |
"-Q", "--queue", metavar="QUEUE", default="new", |
|
218 |
help="consider packages in QUEUE") |
|
219 |
parser.add_option( |
|
220 |
"-n", "--dry-run", default=False, action="store_true", |
|
221 |
help="don't make any modifications") |
|
222 |
parser.add_option( |
|
223 |
"-y", "--confirm-all", default=False, action="store_true", |
|
224 |
help="do not ask for confirmation") |
|
225 |
options, args = parser.parse_args() |
|
226 |
if len(args) != 1: |
|
227 |
parser.error("must supply NEW-ABI") |
|
228 |
newabi = args[0] |
|
229 |
||
230 |
options.launchpad = Launchpad.login_with( |
|
231 |
CONSUMER_KEY, options.launchpad_instance, version="devel") |
|
232 |
||
591
by Colin Watson
kernel-overrides: improve --suite/--old-suite defaults so that the standard case of development kernel uploads can be handled without options |
233 |
if options.suite is None: |
234 |
distribution = options.launchpad.distributions[options.distribution] |
|
235 |
options.suite = "%s-proposed" % distribution.current_series.name |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
236 |
if options.old_suite is None: |
237 |
options.old_suite = options.suite |
|
591
by Colin Watson
kernel-overrides: improve --suite/--old-suite defaults so that the standard case of development kernel uploads can be handled without options |
238 |
if options.old_suite.endswith("-proposed"): |
239 |
options.old_suite = options.old_suite[:-9] |
|
516
by Colin Watson
kernel-overrides: new script, based on a local script on ftpmaster but converted to use the API |
240 |
options.queue = options.queue.title() |
241 |
options.version = None |
|
242 |
lputils.setup_location(options) |
|
243 |
options.old = Values() |
|
244 |
options.old.launchpad = options.launchpad |
|
245 |
options.old.distribution = options.distribution |
|
246 |
options.old.suite = options.old_suite |
|
247 |
lputils.setup_location(options.old) |
|
248 |
||
249 |
apply_kernel_overrides(options, newabi) |
|
250 |
||
251 |
||
252 |
if __name__ == '__main__': |
|
253 |
main() |