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

Source Code for Module zeroinstall.injector.solver

  1  """ 
  2  Chooses a set of components to make a running program. 
  3  """ 
  4   
  5  import os 
  6  from logging import debug, warn, info 
  7   
  8  from zeroinstall.zerostore import BadDigest, NotStored 
  9   
 10  from zeroinstall.injector import selections 
 11  from zeroinstall.injector import model 
 12   
 13  # Copyright (C) 2008, Thomas Leonard 
 14  # See the README file for details, or visit http://0install.net. 
 15   
16 -class Solver(object):
17 """Chooses a set of implementations to satisfy the requirements of a program and its user. 18 Typical use: 19 1. Create a Solver object and configure it 20 2. Call L{solve}. 21 3. If any of the returned feeds_used are stale or missing, you may like to start downloading them 22 4. If it is 'ready' then you can download and run the chosen versions. 23 @ivar selections: the chosen implementation of each interface 24 @type selections: {L{model.Interface}: Implementation} 25 @ivar feeds_used: the feeds which contributed to the choice in L{selections} 26 @type feeds_used: set(str) 27 @ivar record_details: whether to record information about unselected implementations 28 @type record_details: {L{Interface}: [(L{Implementation}, str)]} 29 @ivar details: extra information, if record_details mode was used 30 @type details: {str: [(Implementation, comment)]} 31 """ 32 __slots__ = ['selections', 'feeds_used', 'details', 'record_details', 'ready'] 33
34 - def __init__(self):
35 self.selections = self.feeds_used = self.details = None 36 self.record_details = False 37 self.ready = False
38
39 - def solve(self, root_interface, arch):
40 """Get the best implementation of root_interface and all of its dependencies. 41 @param root_interface: the URI of the program to be solved 42 @type root_interface: str 43 @param arch: the desired target architecture 44 @type arch: L{arch.Architecture} 45 @return: whether we have a viable selection 46 @rtype: bool 47 @postcondition: self.selections and self.feeds_used are updated""" 48 raise NotImplementedError("Abstract")
49
50 -class DefaultSolver(Solver):
51 """The standard (rather naive) Zero Install solver."""
52 - def __init__(self, network_use, iface_cache, stores, extra_restrictions = None):
53 """ 54 @param network_use: how much use to make of the network 55 @type network_use: L{model.network_levels} 56 @param iface_cache: a cache of feeds containing information about available versions 57 @type iface_cache: L{iface_cache.IfaceCache} 58 @param stores: a cached of implementations (affects choice when offline or when minimising network use) 59 @type stores: L{zerostore.Stores} 60 @param extra_restrictions: extra restrictions on the chosen implementations 61 @type extra_restrictions: {L{model.Interface}: [L{model.Restriction}]} 62 """ 63 Solver.__init__(self) 64 self.network_use = network_use 65 self.iface_cache = iface_cache 66 self.stores = stores 67 self.help_with_testing = False 68 self.extra_restrictions = extra_restrictions or {}
69
70 - def solve(self, root_interface, arch):
71 self.selections = {} 72 self.feeds_used = set() 73 self.details = self.record_details and {} 74 75 restrictions = {} 76 debug("Solve! root = %s", root_interface) 77 def process(dep, arch): 78 ready = True 79 iface = self.iface_cache.get_interface(dep.interface) 80 81 if iface in self.selections: 82 debug("Interface requested twice; skipping second %s", iface) 83 if dep.restrictions: 84 warn("Interface requested twice; I've already chosen an implementation " 85 "of '%s' but there are more restrictions! Ignoring the second set.", iface) 86 return ready 87 self.selections[iface] = None # Avoid cycles 88 89 assert iface not in restrictions 90 restrictions[iface] = dep.restrictions 91 92 impl = get_best_implementation(iface, arch) 93 if impl: 94 debug("Will use implementation %s (version %s)", impl, impl.get_version()) 95 self.selections[iface] = impl 96 for d in impl.requires: 97 debug("Considering dependency %s", d) 98 if not process(d, arch.child_arch): 99 ready = False 100 else: 101 debug("No implementation chould be chosen yet"); 102 ready = False 103 104 return ready
105 106 def get_best_implementation(iface, arch): 107 debug("get_best_implementation(%s), with feeds: %s", iface, iface.feeds) 108 109 iface_restrictions = restrictions.get(iface, []) 110 extra_restrictions = self.extra_restrictions.get(iface, None) 111 if extra_restrictions: 112 # Don't modify original 113 iface_restrictions = iface_restrictions + extra_restrictions 114 115 impls = [] 116 for f in usable_feeds(iface, arch): 117 self.feeds_used.add(f) 118 debug("Processing feed %s", f) 119 120 try: 121 feed = self.iface_cache.get_interface(f)._main_feed 122 if not feed.last_modified: continue # DummyFeed 123 if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for: 124 warn("Missing <feed-for> for '%s' in '%s'", iface.uri, f) 125 126 if feed.implementations: 127 impls.extend(feed.implementations.values()) 128 except Exception, ex: 129 warn("Failed to load feed %s for %s: %s", f, iface, str(ex)) 130 131 if not impls: 132 info("Interface %s has no implementations!", iface) 133 return None 134 135 if self.record_details: 136 # In details mode, rank all the implementations and then choose the best 137 impls.sort(lambda a, b: compare(iface, a, b, iface_restrictions, arch)) 138 best = impls[0] 139 self.details[iface] = [(impl, get_unusable_reason(impl, iface_restrictions, arch)) for impl in impls] 140 else: 141 # Otherwise, just choose the best without sorting 142 best = impls[0] 143 for x in impls[1:]: 144 if compare(iface, x, best, iface_restrictions, arch) < 0: 145 best = x 146 unusable = get_unusable_reason(best, iface_restrictions, arch) 147 if unusable: 148 info("Best implementation of %s is %s, but unusable (%s)", iface, best, unusable) 149 return None 150 return best
151 152 def compare(interface, b, a, iface_restrictions, arch): 153 """Compare a and b to see which would be chosen first. 154 @param interface: The interface we are trying to resolve, which may 155 not be the interface of a or b if they are from feeds. 156 @rtype: int""" 157 a_stab = a.get_stability() 158 b_stab = b.get_stability() 159 160 # Usable ones come first 161 r = cmp(is_unusable(b, iface_restrictions, arch), is_unusable(a, iface_restrictions, arch)) 162 if r: return r 163 164 # Preferred versions come first 165 r = cmp(a_stab == model.preferred, b_stab == model.preferred) 166 if r: return r 167 168 if self.network_use != model.network_full: 169 r = cmp(get_cached(a), get_cached(b)) 170 if r: return r 171 172 # Stability 173 stab_policy = interface.stability_policy 174 if not stab_policy: 175 if self.help_with_testing: stab_policy = model.testing 176 else: stab_policy = model.stable 177 178 if a_stab >= stab_policy: a_stab = model.preferred 179 if b_stab >= stab_policy: b_stab = model.preferred 180 181 r = cmp(a_stab, b_stab) 182 if r: return r 183 184 # Newer versions come before older ones 185 r = cmp(a.version, b.version) 186 if r: return r 187 188 # Get best OS 189 r = cmp(arch.os_ranks.get(a.os, None), 190 arch.os_ranks.get(b.os, None)) 191 if r: return r 192 193 # Get best machine 194 r = cmp(arch.machine_ranks.get(a.machine, None), 195 arch.machine_ranks.get(b.machine, None)) 196 if r: return r 197 198 # Slightly prefer cached versions 199 if self.network_use == model.network_full: 200 r = cmp(get_cached(a), get_cached(b)) 201 if r: return r 202 203 return cmp(a.id, b.id) 204 205 def usable_feeds(iface, arch): 206 """Return all feeds for iface that support arch. 207 @rtype: generator(ZeroInstallFeed)""" 208 yield iface.uri 209 210 for f in iface.feeds: 211 if f.os in arch.os_ranks and f.machine in arch.machine_ranks: 212 yield f.uri 213 else: 214 debug("Skipping '%s'; unsupported architecture %s-%s", 215 f, f.os, f.machine) 216 217 def is_unusable(impl, restrictions, arch): 218 """@return: whether this implementation is unusable. 219 @rtype: bool""" 220 return get_unusable_reason(impl, restrictions, arch) != None 221 222 def get_unusable_reason(impl, restrictions, arch): 223 """ 224 @param impl: Implementation to test. 225 @type restrictions: [L{model.Restriction}] 226 @return: The reason why this impl is unusable, or None if it's OK. 227 @rtype: str 228 @note: The restrictions are for the interface being requested, not the interface 229 of the implementation; they may be different when feeds are being used.""" 230 for r in restrictions: 231 if not r.meets_restriction(impl): 232 return "Incompatible with another selected implementation" 233 stability = impl.get_stability() 234 if stability <= model.buggy: 235 return stability.name 236 if self.network_use == model.network_offline and not get_cached(impl): 237 return "Not cached and we are off-line" 238 if impl.os not in arch.os_ranks: 239 return "Unsupported OS" 240 # When looking for source code, we need to known if we're 241 # looking at an implementation of the root interface, even if 242 # it's from a feed, hence the sneaky restrictions identity check. 243 if impl.machine not in arch.machine_ranks: 244 if impl.machine == 'src': 245 return "Source code" 246 return "Unsupported machine type" 247 return None 248 249 def get_cached(impl): 250 """Check whether an implementation is available locally. 251 @type impl: model.Implementation 252 @rtype: bool 253 """ 254 if isinstance(impl, model.DistributionImplementation): 255 return impl.installed 256 if impl.id.startswith('/'): 257 return os.path.exists(impl.id) 258 else: 259 try: 260 path = self.stores.lookup(impl.id) 261 assert path 262 return True 263 except BadDigest: 264 return False 265 except NotStored: 266 return False 267 268 self.ready = process(model.InterfaceDependency(root_interface), arch) 269