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(XMLNS_IFACE
)
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 prefixes
.setAttributeNS(selection_elem
, ns
, localName
, value
)
219 elif name
== 'stability':
221 elif name
== 'from-feed':
222 # Don't bother writing from-feed attr if it's the same as the interface
223 if value
!= selection
.attrs
['interface']:
224 selection_elem
.setAttributeNS(None, name
, value
)
225 elif name
not in ('main', 'self-test'): # (replaced by <command>)
226 selection_elem
.setAttributeNS(None, name
, value
)
228 if selection
.digests
:
229 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
230 for digest
in selection
.digests
:
231 aname
, avalue
= digest
.split('=', 1)
232 assert ':' not in aname
233 manifest_digest
.setAttribute(aname
, avalue
)
234 selection_elem
.appendChild(manifest_digest
)
236 for b
in selection
.bindings
:
237 selection_elem
.appendChild(b
._toxml
(doc
))
239 for dep
in selection
.dependencies
:
240 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
241 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
242 selection_elem
.appendChild(dep_elem
)
244 for m
in dep
.metadata
:
245 parts
= m
.split(' ', 1)
249 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
251 ns
, localName
= parts
252 prefixes
.setAttributeNS(dep_elem
, ns
, localName
, dep
.metadata
[m
])
254 for b
in dep
.bindings
:
255 dep_elem
.appendChild(b
._toxml
(doc
))
257 for command
in self
.commands
:
258 root
.appendChild(command
._toxml
(doc
, prefixes
))
260 for ns
, prefix
in prefixes
.prefixes
.items():
261 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
266 return "Selections for " + self
.interface
268 def download_missing(self
, config
, _old
= None):
269 """Check all selected implementations are available.
270 Download any that are not present.
271 Note: package implementations (distribution packages) are ignored.
272 @param config: used to get iface_cache, stores and fetcher
273 @return: a L{tasks.Blocker} or None"""
274 from zeroinstall
.zerostore
import NotStored
277 config
= get_deprecated_singleton_config()
279 iface_cache
= config
.iface_cache
280 stores
= config
.stores
282 # Check that every required selection is cached
283 needed_downloads
= []
284 for sel
in self
.selections
.values():
285 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
287 stores
.lookup_any(sel
.digests
)
289 needed_downloads
.append(sel
)
290 if not needed_downloads
:
293 if config
.network_use
== model
.network_offline
:
294 from zeroinstall
import NeedDownload
295 raise NeedDownload(', '.join([str(x
) for x
in needed_downloads
]))
299 # We're missing some. For each one, get the feed it came from
300 # and find the corresponding <implementation> in that. This will
301 # tell us where to get it from.
302 # Note: we look for an implementation with the same ID. Maybe we
303 # should check it has the same digest(s) too?
305 for sel
in needed_downloads
:
306 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
307 feed
= iface_cache
.get_feed(feed_url
)
308 if feed
is None or sel
.id not in feed
.implementations
:
309 fetch_feed
= config
.fetcher
.download_and_import_feed(feed_url
, iface_cache
)
311 tasks
.check(fetch_feed
)
313 feed
= iface_cache
.get_feed(feed_url
)
314 assert feed
, "Failed to get feed for %s" % feed_url
315 impl
= feed
.implementations
[sel
.id]
316 needed_impls
.append(impl
)
318 fetch_impls
= config
.fetcher
.download_impls(needed_impls
, stores
)
320 tasks
.check(fetch_impls
)
323 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
325 def __getitem__(self
, key
):
327 if isinstance(key
, basestring
):
328 return self
.selections
[key
]
329 sel
= self
.selections
[key
.uri
]
330 return sel
and sel
.impl
334 iface_cache
= get_deprecated_singleton_config().iface_cache
335 for (uri
, sel
) in self
.selections
.iteritems():
336 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
340 for (uri
, sel
) in self
.selections
.iteritems():
341 yield sel
and sel
.impl
345 iface_cache
= get_deprecated_singleton_config().iface_cache
346 for (uri
, sel
) in self
.selections
.iteritems():
347 yield iface_cache
.get_interface(uri
)
349 def get(self
, iface
, if_missing
):
351 sel
= self
.selections
.get(iface
.uri
, None)
359 s
.interface
= self
.interface
360 s
.selections
= self
.selections
.copy()
365 return list(self
.iteritems())