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