From 0d5e2c6e7d94d1e314399b20da17946ac521a597 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Thu, 21 Jun 2012 08:04:24 +0100 Subject: [PATCH] Added support for site-local packages This makes things easier for 0compile: instead of putting builds in the cache and registering them with the interface, 0compile can now just drop a build in ~/.local/share/0install.net/site-packages/URI/VERSION and 0install will find it and add it automatically. The build can be removed by simply deleting the directory. In order to support the case where 0compile compiles a binary on a platform with an old system 0install, writer.save_interface() adds all such discovered feeds, tagged as 'site-package'. 0install >= 1.9 ignores such entries when reading. --- tests/basetest.py | 3 ++ tests/testwriter.py | 57 ++++++++++++++++++++++++++++++++++- zeroinstall/0launch-gui/properties.py | 5 +-- zeroinstall/injector/model.py | 5 +-- zeroinstall/injector/reader.py | 36 ++++++++++++++++++++-- zeroinstall/injector/writer.py | 2 ++ zeroinstall/support/basedir.py | 10 ++++++ 7 files changed, 110 insertions(+), 8 deletions(-) diff --git a/tests/basetest.py b/tests/basetest.py index 5f10bfe..4038ce3 100755 --- a/tests/basetest.py +++ b/tests/basetest.py @@ -167,12 +167,15 @@ class BaseTest(unittest.TestCase): self.config_home = tempfile.mktemp() self.cache_home = tempfile.mktemp() self.cache_system = tempfile.mktemp() + self.data_home = tempfile.mktemp() self.gnupg_home = tempfile.mktemp() os.environ['GNUPGHOME'] = self.gnupg_home os.environ['XDG_CONFIG_HOME'] = self.config_home os.environ['XDG_CONFIG_DIRS'] = '' os.environ['XDG_CACHE_HOME'] = self.cache_home os.environ['XDG_CACHE_DIRS'] = self.cache_system + os.environ['XDG_DATA_HOME'] = self.data_home + os.environ['XDG_DATA_DIRS'] = '' if 'ZEROINSTALL_PORTABLE_BASE' in os.environ: del os.environ['ZEROINSTALL_PORTABLE_BASE'] imp.reload(basedir) diff --git a/tests/testwriter.py b/tests/testwriter.py index c125c42..9fdbb0a 100755 --- a/tests/testwriter.py +++ b/tests/testwriter.py @@ -1,10 +1,11 @@ #!/usr/bin/env python from basetest import BaseTest -import sys, StringIO +import sys, StringIO, os, shutil import unittest sys.path.insert(0, '..') from zeroinstall.injector import writer, model, reader, qdom +from zeroinstall.support import basedir test_feed = qdom.parse(StringIO.StringIO(""" Test @@ -13,6 +14,8 @@ test_feed = qdom.parse(StringIO.StringIO("""= 1.9): + # - 0compile stores implementations to ~/.local/0install.net/site-packages, and + # - 0install finds them automatically + + # For backwards compatibility, 0install >= 1.9: + # - writes discovered feeds to extra_feeds + # - skips such entries in extra_feeds when loading + + meta_dir = basedir.save_data_path('0install.net', 'site-packages', + 'http:##example.com#prog.xml', '1.0', '0install') + feed = os.path.join(meta_dir, 'feed.xml') + shutil.copyfile(os.path.join(mydir, 'Local.xml'), feed) + + # Check that we find the feed without us having to register it + iface = self.config.iface_cache.get_interface('http://example.com/prog.xml') + self.assertEqual(1, len(iface.extra_feeds)) + site_feed, = iface.extra_feeds + self.assertEqual(True, site_feed.site_package) + + # Check that we write it out, so that older 0installs can find it + writer.save_interface(iface) + + config_file = basedir.load_first_config('0install.net', 'injector', + 'interfaces', 'http:##example.com#prog.xml') + with open(config_file) as s: + doc = qdom.parse(s) + + feed_node = None + for item in doc.childNodes: + if item.name == 'feed': + feed_node = item + self.assertEqual('True', feed_node.getAttribute('site-package')) + + # Check we ignore this element + iface.reset() + self.assertEqual([], iface.extra_feeds) + reader.update_user_overrides(iface) + self.assertEqual([], iface.extra_feeds) + + # Check feeds are automatically removed again + reader.update_from_cache(iface) + self.assertEqual(1, len(iface.extra_feeds)) + shutil.rmtree(basedir.load_first_data('0install.net', 'site-packages', + 'http:##example.com#prog.xml')) + + reader.update_from_cache(iface) + self.assertEqual(0, len(iface.extra_feeds)) if __name__ == '__main__': unittest.main() diff --git a/zeroinstall/0launch-gui/properties.py b/zeroinstall/0launch-gui/properties.py index 8cd11d8..601387e 100644 --- a/zeroinstall/0launch-gui/properties.py +++ b/zeroinstall/0launch-gui/properties.py @@ -231,9 +231,10 @@ class Feeds: enable_remove = False for x in self.interface.extra_feeds: if x.uri == feed_url: - if x.user_override: + if x.user_override and not x.site_package: enable_remove = True - self.remove_feed_button.set_sensitive( enable_remove ) + break + self.remove_feed_button.set_sensitive(enable_remove) try: self.description.set_details(iface_cache, iface_cache.get_feed(feed_url)) except zeroinstall.SafeException as ex: diff --git a/zeroinstall/injector/model.py b/zeroinstall/injector/model.py index 3752a92..dd0ae59 100644 --- a/zeroinstall/injector/model.py +++ b/zeroinstall/injector/model.py @@ -379,14 +379,15 @@ class OverlayBinding(Binding): class Feed(object): """An interface's feeds are other interfaces whose implementations can also be used as implementations of this interface.""" - __slots__ = ['uri', 'os', 'machine', 'user_override', 'langs'] - def __init__(self, uri, arch, user_override, langs = None): + __slots__ = ['uri', 'os', 'machine', 'user_override', 'langs', 'site_package'] + def __init__(self, uri, arch, user_override, langs = None, site_package = False): self.uri = uri # This indicates whether the feed comes from the user's overrides # file. If true, writer.py will write it when saving. self.user_override = user_override self.os, self.machine = _split_arch(arch) self.langs = langs + self.site_package = site_package def __str__(self): return "" % self.uri diff --git a/zeroinstall/injector/reader.py b/zeroinstall/injector/reader.py index 674dc1a..0b4fc48 100644 --- a/zeroinstall/injector/reader.py +++ b/zeroinstall/injector/reader.py @@ -19,6 +19,19 @@ from zeroinstall.injector import model class MissingLocalFeed(InvalidInterface): pass +def _add_site_packages(interface, site_packages, known_site_feeds): + for impl in os.listdir(site_packages): + if impl.startswith('.'): continue + feed = os.path.join(site_packages, impl, '0install', 'feed.xml') + if not os.path.exists(feed): + warn(_("Site-local feed {path} not found").format(path = feed)) + debug("Adding site-local feed '%s'", feed) + + # (we treat these as user overrides in order to let old versions of 0install + # find them) + interface.extra_feeds.append(Feed(feed, None, user_override = True, site_package = True)) + known_site_feeds.add(feed) + def update_from_cache(interface, iface_cache = None): """Read a cached interface and any native feeds or user overrides. @param interface: the interface object to update @@ -33,14 +46,23 @@ def update_from_cache(interface, iface_cache = None): from zeroinstall.injector import policy iface_cache = policy.get_deprecated_singleton_config().iface_cache + escaped_uri = model._pretty_escape(interface.uri) # Add the distribution package manager's version, if any - path = basedir.load_first_data(config_site, 'native_feeds', model._pretty_escape(interface.uri)) + path = basedir.load_first_data(config_site, 'native_feeds', escaped_uri) if path: # Resolve any symlinks info(_("Adding native packager feed '%s'"), path) interface.extra_feeds.append(Feed(os.path.realpath(path), None, False)) - update_user_overrides(interface) + # Add locally-compiled binaries, if any + known_site_feeds = set() + for path in basedir.load_data_paths(config_site, 'site-packages', escaped_uri): + try: + _add_site_packages(interface, path, known_site_feeds) + except Exception as ex: + warn("Error loading site packages from {path}: {ex}".format(path = path, ex = ex)) + + update_user_overrides(interface, known_site_feeds) main_feed = iface_cache.get_feed(interface.uri, force = True) if main_feed: @@ -105,11 +127,12 @@ def update_user_feed_overrides(feed): if user_stability: impl.user_stability = stability_levels[str(user_stability)] -def update_user_overrides(interface): +def update_user_overrides(interface, known_site_feeds = frozenset()): """Update an interface with user-supplied information. Sets preferred stability and updates extra_feeds. @param interface: the interface object to update @type interface: L{model.Interface} + @param known_site_feeds: feeds to ignore (for backwards compatibility) """ user = basedir.load_first_config(config_site, config_prog, 'interfaces', model._pretty_escape(interface.uri)) @@ -136,6 +159,13 @@ def update_user_overrides(interface): feed_src = item.getAttribute('src') if not feed_src: raise InvalidInterface(_('Missing "src" attribute in ')) + if item.getAttribute('site-package'): + # Site packages are detected earlier. This test isn't completely reliable, + # since older versions will remove the attribute when saving the config + # (hence the next test). + continue + if feed_src in known_site_feeds: + continue interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs'))) def check_readable(feed_url, source): diff --git a/zeroinstall/injector/writer.py b/zeroinstall/injector/writer.py index c1a65d4..25f668f 100644 --- a/zeroinstall/injector/writer.py +++ b/zeroinstall/injector/writer.py @@ -71,5 +71,7 @@ def save_interface(interface): elem.setAttribute('src', feed.uri) if feed.arch: elem.setAttribute('arch', feed.arch) + if feed.site_package: + elem.setAttribute('site-package', 'True') _atomic_save(doc, user_overrides, interface.uri) diff --git a/zeroinstall/support/basedir.py b/zeroinstall/support/basedir.py index 429a758..3992852 100644 --- a/zeroinstall/support/basedir.py +++ b/zeroinstall/support/basedir.py @@ -154,3 +154,13 @@ def load_first_data(*resource): for x in load_data_paths(*resource): return x return None + +def save_data_path(*resource): + """Ensure $XDG_DATA_HOME// exists, and return its path. + 'resource' should normally be the name of your application.""" + resource = os.path.join(*resource) + assert not os.path.isabs(resource) + path = os.path.join(xdg_data_home, resource) + if not os.path.isdir(path): + os.makedirs(path, 0o700) + return path -- 2.11.4.GIT