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']
61 def __init__(self
, iface_uri
, impl
, dependencies
):
64 self
.dependencies
= dependencies
66 attrs
= impl
.metadata
.copy()
68 attrs
['version'] = impl
.get_version()
69 attrs
['interface'] = iface_uri
70 attrs
['from-feed'] = impl
.feed
.url
72 attrs
['local-path'] = impl
.local_path
76 def bindings(self
): return self
.impl
.bindings
79 def digests(self
): return self
.impl
.digests
81 class XMLSelection(Selection
):
82 """A Selection created by reading an XML selections document.
83 @ivar digests: a list of manifest digests
86 __slots__
= ['bindings', 'dependencies', 'attrs', 'digests']
88 def __init__(self
, dependencies
, bindings
= None, attrs
= None, digests
= None):
89 if bindings
is None: bindings
= []
90 if digests
is None: digests
= []
91 self
.dependencies
= dependencies
92 self
.bindings
= bindings
94 self
.digests
= digests
101 class Selections(object):
103 A selected set of components which will make up a complete program.
104 @ivar interface: the interface of the program
106 @ivar commands: how to run this selection (will contain more than one item if runners are used)
107 @type commands: [{L{Command}}]
108 @ivar selections: the selected implementations
109 @type selections: {str: L{Selection}}
111 __slots__
= ['interface', 'selections', 'commands']
113 def __init__(self
, source
):
115 @param source: a map of implementations, policy or selections document
116 @type source: {str: L{Selection}} | L{Policy} | L{Element}
122 # (Solver will fill everything in)
123 elif isinstance(source
, Policy
):
124 self
._init
_from
_policy
(source
)
125 elif isinstance(source
, Element
):
126 self
._init
_from
_qdom
(source
)
128 raise Exception(_("Source not a Policy or qdom.Element!"))
130 def _init_from_policy(self
, policy
):
131 """Set the selections from a policy.
132 @deprecated: use Solver.selections instead
133 @param policy: the policy giving the selected implementations."""
134 self
.interface
= policy
.root
135 self
.selections
= policy
.solver
.selections
.selections
136 self
.commands
= policy
.solver
.selections
.commands
138 def _init_from_qdom(self
, root
):
139 """Parse and load a selections document.
140 @param root: a saved set of selections."""
141 self
.interface
= root
.getAttribute('interface')
142 assert self
.interface
145 for selection
in root
.childNodes
:
146 if selection
.uri
!= XMLNS_IFACE
:
148 if selection
.name
!= 'selection':
149 if selection
.name
== 'command':
150 self
.commands
.append(Command(selection
, None))
156 for dep_elem
in selection
.childNodes
:
157 if dep_elem
.uri
!= XMLNS_IFACE
:
159 if dep_elem
.name
in binding_names
:
160 bindings
.append(process_binding(dep_elem
))
161 elif dep_elem
.name
== 'requires':
162 dep
= process_depends(dep_elem
, None)
164 elif dep_elem
.name
== 'manifest-digest':
165 for aname
, avalue
in dep_elem
.attrs
.iteritems():
166 digests
.append('%s=%s' % (aname
, avalue
))
168 # For backwards compatibility, allow getting the digest from the ID
169 sel_id
= selection
.attrs
['id']
170 local_path
= selection
.attrs
.get("local-path", None)
171 if (not digests
and not local_path
) and '=' in sel_id
:
172 alg
= sel_id
.split('=', 1)[0]
173 if alg
in ('sha1', 'sha1new', 'sha256'):
174 digests
.append(sel_id
)
176 iface_uri
= selection
.attrs
['interface']
178 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
)
179 self
.selections
[iface_uri
] = s
181 if not self
.commands
:
182 # Old-style selections document; use the main attribute
183 if iface_uri
== self
.interface
:
184 root_sel
= self
.selections
[self
.interface
]
185 main
= root_sel
.attrs
.get('main', None)
187 self
.commands
= [Command(Element(XMLNS_IFACE
, 'command', {'path': main
}), None)]
190 """Create a DOM document for the selected implementations.
191 The document gives the URI of the root, plus each selected implementation.
192 For each selected implementation, we record the ID, the version, the URI and
193 (if different) the feed URL. We also record all the bindings needed.
194 @return: a new DOM Document"""
195 from xml
.dom
import minidom
, XMLNS_NAMESPACE
197 assert self
.interface
199 impl
= minidom
.getDOMImplementation()
201 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
203 root
= doc
.documentElement
204 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
206 root
.setAttributeNS(None, 'interface', self
.interface
)
208 prefixes
= Prefixes()
210 for iface
, selection
in sorted(self
.selections
.items()):
211 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
212 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
213 root
.appendChild(selection_elem
)
215 for name
, value
in selection
.attrs
.iteritems():
217 ns
, localName
= name
.split(' ', 1)
218 selection_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, value
)
219 elif name
== 'from-feed':
220 # Don't bother writing from-feed attr if it's the same as the interface
221 if value
!= selection
.attrs
['interface']:
222 selection_elem
.setAttributeNS(None, name
, value
)
223 elif name
not in ('main', 'self-test'): # (replaced by <command>)
224 selection_elem
.setAttributeNS(None, name
, value
)
226 if selection
.digests
:
227 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
228 for digest
in selection
.digests
:
229 aname
, avalue
= digest
.split('=', 1)
230 assert ':' not in aname
231 manifest_digest
.setAttribute(aname
, avalue
)
232 selection_elem
.appendChild(manifest_digest
)
234 for b
in selection
.bindings
:
235 selection_elem
.appendChild(b
._toxml
(doc
))
237 for dep
in selection
.dependencies
:
238 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
239 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
240 selection_elem
.appendChild(dep_elem
)
242 for m
in dep
.metadata
:
243 parts
= m
.split(' ', 1)
247 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
249 ns
, localName
= parts
250 dep_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, dep
.metadata
[m
])
252 for b
in dep
.bindings
:
253 dep_elem
.appendChild(b
._toxml
(doc
))
255 for command
in self
.commands
:
256 root
.appendChild(command
._toxml
(doc
, prefixes
))
258 for ns
, prefix
in prefixes
.prefixes
.items():
259 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
264 return "Selections for " + self
.interface
266 def download_missing(self
, config
, _old
= None):
267 """Check all selected implementations are available.
268 Download any that are not present.
269 Note: package implementations (distribution packages) are ignored.
270 @param config: used to get iface_cache, stores and fetcher
271 @return: a L{tasks.Blocker} or None"""
272 from zeroinstall
.zerostore
import NotStored
275 config
= get_deprecated_singleton_config()
277 iface_cache
= config
.iface_cache
278 stores
= config
.stores
280 # Check that every required selection is cached
281 needed_downloads
= []
282 for sel
in self
.selections
.values():
283 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
285 stores
.lookup_any(sel
.digests
)
287 needed_downloads
.append(sel
)
288 if not needed_downloads
:
291 if config
.network_use
== model
.network_offline
:
292 from zeroinstall
import NeedDownload
293 raise NeedDownload(', '.join([str(x
) for x
in needed_downloads
]))
297 # We're missing some. For each one, get the feed it came from
298 # and find the corresponding <implementation> in that. This will
299 # tell us where to get it from.
300 # Note: we look for an implementation with the same ID. Maybe we
301 # should check it has the same digest(s) too?
303 for sel
in needed_downloads
:
304 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
305 feed
= iface_cache
.get_feed(feed_url
)
306 if feed
is None or sel
.id not in feed
.implementations
:
307 fetch_feed
= config
.fetcher
.download_and_import_feed(feed_url
, iface_cache
)
309 tasks
.check(fetch_feed
)
311 feed
= iface_cache
.get_feed(feed_url
)
312 assert feed
, "Failed to get feed for %s" % feed_url
313 impl
= feed
.implementations
[sel
.id]
314 needed_impls
.append(impl
)
316 fetch_impls
= config
.fetcher
.download_impls(needed_impls
, stores
)
318 tasks
.check(fetch_impls
)
321 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
323 def __getitem__(self
, key
):
325 if isinstance(key
, basestring
):
326 return self
.selections
[key
]
327 sel
= self
.selections
[key
.uri
]
328 return sel
and sel
.impl
332 iface_cache
= get_deprecated_singleton_config().iface_cache
333 for (uri
, sel
) in self
.selections
.iteritems():
334 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
338 for (uri
, sel
) in self
.selections
.iteritems():
339 yield sel
and sel
.impl
343 iface_cache
= get_deprecated_singleton_config().iface_cache
344 for (uri
, sel
) in self
.selections
.iteritems():
345 yield iface_cache
.get_interface(uri
)
347 def get(self
, iface
, if_missing
):
349 sel
= self
.selections
.get(iface
.uri
, None)
357 s
.interface
= self
.interface
358 s
.selections
= self
.selections
.copy()
363 return list(self
.iteritems())