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
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 return self
.commands
[name
]
116 def get_commands(self
):
119 class Selections(object):
121 A selected set of components which will make up a complete program.
122 @ivar interface: the interface of the program
124 @ivar command: the command to run on 'interface'
126 @ivar selections: the selected implementations
127 @type selections: {str: L{Selection}}
129 __slots__
= ['interface', 'selections', 'command']
131 def __init__(self
, source
):
133 @param source: a map of implementations, policy or selections document
134 @type source: L{Element}
140 # (Solver will fill everything in)
142 elif isinstance(source
, Policy
):
144 warnings
.warn("Use policy.solver.selections instead", DeprecationWarning, 2)
145 self
._init
_from
_policy
(source
)
146 elif isinstance(source
, Element
):
147 self
._init
_from
_qdom
(source
)
149 raise Exception(_("Source not a qdom.Element!"))
151 def _init_from_policy(self
, policy
):
152 """Set the selections from a policy.
153 @deprecated: use Solver.selections instead
154 @param policy: the policy giving the selected implementations."""
155 self
.interface
= policy
.root
156 self
.selections
= policy
.solver
.selections
.selections
157 self
.commands
= policy
.solver
.selections
.commands
159 def _init_from_qdom(self
, root
):
160 """Parse and load a selections document.
161 @param root: a saved set of selections."""
162 self
.interface
= root
.getAttribute('interface')
163 self
.command
= root
.getAttribute('command')
164 assert self
.interface
167 for selection
in root
.childNodes
:
168 if selection
.uri
!= XMLNS_IFACE
:
170 if selection
.name
!= 'selection':
171 if selection
.name
== 'command':
172 old_commands
.append(Command(selection
, None))
179 for elem
in selection
.childNodes
:
180 if elem
.uri
!= XMLNS_IFACE
:
182 if elem
.name
in binding_names
:
183 bindings
.append(process_binding(elem
))
184 elif elem
.name
== 'requires':
185 dep
= process_depends(elem
, None)
187 elif elem
.name
== 'manifest-digest':
188 for aname
, avalue
in elem
.attrs
.iteritems():
189 digests
.append('%s=%s' % (aname
, avalue
))
190 elif elem
.name
== 'command':
191 commands
[elem
.getAttribute('name')] = Command(elem
, None)
193 # For backwards compatibility, allow getting the digest from the ID
194 sel_id
= selection
.attrs
['id']
195 local_path
= selection
.attrs
.get("local-path", None)
196 if (not digests
and not local_path
) and '=' in sel_id
:
197 alg
= sel_id
.split('=', 1)[0]
198 if alg
in ('sha1', 'sha1new', 'sha256'):
199 digests
.append(sel_id
)
201 iface_uri
= selection
.attrs
['interface']
203 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
, commands
)
204 self
.selections
[iface_uri
] = s
206 if self
.command
is None:
207 # Old style selections document
209 # 0launch 0.52 to 1.1
211 iface
= self
.interface
214 for command
in old_commands
:
215 command
.qdom
.attrs
['name'] = 'run'
216 self
.selections
[iface
].commands
['run'] = command
217 runner
= command
.get_runner()
219 iface
= runner
.interface
224 root_sel
= self
.selections
[self
.interface
]
225 main
= root_sel
.attrs
.get('main', None)
227 root_sel
.commands
['run'] = Command(Element(XMLNS_IFACE
, 'command', {'path': main
}), None)
230 elif self
.command
== '':
231 # New style, but no command requested
233 assert not old_commands
, "<command> list in new-style selections document!"
236 """Create a DOM document for the selected implementations.
237 The document gives the URI of the root, plus each selected implementation.
238 For each selected implementation, we record the ID, the version, the URI and
239 (if different) the feed URL. We also record all the bindings needed.
240 @return: a new DOM Document"""
241 from xml
.dom
import minidom
, XMLNS_NAMESPACE
243 assert self
.interface
245 impl
= minidom
.getDOMImplementation()
247 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
249 root
= doc
.documentElement
250 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
252 root
.setAttributeNS(None, 'interface', self
.interface
)
254 root
.setAttributeNS(None, 'command', self
.command
or "")
256 prefixes
= Prefixes(XMLNS_IFACE
)
258 for iface
, selection
in sorted(self
.selections
.items()):
259 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
260 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
261 root
.appendChild(selection_elem
)
263 for name
, value
in selection
.attrs
.iteritems():
265 ns
, localName
= name
.split(' ', 1)
266 prefixes
.setAttributeNS(selection_elem
, ns
, localName
, value
)
267 elif name
== 'stability':
269 elif name
== 'from-feed':
270 # Don't bother writing from-feed attr if it's the same as the interface
271 if value
!= selection
.attrs
['interface']:
272 selection_elem
.setAttributeNS(None, name
, value
)
273 elif name
not in ('main', 'self-test'): # (replaced by <command>)
274 selection_elem
.setAttributeNS(None, name
, value
)
276 if selection
.digests
:
277 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
278 for digest
in selection
.digests
:
279 aname
, avalue
= digest
.split('=', 1)
280 assert ':' not in aname
281 manifest_digest
.setAttribute(aname
, avalue
)
282 selection_elem
.appendChild(manifest_digest
)
284 for b
in selection
.bindings
:
285 selection_elem
.appendChild(b
._toxml
(doc
, prefixes
))
287 for dep
in selection
.dependencies
:
288 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
289 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
290 selection_elem
.appendChild(dep_elem
)
292 for m
in dep
.metadata
:
293 parts
= m
.split(' ', 1)
297 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
299 ns
, localName
= parts
300 prefixes
.setAttributeNS(dep_elem
, ns
, localName
, dep
.metadata
[m
])
302 for b
in dep
.bindings
:
303 dep_elem
.appendChild(b
._toxml
(doc
, prefixes
))
305 for command
in selection
.get_commands().values():
306 selection_elem
.appendChild(command
._toxml
(doc
, prefixes
))
308 for ns
, prefix
in prefixes
.prefixes
.items():
309 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
314 return "Selections for " + self
.interface
316 def download_missing(self
, config
, _old
= None):
317 """Check all selected implementations are available.
318 Download any that are not present.
319 Note: package implementations (distribution packages) are ignored.
320 @param config: used to get iface_cache, stores and fetcher
321 @return: a L{tasks.Blocker} or None"""
322 from zeroinstall
.zerostore
import NotStored
325 config
= get_deprecated_singleton_config()
327 iface_cache
= config
.iface_cache
328 stores
= config
.stores
330 # Check that every required selection is cached
331 needed_downloads
= []
332 for sel
in self
.selections
.values():
333 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
335 stores
.lookup_any(sel
.digests
)
337 needed_downloads
.append(sel
)
338 if not needed_downloads
:
341 if config
.network_use
== model
.network_offline
:
342 from zeroinstall
import NeedDownload
343 raise NeedDownload(', '.join([str(x
) for x
in needed_downloads
]))
347 # We're missing some. For each one, get the feed it came from
348 # and find the corresponding <implementation> in that. This will
349 # tell us where to get it from.
350 # Note: we look for an implementation with the same ID. Maybe we
351 # should check it has the same digest(s) too?
353 for sel
in needed_downloads
:
354 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
355 feed
= iface_cache
.get_feed(feed_url
)
356 if feed
is None or sel
.id not in feed
.implementations
:
357 fetch_feed
= config
.fetcher
.download_and_import_feed(feed_url
, iface_cache
)
359 tasks
.check(fetch_feed
)
361 feed
= iface_cache
.get_feed(feed_url
)
362 assert feed
, "Failed to get feed for %s" % feed_url
363 impl
= feed
.implementations
[sel
.id]
364 needed_impls
.append(impl
)
366 fetch_impls
= config
.fetcher
.download_impls(needed_impls
, stores
)
368 tasks
.check(fetch_impls
)
371 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
373 def __getitem__(self
, key
):
375 if isinstance(key
, basestring
):
376 return self
.selections
[key
]
377 sel
= self
.selections
[key
.uri
]
378 return sel
and sel
.impl
382 iface_cache
= get_deprecated_singleton_config().iface_cache
383 for (uri
, sel
) in self
.selections
.iteritems():
384 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
388 for (uri
, sel
) in self
.selections
.iteritems():
389 yield sel
and sel
.impl
393 iface_cache
= get_deprecated_singleton_config().iface_cache
394 for (uri
, sel
) in self
.selections
.iteritems():
395 yield iface_cache
.get_interface(uri
)
397 def get(self
, iface
, if_missing
):
399 sel
= self
.selections
.get(iface
.uri
, None)
407 s
.interface
= self
.interface
408 s
.selections
= self
.selections
.copy()
413 return list(self
.iteritems())
421 sel
= self
.selections
[i
]
422 command
= sel
.get_command(c
)
424 commands
.append(command
)
426 runner
= command
.get_runner()
430 i
= runner
.metadata
['interface']
431 c
= runner
.qdom
.attrs
.get('command', 'run')