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  # Copyright (C) 2009, Thomas Leonard 
   6  # See the README file for details, or visit http://0install.net. 
   7   
   8  from zeroinstall import _, logger 
   9  import locale 
  10  import collections 
  11   
  12  from zeroinstall.injector.reader import MissingLocalFeed 
  13  from zeroinstall.injector import model, sat, selections, arch, qdom 
14 15 -class CommandInfo(object):
16 - def __init__(self, name, command, impl, arch):
17 """@type name: str 18 @type command: L{zeroinstall.injector.model.Command} 19 @type impl: L{zeroinstall.injector.model.Implementation} 20 @type arch: L{zeroinstall.injector.arch.Architecture}""" 21 self.name = name 22 self.command = command 23 self.impl = impl 24 self.arch = arch
25
26 - def __repr__(self):
27 """@rtype: str""" 28 name = "%s_%s_%s_%s" % (self.impl.feed.get_name(), self.impl.get_version(), self.impl.arch, self.name) 29 return name.replace('-', '_').replace('.', '_')
30
31 -class ImplInfo(object):
32 is_dummy = False 33
34 - def __init__(self, iface, impl, arch, dummy = False):
35 """@type iface: L{zeroinstall.injector.model.Interface} 36 @type impl: L{zeroinstall.injector.model.Implementation} | L{_DummyImpl} 37 @type arch: L{zeroinstall.injector.arch.Architecture} 38 @type dummy: bool""" 39 self.iface = iface 40 self.impl = impl 41 self.arch = arch 42 if dummy: 43 self.is_dummy = True
44
45 - def __repr__(self):
46 """@rtype: str""" 47 name = "%s_%s_%s" % (self.impl.feed.get_name(), self.impl.get_version(), self.impl.arch) 48 return name.replace('-', '_').replace('.', '_')
49
50 -class _DummyImpl(object):
51 requires = [] 52 version = None 53 arch = None 54 commands = {} 55
56 - def __repr__(self):
57 return "dummy"
58 59 feed = property(lambda self: self) 60
61 - def get_version(self):
62 """@rtype: str""" 63 return "dummy"
64
65 - def get_name(self):
66 """@rtype: str""" 67 return "dummy"
68
69 -class _ForceImpl(model.Restriction):
70 """Used by L{SATSolver.justify_decision}.""" 71 72 reason = "Excluded by justify_decision" # not shown to user 73
74 - def __init__(self, impl):
75 """@type impl: L{zeroinstall.injector.model.Implementation}""" 76 self.impl = impl
77
78 - def meets_restriction(self, impl):
79 """@type impl: L{zeroinstall.injector.model.Implementation} 80 @rtype: bool""" 81 return impl.id == self.impl.id
82
83 - def __str__(self):
84 """@rtype: str""" 85 return _("implementation {version} ({impl})").format(version = self.impl.get_version(), impl = self.impl.id)
86
87 -class Solver(object):
88 """Chooses a set of implementations to satisfy the requirements of a program and its user. 89 Typical use: 90 1. Create a Solver object and configure it 91 2. Call L{solve}. 92 3. If any of the returned feeds_used are stale or missing, you may like to start downloading them 93 4. If it is 'ready' then you can download and run the chosen versions. 94 @ivar selections: the chosen implementation of each interface 95 @type selections: L{selections.Selections} 96 @ivar requires: the selected dependencies for each chosen version 97 @type requires: {L{model.Interface}: [L{model.Dependency}]} 98 @ivar feeds_used: the feeds which contributed to the choice in L{selections} 99 @type feeds_used: set(str) 100 @ivar record_details: whether to record information about unselected implementations 101 @type record_details: {L{Interface}: [(L{Implementation}, str)]} 102 @ivar details: extra information, if record_details mode was used 103 @type details: {str: [(Implementation, comment)]} 104 """ 105 __slots__ = ['selections', 'requires', 'feeds_used', 'details', 'record_details', 'ready'] 106
107 - def __init__(self):
108 self.selections = self.requires = self.feeds_used = self.details = None 109 self.record_details = False 110 self.ready = False
111
112 - def solve_for(self, requirements):
113 """Solve for given requirements. 114 @param requirements: the interface, architecture and command to solve for 115 @type requirements: L{requirements.Requirements} 116 @postcondition: self.ready, self.selections and self.feeds_used are updated 117 @since: 1.8""" 118 return self.solve(requirements.interface_uri, self.get_arch_for(requirements), requirements.command)
119
120 - def solve(self, root_interface, root_arch, command_name = 'run'):
121 """Get the best implementation of root_interface and all of its dependencies. 122 @param root_interface: the URI of the program to be solved 123 @type root_interface: str 124 @param root_arch: the desired target architecture 125 @type root_arch: L{arch.Architecture} 126 @param command_name: which <command> element to select 127 @type command_name: str | None 128 @postcondition: self.ready, self.selections and self.feeds_used are updated""" 129 raise NotImplementedError("Abstract")
130
131 - def get_arch_for(self, requirements, interface = None):
132 """Return the Architecture we would use when solving for this interface. 133 Normally, this architecture is constructed from the OS and CPU type in the requirements, 134 using the host platform's settings if these are not given. 135 If interface is the root, then we wrap this in a SourceArchitecture if looking 136 for source code and (for backwards compatibility) we enable use="testing" dependencies 137 if the command is "test". 138 @param requirements: the overall requirements for the solve 139 @type requirements: L{requirements.Requirements} 140 @param interface: the interface of interest 141 @type interface: L{model.Interface} 142 @return: the architecture that would be used 143 @rtype: L{architecture.Architecture} 144 @since: 1.9""" 145 root_arch = arch.get_architecture(requirements.os, requirements.cpu) 146 if interface is None or interface.uri == requirements.interface_uri: 147 if requirements.source: 148 root_arch = arch.SourceArchitecture(root_arch) 149 if requirements.command == 'test': 150 # This is for old feeds that have use='testing' instead of the newer 151 # 'test' command for giving test-only dependencies. 152 root_arch = arch.Architecture(root_arch.os_ranks, root_arch.machine_ranks) 153 root_arch.use = frozenset([None, "testing"]) 154 return root_arch 155 # Assume we use the same arch for all descendants 156 return root_arch.child_arch
157
158 -class SATSolver(Solver):
159 """Converts the problem to a set of pseudo-boolean constraints and uses a PB solver to solve them. 160 @ivar langs: the preferred languages (e.g. ["es_ES", "en"]). Initialised to the current locale. 161 @type langs: str""" 162 163 __slots__ = ['_impls_for_iface', '_iface_to_vars', '_problem', 'config', 'extra_restrictions', '_lang_ranks', '_langs'] 164 165 @property
166 - def iface_cache(self):
167 return self.config.iface_cache # (deprecated; used by 0compile)
168
169 - def __init__(self, config, extra_restrictions = None):
170 """@param config: policy preferences (e.g. stability), the iface_cache and the stores to use 171 @type config: L{policy.Config} 172 @param extra_restrictions: extra restrictions on the chosen implementations 173 @type extra_restrictions: {L{model.Interface}: [L{model.Restriction}]}""" 174 Solver.__init__(self) 175 assert not isinstance(config, str), "API change!" 176 self.config = config 177 self.extra_restrictions = extra_restrictions or {} 178 179 # By default, prefer the current locale's language first and English second 180 self.langs = [locale.getlocale()[0] or 'en', 'en']
181
182 - def set_langs(self, langs):
183 """Set the preferred languages. 184 @param langs: languages (and regions), first choice first 185 @type langs: [str]""" 186 # _lang_ranks is a map from locale string to score (higher is better) 187 _lang_ranks = {} 188 score = 0 189 i = len(langs) 190 # (is there are duplicates, the first occurance takes precedence) 191 while i > 0: 192 i -= 1 193 lang = langs[i].replace('_', '-') 194 _lang_ranks[lang.split('-')[0]] = score 195 _lang_ranks[lang] = score + 1 196 score += 2 197 self._langs = langs 198 self._lang_ranks = _lang_ranks
199 200 langs = property(lambda self: self._langs, set_langs) 201
202 - def get_rating(self, interface, impl, arch):
203 """@type interface: L{zeroinstall.injector.model.Interface} 204 @type impl: L{zeroinstall.injector.model.Implementation} 205 @type arch: L{zeroinstall.injector.arch.Architecture} 206 @rtype: [object]""" 207 impl_langs = (impl.langs or 'en').split() 208 my_langs = self._lang_ranks 209 210 stores = self.config.stores 211 is_available = impl.is_available(stores) 212 213 # Stability 214 stab_policy = interface.stability_policy 215 if not stab_policy: 216 if self.config.help_with_testing: stab_policy = model.testing 217 else: stab_policy = model.stable 218 219 stability = impl.get_stability() 220 if stability >= stab_policy: 221 stability_limited = model.preferred 222 else: 223 stability_limited = stability 224 225 # Note: this list must match _ranking_component_reason above 226 return [ 227 # Languages we understand come first 228 max(my_langs.get(l.split('-')[0], -1) for l in impl_langs), 229 230 # Preferred versions come first 231 stability == model.preferred, 232 233 # Prefer available implementations next if we have limited network access 234 self.config.network_use != model.network_full and is_available, 235 236 # Packages that require admin access to install come last 237 not impl.requires_root_install, 238 239 # Prefer more stable versions, but treat everything over stab_policy the same 240 # (so we prefer stable over testing if the policy is to prefer "stable", otherwise 241 # we don't care) 242 stability_limited, 243 244 # Newer versions come before older ones (ignoring modifiers) 245 impl.version[0], 246 247 # Prefer native packages if the main part of the versions are the same 248 impl.id.startswith('package:'), 249 250 # Full version compare (after package check, since comparing modifiers between native and non-native 251 # packages doesn't make sense). 252 impl.version, 253 254 # Get best OS 255 -arch.os_ranks.get(impl.os, 999), 256 257 # Get best machine 258 -arch.machine_ranks.get(impl.machine, 999), 259 260 # Slightly prefer languages specialised to our country 261 # (we know a and b have the same base language at this point) 262 max(my_langs.get(l, -1) for l in impl_langs), 263 264 # Slightly prefer cached versions 265 is_available, 266 267 # Order by ID so the order isn't random 268 impl.id 269 ]
270
271 - def solve(self, root_interface, root_arch, command_name = 'run', closest_match = False):
272 """@type root_interface: str 273 @type root_arch: L{zeroinstall.injector.arch.Architecture} 274 @type command_name: str | None 275 @type closest_match: bool""" 276 277 # closest_match is used internally. It adds a lowest-ranked 278 # by valid implementation to every interface, so we can always 279 # select something. Useful for diagnostics. 280 281 # The basic plan is this: 282 # 1. Scan the root interface and all dependencies recursively, building up a SAT problem. 283 # 2. Solve the SAT problem. Whenever there are multiple options, try the most preferred one first. 284 # 3. Create a Selections object from the results. 285 # 286 # All three involve recursively walking the tree in a similar way: 287 # 1) we follow every dependency of every implementation (order not important) 288 # 2) we follow every dependency of every selected implementation (better versions first) 289 # 3) we follow every dependency of every selected implementation (order doesn't matter) 290 # 291 # In all cases, a dependency may be on an <implementation> or on a specific <command>. 292 293 # TODO: We need some way to figure out which feeds to include. 294 # Currently, we include any feed referenced from anywhere but 295 # this is probably too much. We could insert a dummy optimial 296 # implementation in stale/uncached feeds and see whether it 297 # selects that. 298 iface_cache = self.config.iface_cache 299 300 problem = sat.SATProblem() 301 302 # For each (interface, impl) we have a sat variable which, if true, means that we have selected 303 # that impl as the implementation of the interface. We have to index on interface and impl (not 304 # just impl) because the same feed can provide implementations for different interfaces. This 305 # happens, for example, when an interface is renamed and the new interface imports the old feed: 306 # old versions of a program are likely to use the old interface name while new ones use the new 307 # name. 308 iface_to_vars = collections.defaultdict(lambda: {}) # Iface -> (Impl -> sat var) 309 310 self.feeds_used = set() 311 self.requires = {} 312 self.ready = False 313 self.details = {} if self.record_details or closest_match else False 314 315 self.selections = None 316 317 # Only set if there is an error 318 self._impls_for_iface = None 319 self._iface_to_vars = None 320 self._problem = None 321 322 ifaces_processed = set() 323 324 machine_groups = arch.machine_groups 325 impls_for_machine_group = {0 : []} # Machine group (e.g. "64") to [impl] in that group 326 for machine_group in machine_groups.values(): 327 impls_for_machine_group[machine_group] = [] 328 329 impls_for_iface = {} # Iface -> [impl] 330 331 # For each interface, the group clause says we can't select two implementations of it at once. 332 # We use this map at the end to find out what was actually selected. 333 group_clause_for = {} # Iface URI -> AtMostOneClause 334 group_clause_for_command = {} # (Iface URI, command name) -> AtMostOneClause | bool 335 336 # Return the dependencies of impl that we should consider. 337 # Skips dependencies if the use flag isn't what we need. 338 # (note: impl may also be a model.Command) 339 def deps_in_use(impl, arch): 340 for dep in impl.requires: 341 use = dep.metadata.get("use", None) 342 if use not in arch.use: 343 continue 344 345 # Ignore dependency if 'os' attribute is present and doesn't match 346 os = dep.metadata.get("os", None) 347 if os and os not in arch.os_ranks: 348 continue 349 350 yield dep
351 352 def clone_command_for(command, arch): 353 # This is a bit messy. We need to make a copy of the command, without the 354 # unnecessary <requires> elements. 355 all_dep_elems = set(dep.qdom for dep in command.requires) 356 required_dep_elems = set(dep.qdom for dep in deps_in_use(command, arch)) 357 if all_dep_elems == required_dep_elems: 358 return command # No change 359 dep_elems_to_remove = all_dep_elems - required_dep_elems 360 old_root = command.qdom 361 new_qdom = qdom.Element(old_root.uri, old_root.name, old_root.attrs) 362 new_qdom.childNodes = [node for node in command.qdom.childNodes if 363 node not in dep_elems_to_remove] 364 return model.Command(new_qdom, command._local_dir)
365 366 # Must have already done add_iface on dependency.interface. 367 # If dependency is essential: 368 # Add a clause so that if requiring_impl_var is True then an implementation 369 # matching 'dependency' must also be selected. 370 # If dependency is optional: 371 # Require that no incompatible version is selected. 372 # This ignores any 'command' required. Handle that separately. 373 def find_dependency_candidates(requiring_impl_var, dependency): 374 def meets_restrictions(candidate): 375 for r in dependency.restrictions: 376 if not r.meets_restriction(candidate): 377 #warn("%s rejected due to %s", candidate.get_version(), r) 378 return False 379 return True 380 381 essential = dependency.importance == model.Dependency.Essential 382 383 dep_iface = iface_cache.get_interface(dependency.interface) 384 dep_union = [sat.neg(requiring_impl_var)] # Either requiring_impl_var is False, or ... 385 386 impl_to_var = iface_to_vars[dep_iface] # Impl -> sat var 387 388 for candidate in impls_for_iface[dep_iface]: 389 if (candidate.__class__ is _DummyImpl) or meets_restrictions(candidate): 390 if essential: 391 c_var = impl_to_var.get(candidate, None) 392 if c_var is not None: 393 dep_union.append(c_var) 394 # else we filtered that version out, so ignore it 395 else: 396 # Candidate doesn't meet our requirements 397 # If the dependency is optional add a rule to make sure we don't 398 # select this candidate. 399 # (for essential dependencies this isn't necessary because we must 400 # select a good version and we can't select two) 401 if not essential: 402 c_var = impl_to_var.get(candidate, None) 403 if c_var is not None: 404 problem.add_clause(dep_union + [sat.neg(c_var)]) 405 # else we filtered that version out, so ignore it 406 407 if essential: 408 problem.add_clause(dep_union) 409 410 def is_unusable(impl, restrictions, arch): 411 """@return: whether this implementation is unusable. 412 @rtype: bool""" 413 return get_unusable_reason(impl, restrictions, arch) != None 414 415 def get_unusable_reason(impl, restrictions, arch): 416 """ 417 @param impl: Implementation to test. 418 @param restrictions: user-provided restrictions (extra_restrictions) 419 @type restrictions: [L{model.Restriction}] 420 @return: The reason why this impl is unusable, or None if it's OK. 421 @rtype: str 422 @note: The restrictions are for the interface being requested, not the feed 423 of the implementation; they may be different when feeds are being used.""" 424 for r in restrictions: 425 if not r.meets_restriction(impl): 426 return r.reason 427 stability = impl.get_stability() 428 if stability <= model.buggy: 429 return stability.name 430 if (self.config.network_use == model.network_offline or not impl.download_sources) and not impl.is_available(self.config.stores): 431 if not impl.download_sources: 432 return _("No retrieval methods") 433 return _("Not cached and we are off-line") 434 if impl.os not in arch.os_ranks: 435 return _("Unsupported OS") 436 if impl.machine not in arch.machine_ranks: 437 if impl.machine == 'src': 438 return _("Source code") 439 elif 'src' in arch.machine_ranks: 440 return _("Not source code") 441 return _("Unsupported machine type") 442 return None 443 444 def usable_feeds(iface, arch): 445 """Return all feeds for iface that support arch. 446 @rtype: generator(ZeroInstallFeed)""" 447 yield iface.uri 448 449 for f in iface_cache.get_feed_imports(iface): 450 # Note: when searching for src, None is not in machine_ranks 451 if f.os in arch.os_ranks and \ 452 (f.machine is None or f.machine in arch.machine_ranks): 453 yield f.uri 454 else: 455 logger.debug(_("Skipping '%(feed)s'; unsupported architecture %(os)s-%(machine)s"), 456 {'feed': f, 'os': f.os, 'machine': f.machine}) 457 458 # If requiring_var is True then all of requirer's dependencies must be satisfied. 459 # requirer can be a <command> or an <implementation> 460 def process_dependencies(requiring_var, requirer, arch): 461 for d in deps_in_use(requirer, arch): 462 #logger.debug(_("Considering command dependency %s"), d) 463 464 add_iface(d.interface, arch.child_arch) 465 466 for c in d.get_required_commands(): 467 # We depend on a specific command within the implementation. 468 command_vars = add_command_iface(d.interface, arch.child_arch, c) 469 470 # If the parent command/impl is chosen, one of the candidate commands 471 # must be too. If there aren't any, then this command is unselectable. 472 problem.add_clause([sat.neg(requiring_var)] + command_vars) 473 474 # Must choose one version of d if impl is selected 475 find_dependency_candidates(requiring_var, d) 476 477 replacement_for = {} # Interface -> Replacement Interface 478 479 def add_iface(uri, arch): 480 """Name implementations from feed and assert that only one can be selected.""" 481 if uri in ifaces_processed: return 482 ifaces_processed.add(uri) 483 484 iface = iface_cache.get_interface(uri) 485 486 main_feed = iface_cache.get_feed(uri) 487 if main_feed: 488 replacement = main_feed.get_replaced_by() 489 if replacement is not None: 490 replacement_for[iface] = iface_cache.get_interface(replacement) 491 492 impls = [] 493 for f in usable_feeds(iface, arch): 494 self.feeds_used.add(f) 495 logger.debug(_("Processing feed %s"), f) 496 497 try: 498 feed = iface_cache.get_feed(f) 499 if feed is None: continue 500 #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for: 501 # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f}) 502 503 if feed.implementations: 504 impls.extend(feed.implementations.values()) 505 506 distro_feed_url = feed.get_distro_feed() 507 if distro_feed_url: 508 self.feeds_used.add(distro_feed_url) 509 distro_feed = iface_cache.get_feed(distro_feed_url) 510 if distro_feed.implementations: 511 impls.extend(distro_feed.implementations.values()) 512 except MissingLocalFeed as ex: 513 logger.warning(_("Missing local feed; if it's no longer required, remove it with:") + 514 '\n0install remove-feed ' + iface.uri + ' ' + f, 515 {'feed': f, 'interface': iface, 'exception': ex}) 516 except model.SafeException as ex: 517 logger.warning(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f, 'interface': iface, 'exception': ex}) 518 #raise 519 except Exception as ex: 520 import logging 521 logger.warning(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f, 'interface': iface, 'exception': ex}, 522 exc_info = True if logger.isEnabledFor(logging.INFO) else None) 523 524 impls.sort(key = lambda impl: self.get_rating(iface, impl, arch), reverse = True) 525 526 impls_for_iface[iface] = filtered_impls = [] 527 528 my_extra_restrictions = self.extra_restrictions.get(iface, []) 529 530 if self.details is not False: 531 self.details[iface] = [(impl, get_unusable_reason(impl, my_extra_restrictions, arch)) for impl in impls] 532 533 impl_to_var = iface_to_vars[iface] # Impl -> sat var 534 535 var_names = [] 536 for impl in impls: 537 if is_unusable(impl, my_extra_restrictions, arch): 538 continue 539 540 filtered_impls.append(impl) 541 542 assert impl not in impl_to_var, impl 543 v = problem.add_variable(ImplInfo(iface, impl, arch)) 544 impl_to_var[impl] = v 545 var_names.append(v) 546 547 if impl.machine and impl.machine != 'src': 548 impls_for_machine_group[machine_groups.get(impl.machine, 0)].append(v) 549 550 process_dependencies(v, impl, arch) 551 552 if closest_match: 553 dummy_impl = _DummyImpl() 554 dummy_var = problem.add_variable(ImplInfo(iface, dummy_impl, arch, dummy = True)) 555 var_names.append(dummy_var) 556 impl_to_var[dummy_impl] = dummy_var 557 filtered_impls.append(dummy_impl) 558 559 # Only one implementation of this interface can be selected 560 if uri == root_interface: 561 if var_names: 562 clause = problem.at_most_one(var_names) 563 problem.add_clause(var_names) # at least one 564 else: 565 problem.impossible() 566 clause = False 567 elif var_names: 568 clause = problem.at_most_one(var_names) 569 else: 570 # Don't need to add to group_clause_for because we should 571 # never get a possible selection involving this. 572 return 573 574 assert clause is not True 575 assert clause is not None 576 if clause is not False: 577 group_clause_for[uri] = clause 578 579 def add_command_iface(uri, arch, command_name): 580 """Add every <command> in interface 'uri' with this name. 581 Each one depends on the corresponding implementation and only 582 one can be selected. If closest_match is on, include a dummy 583 command that can always be selected.""" 584 585 # Check whether we've already processed this (interface,command) pair 586 existing = group_clause_for_command.get((uri, command_name), None) 587 if existing is not None: 588 return existing.lits 589 590 # First ensure that the interface itself has been processed 591 # We'll reuse the ordering of the implementations to order 592 # the commands too. 593 add_iface(uri, arch) 594 595 iface = iface_cache.get_interface(uri) 596 filtered_impls = impls_for_iface[iface] 597 impl_to_var = iface_to_vars[iface] # Impl -> sat var 598 599 var_names = [] 600 for impl in filtered_impls: 601 command = impl.commands.get(command_name, None) 602 if not command: 603 if not isinstance(impl, _DummyImpl): 604 # Mark implementation as unselectable 605 problem.add_clause([sat.neg(impl_to_var[impl])]) 606 continue 607 608 # We have a candidate <command>. Require that if it's selected 609 # then we select the corresponding <implementation> too. 610 command_var = problem.add_variable(CommandInfo(command_name, command, impl, arch)) 611 problem.add_clause([impl_to_var[impl], sat.neg(command_var)]) 612 613 var_names.append(command_var) 614 615 process_dependencies(command_var, command, arch) 616 617 # Tell the user why we couldn't use this version 618 if self.details is not False: 619 def new_reason(impl, old_reason): 620 if command_name in impl.commands: 621 return old_reason 622 return old_reason or (_('No %s command') % command_name) 623 self.details[iface] = [(impl, new_reason(impl, reason)) for (impl, reason) in self.details[iface]] 624 625 if closest_match: 626 dummy_command = problem.add_variable(None) 627 var_names.append(dummy_command) 628 629 if var_names: 630 # Can't select more than one of them. 631 assert (uri, command_name) not in group_clause_for_command 632 group_clause_for_command[(uri, command_name)] = problem.at_most_one(var_names) 633 634 return var_names 635 636 if command_name is None: 637 add_iface(root_interface, root_arch) 638 else: 639 commands = add_command_iface(root_interface, root_arch, command_name) 640 if len(commands) > int(closest_match): 641 # (we have at least one non-dummy command) 642 problem.add_clause(commands) # At least one 643 else: 644 # (note: might be because we haven't cached it yet) 645 logger.info("No %s <command> in %s", command_name, root_interface) 646 647 problem.impossible() 648 649 # Require m<group> to be true if we select an implementation in that group 650 m_groups = [] 651 for machine_group, impls in impls_for_machine_group.items(): 652 m_group = 'm%d' % machine_group 653 group_var = problem.add_variable(m_group) 654 if impls: 655 for impl in impls: 656 problem.add_clause([group_var, sat.neg(impl)]) 657 m_groups.append(group_var) 658 if m_groups: 659 m_groups_clause = problem.at_most_one(m_groups) 660 else: 661 m_groups_clause = None 662 663 # Can't select an implementation of an interface and of its replacement 664 for original, replacement in replacement_for.items(): 665 if original == replacement: 666 logger.warning("Interface %s replaced-by itself!", original) 667 continue 668 rep_impls = iface_to_vars.get(replacement, None) 669 if rep_impls is None: 670 # We didn't even look at the replacement interface, so no risk here 671 continue 672 # Must select one implementation out of all candidates from both interface. 673 # Dummy implementations don't conflict, though. 674 all_impls = [] 675 for impl, var in rep_impls.items(): 676 if not isinstance(impl, _DummyImpl): 677 all_impls.append(var) 678 for impl, var in iface_to_vars[original].items(): 679 if not isinstance(impl, _DummyImpl): 680 all_impls.append(var) 681 if all_impls: 682 problem.at_most_one(all_impls) 683 # else: neither feed has any usable impls anyway 684 685 def decide(): 686 """This is called by the SAT solver when it cannot simplify the problem further. 687 Our job is to find the most-optimal next selection to try. 688 Recurse through the current selections until we get to an interface with 689 no chosen version, then tell the solver to try the best version from that.""" 690 691 def find_undecided_dep(impl_or_command, arch): 692 # Check for undecided dependencies of impl_or_command 693 for dep in deps_in_use(impl_or_command, arch): 694 # Restrictions don't express that we do or don't want the 695 # dependency, so skip them here. 696 if dep.importance == model.Dependency.Restricts: continue 697 698 for c in dep.get_required_commands(): 699 dep_lit = find_undecided_command(dep.interface, c) 700 if dep_lit is not None: 701 return dep_lit 702 dep_lit = find_undecided(dep.interface) 703 if dep_lit is not None: 704 return dep_lit 705 return None 706 707 seen = set() 708 def find_undecided(uri): 709 if uri in seen: 710 return # Break cycles 711 seen.add(uri) 712 713 group = group_clause_for.get(uri, None) 714 715 if group is None: 716 # (can be None if the selected impl has an optional dependency on 717 # a feed with no implementations) 718 return 719 720 #print "Group for %s = %s" % (uri, group) 721 lit = group.current 722 if lit is None: 723 return group.best_undecided() 724 # else there was only one choice anyway 725 726 # Check for undecided dependencies 727 lit_info = problem.get_varinfo_for_lit(lit).obj 728 return find_undecided_dep(lit_info.impl, lit_info.arch) 729 730 def find_undecided_command(uri, name): 731 if name is None: return find_undecided(uri) 732 733 group = group_clause_for_command[(uri, name)] 734 lit = group.current 735 if lit is None: 736 return group.best_undecided() 737 # else we've already chosen which <command> to use 738 739 # Check for undecided command-specific dependencies, and then for 740 # implementation dependencies. 741 lit_info = problem.get_varinfo_for_lit(lit).obj 742 if lit_info is None: 743 assert closest_match 744 return None # (a dummy command added for better diagnostics; has no dependencies) 745 return find_undecided_dep(lit_info.command, lit_info.arch) or \ 746 find_undecided_dep(lit_info.impl, lit_info.arch) 747 748 best = find_undecided_command(root_interface, command_name) 749 if best is not None: 750 return best 751 752 # If we're chosen everything we need, we can probably 753 # set everything else to False. 754 for group in list(group_clause_for.values()) + list(group_clause_for_command.values()) + [m_groups_clause]: 755 if group.current is None: 756 best = group.best_undecided() 757 if best is not None: 758 return sat.neg(best) 759 760 return None # Failed to find any valid combination 761 762 ready = problem.run_solver(decide) is True 763 764 if not ready and not closest_match: 765 # We failed while trying to do a real solve. 766 # Try a closest match solve to get a better 767 # error report for the user. 768 self.solve(root_interface, root_arch, command_name = command_name, closest_match = True) 769 else: 770 self.ready = ready and not closest_match 771 self.selections = selections.Selections(None) 772 self.selections.interface = root_interface 773 774 if not self.ready: 775 # Store some things useful for get_failure_reason() 776 self._impls_for_iface = impls_for_iface 777 self._iface_to_vars = iface_to_vars 778 self._problem = problem 779 780 sels = self.selections.selections 781 782 commands_needed = [] 783 784 # Populate sels with the selected implementations. 785 # Also, note down all the commands we need. 786 for uri, group in group_clause_for.items(): 787 if group.current is not None: 788 lit_info = problem.get_varinfo_for_lit(group.current).obj 789 if lit_info.is_dummy: 790 sels[lit_info.iface.uri] = None 791 else: 792 # We selected an implementation for interface 'uri' 793 impl = lit_info.impl 794 795 for b in impl.bindings: 796 c = b.command 797 if c is not None: 798 commands.append((uri, c)) 799 800 deps = self.requires[lit_info.iface] = [] 801 for dep in deps_in_use(lit_info.impl, lit_info.arch): 802 deps.append(dep) 803 for c in dep.get_required_commands(): 804 commands_needed.append((dep.interface, c)) 805 806 sel = sels[lit_info.iface.uri] = selections.ImplSelection(lit_info.iface.uri, impl, deps) 807 sel.__arch = lit_info.arch 808 809 # Now all all the commands in too. 810 def add_command(iface, name): 811 sel = sels.get(iface, None) 812 if sel: 813 command = sel.impl.commands[name] 814 if name in sel._used_commands: 815 return # Already added 816 sel._used_commands[name] = clone_command_for(command, sel.__arch) 817 818 for dep in command.requires: 819 for dep_command_name in dep.get_required_commands(): 820 add_command(dep.interface, dep_command_name) 821 822 # A <command> can depend on another <command> in the same interface 823 # (e.g. the tests depending on the main program) 824 for b in command.bindings: 825 c = b.command 826 if c is not None: 827 add_command(iface, c) 828 829 for iface, command in commands_needed: 830 add_command(iface, command) 831 832 if command_name is not None: 833 self.selections.command = command_name 834 add_command(root_interface, command_name) 835 836 if root_interface not in sels: 837 sels[root_interface] = None # Useful for get_failure_reason() 838
839 - def get_failure_reason(self):
840 """Return an exception explaining why the solve failed. 841 @rtype: L{zeroinstall.SafeException}""" 842 assert not self.ready 843 844 sels = self.selections.selections 845 846 iface_cache = self.config.iface_cache 847 848 problem = self._problem 849 impls_for_iface = self._impls_for_iface 850 assert impls_for_iface 851 852 def show(iface_uri): 853 # Find all restrictions that are in play and affect this interface 854 sel = sels[iface_uri] 855 if sel: 856 msg = '{version} ({impl})'.format(version = sel.version, impl = sel.id) 857 else: 858 msg = "(problem)" 859 860 iface = iface_cache.get_interface(iface_uri) 861 862 impls = impls_for_iface[iface_cache.get_interface(iface_uri)] 863 impls = [i for i in impls if not isinstance(i, _DummyImpl)] 864 865 def apply_restrictions(impls, restrictions): 866 for r in restrictions: 867 impls = [i for i in impls if r.meets_restriction(i)] 868 return impls
869 870 # orig_impls is all the implementations passed to the SAT solver (these are the 871 # ones with a compatible OS, CPU, etc). They are sorted most desirable first. 872 orig_impls = impls 873 874 def get_machine_group(impl): 875 machine = impl.machine 876 if machine and machine != 'src': 877 return arch.machine_groups.get(machine, 0) 878 return None 879 880 example_machine_impl = None # An example chosen impl with a machine type 881 882 our_feed = iface_cache.get_feed(iface_uri) 883 our_replacement = our_feed.get_replaced_by() if our_feed else None 884 885 # For each selected implementation... 886 for other_uri, other_sel in sels.items(): 887 # Check for interface-level conflicts 888 other_iface = iface_cache.get_feed(other_uri) 889 if other_iface and other_iface.get_replaced_by() == iface_uri: 890 msg += "\n " + _("Replaces (and therefore conflicts with) {old}").format(old = other_uri) 891 if other_sel: 892 impls = [] 893 894 if our_replacement == other_uri: 895 msg += "\n " + _("Replaced by (and therefore conflicts with) {old}").format(old = other_uri) 896 if other_sel: 897 impls = [] 898 899 # Otherwise, if we didn't select an implementation then that can't be causing a problem 900 if not other_sel: continue 901 902 if example_machine_impl is None: 903 required_machine_group = get_machine_group(other_sel.impl) 904 if required_machine_group is not None: 905 example_machine_impl = other_sel.impl 906 907 for dep in other_sel.impl.requires: 908 if not isinstance(dep, model.InterfaceRestriction): continue 909 # If it depends on us and has restrictions... 910 if dep.interface == iface_uri and dep.restrictions: 911 # Report the restriction 912 msg += "\n " + _("{iface} {version} requires {reqs}").format( 913 iface = other_uri, 914 version = other_sel.version, 915 reqs = ', '.join(str(r) for r in dep.restrictions)) 916 917 # Remove implementations incompatible with the other selections 918 impls = apply_restrictions(impls, dep.restrictions) 919 920 # Check for user-supplied restrictions 921 user = self.extra_restrictions.get(iface, []) 922 if user: 923 msg += "\n " + _("User requested {reqs}").format( 924 reqs = ', '.join(str(r) for r in user)) 925 impls = apply_restrictions(impls, user) 926 927 if sel is None: 928 # Report on available implementations 929 all_impls = self.details.get(iface, {}) 930 if not orig_impls: 931 if not all_impls: 932 msg += "\n " + _("No known implementations at all") 933 else: 934 # No implementations were passed to the solver. 935 msg += "\n " + _("No usable implementations:") 936 for i, reason in all_impls[:5]: 937 msg += "\n {impl}: {reason}".format(impl = i, reason = reason) 938 if len(all_impls) > 5: 939 msg += "\n ..." 940 elif not impls: 941 msg += "\n " + _("No usable implementations satisfy the restrictions") 942 else: 943 # Might still be unusable e.g. if missing a required command. Show reasons, if any. 944 shown = 0 945 candidate_impls = set(impls) 946 for i, reason in all_impls: 947 if reason is _ForceImpl.reason: 948 # We're doing a justify_decision, and this wasn't the one the user specified 949 continue 950 951 if i not in candidate_impls: 952 # Skip, as hopefully obvious from above restrictions why not chosen 953 continue 954 955 if reason is None and example_machine_impl: 956 # Could be an architecture problem 957 this_machine_group = get_machine_group(i) 958 if this_machine_group is not None and this_machine_group != required_machine_group: 959 reason = _("Can't use {this_arch} with selection of {other_name} ({other_arch})").format( 960 this_arch = i.machine, 961 other_name = example_machine_impl.feed.get_name(), 962 other_arch = example_machine_impl.machine) 963 964 if reason is None: 965 # Check if our requirements conflict with an existing selection 966 for dep in i.requires: 967 if not isinstance(dep, model.InterfaceRestriction): continue 968 dep_selection = sels.get(dep.interface) 969 if dep_selection is not None: 970 for r in dep.restrictions: 971 if not r.meets_restriction(dep_selection.impl): 972 reason = _("requires {iface} {reqs}").format( 973 iface = dep.interface, 974 reqs = ', '.join(str(r) for r in dep.restrictions)) 975 break 976 977 if reason is None: 978 var = self._iface_to_vars[iface].get(i, None) 979 if var is None: 980 reason = "BUG: no var for impl!" 981 else: 982 varinfo = problem.get_varinfo_for_lit(var) 983 reason = "Hard to explain. Internal reason: {reason} => {assignment}".format( 984 reason = varinfo.reason, 985 assignment = varinfo) 986 987 if reason is not _ForceImpl.reason: 988 if shown >= 5: 989 msg += "\n ..." 990 break 991 if shown == 0: 992 msg += "\n " + _("Rejected candidates:") 993 msg += "\n {impl}: {reason}".format(impl = i, reason = reason) 994 shown += 1 995 996 return msg 997 998 msg = _("Can't find all required implementations:") + '\n' + \ 999 '\n'.join(["- %s -> %s" % (iface, show(iface)) for iface in sorted(sels)]) 1000 1001 if self.config.network_use == model.network_offline: 1002 msg += "\nNote: 0install is in off-line mode" 1003 1004 return model.SafeException(msg) 1005
1006 - def justify_decision(self, requirements, iface, impl):
1007 """Run a solve with impl_id forced to be selected, and explain why it wasn't (or was) 1008 selected in the normal case. 1009 @type requirements: L{zeroinstall.injector.requirements.Requirements} 1010 @type iface: L{zeroinstall.injector.model.Interface} 1011 @type impl: L{zeroinstall.injector.model.Implementation} 1012 @rtype: str""" 1013 assert isinstance(iface, model.Interface), iface 1014 1015 restrictions = self.extra_restrictions.copy() 1016 restrictions[iface] = restrictions.get(iface, []) + [_ForceImpl(impl)] 1017 s = SATSolver(self.config, restrictions) 1018 s.record_details = True 1019 s.solve_for(requirements) 1020 1021 wanted = "{iface} {version}".format(iface = iface.get_name(), version = impl.get_version()) 1022 1023 # Could a selection involving impl even be valid? 1024 if not s.ready or iface.uri not in s.selections.selections: 1025 reasons = s.details.get(iface, []) 1026 for (rid, rstr) in reasons: 1027 if rid.id == impl.id and rstr is not None: 1028 return _("{wanted} cannot be used (regardless of other components): {reason}").format( 1029 wanted = wanted, 1030 reason = rstr) 1031 1032 if not s.ready: 1033 return _("There is no possible selection using {wanted}.\n{reason}").format( 1034 wanted = wanted, 1035 reason = s.get_failure_reason()) 1036 1037 actual_selection = self.selections.get(iface, None) 1038 if actual_selection is not None: 1039 # Was impl actually selected anyway? 1040 if actual_selection.id == impl.id: 1041 return _("{wanted} was selected as the preferred version.").format(wanted = wanted) 1042 1043 # Was impl ranked below the selected version? 1044 iface_arch = arch.get_architecture(requirements.os, requirements.cpu) 1045 if requirements.source and iface.uri == requirements.interface_uri: 1046 iface_arch = arch.SourceArchitecture(iface_arch) 1047 wanted_rating = self.get_rating(iface, impl, arch) 1048 selected_rating = self.get_rating(iface, actual_selection, arch) 1049 1050 if wanted_rating < selected_rating: 1051 _ranking_component_reason = [ 1052 _("natural languages we understand are preferred"), 1053 _("preferred versions come first"), 1054 _("locally-available versions are preferred when network use is limited"), 1055 _("packages that don't require admin access to install are preferred"), 1056 _("more stable versions preferred"), 1057 _("newer versions are preferred"), 1058 _("native packages are preferred"), 1059 _("newer versions are preferred"), 1060 _("better OS match"), 1061 _("better CPU match"), 1062 _("better locale match"), 1063 _("is locally available"), 1064 _("better ID (tie-breaker)"), 1065 ] 1066 1067 actual = actual_selection.get_version() 1068 if impl.get_version() == actual: 1069 def detail(i): 1070 if len(i.id) < 18: 1071 return " (" + i.id + ")" 1072 else: 1073 return " (" + i.id[:16] + "...)"
1074 1075 wanted += detail(impl) 1076 actual += detail(actual_selection) 1077 1078 for i in range(len(wanted_rating)): 1079 if wanted_rating[i] < selected_rating[i]: 1080 return _("{wanted} is ranked lower than {actual}: {why}").format( 1081 wanted = wanted, 1082 actual = actual, 1083 why = _ranking_component_reason[i]) 1084 1085 used_impl = iface.uri in s.selections.selections 1086 1087 # Impl is selectable and ranked higher than the selected version. Selecting it would cause 1088 # a problem elsewhere. 1089 changes = [] 1090 for old_iface, old_sel in self.selections.selections.items(): 1091 if old_iface == iface.uri and used_impl: continue 1092 new_sel = s.selections.selections.get(old_iface, None) 1093 if new_sel is None: 1094 changes.append(_("{interface}: no longer used").format(interface = old_iface)) 1095 elif old_sel.version != new_sel.version: 1096 changes.append(_("{interface}: {old} to {new}").format(interface = old_iface, old = old_sel.version, new = new_sel.version)) 1097 elif old_sel.id != new_sel.id: 1098 changes.append(_("{interface}: {old} to {new}").format(interface = old_iface, old = old_sel.id, new = new_sel.id)) 1099 1100 if changes: 1101 changes_text = '\n\n' + _('The changes would be:') + '\n\n' + '\n'.join(changes) 1102 else: 1103 changes_text = '' 1104 1105 if used_impl: 1106 return _("{wanted} is selectable, but using it would produce a less optimal solution overall.").format(wanted = wanted) + changes_text 1107 else: 1108 return _("If {wanted} were the only option, the best available solution wouldn't use it.").format(wanted = wanted) + changes_text 1109 1110 DefaultSolver = SATSolver 1111