Package zeroinstall :: Package injector :: Module model
[frames] | no frames]

Source Code for Module zeroinstall.injector.model

  1  """In-memory representation of interfaces and other data structures. 
  2   
  3  The objects in this module are used to build a representation of an XML interface 
  4  file in memory. 
  5   
  6  @see: L{reader} constructs these data-structures 
  7  @see: U{http://0install.net/interface-spec.html} description of the domain model 
  8   
  9  @var defaults: Default values for the 'default' attribute for <environment> bindings of 
 10  well-known variables. 
 11  """ 
 12   
 13  # Copyright (C) 2006, Thomas Leonard 
 14  # See the README file for details, or visit http://0install.net. 
 15   
 16  import os, re 
 17  from logging import warn, info, debug 
 18  from zeroinstall import SafeException, version 
 19  from zeroinstall.injector.namespaces import XMLNS_IFACE 
 20   
 21  # Element names for bindings in feed files 
 22  binding_names = frozenset(['environment']) 
 23   
 24  network_offline = 'off-line' 
 25  network_minimal = 'minimal' 
 26  network_full = 'full' 
 27  network_levels = (network_offline, network_minimal, network_full) 
 28   
 29  stability_levels = {}   # Name -> Stability 
 30   
 31  defaults = { 
 32          'PATH': '/bin:/usr/bin', 
 33          'XDG_CONFIG_DIRS': '/etc/xdg', 
 34          'XDG_DATA_DIRS': '/usr/local/share:/usr/share', 
 35  } 
 36   
37 -class InvalidInterface(SafeException):
38 """Raised when parsing an invalid feed."""
39 - def __init__(self, message, ex = None):
40 if ex: 41 message += "\n\n(exact error: %s)" % ex 42 SafeException.__init__(self, message)
43
44 -def _split_arch(arch):
45 """Split an arch into an (os, machine) tuple. Either or both parts may be None.""" 46 if not arch: 47 return None, None 48 elif '-' not in arch: 49 raise SafeException("Malformed arch '%s'" % arch) 50 else: 51 os, machine = arch.split('-', 1) 52 if os == '*': os = None 53 if machine == '*': machine = None 54 return os, machine
55
56 -def _join_arch(os, machine):
57 if os == machine == None: return None 58 return "%s-%s" % (os or '*', machine or '*')
59
60 -class Stability(object):
61 """A stability rating. Each implementation has an upstream stability rating and, 62 optionally, a user-set rating.""" 63 __slots__ = ['level', 'name', 'description']
64 - def __init__(self, level, name, description):
65 self.level = level 66 self.name = name 67 self.description = description 68 assert name not in stability_levels 69 stability_levels[name] = self
70
71 - def __cmp__(self, other):
72 return cmp(self.level, other.level)
73
74 - def __str__(self):
75 return self.name
76
77 - def __repr__(self):
78 return "<Stability: " + self.description + ">"
79
80 -def process_binding(e):
81 """Internal""" 82 if e.name == 'environment': 83 mode = { 84 None: EnvironmentBinding.PREPEND, 85 'prepend': EnvironmentBinding.PREPEND, 86 'append': EnvironmentBinding.APPEND, 87 'replace': EnvironmentBinding.REPLACE, 88 }[e.getAttribute('mode')] 89 90 binding = EnvironmentBinding(e.getAttribute('name'), 91 insert = e.getAttribute('insert'), 92 default = e.getAttribute('default'), 93 mode = mode) 94 if not binding.name: raise InvalidInterface("Missing 'name' in binding") 95 if binding.insert is None: raise InvalidInterface("Missing 'insert' in binding") 96 return binding 97 else: 98 raise Exception("Unknown binding type '%s'" % e.name)
99
100 -def process_depends(item):
101 """Internal""" 102 # Note: also called from selections 103 dep_iface = item.getAttribute('interface') 104 if not dep_iface: 105 raise InvalidInterface("Missing 'interface' on <requires>") 106 dependency = InterfaceDependency(dep_iface, metadata = item.attrs) 107 108 for e in item.childNodes: 109 if e.uri != XMLNS_IFACE: continue 110 if e.name in binding_names: 111 dependency.bindings.append(process_binding(e)) 112 elif e.name == 'version': 113 dependency.restrictions.append( 114 VersionRangeRestriction(not_before = parse_version(e.getAttribute('not-before')), 115 before = parse_version(e.getAttribute('before')))) 116 return dependency
117 118 119 insecure = Stability(0, 'insecure', 'This is a security risk') 120 buggy = Stability(5, 'buggy', 'Known to have serious bugs') 121 developer = Stability(10, 'developer', 'Work-in-progress - bugs likely') 122 testing = Stability(20, 'testing', 'Stability unknown - please test!') 123 stable = Stability(30, 'stable', 'Tested - no serious problems found') 124 packaged = Stability(35, 'packaged', 'Supplied by the local package manager') 125 preferred = Stability(40, 'preferred', 'Best of all - must be set manually') 126
127 -class Restriction(object):
128 """A Restriction limits the allowed implementations of an Interface.""" 129 __slots__ = [] 130
131 - def meets_restriction(self, impl):
132 raise NotImplementedError("Abstract")
133
134 -class VersionRangeRestriction(Restriction):
135 """Only versions within the given range are acceptable""" 136 __slots__ = ['before', 'not_before']
137 - def __init__(self, before, not_before):
138 self.before = before 139 self.not_before = not_before
140
141 - def meets_restriction(self, impl):
142 if self.not_before and impl.version < self.not_before: 143 return False 144 if self.before and impl.version >= self.before: 145 return False 146 return True
147
148 - def __str__(self):
149 if self.not_before is not None or self.before is not None: 150 range = '' 151 if self.not_before is not None: 152 range += format_version(self.not_before) + ' <= ' 153 range += 'version' 154 if self.before is not None: 155 range += ' < ' + format_version(self.before) 156 else: 157 range = 'none' 158 return "(restriction: %s)" % range
159
160 -class Binding(object):
161 """Information about how the choice of a Dependency is made known 162 to the application being run."""
163
164 -class EnvironmentBinding(Binding):
165 """Indicate the chosen implementation using an environment variable.""" 166 __slots__ = ['name', 'insert', 'default', 'mode'] 167 168 PREPEND = 'prepend' 169 APPEND = 'append' 170 REPLACE = 'replace' 171
172 - def __init__(self, name, insert, default = None, mode = PREPEND):
173 """mode argument added in version 0.28""" 174 self.name = name 175 self.insert = insert 176 self.default = default 177 self.mode = mode
178
179 - def __str__(self):
180 return "<environ %s %s %s>" % (self.name, self.mode, self.insert)
181 182 __repr__ = __str__ 183
184 - def get_value(self, path, old_value):
185 """Calculate the new value of the environment variable after applying this binding. 186 @param path: the path to the selected implementation 187 @param old_value: the current value of the environment variable 188 @return: the new value for the environment variable""" 189 extra = os.path.join(path, self.insert) 190 191 if self.mode == EnvironmentBinding.REPLACE: 192 return extra 193 194 if old_value is None: 195 old_value = self.default or defaults.get(self.name, None) 196 if old_value is None: 197 return extra 198 if self.mode == EnvironmentBinding.PREPEND: 199 return extra + ':' + old_value 200 else: 201 return old_value + ':' + extra
202
203 - def _toxml(self, doc):
204 """Create a DOM element for this binding. 205 @param doc: document to use to create the element 206 @return: the new element 207 """ 208 env_elem = doc.createElementNS(XMLNS_IFACE, 'environment') 209 env_elem.setAttributeNS(None, 'name', self.name) 210 env_elem.setAttributeNS(None, 'insert', self.insert) 211 if self.default: 212 env_elem.setAttributeNS(None, 'default', self.default) 213 return env_elem
214
215 -class Feed(object):
216 """An interface's feeds are other interfaces whose implementations can also be 217 used as implementations of this interface.""" 218 __slots__ = ['uri', 'os', 'machine', 'user_override', 'langs']
219 - def __init__(self, uri, arch, user_override, langs = None):
220 self.uri = uri 221 # This indicates whether the feed comes from the user's overrides 222 # file. If true, writer.py will write it when saving. 223 self.user_override = user_override 224 self.os, self.machine = _split_arch(arch) 225 self.langs = langs
226
227 - def __str__(self):
228 return "<Feed from %s>" % self.uri
229 __repr__ = __str__ 230 231 arch = property(lambda self: _join_arch(self.os, self.machine))
232
233 -class Dependency(object):
234 """A Dependency indicates that an Implementation requires some additional 235 code to function. This is an abstract base class. 236 @ivar metadata: any extra attributes from the XML element 237 @type metadata: {str: str} 238 """ 239 __slots__ = ['metadata'] 240
241 - def __init__(self, metadata):
242 if metadata is None: 243 metadata = {} 244 else: 245 assert not isinstance(metadata, basestring) # Use InterfaceDependency instead! 246 self.metadata = metadata
247
248 -class InterfaceDependency(Dependency):
249 """A Dependency on a Zero Install interface. 250 @ivar interface: the interface required by this dependency 251 @type interface: str 252 @ivar restrictions: a list of constraints on acceptable implementations 253 @type restrictions: [L{Restriction}] 254 @ivar bindings: how to make the choice of implementation known 255 @type bindings: [L{Binding}] 256 @since: 0.28 257 """ 258 __slots__ = ['interface', 'restrictions', 'bindings', 'metadata'] 259
260 - def __init__(self, interface, restrictions = None, metadata = None):
261 Dependency.__init__(self, metadata) 262 assert isinstance(interface, (str, unicode)) 263 assert interface 264 self.interface = interface 265 if restrictions is None: 266 self.restrictions = [] 267 else: 268 self.restrictions = restrictions 269 self.bindings = []
270
271 - def __str__(self):
272 return "<Dependency on %s; bindings: %s%s>" % (self.interface, self.bindings, self.restrictions)
273
274 -class RetrievalMethod(object):
275 """A RetrievalMethod provides a way to fetch an implementation.""" 276 __slots__ = []
277
278 -class DownloadSource(RetrievalMethod):
279 """A DownloadSource provides a way to fetch an implementation.""" 280 __slots__ = ['implementation', 'url', 'size', 'extract', 'start_offset', 'type'] 281
282 - def __init__(self, implementation, url, size, extract, start_offset = 0, type = None):
283 self.implementation = implementation 284 self.url = url 285 self.size = size 286 self.extract = extract 287 self.start_offset = start_offset 288 self.type = type # MIME type - see unpack.py
289
290 -class Recipe(RetrievalMethod):
291 """Get an implementation by following a series of steps. 292 @ivar size: the combined download sizes from all the steps 293 @type size: int 294 @ivar steps: the sequence of steps which must be performed 295 @type steps: [L{RetrievalMethod}]""" 296 __slots__ = ['steps'] 297
298 - def