2 Load and save a set of chosen implementations.
6 # Copyright (C) 2009, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
10 from zeroinstall
import _
11 from zeroinstall
.injector
import model
12 from zeroinstall
.injector
.policy
import Policy
, 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
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
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))
35 local_path
= self
.attrs
.get('local-path', None)
38 if self
.id.startswith('/'):
45 def is_available(self
, stores
):
46 """Is this implementation available locally?
47 (a local implementation or a cached ZeroInstallImplementation)
50 path
= self
.local_path
52 return os
.path
.exists(path
)
53 path
= stores
.lookup_maybe(self
.digests
)
54 return path
is not None
56 class ImplSelection(Selection
):
57 """A Selection created from an Implementation"""
59 __slots__
= ['impl', 'dependencies', 'attrs', '_used_commands']
61 def __init__(self
, iface_uri
, impl
, dependencies
):
64 self
.dependencies
= dependencies
65 self
._used
_commands
= set()
67 attrs
= impl
.metadata
.copy()
69 attrs
['version'] = impl
.get_version()
70 attrs
['interface'] = iface_uri
71 attrs
['from-feed'] = impl
.feed
.url
73 attrs
['local-path'] = impl
.local_path
77 def bindings(self
): return self
.impl
.bindings
80 def digests(self
): return self
.impl
.digests
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
]
86 def get_commands(self
):
88 for c
in self
._used
_commands
:
89 commands
[c
] = self
.impl
.commands
[c
]
92 class XMLSelection(Selection
):
93 """A Selection created by reading an XML selections document.
94 @ivar digests: a list of manifest digests
97 __slots__
= ['bindings', 'dependencies', 'attrs', 'digests', 'commands']
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
105 self
.digests
= digests
106 self
.commands
= commands
108 assert self
.interface
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
]
118 def get_commands(self
):
121 class Selections(object):
123 A selected set of components which will make up a complete program.
124 @ivar interface: the interface of the program
126 @ivar command: the command to run on 'interface'
128 @ivar selections: the selected implementations
129 @type selections: {str: L{Selection}}
131 __slots__
= ['interface', 'selections', 'command']
133 def __init__(self
, source
):
135 @param source: a map of implementations, policy or selections document
136 @type source: L{Element}
142 # (Solver will fill everything in)
144 elif isinstance(source
, Policy
):
146 warnings
.warn("Use policy.solver.selections instead", DeprecationWarning, 2)
147 self
._init
_from
_policy
(source
)
148 elif isinstance(source
, Element
):
149 self
._init
_from
_qdom
(source
)
151 raise Exception(_("Source not a qdom.Element!"))
153 def _init_from_policy(self
, policy
):
154 """Set the selections from a policy.
155 @deprecated: use Solver.selections instead
156 @param policy: the policy giving the selected implementations."""
157 self
.interface
= policy
.root
158 self
.selections
= policy
.solver
.selections
.selections
159 self
.commands
= policy
.solver
.selections
.commands
161 def _init_from_qdom(self
, root
):
162 """Parse and load a selections document.
163 @param root: a saved set of selections."""
164 self
.interface
= root
.getAttribute('interface')
165 self
.command
= root
.getAttribute('command')
166 assert self
.interface
169 for selection
in root
.childNodes
:
170 if selection
.uri
!= XMLNS_IFACE
:
172 if selection
.name
!= 'selection':
173 if selection
.name
== 'command':
174 old_commands
.append(Command(selection
, None))
181 for elem
in selection
.childNodes
:
182 if elem
.uri
!= XMLNS_IFACE
:
184 if elem
.name
in binding_names
:
185 bindings
.append(process_binding(elem
))
186 elif elem
.name
== 'requires':
187 dep
= process_depends(elem
, None)
189 elif elem
.name
== 'manifest-digest':
190 for aname
, avalue
in elem
.attrs
.iteritems():
191 digests
.append('%s=%s' % (aname
, avalue
))
192 elif elem
.name
== 'command':
193 name
= elem
.getAttribute('name')
194 assert name
, "Missing name attribute on <command>"
195 commands
[name
] = Command(elem
, None)
197 # For backwards compatibility, allow getting the digest from the ID
198 sel_id
= selection
.attrs
['id']
199 local_path
= selection
.attrs
.get("local-path", None)
200 if (not digests
and not local_path
) and '=' in sel_id
:
201 alg
= sel_id
.split('=', 1)[0]
202 if alg
in ('sha1', 'sha1new', 'sha256'):
203 digests
.append(sel_id
)
205 iface_uri
= selection
.attrs
['interface']
207 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
, commands
)
208 self
.selections
[iface_uri
] = s
210 if self
.command
is None:
211 # Old style selections document
213 # 0launch 0.52 to 1.1
215 iface
= self
.interface
218 for command
in old_commands
:
219 command
.qdom
.attrs
['name'] = 'run'
220 self
.selections
[iface
].commands
['run'] = command
221 runner
= command
.get_runner()
223 iface
= runner
.interface
228 root_sel
= self
.selections
[self
.interface
]
229 main
= root_sel
.attrs
.get('main', None)
231 root_sel
.commands
['run'] = Command(Element(XMLNS_IFACE
, 'command', {'path': main
, 'name': 'run'}), None)
234 elif self
.command
== '':
235 # New style, but no command requested
237 assert not old_commands
, "<command> list in new-style selections document!"
240 """Create a DOM document for the selected implementations.
241 The document gives the URI of the root, plus each selected implementation.
242 For each selected implementation, we record the ID, the version, the URI and
243 (if different) the feed URL. We also record all the bindings needed.
244 @return: a new DOM Document"""
245 from xml
.dom
import minidom
, XMLNS_NAMESPACE
247 assert self
.interface
249 impl
= minidom
.getDOMImplementation()
251 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
253 root
= doc
.documentElement
254 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
256 root
.setAttributeNS(None, 'interface', self
.interface
)
258 root
.setAttributeNS(None, 'command', self
.command
or "")
260 prefixes
= Prefixes(XMLNS_IFACE
)
262 for iface
, selection
in sorted(self
.selections
.items()):
263 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
264 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
265 root
.appendChild(selection_elem
)
267 for name
, value
in selection
.attrs
.iteritems():
269 ns
, localName
= name
.split(' ', 1)
270 prefixes
.setAttributeNS(selection_elem
, ns
, localName
, value
)
271 elif name
== 'stability':
273 elif name
== 'from-feed':
274 # Don't bother writing from-feed attr if it's the same as the interface
275 if value
!= selection
.attrs
['interface']:
276 selection_elem
.setAttributeNS(None, name
, value
)
277 elif name
not in ('main', 'self-test'): # (replaced by <command>)
278 selection_elem
.setAttributeNS(None, name
, value
)
280 if selection
.digests
:
281 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
282 for digest
in selection
.digests
:
283 aname
, avalue
= digest
.split('=', 1)
284 assert ':' not in aname
285 manifest_digest
.setAttribute(aname
, avalue
)
286 selection_elem
.appendChild(manifest_digest
)
288 for b
in selection
.bindings
:
289 selection_elem
.appendChild(b
._toxml
(doc
, prefixes
))
291 for dep
in selection
.dependencies
:
292 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
293 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
294 selection_elem
.appendChild(dep_elem
)
296 for m
in dep
.metadata
:
297 parts
= m
.split(' ', 1)
301 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
303 ns
, localName
= parts
304 prefixes
.setAttributeNS(dep_elem
, ns
, localName
, dep
.metadata
[m
])
306 for b
in dep
.bindings
:
307 dep_elem
.appendChild(b
._toxml
(doc
, prefixes
))
309 for command
in selection
.get_commands().values():
310 selection_elem
.appendChild(command
._toxml
(doc
, prefixes
))
312 for ns
, prefix
in prefixes
.prefixes
.items():
313 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
318 return "Selections for " + self
.interface
320 def download_missing(self
, config
, _old
= None):
321 """Check all selected implementations are available.
322 Download any that are not present.
323 Note: package implementations (distribution packages) are ignored.
324 @param config: used to get iface_cache, stores and fetcher
325 @return: a L{tasks.Blocker} or None"""
326 from zeroinstall
.zerostore
import NotStored
329 config
= get_deprecated_singleton_config()
331 iface_cache
= config
.iface_cache
332 stores
= config
.stores
334 # Check that every required selection is cached
335 needed_downloads
= []
336 for sel
in self
.selections
.values():
337 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
339 stores
.lookup_any(sel
.digests
)
341 needed_downloads
.append(sel
)
342 if not needed_downloads
:
345 if config
.network_use
== model
.network_offline
:
346 from zeroinstall
import NeedDownload
347 raise NeedDownload(', '.join([str(x
) for x
in needed_downloads
]))
351 # We're missing some. For each one, get the feed it came from
352 # and find the corresponding <implementation> in that. This will
353 # tell us where to get it from.
354 # Note: we look for an implementation with the same ID. Maybe we
355 # should check it has the same digest(s) too?
357 for sel
in needed_downloads
:
358 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
359 feed
= iface_cache
.get_feed(feed_url
)
360 if feed
is None or sel
.id not in feed
.implementations
:
361 fetch_feed
= config
.fetcher
.download_and_import_feed(feed_url
, iface_cache
)
363 tasks
.check(fetch_feed
)
365 feed
= iface_cache
.get_feed(feed_url
)
366 assert feed
, "Failed to get feed for %s" % feed_url
367 impl
= feed
.implementations
[sel
.id]
368 needed_impls
.append(impl
)
370 fetch_impls
= config
.fetcher
.download_impls(needed_impls
, stores
)
372 tasks
.check(fetch_impls
)
375 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
377 def __getitem__(self
, key
):
379 if isinstance(key
, basestring
):
380 return self
.selections
[key
]
381 sel
= self
.selections
[key
.uri
]
382 return sel
and sel
.impl
386 iface_cache
= get_deprecated_singleton_config().iface_cache
387 for (uri
, sel
) in self
.selections
.iteritems():
388 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
392 for (uri
, sel
) in self
.selections
.iteritems():
393 yield sel
and sel
.impl
397 iface_cache
= get_deprecated_singleton_config().iface_cache
398 for (uri
, sel
) in self
.selections
.iteritems():
399 yield iface_cache
.get_interface(uri
)
401 def get(self
, iface
, if_missing
):
403 sel
= self
.selections
.get(iface
.uri
, None)
411 s
.interface
= self
.interface
412 s
.selections
= self
.selections
.copy()
417 return list(self
.iteritems())
425 sel
= self
.selections
[i
]
426 command
= sel
.get_command(c
)
428 commands
.append(command
)
430 runner
= command
.get_runner()
434 i
= runner
.metadata
['interface']
435 c
= runner
.qdom
.attrs
.get('command', 'run')