Merge lp:~pitti/aptdaemon/pk-plugins into lp:aptdaemon

Proposed by Martin Pitt
Status: Merged
Approved by: Sebastian Heinlein
Approved revision: 769
Merged at revision: 769
Proposed branch: lp:~pitti/aptdaemon/pk-plugins
Merge into: lp:aptdaemon
Diff against target: 326 lines (+219/-7)
3 files modified
aptdaemon/pkcompat.py (+72/-4)
tests/repo/Packages (+24/-0)
tests/test_pk.py (+123/-3)
To merge this branch: bzr merge lp:~pitti/aptdaemon/pk-plugins
Reviewer Review Type Date Requested Status
Aptdaemon Developers Pending
Review via email: mp+91200@code.launchpad.net

Description of the change

This supports PackageKit's new apt plugins to extend WhatProvides. With this, we can write third-party plugins which work with both PK and aptdaemon. Rodrigo Moya is currently working on gnome-control-center's region panel which will make use of this, so it would be good to have proper support for this in aptdaemon.

This is a relatively straightforward port of https://gitorious.org/packagekit/packagekit/commit/b516f18e2

The previous two commits just fix an unrelated bug in the test suite which left a lot of dbus-daemon processes around, and fix the gstreamer test package.

To post a comment you must log in.
lp:~pitti/aptdaemon/pk-plugins updated
769. By Martin Pitt

Add plugin support to PackageKit compat layer

Support plugins for PackageKit's apt backend, to easily extend the
functionality of "what-provides".

This is a pretty straightforward port of the corresponding commit in
PackageKit:
https://gitorious.org/packagekit/packagekit/commit/b516f18e2

Revision history for this message
Martin Pitt (pitti) wrote :

Please note, if you approve this, I'm happy to push my branch into trunk instead of merging, to preserve the individual commits and logs.

Revision history for this message
Sebastian Heinlein (glatzor) wrote :

Thanks for your work

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'aptdaemon/pkcompat.py'
2--- aptdaemon/pkcompat.py 2012-01-23 10:15:32 +0000
3+++ aptdaemon/pkcompat.py 2012-02-02 05:55:21 +0000
4@@ -39,6 +39,12 @@
5 import lsb_release
6 import packagekit.enums as pk_enums
7
8+# for optional plugin support
9+try:
10+ import pkg_resources
11+except ImportError:
12+ pkg_resources = None
13+
14 from aptdaemon import policykit1
15 import aptdaemon.core
16 from aptdaemon.core import APTDAEMON_TRANSACTION_DBUS_INTERFACE
17@@ -256,6 +262,7 @@
18 pk_enums.ROLE_SEARCH_DETAILS,
19 pk_enums.ROLE_SEARCH_GROUP,
20 pk_enums.ROLE_SEARCH_FILE,
21+ pk_enums.ROLE_WHAT_PROVIDES,
22 pk_enums.ROLE_DOWNLOAD_PACKAGES]
23
24 SUPPORTED_FILTERS = [pk_enums.FILTER_INSTALLED,
25@@ -1744,9 +1751,9 @@
26 self.role = pk_enums.ROLE_GET_CATEGORIES
27 GObject.idle_add(self._fail_not_implemented)
28
29- @dbus.service.method(PACKAGEKIT_TRANS_DBUS_INTERFACE,
30- in_signature="ssas", out_signature="",
31- sender_keyword="sender")
32+ @dbus_deferred_method(PACKAGEKIT_TRANS_DBUS_INTERFACE,
33+ in_signature="ssas", out_signature="",
34+ sender_keyword="sender")
35 def WhatProvides(self, filter, type, values, sender):
36 """This method returns packages that provide the supplied attributes.
37 This method is useful for finding out what package(s) provide a
38@@ -1765,7 +1772,10 @@
39 is backend specific.
40 """
41 self.role = pk_enums.ROLE_WHAT_PROVIDES
42- GObject.idle_add(self._fail_not_implemented)
43+ kwargs = {"filters": filter.split(";"),
44+ "type": type,
45+ "values": values}
46+ return self._run_query(kwargs, sender)
47
48 @dbus.service.method(PACKAGEKIT_TRANS_DBUS_INTERFACE,
49 in_signature="", out_signature="",
50@@ -1883,6 +1893,8 @@
51
52 class PackageKitWorker(aptdaemon.worker.AptWorker):
53
54+ _plugins = None
55+
56 """Process PackageKit Query transactions."""
57
58 def query(self, trans):
59@@ -2484,6 +2496,32 @@
60 files = ";".join(self._get_installed_files(pkg))
61 trans.emit_files(id, files)
62
63+ def what_provides(self, trans, filters, type, values):
64+ """Emit all dependencies of the given package ids.
65+
66+ Doesn't support recursive dependency resolution.
67+ """
68+ self._init_plugins()
69+
70+ supported_type = False
71+
72+ # run plugins
73+ for plugin in self._plugins.get("what_provides", []):
74+ pklog.debug("calling what_provides plugin %s %s" % (str(plugin), str(filters)))
75+ for search_item in values:
76+ try:
77+ for package in plugin(self._cache, type, search_item):
78+ self._emit_visible_package(trans, filters, package)
79+ supported_type = True
80+ except NotImplementedError:
81+ pass # keep supported_type as False
82+
83+ if not supported_type and type != pk_enums.PROVIDES_ANY:
84+ # none of the plugins felt responsible for this type
85+ raise TransactionFailed(aptd_enums.ERROR_NOT_SUPPORTED,
86+ "Query type '%s' is not supported" % type)
87+
88+
89 # Helpers
90
91 def _get_id_from_version(self, version):
92@@ -2716,6 +2754,36 @@
93 pkg.name))
94 return pk_enums.GROUP_UNKNOWN
95
96+ def _init_plugins(self):
97+ """Initialize PackageKit apt backend plugins.
98+
99+ Do nothing if plugins are already initialized.
100+ """
101+ if self._plugins is not None:
102+ return
103+
104+ if not pkg_resources:
105+ return
106+
107+ self._plugins = {} # plugin_name -> [plugin_fn1, ...]
108+
109+ # just look in standard Python paths for now
110+ dists, errors = pkg_resources.working_set.find_plugins(pkg_resources.Environment())
111+ for dist in dists:
112+ pkg_resources.working_set.add(dist)
113+ for plugin_name in ["what_provides"]:
114+ for entry_point in pkg_resources.iter_entry_points(
115+ "packagekit.apt.plugins", plugin_name):
116+ try:
117+ plugin = entry_point.load()
118+ except Exception as e:
119+ pklog.warning("Failed to load %s from plugin %s: %s" % (
120+ plugin_name, str(entry_point.dist), str(e)))
121+ continue
122+ pklog.debug("Loaded %s from plugin %s" % (
123+ plugin_name, str(entry_point.dist)))
124+ self._plugins.setdefault(plugin_name, []).append(plugin)
125+
126
127 if META_RELEASE_SUPPORT:
128
129
130=== modified file 'tests/repo/Packages'
131--- tests/repo/Packages 2011-11-18 13:12:49 +0000
132+++ tests/repo/Packages 2012-02-02 05:55:21 +0000
133@@ -192,3 +192,27 @@
134 .
135 This package cannot be installed because of a missing dependency.
136
137+Package: gstreamer0.10-silly
138+Priority: extra
139+Section: admin
140+Installed-Size: 168
141+Maintainer: Sebastian Heinlein <devel@glatzor.de>
142+Architecture: all
143+Source: silly-packages
144+Version: 0.1-0
145+Depends: libc6 (>= 2.7-1), libglib2.0-0 (>= 2.16.0), libgstreamer-plugins-base0.10-0 (>= 0.10.0), libgstreamer0.10-0 (>= 0.10.14), libogg0 (>= 1.0rc3)
146+Filename: gstreamer0.10-silly_0.1-0_all.deb
147+Size: 52016
148+MD5sum: bbaf259e0dfcb39050061181d2a13755
149+SHA1: be59be2d82b7f57097f387a22f5f295d202d9c1d
150+SHA256: 6b91b67de10ae547cd4c648ef343c8acd35e626e4a1ac20df1aaae51d6ec6328
151+SHA512: 5f8457338d9bb5de5e65544828bc4be8a32bb3afbe3dd7c035e7a11216831c5aa2faec2c138c7f4ee3d2e9febc0d8fa7689a0781a00332c5e3441a96d633b1e7
152+Description: gstreamer plugin test package
153+ Silly packages is a set of packages which will break your package
154+ management tool. They are created only for debugging purposes.
155+ .
156+ This package is a GStreamer test plugin package.
157+Gstreamer-Version: 0.10
158+Gstreamer-Decoders: audio/ac3; audio/mpeg, mpegversion=(int){ 1, 2, 4 };
159+Gstreamer-Elements: ac3parse
160+
161
162=== modified file 'tests/repo/gstreamer0.10-silly_0.1-0_all.deb'
163Binary files tests/repo/gstreamer0.10-silly_0.1-0_all.deb 2011-01-10 07:37:44 +0000 and tests/repo/gstreamer0.10-silly_0.1-0_all.deb 2012-02-02 05:55:21 +0000 differ
164=== modified file 'tests/test_pk.py'
165--- tests/test_pk.py 2011-12-06 04:57:18 +0000
166+++ tests/test_pk.py 2012-02-02 05:55:21 +0000
167@@ -8,8 +8,10 @@
168 import subprocess
169 import tempfile
170 import time
171+import sys
172 import unittest
173
174+from gi.repository import GLib
175 from gi.repository import PackageKitGlib as pk
176
177 import aptdaemon.test
178@@ -29,11 +31,21 @@
179 self.chroot.setup()
180 self.addCleanup(self.chroot.remove)
181 self.chroot.add_test_repository()
182+ # set up scratch dir
183+ self.workdir = tempfile.mkdtemp()
184+ # allow tests to add plugins, etc.
185+ self.orig_pythonpath = os.environ.get("PYTHONPATH")
186+ os.environ["PYTHONPATH"] = "%s:%s" % (self.workdir, os.environ.get("PYTHONPATH", ""))
187 self.start_session_aptd(self.chroot.path)
188 # Start the fake PolikcyKit daemon
189 self.start_fake_polkitd()
190 time.sleep(0.5)
191
192+ def tearDown(self):
193+ shutil.rmtree(self.workdir)
194+ if self.orig_pythonpath:
195+ os.environ["PYTHONPATH"] = self.orig_pythonpath
196+
197 def test_install(self):
198 """Test installing a package."""
199 self.chroot.add_test_repository()
200@@ -177,6 +189,112 @@
201 else:
202 self.fail("Failed to get dependants of %s" % pkg_id)
203
204+ def test_what_provides_unsupported(self):
205+ """Test querying for provides for unsupported type."""
206+
207+ client = pk.Client()
208+
209+ try:
210+ client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.CODEC,
211+ ["gstreamer0.10(decoder-audio/ac3)"],
212+ None, lambda p, t, d: True, None)
213+ self.fail("expected GLib.Error failure")
214+ except GLib.GError as e:
215+ self.assertTrue("codec" in str(e), e)
216+ self.assertTrue("not supported" in str(e), e)
217+
218+ def test_what_provides_plugin(self):
219+ """Test querying for provides with plugins."""
220+
221+ # add plugin for extra codecs
222+ f = open(os.path.join(self.workdir, "extra_codecs.py"), "w")
223+ f.write("""from packagekit import enums
224+
225+def fake_what_provides(cache, type, search):
226+ if type in (enums.PROVIDES_CODEC, enums.PROVIDES_ANY):
227+ if search.startswith('gstreamer'):
228+ return [cache["gstreamer0.10-silly"]]
229+ raise NotImplementedError('cannot handle type ' + str(type))
230+""")
231+ f.close()
232+ os.mkdir(os.path.join(self.workdir, "extra_codecs-0.egg-info"))
233+ f = open(os.path.join(self.workdir, "extra_codecs-0.egg-info", 'entry_points.txt'), "w")
234+ f.write("[packagekit.apt.plugins]\nwhat_provides=extra_codecs:fake_what_provides\n")
235+ f.close()
236+
237+ # invalid plugin, should not stop the valid ones
238+ os.mkdir(os.path.join(self.workdir, "nonexisting-1.egg-info"))
239+ f = open(os.path.join(self.workdir, "nonexisting-1.egg-info", 'entry_points.txt'), "w")
240+ f.write("[packagekit.apt.plugins]\nwhat_provides=nonexisting:what_provides\n")
241+ f.close()
242+
243+ # another plugin to test chaining and a new type
244+ f = open(os.path.join(self.workdir, "more_stuff.py"), "w")
245+ f.write("""from packagekit import enums
246+
247+def my_what_provides(cache, type, search):
248+ if type in (enums.PROVIDES_CODEC, enums.PROVIDES_ANY):
249+ if search.startswith('gstreamer'):
250+ return [cache["silly-base"]]
251+ if type in (enums.PROVIDES_LANGUAGE_SUPPORT, enums.PROVIDES_ANY):
252+ if search.startswith('locale('):
253+ return [cache["silly-important"]]
254+ raise NotImplementedError('cannot handle type ' + str(type))
255+""")
256+ f.close()
257+ os.mkdir(os.path.join(self.workdir, "more_stuff-0.egg-info"))
258+ f = open(os.path.join(self.workdir, "more_stuff-0.egg-info", 'entry_points.txt'), "w")
259+ f.write("[packagekit.apt.plugins]\nwhat_provides=more_stuff:my_what_provides\n")
260+ f.close()
261+
262+ client = pk.Client()
263+
264+ # search for CODEC
265+ res = client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.CODEC,
266+ ["gstreamer0.10(decoder-audio/vorbis)"],
267+ None, lambda p, t, d: True, None)
268+ self.assertEqual(res.get_exit_code(), pk.ExitEnum.SUCCESS)
269+ pkgs = [p.get_id().split(";")[0] for p in res.get_package_array()]
270+ self.assertEqual(pkgs, ["gstreamer0.10-silly", "silly-base"])
271+
272+ # search for LANGUAGE_SUPPORT
273+ res = client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.LANGUAGE_SUPPORT,
274+ ["locale(de_DE)"],
275+ None, lambda p, t, d: True, None)
276+ self.assertEqual(res.get_exit_code(), pk.ExitEnum.SUCCESS)
277+ pkgs = [p.get_id().split(";")[0] for p in res.get_package_array()]
278+ self.assertEqual(pkgs, ["silly-important"])
279+
280+ # search ANY
281+ res = client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.ANY,
282+ ["gstreamer0.10(decoder-audio/vorbis)"],
283+ None, lambda p, t, d: True, None)
284+ self.assertEqual(res.get_exit_code(), pk.ExitEnum.SUCCESS)
285+ pkgs = [p.get_id().split(";")[0] for p in res.get_package_array()]
286+ self.assertEqual(pkgs, ["gstreamer0.10-silly", "silly-base"])
287+
288+ res = client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.ANY,
289+ ["locale(de_DE)"],
290+ None, lambda p, t, d: True, None)
291+ self.assertEqual(res.get_exit_code(), pk.ExitEnum.SUCCESS)
292+ pkgs = [p.get_id().split(";")[0] for p in res.get_package_array()]
293+ self.assertEqual(pkgs, ["silly-important"])
294+
295+ res = client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.ANY,
296+ ["modalias(pci:1)"],
297+ None, lambda p, t, d: True, None)
298+ self.assertEqual(res.get_exit_code(), pk.ExitEnum.SUCCESS)
299+ self.assertEqual(res.get_package_array(), [])
300+
301+ # unsupported type with plugins
302+ try:
303+ client.what_provides(pk.FilterEnum.NONE, pk.ProvidesEnum.PLASMA_SERVICE,
304+ ["plasma4(dataengine-weather)"],
305+ None, lambda p, t, d: True, None)
306+ self.fail("expected GLib.Error failure")
307+ except GLib.GError as e:
308+ self.assertTrue("plasma" in str(e), e)
309+ self.assertTrue("not supported" in str(e), e)
310
311 def start_dbus_daemon():
312 """Start a dbus system daemon.
313@@ -205,8 +323,10 @@
314 if DEBUG:
315 logging.basicConfig(level=logging.DEBUG)
316 dbus_proc = start_dbus_daemon()
317- unittest.main()
318- dbus_proc.kill()
319- dbus_proc.wait()
320+ try:
321+ unittest.main()
322+ finally:
323+ dbus_proc.kill()
324+ dbus_proc.wait()
325
326 # vim: ts=4 et sts=4

Subscribers

People subscribed via source and target branches

to status/vote changes: