Package zeroinstall :: Package injector :: Module selections
[frames] | no frames]

Source Code for Module zeroinstall.injector.selections

  1  """ 
  2  Load and save a set of chosen implementations. 
  3  @since: 0.27 
  4  """ 
  5   
  6  # Copyright (C) 2009, Thomas Leonard 
  7  # See the README file for details, or visit http://0install.net. 
  8   
  9  import os 
 10  from zeroinstall import _, zerostore 
 11  from zeroinstall.injector import model 
 12  from zeroinstall.injector.policy import get_deprecated_singleton_config 
 13  from zeroinstall.injector.model import process_binding, process_depends, binding_names, Command 
 14  from zeroinstall.injector.namespaces import XMLNS_IFACE 
 15  from zeroinstall.injector.qdom import Element, Prefixes 
 16  from zeroinstall.support import tasks, basestring 
17 18 -class Selection(object):
19 """A single selected implementation in a L{Selections} set. 20 @ivar dependencies: list of dependencies 21 @type dependencies: [L{model.Dependency}] 22 @ivar attrs: XML attributes map (name is in the format "{namespace} {localName}") 23 @type attrs: {str: str} 24 @ivar version: the implementation's version number 25 @type version: str""" 26 27 interface = property(lambda self: self.attrs['interface']) 28 id = property(lambda self: self.attrs['id']) 29 version = property(lambda self: self.attrs['version']) 30 feed = property(lambda self: self.attrs.get('from-feed', self.interface)) 31 main = property(lambda self: self.attrs.get('main', None)) 32 33 @property
34 - def local_path(self):
35 local_path = self.attrs.get('local-path', None) 36 if local_path: 37 return local_path 38 if self.id.startswith('/'): 39 return self.id 40 return None
41
42 - def __repr__(self):
43 """@rtype: str""" 44 return self.id
45
46 - def is_available(self, stores):
47 """Is this implementation available locally? 48 (a local implementation or a cached ZeroInstallImplementation) 49 @rtype: bool 50 @since: 0.53""" 51 path = self.local_path 52 if path is not None: 53 return os.path.exists(path) 54 path = stores.lookup_maybe(self.digests) 55 return path is not None
56
57 - def get_path(self, stores, missing_ok = False):
58 """Return the root directory of this implementation. 59 For local implementations, this is L{local_path}. 60 For cached implementations, this is the directory in the cache. 61 @param stores: stores to search 62 @type stores: L{zerostore.Stores} 63 @param missing_ok: return None for uncached implementations 64 @type missing_ok: bool 65 @return: the path of the directory 66 @rtype: str | None 67 @since: 1.8""" 68 if self.local_path is not None: 69 return self.local_path 70 if not self.digests: 71 # (for now, we assume this is always an error, even for missing_ok) 72 raise model.SafeException("No digests for {feed} {version}".format(feed = self.feed, version = self.version)) 73 if missing_ok: 74 return stores.lookup_maybe(self.digests) 75 else: 76 return stores.lookup_any(self.digests)
77
78 -class ImplSelection(Selection):
79 """A Selection created from an Implementation""" 80 81 __slots__ = ['impl', 'dependencies', 'attrs', '_used_commands'] 82
83 - def __init__(self, iface_uri, impl, dependencies):
84 """@type iface_uri: str 85 @type impl: L{zeroinstall.injector.model.Implementation} 86 @type dependencies: [L{zeroinstall.injector.model.Dependency}]""" 87 assert impl 88 self.impl = impl 89 self.dependencies = dependencies 90 self._used_commands = {} # name -> Command 91 92 attrs = impl.metadata.copy() 93 attrs['id'] = impl.id 94 attrs['version'] = impl.get_version() 95 attrs['interface'] = iface_uri 96 attrs['from-feed'] = impl.feed.url 97 if impl.local_path: 98 attrs['local-path'] = impl.local_path 99 self.attrs = attrs
100 101 @property
102 - def bindings(self): return self.impl.bindings
103 104 @property
105 - def digests(self): return self.impl.digests
106
107 - def get_command(self, name):
108 assert name in self._used_commands, "internal error: '{command}' not in my commands list".format(command = name) 109 return self._used_commands[name]
110
111 - def get_commands(self):
112 return self._used_commands
113
114 -class XMLSelection(Selection):
115 """A Selection created by reading an XML selections document. 116 @ivar digests: a list of manifest digests 117 @type digests: [str] 118 """ 119 __slots__ = ['bindings', 'dependencies', 'attrs', 'digests', 'commands'] 120
121 - def __init__(self, dependencies, bindings = None, attrs = None, digests = None, commands = None):
122 """@type dependencies: [L{zeroinstall.injector.model.Dependency}] 123 @type bindings: [L{zeroinstall.injector.model.Binding}] | None 124 @type attrs: {str: str} | None 125 @type digests: [str] | None 126 @type commands: {str: L{Command}} | None""" 127 if bindings is None: bindings = [] 128 if digests is None: digests = [] 129 self.dependencies = dependencies 130 self.bindings = bindings 131 self.attrs = attrs 132 self.digests = digests 133 self.commands = commands 134 135 assert self.interface 136 assert self.id 137 assert self.version 138 assert self.feed
139
140 - def get_command(self, name):
141 """@type name: str 142 @rtype: L{Command}""" 143 if name not in self.commands: 144 raise model.SafeException("Command '{name}' not present in selections for {iface}".format(name = name, iface = self.interface)) 145 return self.commands[name]
146
147 - def get_commands(self):
148 """@rtype: {str: L{Command}}""" 149 return self.commands
150
151 -class Selections(object):
152 """ 153 A selected set of components which will make up a complete program. 154 @ivar interface: the interface of the program 155 @type interface: str 156 @ivar command: the command to run on 'interface' 157 @type command: str 158 @ivar selections: the selected implementations 159 @type selections: {str: L{Selection}} 160 """ 161 __slots__ = ['interface', 'selections', 'command'] 162
163 - def __init__(self, source):
164 """Constructor. 165 @param source: a map of implementations, policy or selections document 166 @type source: L{Element}""" 167 self.selections = {} 168 self.command = None 169 170 if source is None: 171 # (Solver will fill everything in) 172 pass 173 elif isinstance(source, Element): 174 self._init_from_qdom(source) 175 else: 176 raise Exception(_("Source not a qdom.Element!"))
177
178 - def _init_from_qdom(self, root):
179 """Parse and load a selections document. 180 @param root: a saved set of selections. 181 @type root: L{Element}""" 182 self.interface = root.getAttribute('interface') 183 self.command = root.getAttribute('command') 184 if self.interface is None: 185 raise model.SafeException(_("Not a selections document (no 'interface' attribute on root)")) 186 old_commands = [] 187 188 for selection in root.childNodes: 189 if selection.uri != XMLNS_IFACE: 190 continue 191 if selection.name != 'selection': 192 if selection.name == 'command': 193 old_commands.append(Command(selection, None)) 194 continue 195 196 requires = [] 197 bindings = [] 198 digests = [] 199 commands = {} 200 for elem in selection.childNodes: 201 if elem.uri != XMLNS_IFACE: 202 continue 203 if elem.name in binding_names: 204 bindings.append(process_binding(elem)) 205 elif elem.name == 'requires': 206 dep = process_depends(elem, None) 207 requires.append(dep) 208 elif elem.name == 'manifest-digest': 209 for aname, avalue in elem.attrs.items(): 210 digests.append(zerostore.format_algorithm_digest_pair(aname, avalue)) 211 elif elem.name == 'command': 212 name = elem.getAttribute('name') 213 assert name, "Missing name attribute on <command>" 214 commands[name] = Command(elem, None) 215 216 # For backwards compatibility, allow getting the digest from the ID 217 sel_id = selection.attrs['id'] 218 local_path = selection.attrs.get("local-path", None) 219 if (not digests and not local_path) and '=' in sel_id: 220 alg = sel_id.split('=', 1)[0] 221 if alg in ('sha1', 'sha1new', 'sha256'): 222 digests.append(sel_id) 223 224 iface_uri = selection.attrs['interface'] 225 226 s = XMLSelection(requires, bindings, selection.attrs, digests, commands) 227 self.selections[iface_uri] = s 228 229 if self.command is None: 230 # Old style selections document 231 if old_commands: 232 # 0launch 0.52 to 1.1 233 self.command = 'run' 234 iface = self.interface 235 236 for command in old_commands: 237 command.qdom.attrs['name'] = 'run' 238 self.selections[iface].commands['run'] = command 239 runner = command.get_runner() 240 if runner: 241 iface = runner.interface 242 else: 243 iface = None 244 else: 245 # 0launch < 0.51 246 root_sel = self.selections[self.interface] 247 main = root_sel.attrs.get('main', None) 248 if main is not None: 249 root_sel.commands['run'] = Command(Element(XMLNS_IFACE, 'command', {'path': main, 'name': 'run'}), None) 250 self.command = 'run' 251 252 elif self.command == '': 253 # New style, but no command requested 254 self.command = None 255 assert not old_commands, "<command> list in new-style selections document!"
256
257 - def toDOM(self):
258 """Create a DOM document for the selected implementations. 259 The document gives the URI of the root, plus each selected implementation. 260 For each selected implementation, we record the ID, the version, the URI and 261 (if different) the feed URL. We also record all the bindings needed. 262 @return: a new DOM Document""" 263 from xml.dom import minidom, XMLNS_NAMESPACE 264 265 assert self.interface 266 267 impl = minidom.getDOMImplementation() 268 269 doc = impl.createDocument(XMLNS_IFACE, "selections", None) 270 271 root = doc.documentElement 272 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns', XMLNS_IFACE) 273 274 root.setAttributeNS(None, 'interface', self.interface) 275 276 root.setAttributeNS(None, 'command', self.command or "") 277 278 prefixes = Prefixes(XMLNS_IFACE) 279 280 for iface, selection in sorted(self.selections.items()): 281 selection_elem = doc.createElementNS(XMLNS_IFACE, 'selection') 282 selection_elem.setAttributeNS(None, 'interface', selection.interface) 283 root.appendChild(selection_elem) 284 285 for name, value in selection.attrs.items(): 286 if ' ' in name: 287 ns, localName = name.split(' ', 1) 288 prefixes.setAttributeNS(selection_elem, ns, localName, value) 289 elif name == 'stability': 290 pass 291 elif name == 'from-feed': 292 # Don't bother writing from-feed attr if it's the same as the interface 293 if value != selection.attrs['interface']: 294 selection_elem.setAttributeNS(None, name, value) 295 elif name not in ('main', 'self-test'): # (replaced by <command>) 296 selection_elem.setAttributeNS(None, name, value) 297 298 if selection.digests: 299 manifest_digest = doc.createElementNS(XMLNS_IFACE, 'manifest-digest') 300 for digest in selection.digests: 301 aname, avalue = zerostore.parse_algorithm_digest_pair(digest) 302 assert ':' not in aname 303 manifest_digest.setAttribute(aname, avalue) 304 selection_elem.appendChild(manifest_digest) 305 306 for b in selection.bindings: 307 selection_elem.appendChild(b._toxml(doc, prefixes)) 308 309 for dep in selection.dependencies: 310 if not isinstance(dep, model.InterfaceDependency): continue 311 312 dep_elem = doc.createElementNS(XMLNS_IFACE, 'requires') 313 dep_elem.setAttributeNS(None, 'interface', dep.interface) 314 selection_elem.appendChild(dep_elem) 315 316 for m in dep.metadata: 317 parts = m.split(' ', 1) 318 if len(parts) == 1: 319 ns = None 320 localName = parts[0] 321 dep_elem.setAttributeNS(None, localName, dep.metadata[m]) 322 else: 323 ns, localName = parts 324 prefixes.setAttributeNS(dep_elem, ns, localName, dep.metadata[m]) 325 326 for b in dep.bindings: 327 dep_elem.appendChild(b._toxml(doc, prefixes)) 328 329 for command in selection.get_commands().values(): 330 selection_elem.appendChild(command._toxml(doc, prefixes)) 331 332 for ns, prefix in prefixes.prefixes.items(): 333 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, ns) 334 335 return doc
336
337 - def __repr__(self):
338 return "Selections for " + self.interface
339
340 - def get_unavailable_selections(self, config, include_packages):
341 """Find those selections which are not present. 342 Local implementations are available if their directory exists. 343 Other 0install implementations are available if they are in the cache. 344 Package implementations are available if the Distribution says so. 345 @param include_packages: whether to include <package-implementation>s 346 @type include_packages: bool 347 @rtype: [Selection] 348 @since: 1.16""" 349 iface_cache = config.iface_cache 350 stores = config.stores 351 352 # Check that every required selection is cached 353 def needs_download(sel): 354 if sel.id.startswith('package:'): 355 if not include_packages: return False 356 feed = iface_cache.get_feed(sel.feed) 357 if not feed: return False 358 impl = feed.implementations.get(sel.id, None) 359 return impl is None or not impl.installed 360 elif sel.local_path: 361 return False 362 else: 363 return sel.get_path(stores, missing_ok = True) is None
364 365 return [sel for sel in self.selections.values() if needs_download(sel)]
366
367 - def download_missing(self, config, _old = None, include_packages = False):
368 """Check all selected implementations are available. 369 Download any that are not present. Since native distribution packages are usually 370 only available in a single version, which is unlikely to be the one in the 371 selections document, we ignore them by default. 372 Note: package implementations (distribution packages) are ignored. 373 @param config: used to get iface_cache, stores and fetcher 374 @param include_packages: also try to install native packages (since 1.5) 375 @type include_packages: bool 376 @rtype: L{zeroinstall.support.tasks.Blocker} | None""" 377 if _old: 378 config = get_deprecated_singleton_config() 379 380 iface_cache = config.iface_cache 381 stores = config.stores 382 383 needed_downloads = self.get_unavailable_selections(config, include_packages) 384 if not needed_downloads: 385 return 386 387 if config.network_use == model.network_offline: 388 from zeroinstall import NeedDownload 389 raise NeedDownload(', '.join([str(x) for x in needed_downloads])) 390 391 @tasks.async 392 def download(): 393 # We're missing some. For each one, get the feed it came from 394 # and find the corresponding <implementation> in that. This will 395 # tell us where to get it from. 396 # Note: we look for an implementation with the same ID. Maybe we 397 # should check it has the same digest(s) too? 398 needed_impls = [] 399 for sel in needed_downloads: 400 feed_url = sel.attrs.get('from-feed', None) or sel.attrs['interface'] 401 feed = iface_cache.get_feed(feed_url) 402 if feed is None or sel.id not in feed.implementations: 403 fetch_feed = config.fetcher.download_and_import_feed(feed_url, iface_cache) 404 yield fetch_feed 405 tasks.check(fetch_feed) 406 407 feed = iface_cache.get_feed(feed_url) 408 assert feed, "Failed to get feed for %s" % feed_url 409 impl = feed.implementations[sel.id] 410 needed_impls.append(impl) 411 412 fetch_impls = config.fetcher.download_impls(needed_impls, stores) 413 yield fetch_impls 414 tasks.check(fetch_impls)
415 return download() 416 417 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map... 418
419 - def __getitem__(self, key):
420 # Deprecated 421 """@type key: str 422 @rtype: L{ImplSelection}""" 423 if isinstance(key, basestring): 424 return self.selections[key] 425 sel = self.selections[key.uri] 426 return sel and sel.impl
427
428 - def iteritems(self):
429 # Deprecated 430 iface_cache = get_deprecated_singleton_config().iface_cache 431 for (uri, sel) in self.selections.items(): 432 yield (iface_cache.get_interface(uri), sel and sel.impl)
433
434 - def values(self):
435 # Deprecated 436 """@rtype: L{zeroinstall.injector.model.Implementation}""" 437 for (uri, sel) in self.selections.items(): 438 yield sel and sel.impl
439
440 - def __iter__(self):
441 # Deprecated 442 iface_cache = get_deprecated_singleton_config().iface_cache 443 for (uri, sel) in self.selections.items(): 444 yield iface_cache.get_interface(uri)
445
446 - def get(self, iface, if_missing):
447 # Deprecated 448 """@type iface: L{zeroinstall.injector.model.Interface} 449 @rtype: L{zeroinstall.injector.model.Implementation}""" 450 sel = self.selections.get(iface.uri, None) 451 if sel: 452 return sel.impl 453 return if_missing
454
455 - def copy(self):
456 # Deprecated 457 s = Selections(None) 458 s.interface = self.interface 459 s.selections = self.selections.copy() 460 return s
461
462 - def items(self):
463 # Deprecated 464 return list(self.iteritems())
465 466 @property
467 - def commands(self):
468 i = self.interface 469 c = self.command 470 commands = [] 471 while c is not None: 472 sel = self.selections[i] 473 command = sel.get_command(c) 474 475 commands.append(command) 476 477 runner = command.get_runner() 478 if not runner: 479 break 480 481 i = runner.metadata['interface'] 482 c = runner.qdom.attrs.get('command', 'run') 483 484 return commands
485