Added <restricts> element
[zeroinstall/solver.git] / zeroinstall / injector / solver.py
blob2bd0378157938ea2ee3ac6e501819be7fea6d47f
1 """
2 Chooses a set of components to make a running program.
3 """
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall import _
9 import locale
10 from logging import debug, warn, info
12 from zeroinstall.injector.reader import MissingLocalFeed
13 from zeroinstall.injector import model, sat, selections, arch
15 class CommandInfo:
16 def __init__(self, name, command, impl, arch):
17 self.name = name
18 self.command = command
19 self.impl = impl
20 self.arch = arch
22 def __repr__(self):
23 name = "%s_%s_%s_%s" % (self.impl.feed.get_name(), self.impl.get_version(), self.impl.arch, self.name)
24 return name.replace('-', '_').replace('.', '_')
26 class ImplInfo:
27 is_dummy = False
29 def __init__(self, iface, impl, arch, dummy = False):
30 self.iface = iface
31 self.impl = impl
32 self.arch = arch
33 if dummy:
34 self.is_dummy = True
36 def __repr__(self):
37 name = "%s_%s_%s" % (self.impl.feed.get_name(), self.impl.get_version(), self.impl.arch)
38 return name.replace('-', '_').replace('.', '_')
40 class _DummyImpl(object):
41 requires = []
42 version = None
43 arch = None
44 commands = {}
46 def __repr__(self):
47 return "dummy"
49 feed = property(lambda self: self)
51 def get_version(self):
52 return "dummy"
54 def get_name(self):
55 return "dummy"
57 class _ForceImpl(model.Restriction):
58 """Used by L{SATSolver.justify_decision}."""
60 def __init__(self, impl_id):
61 self.impl_id = impl_id
63 def meets_restriction(self, impl):
64 return impl.id == self.impl_id
66 class Solver(object):
67 """Chooses a set of implementations to satisfy the requirements of a program and its user.
68 Typical use:
69 1. Create a Solver object and configure it
70 2. Call L{solve}.
71 3. If any of the returned feeds_used are stale or missing, you may like to start downloading them
72 4. If it is 'ready' then you can download and run the chosen versions.
73 @ivar selections: the chosen implementation of each interface
74 @type selections: L{selections.Selections}
75 @ivar requires: the selected dependencies for each chosen version
76 @type requires: {L{model.Interface}: [L{model.Dependency}]}
77 @ivar feeds_used: the feeds which contributed to the choice in L{selections}
78 @type feeds_used: set(str)
79 @ivar record_details: whether to record information about unselected implementations
80 @type record_details: {L{Interface}: [(L{Implementation}, str)]}
81 @ivar details: extra information, if record_details mode was used
82 @type details: {str: [(Implementation, comment)]}
83 """
84 __slots__ = ['selections', 'requires', 'feeds_used', 'details', 'record_details', 'ready']
86 def __init__(self):
87 self.selections = self.requires = self.feeds_used = self.details = None
88 self.record_details = False
89 self.ready = False
91 def solve_for(self, requirements):
92 """Solve for given requirements.
93 @param requirements: the interface, architecture and command to solve for
94 @type requirements: L{requirements.Requirements}
95 @postcondition: self.ready, self.selections and self.feeds_used are updated
96 @since: 1.8"""
97 return self.solve(requirements.interface_uri, self.get_arch_for(requirements), requirements.command)
99 def solve(self, root_interface, root_arch, command_name = 'run'):
100 """Get the best implementation of root_interface and all of its dependencies.
101 @param root_interface: the URI of the program to be solved
102 @type root_interface: str
103 @param root_arch: the desired target architecture
104 @type root_arch: L{arch.Architecture}
105 @param command_name: which <command> element to select
106 @type command_name: str | None
107 @postcondition: self.ready, self.selections and self.feeds_used are updated"""
108 raise NotImplementedError("Abstract")
110 def get_arch_for(self, requirements, interface = None):
111 """Return the Architecture we would use when solving for this interface.
112 Normally, this architecture is constructed from the OS and CPU type in the requirements,
113 using the host platform's settings if these are not given.
114 If interface is the root, then we wrap this in a SourceArchitecture if looking
115 for source code and (for backwards compatibility) we enable use="testing" dependencies
116 if the command is "test".
117 @param requirements: the overall requirements for the solve
118 @type requirements: L{requirements.Requirements}
119 @param interface: the interface of interest
120 @type interface: L{model.Interface}
121 @return: the architecture that would be used
122 @rtype: L{architecture.Architecture}
123 @since: 1.9"""
124 root_arch = arch.get_architecture(requirements.os, requirements.cpu)
125 if interface is None or interface.uri == requirements.interface_uri:
126 if requirements.source:
127 root_arch = arch.SourceArchitecture(root_arch)
128 if requirements.command == 'test':
129 # This is for old feeds that have use='testing' instead of the newer
130 # 'test' command for giving test-only dependencies.
131 root_arch = arch.Architecture(root_arch.os_ranks, root_arch.machine_ranks)
132 root_arch.use = frozenset([None, "testing"])
133 return root_arch
134 # Assume we use the same arch for all descendants
135 return root_arch.child_arch
137 class SATSolver(Solver):
138 """Converts the problem to a set of pseudo-boolean constraints and uses a PB solver to solve them.
139 @ivar langs: the preferred languages (e.g. ["es_ES", "en"]). Initialised to the current locale.
140 @type langs: str"""
142 __slots__ = ['_failure_reason', 'config', 'extra_restrictions', '_lang_ranks', '_langs']
144 @property
145 def iface_cache(self):
146 return self.config.iface_cache # (deprecated; used by 0compile)
148 def __init__(self, config, extra_restrictions = None):
150 @param config: policy preferences (e.g. stability), the iface_cache and the stores to use
151 @type config: L{policy.Config}
152 @param extra_restrictions: extra restrictions on the chosen implementations
153 @type extra_restrictions: {L{model.Interface}: [L{model.Restriction}]}
155 Solver.__init__(self)
156 assert not isinstance(config, str), "API change!"
157 self.config = config
158 self.extra_restrictions = extra_restrictions or {}
160 # By default, prefer the current locale's language first and English second
161 self.langs = [locale.getlocale()[0] or 'en', 'en']
163 def set_langs(self, langs):
164 """Set the preferred languages.
165 @param langs: languages (and regions), first choice first
166 @type langs: [str]
168 # _lang_ranks is a map from locale string to score (higher is better)
169 _lang_ranks = {}
170 score = 0
171 i = len(langs)
172 # (is there are duplicates, the first occurance takes precedence)
173 while i > 0:
174 i -= 1
175 lang = langs[i].replace('_', '-')
176 _lang_ranks[lang.split('-')[0]] = score
177 _lang_ranks[lang] = score + 1
178 score += 2
179 self._langs = langs
180 self._lang_ranks = _lang_ranks
182 langs = property(lambda self: self._langs, set_langs)
184 def get_rating(self, interface, impl, arch):
185 impl_langs = (impl.langs or 'en').split()
186 my_langs = self._lang_ranks
188 stores = self.config.stores
189 is_available = impl.is_available(stores)
191 # Stability
192 stab_policy = interface.stability_policy
193 if not stab_policy:
194 if self.config.help_with_testing: stab_policy = model.testing
195 else: stab_policy = model.stable
197 stability = impl.get_stability()
198 if stability >= stab_policy:
199 stability_limited = model.preferred
200 else:
201 stability_limited = stability
203 # Note: this list must match _ranking_component_reason above
204 return [
205 # Languages we understand come first
206 max(my_langs.get(l.split('-')[0], -1) for l in impl_langs),
208 # Preferred versions come first
209 stability == model.preferred,
211 # Prefer available implementations next if we have limited network access
212 self.config.network_use != model.network_full and is_available,
214 # Packages that require admin access to install come last
215 not impl.requires_root_install,
217 # Prefer more stable versions, but treat everything over stab_policy the same
218 # (so we prefer stable over testing if the policy is to prefer "stable", otherwise
219 # we don't care)
220 stability_limited,
222 # Newer versions come before older ones (ignoring modifiers)
223 impl.version[0],
225 # Prefer native packages if the main part of the versions are the same
226 impl.id.startswith('package:'),
228 # Full version compare (after package check, since comparing modifiers between native and non-native
229 # packages doesn't make sense).
230 impl.version,
232 # Get best OS
233 -arch.os_ranks.get(impl.os, 999),
235 # Get best machine
236 -arch.machine_ranks.get(impl.machine, 999),
238 # Slightly prefer languages specialised to our country
239 # (we know a and b have the same base language at this point)
240 max(my_langs.get(l, -1) for l in impl_langs),
242 # Slightly prefer cached versions
243 is_available,
245 # Order by ID so the order isn't random
246 impl.id
249 def solve(self, root_interface, root_arch, command_name = 'run', closest_match = False):
250 # closest_match is used internally. It adds a lowest-ranked
251 # by valid implementation to every interface, so we can always
252 # select something. Useful for diagnostics.
254 # The basic plan is this:
255 # 1. Scan the root interface and all dependencies recursively, building up a SAT problem.
256 # 2. Solve the SAT problem. Whenever there are multiple options, try the most preferred one first.
257 # 3. Create a Selections object from the results.
259 # All three involve recursively walking the tree in a similar way:
260 # 1) we follow every dependency of every implementation (order not important)
261 # 2) we follow every dependency of every selected implementation (better versions first)
262 # 3) we follow every dependency of every selected implementation (order doesn't matter)
264 # In all cases, a dependency may be on an <implementation> or on a specific <command>.
266 # TODO: We need some way to figure out which feeds to include.
267 # Currently, we include any feed referenced from anywhere but
268 # this is probably too much. We could insert a dummy optimial
269 # implementation in stale/uncached feeds and see whether it
270 # selects that.
271 iface_cache = self.config.iface_cache
273 problem = sat.SATProblem()
275 impl_to_var = {} # Impl -> sat var
276 self.feeds_used = set()
277 self.requires = {}
278 self.ready = False
279 self.details = self.record_details and {}
281 self.selections = None
282 self._failure_reason = None
284 ifaces_processed = set()
286 machine_groups = arch.machine_groups
287 impls_for_machine_group = {0 : []} # Machine group (e.g. "64") to [impl] in that group
288 for machine_group in machine_groups.values():
289 impls_for_machine_group[machine_group] = []
291 impls_for_iface = {} # Iface -> [impl]
293 # For each interface, the group clause says we can't select two implementations of it at once.
294 # We use this map at the end to find out what was actually selected.
295 group_clause_for = {} # Iface URI -> AtMostOneClause
296 group_clause_for_command = {} # (Iface URI, command name) -> AtMostOneClause | bool
298 # Return the dependencies of impl that we should consider.
299 # Skips dependencies if the use flag isn't what we need.
300 # (note: impl may also be a model.Command)
301 def deps_in_use(impl, arch):
302 for dep in impl.requires:
303 use = dep.metadata.get("use", None)
304 if use not in arch.use:
305 continue
306 yield dep
308 # Must have already done add_iface on dependency.interface.
309 # If dependency is essential:
310 # Add a clause so that if requiring_impl_var is True then an implementation
311 # matching 'dependency' must also be selected.
312 # If dependency is optional:
313 # Require that no incompatible version is selected.
314 # This ignores any 'command' required. Handle that separately.
315 def find_dependency_candidates(requiring_impl_var, dependency):
316 def meets_restrictions(candidate):
317 for r in dependency.restrictions:
318 if not r.meets_restriction(candidate):
319 #warn("%s rejected due to %s", candidate.get_version(), r)
320 return False
321 return True
323 essential = dependency.importance == model.Dependency.Essential
325 dep_iface = iface_cache.get_interface(dependency.interface)
326 dep_union = [sat.neg(requiring_impl_var)] # Either requiring_impl_var is False, or ...
327 for candidate in impls_for_iface[dep_iface]:
328 if (candidate.__class__ is _DummyImpl) or meets_restrictions(candidate):
329 if essential:
330 c_var = impl_to_var.get(candidate, None)
331 if c_var is not None:
332 dep_union.append(c_var)
333 # else we filtered that version out, so ignore it
334 else:
335 # Candidate doesn't meet our requirements
336 # If the dependency is optional add a rule to make sure we don't
337 # select this candidate.
338 # (for essential dependencies this isn't necessary because we must
339 # select a good version and we can't select two)
340 if not essential:
341 c_var = impl_to_var.get(candidate, None)
342 if c_var is not None:
343 problem.add_clause(dep_union + [sat.neg(c_var)])
344 # else we filtered that version out, so ignore it
346 if essential:
347 problem.add_clause(dep_union)
349 def is_unusable(impl, restrictions, arch):
350 """@return: whether this implementation is unusable.
351 @rtype: bool"""
352 return get_unusable_reason(impl, restrictions, arch) != None
354 def get_unusable_reason(impl, restrictions, arch):
356 @param impl: Implementation to test.
357 @type restrictions: [L{model.Restriction}]
358 @return: The reason why this impl is unusable, or None if it's OK.
359 @rtype: str
360 @note: The restrictions are for the interface being requested, not the feed
361 of the implementation; they may be different when feeds are being used."""
362 for r in restrictions:
363 if not r.meets_restriction(impl):
364 return _("Incompatible with another selected implementation")
365 stability = impl.get_stability()
366 if stability <= model.buggy:
367 return stability.name
368 if (self.config.network_use == model.network_offline or not impl.download_sources) and not impl.is_available(self.config.stores):
369 if not impl.download_sources:
370 return _("No retrieval methods")
371 return _("Not cached and we are off-line")
372 if impl.os not in arch.os_ranks:
373 return _("Unsupported OS")
374 if impl.machine not in arch.machine_ranks:
375 if impl.machine == 'src':
376 return _("Source code")
377 elif 'src' in arch.machine_ranks:
378 return _("Not source code")
379 return _("Unsupported machine type")
380 return None
382 def usable_feeds(iface, arch):
383 """Return all feeds for iface that support arch.
384 @rtype: generator(ZeroInstallFeed)"""
385 yield iface.uri
387 for f in iface_cache.get_feed_imports(iface):
388 # Note: when searching for src, None is not in machine_ranks
389 if f.os in arch.os_ranks and \
390 (f.machine is None or f.machine in arch.machine_ranks):
391 yield f.uri
392 else:
393 debug(_("Skipping '%(feed)s'; unsupported architecture %(os)s-%(machine)s"),
394 {'feed': f, 'os': f.os, 'machine': f.machine})
396 # If requiring_var is True then all of requirer's dependencies must be satisfied.
397 # requirer can be a <command> or an <implementation>
398 def process_dependencies(requiring_var, requirer, arch):
399 for d in deps_in_use(requirer, arch):
400 debug(_("Considering command dependency %s"), d)
402 add_iface(d.interface, arch.child_arch)
404 for c in d.get_required_commands():
405 # We depend on a specific command within the implementation.
406 command_vars = add_command_iface(d.interface, arch.child_arch, c)
408 # If the parent command/impl is chosen, one of the candidate commands
409 # must be too. If there aren't any, then this command is unselectable.
410 problem.add_clause([sat.neg(requiring_var)] + command_vars)
412 # Must choose one version of d if impl is selected
413 find_dependency_candidates(requiring_var, d)
415 def add_iface(uri, arch):
416 """Name implementations from feed and assert that only one can be selected."""
417 if uri in ifaces_processed: return
418 ifaces_processed.add(uri)
420 iface = iface_cache.get_interface(uri)
422 impls = []
423 for f in usable_feeds(iface, arch):
424 self.feeds_used.add(f)
425 debug(_("Processing feed %s"), f)
427 try:
428 feed = iface_cache.get_feed(f)
429 if feed is None: continue
430 #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for:
431 # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f})
433 if feed.implementations:
434 impls.extend(feed.implementations.values())
436 distro_feed_url = feed.get_distro_feed()
437 if distro_feed_url:
438 self.feeds_used.add(distro_feed_url)
439 distro_feed = iface_cache.get_feed(distro_feed_url)
440 if distro_feed.implementations:
441 impls.extend(distro_feed.implementations.values())
442 except MissingLocalFeed as ex:
443 warn(_("Missing local feed; if it's no longer required, remove it with:") +
444 '\n0install remove-feed ' + iface.uri + ' ' + f,
445 {'feed': f, 'interface': iface, 'exception': ex})
446 except Exception as ex:
447 warn(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f, 'interface': iface, 'exception': ex})
448 #raise
450 impls.sort(key = lambda impl: self.get_rating(iface, impl, arch), reverse = True)
452 impls_for_iface[iface] = filtered_impls = []
454 my_extra_restrictions = self.extra_restrictions.get(iface, [])
456 if self.record_details:
457 self.details[iface] = [(impl, get_unusable_reason(impl, my_extra_restrictions, arch)) for impl in impls]
459 var_names = []
460 for impl in impls:
461 if is_unusable(impl, my_extra_restrictions, arch):
462 continue
464 filtered_impls.append(impl)
466 assert impl not in impl_to_var
467 v = problem.add_variable(ImplInfo(iface, impl, arch))
468 impl_to_var[impl] = v
469 var_names.append(v)
471 if impl.machine and impl.machine != 'src':
472 impls_for_machine_group[machine_groups.get(impl.machine, 0)].append(v)
474 process_dependencies(v, impl, arch)
476 if closest_match:
477 dummy_impl = _DummyImpl()
478 dummy_var = problem.add_variable(ImplInfo(iface, dummy_impl, arch, dummy = True))
479 var_names.append(dummy_var)
480 impl_to_var[dummy_impl] = dummy_var
481 filtered_impls.append(dummy_impl)
483 # Only one implementation of this interface can be selected
484 if uri == root_interface:
485 if var_names:
486 clause = problem.at_most_one(var_names)
487 problem.add_clause(var_names) # at least one
488 else:
489 problem.impossible()
490 clause = False
491 elif var_names:
492 clause = problem.at_most_one(var_names)
493 else:
494 # Don't need to add to group_clause_for because we should
495 # never get a possible selection involving this.
496 return
498 assert clause is not True
499 assert clause is not None
500 if clause is not False:
501 group_clause_for[uri] = clause
503 def add_command_iface(uri, arch, command_name):
504 """Add every <command> in interface 'uri' with this name.
505 Each one depends on the corresponding implementation and only
506 one can be selected. If closest_match is on, include a dummy
507 command that can always be selected."""
509 # Check whether we've already processed this (interface,command) pair
510 existing = group_clause_for_command.get((uri, command_name), None)
511 if existing is not None:
512 return existing.lits
514 # First ensure that the interface itself has been processed
515 # We'll reuse the ordering of the implementations to order
516 # the commands too.
517 add_iface(uri, arch)
519 iface = iface_cache.get_interface(uri)
520 filtered_impls = impls_for_iface[iface]
522 var_names = []
523 for impl in filtered_impls:
524 command = impl.commands.get(command_name, None)
525 if not command:
526 if not isinstance(impl, _DummyImpl):
527 # Mark implementation as unselectable
528 problem.add_clause([sat.neg(impl_to_var[impl])])
529 continue
531 # We have a candidate <command>. Require that if it's selected
532 # then we select the corresponding <implementation> too.
533 command_var = problem.add_variable(CommandInfo(command_name, command, impl, arch))
534 problem.add_clause([impl_to_var[impl], sat.neg(command_var)])
536 var_names.append(command_var)
538 process_dependencies(command_var, command, arch)
540 # Tell the user why we couldn't use this version
541 if self.record_details:
542 def new_reason(impl, old_reason):
543 if command_name in impl.commands:
544 return old_reason
545 return old_reason or (_('No %s command') % command_name)
546 self.details[iface] = [(impl, new_reason(impl, reason)) for (impl, reason) in self.details[iface]]
548 if closest_match:
549 dummy_command = problem.add_variable(None)
550 var_names.append(dummy_command)
552 if var_names:
553 # Can't select more than one of them.
554 assert (uri, command_name) not in group_clause_for_command
555 group_clause_for_command[(uri, command_name)] = problem.at_most_one(var_names)
557 return var_names
559 if command_name is None:
560 add_iface(root_interface, root_arch)
561 else:
562 commands = add_command_iface(root_interface, root_arch, command_name)
563 if len(commands) > int(closest_match):
564 # (we have at least one non-dummy command)
565 problem.add_clause(commands) # At least one
566 else:
567 # (note: might be because we haven't cached it yet)
568 info("No %s <command> in %s", command_name, root_interface)
570 impls = impls_for_iface[iface_cache.get_interface(root_interface)]
571 if impls == [] or (len(impls) == 1 and isinstance(impls[0], _DummyImpl)):
572 # There were no candidates at all.
573 if self.config.network_use == model.network_offline:
574 self._failure_reason = _("Interface '%s' has no usable implementations in the cache (and 0install is in off-line mode)") % root_interface
575 else:
576 self._failure_reason = _("Interface '%s' has no usable implementations") % root_interface
577 else:
578 # We had some candidates implementations, but none for the command we need
579 self._failure_reason = _("Interface '%s' cannot be executed directly; it is just a library "
580 "to be used by other programs (or missing '%s' command)") % (root_interface, command_name)
582 problem.impossible()
584 # Require m<group> to be true if we select an implementation in that group
585 m_groups = []
586 for machine_group, impls in impls_for_machine_group.items():
587 m_group = 'm%d' % machine_group
588 group_var = problem.add_variable(m_group)
589 if impls:
590 for impl in impls:
591 problem.add_clause([group_var, sat.neg(impl)])
592 m_groups.append(group_var)
593 if m_groups:
594 m_groups_clause = problem.at_most_one(m_groups)
595 else:
596 m_groups_clause = None
598 def decide():
599 """This is called by the SAT solver when it cannot simplify the problem further.
600 Our job is to find the most-optimal next selection to try.
601 Recurse through the current selections until we get to an interface with
602 no chosen version, then tell the solver to try the best version from that."""
604 def find_undecided_dep(impl_or_command, arch):
605 # Check for undecided dependencies of impl_or_command
606 for dep in deps_in_use(impl_or_command, arch):
607 # Restrictions don't express that we do or don't want the
608 # dependency, so skip them here.
609 if dep.importance == model.Dependency.Restricts: continue
611 for c in dep.get_required_commands():
612 dep_lit = find_undecided_command(dep.interface, c)
613 if dep_lit is not None:
614 return dep_lit
615 dep_lit = find_undecided(dep.interface)
616 if dep_lit is not None:
617 return dep_lit
618 return None
620 seen = set()
621 def find_undecided(uri):
622 if uri in seen:
623 return # Break cycles
624 seen.add(uri)
626 group = group_clause_for.get(uri, None)
628 if group is None:
629 # (can be None if the selected impl has an optional dependency on
630 # a feed with no implementations)
631 return
633 #print "Group for %s = %s" % (uri, group)
634 lit = group.current
635 if lit is None:
636 return group.best_undecided()
637 # else there was only one choice anyway
639 # Check for undecided dependencies
640 lit_info = problem.get_varinfo_for_lit(lit).obj
641 return find_undecided_dep(lit_info.impl, lit_info.arch)
643 def find_undecided_command(uri, name):
644 if name is None: return find_undecided(uri)
646 group = group_clause_for_command[(uri, name)]
647 lit = group.current
648 if lit is None:
649 return group.best_undecided()
650 # else we've already chosen which <command> to use
652 # Check for undecided command-specific dependencies, and then for
653 # implementation dependencies.
654 lit_info = problem.get_varinfo_for_lit(lit).obj
655 if lit_info is None:
656 assert closest_match
657 return None # (a dummy command added for better diagnostics; has no dependencies)
658 return find_undecided_dep(lit_info.command, lit_info.arch) or \
659 find_undecided_dep(lit_info.impl, lit_info.arch)
661 best = find_undecided_command(root_interface, command_name)
662 if best is not None:
663 return best
665 # If we're chosen everything we need, we can probably
666 # set everything else to False.
667 for group in list(group_clause_for.values()) + list(group_clause_for_command.values()) + [m_groups_clause]:
668 if group.current is None:
669 best = group.best_undecided()
670 if best is not None:
671 return sat.neg(best)
673 return None # Failed to find any valid combination
675 ready = problem.run_solver(decide) is True
677 if not ready and not closest_match:
678 # We failed while trying to do a real solve.
679 # Try a closest match solve to get a better
680 # error report for the user.
681 self.solve(root_interface, root_arch, command_name = command_name, closest_match = True)
682 else:
683 self.ready = ready and not closest_match
684 self.selections = selections.Selections(None)
685 self.selections.interface = root_interface
687 sels = self.selections.selections
689 commands_needed = []
691 # Populate sels with the selected implementations.
692 # Also, note down all the commands we need.
693 for uri, group in group_clause_for.items():
694 if group.current is not None:
695 lit_info = problem.get_varinfo_for_lit(group.current).obj
696 if lit_info.is_dummy:
697 sels[lit_info.iface.uri] = None
698 else:
699 # We selected an implementation for interface 'uri'
700 impl = lit_info.impl
702 for b in impl.bindings:
703 c = b.command
704 if c is not None:
705 commands.append((uri, c))
707 deps = self.requires[lit_info.iface] = []
708 for dep in deps_in_use(lit_info.impl, lit_info.arch):
709 deps.append(dep)
710 for c in dep.get_required_commands():
711 commands_needed.append((dep.interface, c))
713 sels[lit_info.iface.uri] = selections.ImplSelection(lit_info.iface.uri, impl, deps)
715 # Now all all the commands in too.
716 def add_command(iface, name):
717 sel = sels.get(iface, None)
718 if sel:
719 command = sel.impl.commands[name]
720 if name in sel._used_commands:
721 return # Already added
722 sel._used_commands.add(name)
724 for dep in command.requires:
725 for dep_command_name in dep.get_required_commands():
726 add_command(dep.interface, dep_command_name)
728 # A <command> can depend on another <command> in the same interface
729 # (e.g. the tests depending on the main program)
730 for b in command.bindings:
731 c = b.command
732 if c is not None:
733 add_command(iface, c)
735 for iface, command in commands_needed:
736 add_command(iface, command)
738 if command_name is not None:
739 self.selections.command = command_name
740 add_command(root_interface, command_name)
742 def get_failure_reason(self):
743 """Return an exception explaining why the solve failed."""
744 assert not self.ready
746 if self._failure_reason:
747 return model.SafeException(self._failure_reason)
749 return model.SafeException(_("Can't find all required implementations:") + '\n' +
750 '\n'.join(["- %s -> %s" % (iface, self.selections[iface])
751 for iface in self.selections]))
753 def justify_decision(self, requirements, iface, impl):
754 """Run a solve with impl_id forced to be selected, and explain why it wasn't (or was)
755 selected in the normal case."""
756 assert isinstance(iface, model.Interface), iface
758 restrictions = self.extra_restrictions.copy()
759 restrictions[iface] = restrictions.get(iface, []) + [_ForceImpl(impl.id)]
760 s = SATSolver(self.config, restrictions)
761 s.record_details = True
762 s.solve_for(requirements)
764 wanted = "{iface} {version}".format(iface = iface.get_name(), version = impl.get_version())
766 # Could a selection involving impl even be valid?
767 if not s.ready or iface.uri not in s.selections.selections:
768 reasons = s.details.get(iface, [])
769 for (rid, rstr) in reasons:
770 if rid.id == impl.id and rstr is not None:
771 return _("{wanted} cannot be used (regardless of other components): {reason}").format(
772 wanted = wanted,
773 reason = rstr)
775 if not s.ready:
776 return _("There is no possible selection using {wanted}.\n{reason}").format(
777 wanted = wanted,
778 reason = s.get_failure_reason())
780 actual_selection = self.selections.get(iface, None)
781 if actual_selection is not None:
782 # Was impl actually selected anyway?
783 if actual_selection.id == impl.id:
784 return _("{wanted} was selected as the preferred version.").format(wanted = wanted)
786 # Was impl ranked below the selected version?
787 iface_arch = arch.get_architecture(requirements.os, requirements.cpu)
788 if requirements.source and iface.uri == requirements.interface_uri:
789 iface_arch = arch.SourceArchitecture(iface_arch)
790 wanted_rating = self.get_rating(iface, impl, arch)
791 selected_rating = self.get_rating(iface, actual_selection, arch)
793 if wanted_rating < selected_rating:
794 _ranking_component_reason = [
795 _("natural languages we understand are preferred"),
796 _("preferred versions come first"),
797 _("locally-available versions are preferred when network use is limited"),
798 _("packages that don't require admin access to install are preferred"),
799 _("more stable versions preferred"),
800 _("newer versions are preferred"),
801 _("native packages are preferred"),
802 _("newer versions are preferred"),
803 _("better OS match"),
804 _("better CPU match"),
805 _("better locale match"),
806 _("is locally available"),
807 _("better ID (tie-breaker)"),
810 actual = actual_selection.get_version()
811 if impl.get_version() == actual:
812 def detail(i):
813 if len(i.id) < 18:
814 return " (" + i.id + ")"
815 else:
816 return " (" + i.id[:16] + "...)"
818 wanted += detail(impl)
819 actual += detail(actual_selection)
821 for i in range(len(wanted_rating)):
822 if wanted_rating[i] < selected_rating[i]:
823 return _("{wanted} is ranked lower than {actual}: {why}").format(
824 wanted = wanted,
825 actual = actual,
826 why = _ranking_component_reason[i])
828 used_impl = iface.uri in s.selections.selections
830 # Impl is selectable and ranked higher than the selected version. Selecting it would cause
831 # a problem elsewhere.
832 changes = []
833 for old_iface, old_sel in self.selections.selections.items():
834 if old_iface == iface.uri and used_impl: continue
835 new_sel = s.selections.selections.get(old_iface, None)
836 if new_sel is None:
837 changes.append(_("{interface}: no longer used").format(interface = old_iface))
838 elif old_sel.version != new_sel.version:
839 changes.append(_("{interface}: {old} to {new}").format(interface = old_iface, old = old_sel.version, new = new_sel.version))
840 elif old_sel.id != new_sel.id:
841 changes.append(_("{interface}: {old} to {new}").format(interface = old_iface, old = old_sel.id, new = new_sel.id))
843 if changes:
844 changes_text = '\n\n' + _('The changes would be:') + '\n\n' + '\n'.join(changes)
845 else:
846 changes_text = ''
848 if used_impl:
849 return _("{wanted} is selectable, but using it would produce a less optimal solution overall.").format(wanted = wanted) + changes_text
850 else:
851 return _("If {wanted} were the only option, the best available solution wouldn't use it.").format(wanted = wanted) + changes_text
853 DefaultSolver = SATSolver