Don't try to install the README that no longer exists
[zeroinstall/solver.git] / zeroinstall / injector / reader.py
blobcec51fe7a486d0675b2981390194e39ce691f336
1 """
2 Parses an XML feed into a Python representation. You should probably use L{iface_cache.iface_cache} rather than the functions here.
3 """
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall import _
9 import os
10 from logging import debug, info, warn
11 import errno
13 from zeroinstall import support
14 from zeroinstall.support import basedir
15 from zeroinstall.injector import qdom
16 from zeroinstall.injector.namespaces import config_site, config_prog, XMLNS_IFACE
17 from zeroinstall.injector.model import Interface, InvalidInterface, ZeroInstallFeed, escape, Feed, stability_levels
18 from zeroinstall.injector import model
20 class MissingLocalFeed(InvalidInterface):
21 pass
23 def _add_site_packages(interface, site_packages, known_site_feeds):
24 for impl in os.listdir(site_packages):
25 if impl.startswith('.'): continue
26 feed = os.path.join(site_packages, impl, '0install', 'feed.xml')
27 if not os.path.exists(feed):
28 warn(_("Site-local feed {path} not found").format(path = feed))
29 debug("Adding site-local feed '%s'", feed)
31 # (we treat these as user overrides in order to let old versions of 0install
32 # find them)
33 interface.extra_feeds.append(Feed(feed, None, user_override = True, site_package = True))
34 known_site_feeds.add(feed)
36 def update_from_cache(interface, iface_cache = None):
37 """Read a cached interface and any native feeds or user overrides.
38 @param interface: the interface object to update
39 @type interface: L{model.Interface}
40 @return: True if cached version and user overrides loaded OK.
41 False if upstream not cached. Local interfaces (starting with /) are
42 always considered to be cached, although they are not actually stored in the cache.
43 Internal: use L{iface_cache.IfaceCache.get_interface} instread.
44 @rtype: bool"""
45 interface.reset()
46 if iface_cache is None:
47 from zeroinstall.injector import policy
48 iface_cache = policy.get_deprecated_singleton_config().iface_cache
50 escaped_uri = model._pretty_escape(interface.uri)
51 # Add the distribution package manager's version, if any
52 path = basedir.load_first_data(config_site, 'native_feeds', escaped_uri)
53 if path:
54 # Resolve any symlinks
55 info(_("Adding native packager feed '%s'"), path)
56 interface.extra_feeds.append(Feed(os.path.realpath(path), None, False))
58 # Add locally-compiled binaries, if any
59 known_site_feeds = set()
60 for path in basedir.load_data_paths(config_site, 'site-packages', escaped_uri):
61 try:
62 _add_site_packages(interface, path, known_site_feeds)
63 except Exception as ex:
64 warn("Error loading site packages from {path}: {ex}".format(path = path, ex = ex))
66 update_user_overrides(interface, known_site_feeds)
68 main_feed = iface_cache.get_feed(interface.uri, force = True)
69 if main_feed:
70 update_user_feed_overrides(main_feed)
72 return main_feed is not None
74 def load_feed_from_cache(url, selections_ok = False):
75 """Load a feed. If the feed is remote, load from the cache. If local, load it directly.
76 @return: the feed, or None if it's remote and not cached."""
77 try:
78 if os.path.isabs(url):
79 debug(_("Loading local feed file '%s'"), url)
80 return load_feed(url, local = True, selections_ok = selections_ok)
81 else:
82 cached = basedir.load_first_cache(config_site, 'interfaces', escape(url))
83 if cached:
84 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url, 'cached': cached})
85 return load_feed(cached, local = False)
86 else:
87 return None
88 except InvalidInterface as ex:
89 ex.feed_url = url
90 raise
92 def update_user_feed_overrides(feed):
93 """Update a feed with user-supplied information.
94 Sets last_checked and user_stability ratings.
95 @param feed: feed to update
96 @since 0.49
97 """
98 user = basedir.load_first_config(config_site, config_prog,
99 'feeds', model._pretty_escape(feed.url))
100 if user is None:
101 # For files saved by 0launch < 0.49
102 user = basedir.load_first_config(config_site, config_prog,
103 'user_overrides', escape(feed.url))
104 if not user:
105 return
107 try:
108 with open(user, 'rb') as stream:
109 root = qdom.parse(stream)
110 except Exception as ex:
111 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
112 raise
114 last_checked = root.getAttribute('last-checked')
115 if last_checked:
116 feed.last_checked = int(last_checked)
118 for item in root.childNodes:
119 if item.uri != XMLNS_IFACE: continue
120 if item.name == 'implementation':
121 id = item.getAttribute('id')
122 assert id is not None
123 impl = feed.implementations.get(id, None)
124 if not impl:
125 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': feed})
126 continue
128 user_stability = item.getAttribute('user-stability')
129 if user_stability:
130 impl.user_stability = stability_levels[str(user_stability)]
132 def update_user_overrides(interface, known_site_feeds = frozenset()):
133 """Update an interface with user-supplied information.
134 Sets preferred stability and updates extra_feeds.
135 @param interface: the interface object to update
136 @type interface: L{model.Interface}
137 @param known_site_feeds: feeds to ignore (for backwards compatibility)
139 user = basedir.load_first_config(config_site, config_prog,
140 'interfaces', model._pretty_escape(interface.uri))
141 if user is None:
142 # For files saved by 0launch < 0.49
143 user = basedir.load_first_config(config_site, config_prog,
144 'user_overrides', escape(interface.uri))
145 if not user:
146 return
148 try:
149 with open(user, 'rb') as stream:
150 root = qdom.parse(stream)
151 except Exception as ex:
152 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
153 raise
155 stability_policy = root.getAttribute('stability-policy')
156 if stability_policy:
157 interface.set_stability_policy(stability_levels[str(stability_policy)])
159 for item in root.childNodes:
160 if item.uri != XMLNS_IFACE: continue
161 if item.name == 'feed':
162 feed_src = item.getAttribute('src')
163 if not feed_src:
164 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
165 if item.getAttribute('site-package'):
166 # Site packages are detected earlier. This test isn't completely reliable,
167 # since older versions will remove the attribute when saving the config
168 # (hence the next test).
169 continue
170 if feed_src in known_site_feeds:
171 continue
172 interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs')))
174 def check_readable(feed_url, source):
175 """Test whether a feed file is valid.
176 @param feed_url: the feed's expected URL
177 @type feed_url: str
178 @param source: the name of the file to test
179 @type source: str
180 @return: the modification time in src (usually just the mtime of the file)
181 @rtype: int
182 @raise InvalidInterface: If the source's syntax is incorrect,
184 try:
185 feed = load_feed(source, local = False)
187 if feed.url != feed_url:
188 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
189 "%(feed_url)s is given in the feed, but\n"
190 "%(interface_uri)s was requested") %
191 {'feed_url': feed.url, 'interface_uri': feed_url})
192 return feed.last_modified
193 except InvalidInterface as ex:
194 info(_("Error loading feed:\n"
195 "Interface URI: %(uri)s\n"
196 "Local file: %(source)s\n"
197 "%(exception)s") %
198 {'uri': feed_url, 'source': source, 'exception': ex})
199 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': feed_url, 'exception': ex})
201 def update(interface, source, local = False, iface_cache = None):
202 """Read in information about an interface.
203 Deprecated.
204 @param interface: the interface object to update
205 @type interface: L{model.Interface}
206 @param source: the name of the file to read
207 @type source: str
208 @param local: use file's mtime for last-modified, and uri attribute is ignored
209 @raise InvalidInterface: if the source's syntax is incorrect
210 @return: the new feed (since 0.32)
211 @see: L{update_from_cache}, which calls this"""
212 assert isinstance(interface, Interface)
214 feed = load_feed(source, local)
216 if not local:
217 if feed.url != interface.uri:
218 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
219 "%(feed_url)s is given in the feed, but\n"
220 "%(interface_uri)s was requested") %
221 {'feed_url': feed.url, 'interface_uri': interface.uri})
223 if iface_cache is None:
224 from zeroinstall.injector import policy
225 iface_cache = policy.get_deprecated_singleton_config().iface_cache
226 iface_cache._feeds[support.unicode(interface.uri)] = feed
228 return feed
230 def load_feed(source, local = False, selections_ok = False):
231 """Load a feed from a local file.
232 @param source: the name of the file to read
233 @type source: str
234 @param local: this is a local feed
235 @type local: bool
236 @param selections_ok: if it turns out to be a local selections document, return that instead
237 @type selections_ok: bool
238 @raise InvalidInterface: if the source's syntax is incorrect
239 @return: the new feed
240 @since: 0.48
241 @see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
242 try:
243 with open(source, 'rb') as stream:
244 root = qdom.parse(stream)
245 except IOError as ex:
246 if ex.errno == errno.ENOENT and local:
247 raise MissingLocalFeed(_("Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case."))
248 raise InvalidInterface(_("Can't read file"), ex)
249 except Exception as ex:
250 raise InvalidInterface(_("Invalid XML"), ex)
252 if local:
253 if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections':
254 from zeroinstall.injector import selections
255 return selections.Selections(root)
256 local_path = source
257 else:
258 local_path = None
259 feed = ZeroInstallFeed(root, local_path)
260 feed.last_modified = int(os.stat(source).st_mtime)
261 return feed