Only process each <package-implementation> once, even if several distributions match...
[zeroinstall/solver.git] / zeroinstall / injector / solver.py
blobf76a1ee00906631ee35068e6d20915c0adbb5653
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 group_clause_for = {} # Iface URI -> AtMostOneClause | bool
294 group_clause_for_command = {} # (Iface URI, command name) -> AtMostOneClause | bool
296 # Return the dependencies of impl that we should consider.
297 # Skips dependencies if the use flag isn't what we need.
298 # (note: impl may also be a model.Command)
299 def deps_in_use(impl, arch):
300 for dep in impl.requires:
301 use = dep.metadata.get("use", None)
302 if use not in arch.use:
303 continue
304 yield dep
306 # Must have already done add_iface on dependency.interface.
307 # If dependency is essential:
308 # Add a clause so that if requiring_impl_var is True then an implementation
309 # matching 'dependency' must also be selected.
310 # If dependency is optional:
311 # Require that no incompatible version is selected.
312 # This ignores any 'command' required. Handle that separately.
313 def find_dependency_candidates(requiring_impl_var, dependency):
314 def meets_restrictions(candidate):
315 for r in dependency.restrictions:
316 if not r.meets_restriction(candidate):
317 #warn("%s rejected due to %s", candidate.get_version(), r)
318 return False
319 return True
321 essential = dependency.importance == model.Dependency.Essential
323 dep_iface = iface_cache.get_interface(dependency.interface)
324 dep_union = [sat.neg(requiring_impl_var)] # Either requiring_impl_var is False, or ...
325 for candidate in impls_for_iface[dep_iface]:
326 if (candidate.__class__ is _DummyImpl) or meets_restrictions(candidate):
327 if essential:
328 c_var = impl_to_var.get(candidate, None)
329 if c_var is not None:
330 dep_union.append(c_var)
331 # else we filtered that version out, so ignore it
332 else:
333 # Candidate doesn't meet our requirements
334 # If the dependency is optional add a rule to make sure we don't
335 # select this candidate.
336 # (for essential dependencies this isn't necessary because we must
337 # select a good version and we can't select two)
338 if not essential:
339 c_var = impl_to_var.get(candidate, None)
340 if c_var is not None:
341 problem.add_clause(dep_union + [sat.neg(c_var)])
342 # else we filtered that version out, so ignore it
344 if essential:
345 problem.add_clause(dep_union)
347 def is_unusable(impl, restrictions, arch):
348 """@return: whether this implementation is unusable.
349 @rtype: bool"""
350 return get_unusable_reason(impl, restrictions, arch) != None
352 def get_unusable_reason(impl, restrictions, arch):
354 @param impl: Implementation to test.
355 @type restrictions: [L{model.Restriction}]
356 @return: The reason why this impl is unusable, or None if it's OK.
357 @rtype: str
358 @note: The restrictions are for the interface being requested, not the feed
359 of the implementation; they may be different when feeds are being used."""
360 for r in restrictions:
361 if not r.meets_restriction(impl):
362 return _("Incompatible with another selected implementation")
363 stability = impl.get_stability()
364 if stability <= model.buggy:
365 return stability.name
366 if (self.config.network_use == model.network_offline or not impl.download_sources) and not impl.is_available(self.config.stores):
367 if not impl.download_sources:
368 return _("No retrieval methods")
369 return _("Not cached and we are off-line")
370 if impl.os not in arch.os_ranks:
371 return _("Unsupported OS")
372 if impl.machine not in arch.machine_ranks:
373 if impl.machine == 'src':
374 return _("Source code")
375 elif 'src' in arch.machine_ranks:
376 return _("Not source code")
377 return _("Unsupported machine type")
378 return None
380 def usable_feeds(iface, arch):
381 """Return all feeds for iface that support arch.
382 @rtype: generator(ZeroInstallFeed)"""
383 yield iface.uri
385 for f in iface_cache.get_feed_imports(iface):
386 # Note: when searching for src, None is not in machine_ranks
387 if f.os in arch.os_ranks and \
388 (f.machine is None or f.machine in arch.machine_ranks):
389 yield f.uri
390 else:
391 debug(_("Skipping '%(feed)s'; unsupported architecture %(os)s-%(machine)s"),
392 {'feed': f, 'os': f.os, 'machine': f.machine})
394 # If requiring_var is True then all of requirer's dependencies must be satisfied.
395 # requirer can be a <command> or an <implementation>
396 def process_dependencies(requiring_var, requirer, arch):
397 for d in deps_in_use(requirer, arch):
398 debug(_("Considering command dependency %s"), d)
400 add_iface(d.interface, arch.child_arch)
402 for c in d.get_required_commands():
403 # We depend on a specific command within the implementation.
404 command_vars = add_command_iface(d.interface, arch.child_arch, c)
406 # If the parent command/impl is chosen, one of the candidate commands
407 # must be too. If there aren't any, then this command is unselectable.
408 problem.add_clause([sat.neg(requiring_var)] + command_vars)
410 # Must choose one version of d if impl is selected
411 find_dependency_candidates(requiring_var, d)
413 def add_iface(uri, arch):
414 """Name implementations from feed and assert that only one can be selected."""
415 if uri in ifaces_processed: return
416 ifaces_processed.add(uri)
418 iface = iface_cache.get_interface(uri)
420 impls = []
421 for f in usable_feeds(iface, arch):
422 self.feeds_used.add(f)
423 debug(_("Processing feed %s"), f)
425 try:
426 feed = iface_cache.get_feed(f)
427 if feed is None: continue
428 #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for:
429 # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f})
431 if feed.implementations:
432 impls.extend(feed.implementations.values())
434 distro_feed_url = feed.get_distro_feed()
435 if distro_feed_url:
436 self.feeds_used.add(distro_feed_url)
437 distro_feed = iface_cache.get_feed(distro_feed_url)
438 if distro_feed.implementations:
439 impls.extend(distro_feed.implementations.values())
440 except MissingLocalFeed as ex:
441 warn(_("Missing local feed; if it's no longer required, remove it with:") +
442 '\n0install remove-feed ' + iface.uri + ' ' + f,
443 {'feed': f, 'interface': iface, 'exception': ex})
444 except Exception as ex:
445 warn(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f, 'interface': iface, 'exception': ex})
446 #raise
448 impls.sort(key = lambda impl: self.get_rating(iface, impl, arch), reverse = True)
450 impls_for_iface[iface] = filtered_impls = []
452 my_extra_restrictions = self.extra_restrictions.get(iface, [])
454 if self.record_details:
455 self.details[iface] = [(impl, get_unusable_reason(impl, my_extra_restrictions, arch)) for impl in impls]
457 var_names = []
458 for impl in impls:
459 if is_unusable(impl, my_extra_restrictions, arch):
460 continue
462 filtered_impls.append(impl)
464 assert impl not in impl_to_var
465 v = problem.add_variable(ImplInfo(iface, impl, arch))
466 impl_to_var[impl] = v
467 var_names.append(v)
469 if impl.machine and impl.machine != 'src':
470 impls_for_machine_group[machine_groups.get(impl.machine, 0)].append(v)
472 process_dependencies(v, impl, arch)
474 if closest_match:
475 dummy_impl = _DummyImpl()
476 dummy_var = problem.add_variable(ImplInfo(iface, dummy_impl, arch, dummy = True))
477 var_names.append(dummy_var)
478 impl_to_var[dummy_impl] = dummy_var
479 filtered_impls.append(dummy_impl)
481 # Only one implementation of this interface can be selected
482 if uri == root_interface:
483 if var_names:
484 clause = problem.at_most_one(var_names)
485 problem.add_clause(var_names) # at least one
486 else:
487 problem.impossible()
488 clause = False
489 elif var_names:
490 clause = problem.at_most_one(var_names)
491 else:
492 # Don't need to add to group_clause_for because we should
493 # never get a possible selection involving this.
494 return
496 assert clause is not True
497 assert clause is not None
498 if clause is not False:
499 group_clause_for[uri] = clause
501 def add_command_iface(uri, arch, command_name):
502 """Add every <command> in interface 'uri' with this name.
503 Each one depends on the corresponding implementation and only
504 one can be selected. If closest_match is on, include a dummy
505 command that can always be selected."""
507 # Check whether we've already processed this (interface,command) pair
508 existing = group_clause_for_command.get((uri, command_name), None)
509 if existing is not None:
510 return existing.lits
512 # First ensure that the interface itself has been processed
513 # We'll reuse the ordering of the implementations to order
514 # the commands too.
515 add_iface(uri, arch)
517 iface = iface_cache.get_interface(uri)
518 filtered_impls = impls_for_iface[iface]
520 var_names = []
521 for impl in filtered_impls:
522 command = impl.commands.get(command_name, None)
523 if not command:
524 if not isinstance(impl, _DummyImpl):
525 # Mark implementation as unselectable
526 problem.add_clause([sat.neg(impl_to_var[impl])])
527 continue
529 # We have a candidate <command>. Require that if it's selected
530 # then we select the corresponding <implementation> too.
531 command_var = problem.add_variable(CommandInfo(command_name, command, impl, arch))
532 problem.add_clause([impl_to_var[impl], sat.neg(command_var)])
534 var_names.append(command_var)
536 process_dependencies(command_var, command, arch)
538 # Tell the user why we couldn't use this version
539 if self.record_details:
540 def new_reason(impl, old_reason):
541 if command_name in impl.commands:
542 return old_reason
543 return old_reason or (_('No %s command') % command_name)
544 self.details[iface] = [(impl, new_reason(impl, reason)) for (impl, reason) in self.details[iface]]
546 if closest_match:
547 dummy_command = problem.add_variable(None)
548 var_names.append(dummy_command)
550 if var_names:
551 # Can't select more than one of them.
552 assert (uri, command_name) not in group_clause_for_command
553 group_clause_for_command[(uri, command_name)] = problem.at_most_one(var_names)
555 return var_names
557 if command_name is None:
558 add_iface(root_interface, root_arch)
559 else:
560 commands = add_command_iface(root_interface, root_arch, command_name)
561 if len(commands) > int(closest_match):
562 # (we have at least one non-dummy command)
563 problem.add_clause(commands) # At least one
564 else:
565 # (note: might be because we haven't cached it yet)
566 info("No %s <command> in %s", command_name, root_interface)
568 impls = impls_for_iface[iface_cache.get_interface(root_interface)]
569 if impls == [] or (len(impls) == 1 and isinstance(impls[0], _DummyImpl)):
570 # There were no candidates at all.
571 if self.config.network_use == model.network_offline:
572 self._failure_reason = _("Interface '%s' has no usable implementations in the cache (and 0install is in off-line mode)") % root_interface
573 else:
574 self._failure_reason = _("Interface '%s' has no usable implementations") % root_interface
575 else:
576 # We had some candidates implementations, but none for the command we need
577 self._failure_reason = _("Interface '%s' cannot be executed directly; it is just a library "
578 "to be used by other programs (or missing '%s' command)") % (root_interface, command_name)
580 problem.impossible()
582 # Require m<group> to be true if we select an implementation in that group
583 m_groups = []
584 for machine_group, impls in impls_for_machine_group.items():
585 m_group = 'm%d' % machine_group
586 group_var = problem.add_variable(m_group)
587 if impls:
588 for impl in impls:
589 problem.add_clause([group_var, sat.neg(impl)])
590 m_groups.append(group_var)
591 if m_groups:
592 m_groups_clause = problem.at_most_one(m_groups)
593 else:
594 m_groups_clause = None
596 def decide():
597 """Recurse through the current selections until we get to an interface with
598 no chosen version, then tell the solver to try the best version from that."""
600 def find_undecided_dep(impl_or_command, arch):
601 # Check for undecided dependencies of impl_or_command
602 for dep in deps_in_use(impl_or_command, arch):
603 for c in dep.get_required_commands():
604 dep_lit = find_undecided_command(dep.interface, c)
605 if dep_lit is not None:
606 return dep_lit
607 dep_lit = find_undecided(dep.interface)
608 if dep_lit is not None:
609 return dep_lit
610 return None
612 seen = set()
613 def find_undecided(uri):
614 if uri in seen:
615 return # Break cycles
616 seen.add(uri)
618 group = group_clause_for.get(uri, None)
620 if group is None:
621 # (can be None if the selected impl has an optional dependency on
622 # a feed with no implementations)
623 return
625 #print "Group for %s = %s" % (uri, group)
626 lit = group.current
627 if lit is None:
628 return group.best_undecided()
629 # else there was only one choice anyway
631 # Check for undecided dependencies
632 lit_info = problem.get_varinfo_for_lit(lit).obj
633 return find_undecided_dep(lit_info.impl, lit_info.arch)
635 def find_undecided_command(uri, name):
636 if name is None: return find_undecided(uri)
638 group = group_clause_for_command[(uri, name)]
639 lit = group.current
640 if lit is None:
641 return group.best_undecided()
642 # else we've already chosen which <command> to use
644 # Check for undecided command-specific dependencies, and then for
645 # implementation dependencies.
646 lit_info = problem.get_varinfo_for_lit(lit).obj
647 if lit_info is None:
648 assert closest_match
649 return None # (a dummy command added for better diagnostics; has no dependencies)
650 return find_undecided_dep(lit_info.command, lit_info.arch) or \
651 find_undecided_dep(lit_info.impl, lit_info.arch)
653 best = find_undecided_command(root_interface, command_name)
654 if best is not None:
655 return best
657 # If we're chosen everything we need, we can probably
658 # set everything else to False.
659 for group in list(group_clause_for.values()) + list(group_clause_for_command.values()) + [m_groups_clause]:
660 if group.current is None:
661 best = group.best_undecided()
662 if best is not None:
663 return sat.neg(best)
665 return None # Failed to find any valid combination
667 ready = problem.run_solver(decide) is True
669 if not ready and not closest_match:
670 # We failed while trying to do a real solve.
671 # Try a closest match solve to get a better
672 # error report for the user.
673 self.solve(root_interface, root_arch, command_name = command_name, closest_match = True)
674 else:
675 self.ready = ready and not closest_match
676 self.selections = selections.Selections(None)
677 self.selections.interface = root_interface
679 sels = self.selections.selections
681 commands_needed = []
683 # Populate sels with the selected implementations.
684 # Also, note down all the commands we need.
685 for uri, group in group_clause_for.items():
686 if group.current is not None:
687 lit_info = problem.get_varinfo_for_lit(group.current).obj
688 if lit_info.is_dummy:
689 sels[lit_info.iface.uri] = None
690 else:
691 # We selected an implementation for interface 'uri'
692 impl = lit_info.impl
694 for b in impl.bindings:
695 c = b.command
696 if c is not None:
697 commands.append((uri, c))
699 deps = self.requires[lit_info.iface] = []
700 for dep in deps_in_use(lit_info.impl, lit_info.arch):
701 deps.append(dep)
702 for c in dep.get_required_commands():
703 commands_needed.append((dep.interface, c))
705 sels[lit_info.iface.uri] = selections.ImplSelection(lit_info.iface.uri, impl, deps)
707 # Now all all the commands in too.
708 def add_command(iface, name):
709 sel = sels.get(iface, None)
710 if sel:
711 command = sel.impl.commands[name]
712 if name in sel._used_commands:
713 return # Already added
714 sel._used_commands.add(name)
716 for dep in command.requires:
717 for dep_command_name in dep.get_required_commands():
718 add_command(dep.interface, dep_command_name)
720 # A <command> can depend on another <command> in the same interface
721 # (e.g. the tests depending on the main program)
722 for b in command.bindings:
723 c = b.command
724 if c is not None:
725 add_command(iface, c)
727 for iface, command in commands_needed:
728 add_command(iface, command)
730 if command_name is not None:
731 self.selections.command = command_name
732 add_command(root_interface, command_name)
734 def get_failure_reason(self):
735 """Return an exception explaining why the solve failed."""
736 assert not self.ready
738 if self._failure_reason:
739 return model.SafeException(self._failure_reason)
741 return model.SafeException(_("Can't find all required implementations:") + '\n' +
742 '\n'.join(["- %s -> %s" % (iface, self.selections[iface])
743 for iface in self.selections]))
745 def justify_decision(self, requirements, iface, impl):
746 """Run a solve with impl_id forced to be selected, and explain why it wasn't (or was)
747 selected in the normal case."""
748 assert isinstance(iface, model.Interface), iface
750 restrictions = self.extra_restrictions.copy()
751 restrictions[iface] = restrictions.get(iface, []) + [_ForceImpl(impl.id)]
752 s = SATSolver(self.config, restrictions)
753 s.record_details = True
754 s.solve_for(requirements)
756 wanted = "{iface} {version}".format(iface = iface.get_name(), version = impl.get_version())
758 # Could a selection involving impl even be valid?
759 if not s.ready or iface.uri not in s.selections.selections:
760 reasons = s.details.get(iface, [])
761 for (rid, rstr) in reasons:
762 if rid.id == impl.id and rstr is not None:
763 return _("{wanted} cannot be used (regardless of other components): {reason}").format(
764 wanted = wanted,
765 reason = rstr)
767 if not s.ready:
768 return _("There is no possible selection using {wanted}.\n{reason}").format(
769 wanted = wanted,
770 reason = s.get_failure_reason())
772 actual_selection = self.selections.get(iface, None)
773 if actual_selection is not None:
774 # Was impl actually selected anyway?
775 if actual_selection.id == impl.id:
776 return _("{wanted} was selected as the preferred version.").format(wanted = wanted)
778 # Was impl ranked below the selected version?
779 iface_arch = arch.get_architecture(requirements.os, requirements.cpu)
780 if requirements.source and iface.uri == requirements.interface_uri:
781 iface_arch = arch.SourceArchitecture(iface_arch)
782 wanted_rating = self.get_rating(iface, impl, arch)
783 selected_rating = self.get_rating(iface, actual_selection, arch)
785 if wanted_rating < selected_rating:
786 _ranking_component_reason = [
787 _("natural languages we understand are preferred"),
788 _("preferred versions come first"),
789 _("locally-available versions are preferred when network use is limited"),
790 _("packages that don't require admin access to install are preferred"),
791 _("more stable versions preferred"),
792 _("newer versions are preferred"),
793 _("native packages are preferred"),
794 _("newer versions are preferred"),
795 _("better OS match"),
796 _("better CPU match"),
797 _("better locale match"),
798 _("is locally available"),
799 _("better ID (tie-breaker)"),
802 actual = actual_selection.get_version()
803 if impl.get_version() == actual:
804 def detail(i):
805 if len(i.id) < 18:
806 return " (" + i.id + ")"
807 else:
808 return " (" + i.id[:16] + "...)"
810 wanted += detail(impl)
811 actual += detail(actual_selection)
813 for i in range(len(wanted_rating)):
814 if wanted_rating[i] < selected_rating[i]:
815 return _("{wanted} is ranked lower than {actual}: {why}").format(
816 wanted = wanted,
817 actual = actual,
818 why = _ranking_component_reason[i])
820 used_impl = iface.uri in s.selections.selections
822 # Impl is selectable and ranked higher than the selected version. Selecting it would cause
823 # a problem elsewhere.
824 changes = []
825 for old_iface, old_sel in self.selections.selections.items():
826 if old_iface == iface.uri and used_impl: continue
827 new_sel = s.selections.selections.get(old_iface, None)
828 if new_sel is None:
829 changes.append(_("{interface}: no longer used").format(interface = old_iface))
830 elif old_sel.version != new_sel.version:
831 changes.append(_("{interface}: {old} to {new}").format(interface = old_iface, old = old_sel.version, new = new_sel.version))
832 elif old_sel.id != new_sel.id:
833 changes.append(_("{interface}: {old} to {new}").format(interface = old_iface, old = old_sel.id, new = new_sel.id))
835 if changes:
836 changes_text = '\n\n' + _('The changes would be:') + '\n\n' + '\n'.join(changes)
837 else:
838 changes_text = ''
840 if used_impl:
841 return _("{wanted} is selectable, but using it would produce a less optimal solution overall.").format(wanted = wanted) + changes_text
842 else:
843 return _("If {wanted} were the only option, the best available solution wouldn't use it.").format(wanted = wanted) + changes_text
845 DefaultSolver = SATSolver