Merge lp:~jml/pkgme-devportal/remove-database-code into lp:pkgme-devportal

Proposed by Jonathan Lange
Status: Merged
Approved by: James Westby
Approved revision: 170
Merged at revision: 144
Proposed branch: lp:~jml/pkgme-devportal/remove-database-code
Merge into: lp:pkgme-devportal
Diff against target: 1182 lines (+69/-825)
10 files modified
NEWS (+10/-0)
bin/fetch-symbol-files (+0/-26)
devportalbinary/acceptance/tests/__init__.py (+10/-9)
devportalbinary/aptfile.py (+2/-0)
devportalbinary/database.py (+5/-427)
devportalbinary/testing.py (+30/-1)
devportalbinary/tests/test_binary.py (+9/-26)
devportalbinary/tests/test_database.py (+2/-335)
devportalbinary/utils.py (+1/-0)
setup.py (+0/-1)
To merge this branch: bzr merge lp:~jml/pkgme-devportal/remove-database-code
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+134083@code.launchpad.net

Commit message

Remove fetch-symbol-files and all of the code it uses

Description of the change

This is the first branch of many.

It removes fetch-symbol-files and all of the code that it calls that
is not used by others.

Other things that are now probably safe to remove are marked up with
XXX comments, and should be deleted in future branches.

I have tried to be careful and remove absolutely everything.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2012-10-29 16:31:07 +0000
+++ NEWS 2012-11-13 12:03:23 +0000
@@ -2,6 +2,16 @@
2NEWS for pkgme-devportal2NEWS for pkgme-devportal
3========================3========================
44
5NEXT
6====
7
8Changes
9-------
10
11 * ``fetch-symbol-files`` and everything needed to parse symbol files and
12 maintain a database of library dependencies based on published packages
13 from Launchpad has been removed. (Jonathan Lange)
14
50.4.11 (2012-10-29)150.4.11 (2012-10-29)
6===================16===================
717
818
=== removed file 'bin/fetch-symbol-files'
--- bin/fetch-symbol-files 2012-08-30 12:35:31 +0000
+++ bin/fetch-symbol-files 1970-01-01 00:00:00 +0000
@@ -1,26 +0,0 @@
1#!/usr/bin/python2.7 -S
2
3import os
4
5join = os.path.join
6base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
7base = os.path.dirname(base)
8
9import sys
10sys.path[0:0] = [
11 join(base, 'parts/scripts'),
12 ]
13
14
15import os
16path = sys.path[0]
17if os.environ.get('PYTHONPATH'):
18 os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ['PYTHONPATH']
19 path = os.pathsep.join([path, os.environ['PYTHONPATH']])
20os.environ['PYTHONPATH'] = path
21import site # imports custom buildout-generated site.py
22
23import devportalbinary.database
24
25if __name__ == '__main__':
26 devportalbinary.database.main()
270
=== modified file 'devportalbinary/acceptance/tests/__init__.py'
--- devportalbinary/acceptance/tests/__init__.py 2012-09-17 16:20:42 +0000
+++ devportalbinary/acceptance/tests/__init__.py 2012-11-13 12:03:23 +0000
@@ -15,7 +15,10 @@
15from pkgme.debuild import build_source_package15from pkgme.debuild import build_source_package
16from pkgme.run_script import ScriptUserError16from pkgme.run_script import ScriptUserError
1717
18from devportalbinary.testing import DatabaseFixture, IsImage18from devportalbinary.testing import (
19 IsImage,
20 LibdepFixture,
21 )
1922
2023
21class TestData(Fixture):24class TestData(Fixture):
@@ -76,20 +79,18 @@
7679
77 def test_gtk(self):80 def test_gtk(self):
78 """Runs successfully for a basic GTK+ application."""81 """Runs successfully for a basic GTK+ application."""
79 dep_db = self.useFixture(DatabaseFixture())82 self.useFixture(
80 dep_db.db.update_package("pthreads",83 LibdepFixture(
81 {'i386': {"libpthread.so.0": "libpthread0"}})84 [("libpthread0", {'i386': {"libpthread.so.0": "libpthread0"}}),
82 dep_db.db.update_package("eglibc",85 ("libc6", {'i386': {"libc.so.6": "libc6"}})]))
83 {'i386': {"libc.so.6": "libc6"}})
84 test_data = self.useFixture(TestData("gtk"))86 test_data = self.useFixture(TestData("gtk"))
85 run_pkgme(test_data)87 run_pkgme(test_data)
86 self.assertThat(test_data.path, HasFileTree({'debian/control': {}}))88 self.assertThat(test_data.path, HasFileTree({'debian/control': {}}))
8789
88 def test_bundled_library(self):90 def test_bundled_library(self):
89 """Runs successfully for a basic bundled libary."""91 """Runs successfully for a basic bundled libary."""
90 dep_db = self.useFixture(DatabaseFixture())92 self.useFixture(
91 dep_db.db.update_package("eglibc",93 LibdepFixture([("libc6", {'i386': {"libc.so.6": "libc6"}})]))
92 {'i386': {"libc.so.6": "libc6"}})
93 test_data = self.useFixture(TestData("bundled_lib"))94 test_data = self.useFixture(TestData("bundled_lib"))
94 run_pkgme(test_data)95 run_pkgme(test_data)
95 self.assertThat(96 self.assertThat(
9697
=== modified file 'devportalbinary/aptfile.py'
--- devportalbinary/aptfile.py 2012-10-26 14:38:49 +0000
+++ devportalbinary/aptfile.py 2012-11-13 12:03:23 +0000
@@ -1,6 +1,8 @@
1# Copyright 2012 Canonical Ltd. This software is licensed under the1# Copyright 2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# XXX: Remove this. No longer used.
5
4__all__ = [6__all__ = [
5 'AptFilePackageDatabase',7 'AptFilePackageDatabase',
6 ]8 ]
79
=== modified file 'devportalbinary/database.py'
--- devportalbinary/database.py 2012-10-26 14:38:49 +0000
+++ devportalbinary/database.py 2012-11-13 12:03:23 +0000
@@ -1,24 +1,6 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from contextlib import closing, contextmanager
5import errno
6from itertools import chain
7import os
8import shutil
9import tempfile
10
11from bzrlib import urlutils
12from fixtures import (
13 Fixture,
14 TempDir,
15 )
16
17from launchpadlib import (
18 uris,
19 )
20from launchpadlib.launchpad import Launchpad
21from pkgme.run_script import run_subprocess
22from storm.expr import And, Column, Select, Table4from storm.expr import And, Column, Select, Table
23from storm.locals import create_database, Store5from storm.locals import create_database, Store
24from storm.uri import URI as StormURI6from storm.uri import URI as StormURI
@@ -28,338 +10,11 @@
28 CONF_FILE_ENV_VAR,10 CONF_FILE_ENV_VAR,
29 get_config_file_path,11 get_config_file_path,
30 load_configuration,12 load_configuration,
31 load_configuration_with_command_line,
32 )13 )
33from .utils import download_file
3414
35from libdep_service_client.client import Client15from libdep_service_client.client import Client
3616
3717
38# XXX: Historic name of this package. Update to 'pkgme-devportal' and
39# re-authorize.
40APPLICATION_NAME = 'pkgme-binary'
41SERVICE_ROOT = uris.LPNET_SERVICE_ROOT
42
43SHLIBS_FILENAME = 'shlibs'
44SYMBOLS_FILENAME = 'symbols'
45
46
47# A list of package names that don't match lib*, but which we want
48# to scan anyway.
49PACKAGE_NAME_WHITELIST = [
50 "e2fslibs",
51 "odbcinst1debian2",
52 "python2.7-dbg",
53 "uno-libs3",
54 "zlib1g",
55 ]
56
57
58def is_library_package(url):
59 """Is ``url`` likely to contain libraries?"""
60 filename = os.path.splitext(urlutils.basename(url))[0]
61 if filename.startswith('lib'):
62 return True
63 for prefix in PACKAGE_NAME_WHITELIST:
64 if filename.startswith(prefix):
65 return True
66 return False
67
68
69class LaunchpadFixture(Fixture):
70
71 def __init__(self, application_name, service_root):
72 super(LaunchpadFixture, self).__init__()
73 self._app_name = application_name
74 self._service_root = service_root
75
76 def setUp(self):
77 super(LaunchpadFixture, self).setUp()
78 tempdir = self.useFixture(TempDir())
79 self.anonymous = Launchpad.login_anonymously(
80 self._app_name, self._service_root, tempdir.path)
81
82
83def iter_published_binaries(lp, since=None, name=None, exact_match=True):
84 architectures = load_configuration().architectures_supported
85 ubuntu = lp.distributions['ubuntu']
86 archive = ubuntu.main_archive
87 # XXX: oneiric is a puppy that is just for christmas. Specifically, it's a
88 # bug that this is looking in oneiric, should instead be looking in
89 # ... well, we don't know.
90 oneiric = ubuntu.getSeries(name_or_version='oneiric')
91 our_series = (
92 oneiric.getDistroArchSeries(archtag=tag) for tag in architectures)
93 filters = dict(status='Published')
94 if since:
95 filters['created_since_date'] = since
96 if name:
97 filters['binary_name'] = name
98 filters['exact_match'] = exact_match
99 return chain(
100 *[archive.getPublishedBinaries(distro_arch_series=series, **filters)
101 for series in our_series])
102
103
104def possible_sonames_for_shared_object(libname, version):
105 """Return the possible sonames gives info about a shared object.
106
107 :return: a set of candidate sonames (as strings).
108 """
109 candidates = set(['{0}-{1}.so'.format(libname, version)])
110 if '.' not in version:
111 candidates.add('{0}.so.{1}'.format(libname, version))
112 return candidates
113
114
115def find_file_under_dir(filename, directory):
116 """Find `filename` under `directory`.
117
118 :return: the path of the first matching file, or None if the file
119 wasn't found.
120 """
121 for dirpath, dirnames, filenames in os.walk(directory):
122 if filename in filenames:
123 return os.path.join(dirpath, filename)
124
125
126class NoSharedObject(Exception):
127
128 def __init__(self, libname, version, candidates):
129 super(NoSharedObject, self).__init__(
130 "No shared object matching {0} {1} could be found, "
131 "looked for {2}".format(libname, version, candidates))
132
133
134class TooManySharedObjects(Exception):
135
136 def __init__(self, libname, version, candidates):
137 super(TooManySharedObjects, self).__init__(
138 "Too many objects matching {0} {1} could be found, "
139 "found {2}".format(libname, version, candidates))
140
141
142
143def shared_object_info_to_soname(libname, version, directory):
144 """Resolve a (libname, version) tuple to a soname.
145
146 Using the files from a package decide on the soname that
147 the shared object info refers to. E.g. if the info is ("libfoo", "1")
148 and there is a file named "libfoo.so.1" then that is the soname.
149
150 :param directory: a directory of files to use to resolve.
151 :return: the soname as a string
152 :raises NoSharedObject: if a file corresponding to the info can't be
153 found.
154 :raises TooManySharedObjects: if multiple files matching the shared object
155 info in different ways are found (mutliple files matching in
156 the same manner are ignored.)
157 """
158 candidates = possible_sonames_for_shared_object(libname, version)
159 files = {soname: find_file_under_dir(soname, directory) for soname in candidates}
160 found_files = dict(filter(lambda (x, y): y is not None, files.items()))
161 if len(found_files) < 1:
162 raise NoSharedObject(libname, version, candidates)
163 elif len(found_files) > 1:
164 raise TooManySharedObjects(libname, version, found_files.values())
165 return found_files.keys()[0]
166
167
168
169def libdep_mapping_for_deb(deb_file):
170 """Returns the library -> dependency mapping information from a package.
171
172 The symbols file will be read in preference to the shlibs file.
173 If neither is present an empty dict will be returned indicating
174 that there are no libraries.
175
176 If there are libraries the dict will map from the library names
177 to the package dependencies that should be used to get the
178 library, e.g.
179
180 {'libfoo.so.1': 'libfoo1', ...}
181 """
182 with extract_deb_control(deb_file) as control_dir:
183 # Try symbols
184 symbols_path = os.path.join(control_dir, SYMBOLS_FILENAME)
185 symbols = get_file_contents(symbols_path)
186 if symbols is not None:
187 return libdep_mapping_from_symbols(symbols)
188
189 # Try shlibs
190 shlibs_path = os.path.join(control_dir, SHLIBS_FILENAME)
191 shlibs = get_file_contents(shlibs_path)
192 if shlibs is not None:
193 shared_object_mapping = shared_objects_from_shlibs(shlibs)
194 if shared_object_mapping:
195 with extract_deb_content(deb_file) as deb_content:
196 def resolve_shared_objects(map_entry):
197 (libname, version), dependency = map_entry
198 soname = shared_object_info_to_soname(libname, version,
199 deb_content)
200 return soname, dependency
201 return dict(
202 map(resolve_shared_objects,
203 shared_object_mapping.items())
204 )
205 return {}
206
207
208def deb_file_url_for_publication(bpph):
209 """Get the download URL for a binary package."""
210 version = bpph.binary_package_version
211 if ':' in version:
212 version = version[version.index(':')+1:]
213 arch = bpph.distro_arch_series.architecture_tag
214 if not bpph.architecture_specific:
215 arch = 'all'
216 return '%s/+files/%s_%s_%s.deb' % (
217 bpph.archive.web_link,
218 bpph.binary_package_name,
219 version,
220 arch,
221 )
222
223
224def get_package_info_from_publication(bpph):
225 arch = bpph.distro_arch_series.architecture_tag
226 url = deb_file_url_for_publication(bpph)
227 return url, arch
228
229
230def get_libdep_mapping_for_package(url):
231 """Return the library -> dependency mapping for the package at url.
232
233 :return: a dict mapping library names to dependencies, e.g.
234 {'libfoo.so.1': 'libfoo1', ...}
235 """
236 directory = tempfile.mkdtemp()
237 try:
238 deb_file = download_file(url, directory)
239 return libdep_mapping_for_deb(deb_file)
240 finally:
241 shutil.rmtree(directory)
242
243
244@contextmanager
245def extract_deb_control(binary_package_path):
246 """Extract a deb control archive, returning the extracted path.
247
248 This is a context manager, and the tempdir will be cleaned up when closed.
249 """
250 temp_dir = tempfile.mkdtemp()
251 try:
252 run_subprocess(['dpkg-deb', '-e', binary_package_path, temp_dir])
253 yield temp_dir
254 finally:
255 shutil.rmtree(temp_dir)
256
257
258@contextmanager
259def extract_deb_content(binary_package_path):
260 """Extract the files from a .deb package to a tempdir.
261
262 This is a context manager, the path to the tempdir will be yielded
263 to the caller, and the tempdir will be cleaned up when closed.
264 """
265 temp_dir = tempfile.mkdtemp()
266 try:
267 run_subprocess(['dpkg-deb', '-x', binary_package_path, temp_dir])
268 yield temp_dir
269 finally:
270 shutil.rmtree(temp_dir)
271
272
273def get_file_contents(path):
274 """Read the contents of the file at `path`.
275
276 :return: the contents of the file, or None if there was
277 no such file.
278 :raises: Any errors opening or reading the file.
279 """
280 try:
281 return open(path).read()
282 except (OSError, IOError), e:
283 if e.errno == errno.ENOENT:
284 return
285 raise
286
287
288def libdep_mapping_from_symbols(symbol_contents):
289 """Returns a dict mapping libraries to dependencies based on the symbols
290
291 Ignores versions and a whole bunch of other stuff and is probably the
292 wrong API even.
293 """
294
295 # XXX: This is going to yield sonames and package names for now. Really,
296 # there should be something parsing the symbols file and another thing to
297 # somehow turn those into dependencies with versions.
298
299 # Doesn't know how to handle lines like this:
300 #
301 # libformw.so.5 #PACKAGE#.
302 #
303 # This is OK, as we're only processing symbols files from binary packages
304 # rather than source packages.
305
306 mapping = {}
307 for line in symbol_contents.splitlines():
308 if not line:
309 # Blank lines are skipped
310 continue
311 if line.startswith('|'):
312 # Alternative dependency template
313 # e.g. | libgl1-mesa-glx #MINVER#
314 continue
315 if line.startswith('*'):
316 # Meta-information
317 # e.g. * Build-Depends-Package: libgl1-mesa-dev
318 continue
319 if line.startswith(' '):
320 # Symbol
321 # e.g. gdk_add_client_message_filter@Base 2.8.0
322 continue
323 if line.startswith('#'):
324 # Lines starting with '#' are comments. There are also DEPRECATED
325 # and MISSING, and jml doesn't really know what they mean
326 continue
327 library, dependency = tuple(line.split()[:2])
328 if '#include' in library:
329 # To skip lines that are includes. XXX: Properly ought to process
330 # the tags that might appear before the include
331 # line.
332 #
333 # e.g. (arch=!armel)#include "libdiagnostics0.symbols.backtrace"
334 continue
335 mapping[library] = dependency
336 return mapping
337
338
339def shared_objects_from_shlibs(shlibs_contents):
340 """Get the shared object info from the shlibs file.
341
342 http://www.debian.org/doc/debian-policy/ch-sharedlibs.html#s-sharedlibs-shlibdeps
343 defines the format.
344
345 :return: a dict mapping (libname, soname) to dependencies.
346 """
347 mapping = {}
348 for line in shlibs_contents.splitlines():
349 line = line.strip()
350 if not line:
351 continue
352 if line.startswith("#"):
353 continue
354 libname, soname, dependency = line.split(' ', 2)
355 if libname.endswith(':'):
356 # This is a 'type' marker, currently only udeb is used, and
357 # we don't want those
358 continue
359 mapping[(libname, soname)] = dependency
360 return mapping
361
362
363class URI(StormURI):18class URI(StormURI):
364 """A stand-in for Storm's URI class.19 """A stand-in for Storm's URI class.
36520
@@ -369,6 +24,8 @@
369 have a parsed version and just need to create the object.24 have a parsed version and just need to create the object.
370 """25 """
37126
27 # XXX: Only used by PackageDatabase, which is flagged for deletion.
28
372 def __init__(self, scheme=None, host=None, port=None, username=None,29 def __init__(self, scheme=None, host=None, port=None, username=None,
373 password=None, database=None, options=None):30 password=None, database=None, options=None):
374 self.scheme = scheme31 self.scheme = scheme
@@ -384,6 +41,8 @@
38441
385class PackageDatabase(object):42class PackageDatabase(object):
38643
44 # XXX: No longer used within pkgme-devportal
45
387 SQLITE = 'sqlite'46 SQLITE = 'sqlite'
388 POSTGRES = 'postgres'47 POSTGRES = 'postgres'
38948
@@ -468,40 +127,6 @@
468 found[lib] = set([dependency])127 found[lib] = set([dependency])
469 return found128 return found
470129
471 def insert_new_library(self, package_name, library_name,
472 dependency, arch):
473 """Insert a library and its needed dependency into the database.
474
475 :param library_name: A full soname, e.g. libfoo.so.1.
476 :param dependency: A binary package dependency, possibly including
477 version.
478 """
479 self._store.execute(
480 "INSERT INTO libdep VALUES (?, ?, ?, ?)",
481 (unicode(package_name),
482 unicode(library_name),
483 unicode(dependency),
484 unicode(arch)))
485
486 def update_package(self, package_name, arch_libdep_mapping):
487 """Update the database with the libdep info from 'package_name'.
488
489 :param package_name: The name of the package where the
490 symbols came from.
491 :param arch_libdep_mapping: a dict mapping architecture tags to dicts
492 mapping library names to dependencies, e.g.
493 {'amd64': {'libfoo.so.1': 'libfoo1', ...}, ...}
494 """
495 for arch, libdep_mapping in arch_libdep_mapping.items():
496 self._store.execute(
497 "DELETE FROM libdep WHERE source_package_name = ? "
498 "AND architecture = ?",
499 (unicode(package_name), unicode(arch)))
500 for library, dependency in libdep_mapping.items():
501 self.insert_new_library(
502 package_name, library, dependency, arch)
503 self._store.commit()
504
505 def close(self):130 def close(self):
506 self._store.close()131 self._store.close()
507132
@@ -534,57 +159,10 @@
534159
535def get_dependency_database():160def get_dependency_database():
536 """Return an object that can get dependencies."""161 """Return an object that can get dependencies."""
537 # TODO: Change this to return LibdepServiceClient sometimes162 # XXX: Remove AptFilePackageDatabase from here and simplify the method.
538 databases = {163 databases = {
539 AptFilePackageDatabase.DB_TYPE: AptFilePackageDatabase.from_options,164 AptFilePackageDatabase.DB_TYPE: AptFilePackageDatabase.from_options,
540 PackageDatabase.POSTGRES: PackageDatabase.from_options,
541 PackageDatabase.SQLITE: PackageDatabase.from_options,
542 LibdepServiceClient.DB_TYPE: LibdepServiceClient.from_options,165 LibdepServiceClient.DB_TYPE: LibdepServiceClient.from_options,
543 }166 }
544 options = load_configuration()167 options = load_configuration()
545 return databases[options.database_db_type](options)168 return databases[options.database_db_type](options)
546
547
548def dict_add(*dicts):
549 """Add dicts, with later dicts taking precedence."""
550 result = dict()
551 for d in dicts:
552 result.update(d)
553 return result
554
555
556def publishings_to_package_info(publishings):
557 """Takes a list of publishings and returns the info for the library packages.
558
559 :param publishings: an iterable of launchpadlib
560 binary_package_publishing_history objects.
561 :return: an iterable of (.deb url, arch tag) for the publishings
562 that represent a library package.
563 """
564 packages_info = map(get_package_info_from_publication, publishings)
565 return filter(
566 lambda (url, arch): is_library_package(url),
567 packages_info)
568
569
570def fetch_symbol_files(scan_mode, package_name, db):
571 """Insert the libdep info for ``package_name`` into ``db``."""
572 if scan_mode != 'binary':
573 raise AssertionError("Unsupported scan mode: {0}".format(scan_mode))
574 with LaunchpadFixture(APPLICATION_NAME, SERVICE_ROOT) as lp:
575 publishings = iter_published_binaries(lp.anonymous, name=package_name)
576 lib_packages_info = publishings_to_package_info(publishings)
577 def arch_libdep_for_package((url, arch)):
578 return {arch: get_libdep_mapping_for_package(url)}
579 arch_libdep_mapping = dict_add(
580 *map(arch_libdep_for_package, lib_packages_info))
581 db.update_package(package_name, arch_libdep_mapping)
582
583
584def main():
585 """Import the symbol files for 'package_name'."""
586 glue = load_configuration_with_command_line()
587 with closing(PackageDatabase.get_store_from_config(glue.options)) as store:
588 package_name = glue.args[0]
589 db = PackageDatabase(store)
590 fetch_symbol_files(glue.options.scan_mode, package_name, db)
591169
=== modified file 'devportalbinary/testing.py'
--- devportalbinary/testing.py 2012-09-12 08:45:38 +0000
+++ devportalbinary/testing.py 2012-11-13 12:03:23 +0000
@@ -33,10 +33,17 @@
33 )33 )
3434
35from devportalbinary.binary import MetadataBackend35from devportalbinary.binary import MetadataBackend
36from devportalbinary.database import PackageDatabase, URI36from devportalbinary.database import (
37 LibdepServiceClient,
38 PackageDatabase,
39 URI,
40 )
3741
38from devportalbinary.configuration import CONF_FILE_ENV_VAR42from devportalbinary.configuration import CONF_FILE_ENV_VAR
3943
44from djlibdep.test_double import LibdepServiceDouble
45from libdep_service_client.client import Client
46
4047
41class IsChildPath(Matcher):48class IsChildPath(Matcher):
4249
@@ -235,6 +242,23 @@
235 self.useFixture(ConfigFileFixture(config_file_path))242 self.useFixture(ConfigFileFixture(config_file_path))
236243
237244
245class LibdepFixture(Fixture):
246
247 def __init__(self, data):
248 super(LibdepFixture, self).__init__()
249 self._data = data
250
251 def setUp(self):
252 super(LibdepFixture, self).setUp()
253 double = self.useFixture(LibdepServiceDouble(self._data))
254 self.useFixture(
255 ConfigSettings(
256 ('database', {'db_type': 'libdep-service',
257 'base_url': double.base_url,
258 })))
259 self.client = LibdepServiceClient(Client(double.base_url))
260
261
238def LibsConfigSettings(test_libs):262def LibsConfigSettings(test_libs):
239 """Create a lib_overrides config file."""263 """Create a lib_overrides config file."""
240 return ConfigSettings(264 return ConfigSettings(
@@ -355,3 +379,8 @@
355 for section, values in settings:379 for section, values in settings:
356 f.write(make_config_section(section, values))380 f.write(make_config_section(section, values))
357 f.write('\n')381 f.write('\n')
382
383
384def get_libdep_service_client(fixture, test_data):
385 double = fixture.useFixture(LibdepServiceDouble(test_data))
386 return LibdepServiceClient(Client(double.base_url))
358387
=== modified file 'devportalbinary/tests/test_binary.py'
--- devportalbinary/tests/test_binary.py 2012-10-26 11:47:52 +0000
+++ devportalbinary/tests/test_binary.py 2012-11-13 12:03:23 +0000
@@ -1,12 +1,10 @@
1# Copyright 2011-2012 Canonical Ltd. This software is licensed under the1# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from contextlib import closing
5import os4import os
6import shutil5import shutil
76
8from fixtures import TempDir7from fixtures import TempDir
9from testresources import ResourcedTestCase
10from testtools import TestCase8from testtools import TestCase
11from testtools.matchers import (9from testtools.matchers import (
12 SameMembers,10 SameMembers,
@@ -39,19 +37,17 @@
39 UnknownDependency,37 UnknownDependency,
40 )38 )
41from devportalbinary.configuration import load_configuration39from devportalbinary.configuration import load_configuration
42from devportalbinary.database import PackageDatabase
43from devportalbinary.metadata import (40from devportalbinary.metadata import (
44 MetadataBackend,41 MetadataBackend,
45 )42 )
46from devportalbinary.testing import (43from devportalbinary.testing import (
47 BackendTests,44 BackendTests,
48 BinaryFileFixture,45 BinaryFileFixture,
49 DatabaseConfig,
50 get_test_data_dir_path,46 get_test_data_dir_path,
51 get_test_data_file_path,47 get_test_data_file_path,
48 LibdepFixture,
52 LibsConfigSettings,49 LibsConfigSettings,
53 MetadataFixture,50 MetadataFixture,
54 postgres_db_resource,
55 )51 )
5652
5753
@@ -258,21 +254,16 @@
258 { "libfoo.so.1" : ["."], "libbar.so.2" : ["."]}, found)254 { "libfoo.so.1" : ["."], "libbar.so.2" : ["."]}, found)
259255
260256
261class GuessDependenciesTests(TestCase, ResourcedTestCase):257class GuessDependenciesTests(TestCase):
262
263 resources = [
264 ('db_fixture', postgres_db_resource),
265 ]
266258
267 def test_guess_dependencies(self):259 def test_guess_dependencies(self):
268 self.useFixture(DatabaseConfig(self.db_fixture))260 self.useFixture(
269 with closing(PackageDatabase(self.db_fixture.conn)) as db:261 LibdepFixture([('libc6', {'i386': {'libc.so.6': 'libc6'}})]))
270 db.update_package('eglibc', {'i386': {'libc.so.6': 'libc6'}})
271 deps, arch = guess_dependencies(get_test_data_dir_path('hello'))262 deps, arch = guess_dependencies(get_test_data_dir_path('hello'))
272 self.assertEqual(set(['libc6']), deps)263 self.assertEqual(set(['libc6']), deps)
273264
274 def test_guess_dependencies_error_on_unknown_dependency(self):265 def test_guess_dependencies_error_on_unknown_dependency(self):
275 self.useFixture(DatabaseConfig(self.db_fixture))266 self.useFixture(LibdepFixture([]))
276 e = self.assertRaises(UnknownDependency,267 e = self.assertRaises(UnknownDependency,
277 guess_dependencies, get_test_data_dir_path('hello'))268 guess_dependencies, get_test_data_dir_path('hello'))
278 self.assertEqual('Can\'t find dependency for "libc.so.6".', str(e))269 self.assertEqual('Can\'t find dependency for "libc.so.6".', str(e))
@@ -286,17 +277,15 @@
286 self.assertEqual(set(["."]), paths)277 self.assertEqual(set(["."]), paths)
287278
288279
289class BinaryBackendTests(BackendTests, ResourcedTestCase):280class BinaryBackendTests(BackendTests):
290281
291 BACKEND = BinaryBackend282 BACKEND = BinaryBackend
292283
293 resources = [
294 ('db_fixture', postgres_db_resource),
295 ]
296
297 def setUp(self):284 def setUp(self):
298 super(BinaryBackendTests, self).setUp()285 super(BinaryBackendTests, self).setUp()
299 self.useFixture(DatabaseConfig(self.db_fixture))286 self.useFixture(
287 LibdepFixture([('libc6', {'i386': {'libc.so.6': 'libc6'}}),
288 ('libfoo', {'i386': {'libasound.so.2': 'libfoo'}})]))
300289
301 def test_want_with_metadata_and_binaries(self):290 def test_want_with_metadata_and_binaries(self):
302 # If we detect a binary, then we score 10. The way we determine if291 # If we detect a binary, then we score 10. The way we determine if
@@ -338,8 +327,6 @@
338 backend = self.make_backend()327 backend = self.make_backend()
339 shutil.copy(328 shutil.copy(
340 get_test_data_file_path('hello', 'hello'), backend.path)329 get_test_data_file_path('hello', 'hello'), backend.path)
341 with closing(PackageDatabase(self.db_fixture.conn)) as db:
342 db.update_package('eglibc', {'i386': {'libc.so.6': 'libc6'}})
343 deps, arch = guess_dependencies(backend.path)330 deps, arch = guess_dependencies(backend.path)
344 expected_deps = ', '.join(deps)331 expected_deps = ', '.join(deps)
345 build_deps = backend.get_build_depends()332 build_deps = backend.get_build_depends()
@@ -375,8 +362,6 @@
375362
376 def test_get_lib_overrides_for_packages_for_libraries(self):363 def test_get_lib_overrides_for_packages_for_libraries(self):
377 # The configuration file overrides the found library dependencies.364 # The configuration file overrides the found library dependencies.
378 with closing(PackageDatabase(self.db_fixture.conn)) as db:
379 db.update_package('foo', {'i386': {'libasound.so.2': 'libfoo'}})
380 self.assertEqual(365 self.assertEqual(
381 get_packages_for_libraries(set(["libasound.so.2"]), "i386"),366 get_packages_for_libraries(set(["libasound.so.2"]), "i386"),
382 set(["libasound2"]))367 set(["libasound2"]))
@@ -389,8 +374,6 @@
389 path = self.useFixture(MetadataFixture({})).path374 path = self.useFixture(MetadataFixture({})).path
390 self.useFixture(BinaryFileFixture(path))375 self.useFixture(BinaryFileFixture(path))
391 backend = self.make_backend(path)376 backend = self.make_backend(path)
392 with closing(PackageDatabase(self.db_fixture.conn)) as db:
393 db.update_package('eglibc', {'i386': {'libc.so.6': 'libc6'}})
394 self.assertEqual(backend.get_architecture(), "i386")377 self.assertEqual(backend.get_architecture(), "i386")
395378
396 def test_get_extra_targets_makes_executable_executable(self):379 def test_get_extra_targets_makes_executable_executable(self):
397380
=== modified file 'devportalbinary/tests/test_database.py'
--- devportalbinary/tests/test_database.py 2012-10-26 14:38:49 +0000
+++ devportalbinary/tests/test_database.py 2012-11-13 12:03:23 +0000
@@ -1,47 +1,27 @@
1from collections import namedtuple
2import os1import os
32
4from fixtures import TempDir3from fixtures import TempDir
5from storm.databases.postgres import psycopg2
6from storm.exceptions import ClosedError
7from testresources import ResourcedTestCase4from testresources import ResourcedTestCase
8from testtools import TestCase5from testtools import TestCase
9from testtools.matchers import (6from testtools.matchers import (
10 Equals,7 Equals,
11 Matcher,8 Matcher,
12 )9 )
13from treeshape import (
14 CONTENT,
15 FileTree,
16 )
1710
18from devportalbinary.database import (11from devportalbinary.database import (
19 AptFilePackageDatabase,12 AptFilePackageDatabase,
20 deb_file_url_for_publication,
21 dict_add,
22 find_file_under_dir,
23 get_dependency_database,13 get_dependency_database,
24 get_file_contents,
25 get_package_info_from_publication,
26 is_library_package,
27 LibdepServiceClient,14 LibdepServiceClient,
28 libdep_mapping_from_symbols,
29 load_configuration,15 load_configuration,
30 NoSharedObject,
31 possible_sonames_for_shared_object,
32 PackageDatabase,16 PackageDatabase,
33 publishings_to_package_info,
34 shared_object_info_to_soname,
35 shared_objects_from_shlibs,
36 TooManySharedObjects,
37 )17 )
38from devportalbinary.testing import (18from devportalbinary.testing import (
39 ConfigFileFixture,19 ConfigFileFixture,
40 ConfigSettings,20 ConfigSettings,
21 get_libdep_service_client,
41 postgres_db_resource,22 postgres_db_resource,
42 )23 )
4324
44from djlibdep.test_double import LibdepServiceDouble
45from libdep_service_client.client import Client25from libdep_service_client.client import Client
4626
4727
@@ -68,76 +48,15 @@
68 self.addCleanup(db.close)48 self.addCleanup(db.close)
69 return db49 return db
7050
71 def test_insert_new_library(self):
72 db = self.get_package_db()
73 db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
74 self.assertThat(
75 "SELECT source_package_name, library, dependency, architecture FROM libdep",
76 ResultsIn(db, [('foo-src', 'libfoo.so.0', 'foo', 'i386')]))
77
78 def test_double_insert(self):
79 db = self.get_package_db()
80 db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
81 self.assertRaises(
82 psycopg2.IntegrityError,
83 db.insert_new_library, 'foo-src', 'libfoo.so.0', 'foo', 'i386')
84
85 def test_differing_dependencies(self):
86 db = self.get_package_db()
87 db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
88 db.insert_new_library('foo-src', 'libfoo.so.0', 'bar', 'i386')
89 deps = db.get_multiple_dependencies(['libfoo.so.0'], 'i386')
90 self.assertEqual(deps, {'libfoo.so.0': set(['foo', 'bar'])})
91
92 def test_get_dependencies(self):
93 db = self.get_package_db()
94 db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
95 deps = db.get_multiple_dependencies(['libfoo.so.0'], 'i386')
96 self.assertEqual(deps, {'libfoo.so.0': set(['foo'])})
97
98 def test_respects_architecture(self):
99 db = self.get_package_db()
100 db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
101 db.insert_new_library('foo-src', 'libfoo.so.0', 'foo-amd64', 'amd64')
102 deps = db.get_multiple_dependencies(['libfoo.so.0'], arch='amd64')
103 self.assertEqual(deps, {'libfoo.so.0': set(['foo-amd64'])})
104
105 def test_unknown_library(self):51 def test_unknown_library(self):
106 db = self.get_package_db()52 db = self.get_package_db()
107 deps = db.get_multiple_dependencies(['libfoo.so.0'], 'i386')53 deps = db.get_multiple_dependencies(['libfoo.so.0'], 'i386')
108 self.assertEqual(deps, {})54 self.assertEqual(deps, {})
10955
110 def test_update_package(self):
111 db = self.get_package_db()
112 db.update_package(
113 'foo', {'i386': {'libfoo.so.1': 'foo-bin'}})
114 deps = db.get_multiple_dependencies(['libfoo.so.1'], 'i386')
115 self.assertEqual(deps, {'libfoo.so.1': set(['foo-bin'])})
116
117 def test_update_existing_package_no_libraries(self):
118 db = self.get_package_db()
119 db.update_package('foo', {'i386': {'libfoo.so.1': 'foo-bin'}})
120 # Run again, this time with no symbols, representing that a newer
121 # version of the package no longer exports any libraries.
122 db.update_package('foo', {'i386': {}})
123 deps = db.get_multiple_dependencies(['libfoo.so.1'], 'i386')
124 self.assertEqual(deps, {})
125
126 def test_update_package_two_architectures(self):
127 # If two architectures are updated separately then they
128 # shouldn't interfere
129 db = self.get_package_db()
130 db.update_package('foo', {'i386': {'libfoo.so.1': 'foo-bin'}})
131 db.update_package('foo', {'amd64': {'libfoo.so.1': 'foo-bin-amd64'}})
132 deps = db.get_multiple_dependencies(['libfoo.so.1'], arch='i386')
133 self.assertEqual(deps, {'libfoo.so.1': set(['foo-bin'])})
134
135 def test_close(self):56 def test_close(self):
136 # Test that we can close the package db.57 # Test that we can close the package db.
137 db = PackageDatabase(self.db_fixture.conn)58 db = PackageDatabase(self.db_fixture.conn)
138 db.close()59 db.close()
139 self.assertRaises(ClosedError, db.insert_new_library, 'foo',
140 'libfoo.so.1', 'foo-bin', 'i386')
14160
142 def test_close_twice(self):61 def test_close_twice(self):
143 # Test that we can close the package db twice with no exception.62 # Test that we can close the package db twice with no exception.
@@ -200,264 +119,12 @@
200 self.assertEqual(expected_username, uri.username)119 self.assertEqual(expected_username, uri.username)
201120
202121
203class FakeBPPH(object):
204
205 def __init__(self):
206 self.archive = namedtuple(
207 'Archive', 'web_link')('http://lp.net/archive')
208 self.distro_arch_series = namedtuple(
209 'DistroArchSeries', 'architecture_tag')('i386')
210 self.binary_package_name = 'foo'
211 self.binary_package_version = '1'
212 self.architecture_specific = True
213
214
215class TestDebFileUrlForPublication(TestCase):
216
217 def test_get_url(self):
218 bpph = FakeBPPH()
219 expected_url = '%s/+files/%s_%s_%s.deb' % (
220 bpph.archive.web_link,
221 bpph.binary_package_name,
222 bpph.binary_package_version,
223 bpph.distro_arch_series.architecture_tag,
224 )
225 self.assertEqual(expected_url, deb_file_url_for_publication(bpph))
226
227 def test_get_url_with_epoch(self):
228 # epochs are stripped from the version number
229 bpph = FakeBPPH()
230 bpph.binary_package_version = '1:1'
231 expected_url = '%s/+files/%s_%s_%s.deb' % (
232 bpph.archive.web_link,
233 bpph.binary_package_name,
234 '1',
235 bpph.distro_arch_series.architecture_tag,
236 )
237 self.assertEqual(expected_url, deb_file_url_for_publication(bpph))
238
239 def test_get_url_for_arch_indep(self):
240 # epochs are stripped from the version number
241 bpph = FakeBPPH()
242 bpph.architecture_specific = False
243 expected_url = '%s/+files/%s_%s_all.deb' % (
244 bpph.archive.web_link,
245 bpph.binary_package_name,
246 '1',
247 )
248 self.assertEqual(expected_url, deb_file_url_for_publication(bpph))
249
250
251class TestShlibs(TestCase):
252
253 def test_empty(self):
254 self.assertEqual({}, shared_objects_from_shlibs(""))
255
256 def test_comments(self):
257 self.assertEqual({}, shared_objects_from_shlibs("# aaaaa\n"))
258
259 def test_blank_line(self):
260 self.assertEqual({}, shared_objects_from_shlibs("\n"))
261
262 def test_whitespace_line(self):
263 self.assertEqual({}, shared_objects_from_shlibs(" \n"))
264
265 def test_udeb_skipped(self):
266 self.assertEqual({},
267 shared_objects_from_shlibs("udeb: libfoo 1 libfoo\n"))
268
269 def test_simple_soname(self):
270 self.assertEqual({('libfoo', '1'): 'libfoo'},
271 shared_objects_from_shlibs("libfoo 1 libfoo\n"))
272
273 def test_other_type_of_soname(self):
274 self.assertEqual({('libfoo', '4.8'): 'libfoo'},
275 shared_objects_from_shlibs("libfoo 4.8 libfoo\n"))
276
277
278class TestPossibleSonamesForSharedObject(TestCase):
279
280 def test_no_dot(self):
281 self.assertEqual(
282 set(['libfoo.so.1', 'libfoo-1.so']),
283 possible_sonames_for_shared_object('libfoo', '1'))
284
285 def test_dot(self):
286 self.assertEqual(
287 set(['libfoo-1.0.so']),
288 possible_sonames_for_shared_object('libfoo', '1.0'))
289
290
291class TestFindFileUnderDir(TestCase):
292
293 def test_file_missing(self):
294 tree = {}
295 path = self.useFixture(FileTree(tree)).path
296 self.assertEqual(None, find_file_under_dir('nothere', path))
297
298 def test_file_in_basedir(self):
299 filename = 'here'
300 tree = {filename: {}}
301 built_tree = self.useFixture(FileTree(tree))
302 self.assertEqual(built_tree.join(filename),
303 find_file_under_dir(filename, built_tree.path))
304
305 def test_file_in_subdir(self):
306 filename = 'here'
307 relpath = 'there/' + filename
308 tree = {relpath: {}}
309 built_tree = self.useFixture(FileTree(tree))
310 self.assertEqual(built_tree.join(relpath),
311 find_file_under_dir(filename, built_tree.path))
312
313 def test_handles_multiple_matches(self):
314 filename = 'here'
315 relpath = 'there/' + filename
316 tree = {filename: {}, relpath: {}}
317 built_tree = self.useFixture(FileTree(tree))
318 self.assertEqual(built_tree.join(filename),
319 find_file_under_dir(filename, built_tree.path))
320
321
322class TestSharedObjectInfoToSoname(TestCase):
323
324 def test_no_files(self):
325 tree = {}
326 path = self.useFixture(FileTree(tree)).path
327 self.assertRaises(NoSharedObject,
328 shared_object_info_to_soname, 'libfoo', '1', path)
329
330 def test_too_many_files(self):
331 libname = 'libfoo'
332 version = '1'
333 possible_sonames = possible_sonames_for_shared_object(libname, version)
334 tree = dict((name, {}) for name in possible_sonames)
335 path = self.useFixture(FileTree(tree)).path
336 self.assertRaises(TooManySharedObjects,
337 shared_object_info_to_soname, libname, version, path)
338
339 def test_file_found(self):
340 libname = 'libfoo'
341 version = '1'
342 expected_soname = '{0}.so.{1}'.format(libname, version)
343 tree = {expected_soname: {}}
344 path = self.useFixture(FileTree(tree)).path
345 self.assertEqual(expected_soname,
346 shared_object_info_to_soname(libname, version, path))
347
348
349class TestDictAdd(TestCase):
350
351 def test_no_dicts(self):
352 self.assertEqual({}, dict_add())
353
354 def test_one_dict(self):
355 self.assertEqual(dict(a=1), dict_add(dict(a=1)))
356
357 def test_two_dicts(self):
358 self.assertEqual(dict(a=1, b=2), dict_add(dict(a=1), dict(b=2)))
359
360 def test_precedence(self):
361 self.assertEqual(dict(a=2), dict_add(dict(a=1), dict(a=2)))
362
363
364class TestIsLibraryPackage(TestCase):
365
366 def test_lib_filename(self):
367 self.assertEqual(True,
368 is_library_package('http://launchpad.net/libfoo.deb'))
369
370 def test_whitelisted(self):
371 self.assertEqual(True,
372 is_library_package('http://launchpad.net/zlib1g.deb'))
373
374 def test_other(self):
375 self.assertEqual(False,
376 is_library_package('http://launchpad.net/bzr.deb'))
377
378
379class TestGetPackageInfoFromPublication(TestCase):
380
381 def test_returns_url_and_arch(self):
382 bpph = FakeBPPH()
383 self.assertEqual(
384 (deb_file_url_for_publication(bpph),
385 bpph.distro_arch_series.architecture_tag),
386 get_package_info_from_publication(bpph))
387
388
389class TestPublishingsToPackageInfo(TestCase):
390
391 def test_integration(self):
392 # This function is split out as it is the testable part of
393 # fetch symbols files. This tests that the functions glue
394 # together
395 publishings = [FakeBPPH(), FakeBPPH()]
396 expected_bpph = publishings[1]
397 expected_bpph.binary_package_name = 'libfoo'
398 expected_info = (deb_file_url_for_publication(expected_bpph),
399 expected_bpph.distro_arch_series.architecture_tag)
400 self.assertEqual([expected_info],
401 publishings_to_package_info(publishings))
402
403
404class TestGetFileContents(TestCase):
405
406 def test_exists(self):
407 expected_content = 'boring content\n'
408 filename = 'foo.txt'
409 tree = {filename: {CONTENT: expected_content}}
410 path = self.useFixture(FileTree(tree)).join(filename)
411 self.assertEqual(expected_content, get_file_contents(path))
412
413 def test_not_exists(self):
414 tree = {}
415 path = self.useFixture(FileTree(tree)).join('nothere')
416 self.assertEqual(None, get_file_contents(path))
417
418 def test_directory(self):
419 tree = {}
420 path = self.useFixture(FileTree(tree)).path
421 self.assertRaises(IOError, get_file_contents, path)
422
423
424class TestLibdepMappingFromSymbols(TestCase):
425
426 def test_empty(self):
427 self.assertEqual({}, libdep_mapping_from_symbols(''))
428
429 def test_blank_line_ignored(self):
430 self.assertEqual({}, libdep_mapping_from_symbols('\n'))
431
432 def test_alternate_template_ignored(self):
433 self.assertEqual({}, libdep_mapping_from_symbols('| foo\n'))
434
435 def test_meta_information_ignored(self):
436 self.assertEqual({}, libdep_mapping_from_symbols('* foo\n'))
437
438 def test_symbols_ignored(self):
439 self.assertEqual({}, libdep_mapping_from_symbols(' foo\n'))
440
441 def test_comments_ignored(self):
442 self.assertEqual({}, libdep_mapping_from_symbols('# foo\n'))
443
444 def test_include_ignored(self):
445 self.assertEqual({},
446 libdep_mapping_from_symbols('(arch=!armel)#include foo\n'))
447
448 def test_includes_mapping(self):
449 self.assertEqual({'libfoo.so.1': 'libfoo'},
450 libdep_mapping_from_symbols('libfoo.so.1 libfoo #MINVER#\n'))
451
452
453class TestLibdepServiceClient(TestCase):122class TestLibdepServiceClient(TestCase):
454123
455 TEST_DATA = [('libfoo', {'i386': {'libfoo': 'libfoo-bin'}})]124 TEST_DATA = [('libfoo', {'i386': {'libfoo': 'libfoo-bin'}})]
456125
457 def test_wraps_libdep_service(self):126 def test_wraps_libdep_service(self):
458 double = self.useFixture(LibdepServiceDouble(self.TEST_DATA))127 wrapper = get_libdep_service_client(self, self.TEST_DATA)
459 client = Client(double.base_url)
460 wrapper = LibdepServiceClient(client)
461 self.assertEqual(128 self.assertEqual(
462 {'libfoo': set(['libfoo-bin'])},129 {'libfoo': set(['libfoo-bin'])},
463 wrapper.get_multiple_dependencies(['libfoo'], 'i386'))130 wrapper.get_multiple_dependencies(['libfoo'], 'i386'))
464131
=== modified file 'devportalbinary/utils.py'
--- devportalbinary/utils.py 2012-09-12 15:07:16 +0000
+++ devportalbinary/utils.py 2012-11-13 12:03:23 +0000
@@ -68,6 +68,7 @@
68def download_file(url, directory=None, name=None, working_dir=None,68def download_file(url, directory=None, name=None, working_dir=None,
69 bufsize=4 * 2 ** 10, headers=None):69 bufsize=4 * 2 ** 10, headers=None):
70 """Download 'url' into 'directory'."""70 """Download 'url' into 'directory'."""
71 # XXX: download_file is no longer used within pkgme-devportal
71 request = Request(url)72 request = Request(url)
72 if headers is not None:73 if headers is not None:
73 for h in headers:74 for h in headers:
7475
=== modified file 'setup.py'
--- setup.py 2012-10-18 13:36:37 +0000
+++ setup.py 2012-11-13 12:03:23 +0000
@@ -46,7 +46,6 @@
46 entry_points = {46 entry_points = {
47 'console_scripts': [47 'console_scripts': [
48 'dump-apt-file-db=devportalbinary.aptfile:dump_apt_file_db',48 'dump-apt-file-db=devportalbinary.aptfile:dump_apt_file_db',
49 'fetch-symbol-files=devportalbinary.database:main',
50 'guess-executable=devportalbinary.binary:print_executable',49 'guess-executable=devportalbinary.binary:print_executable',
51 'guess-deps=devportalbinary.binary:print_dependencies',50 'guess-deps=devportalbinary.binary:print_dependencies',
52 ],51 ],

Subscribers

People subscribed via source and target branches