2 Chooses a set of components to make a running program.
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall
import _
10 from logging
import debug
, warn
, info
12 from zeroinstall
.injector
.reader
import MissingLocalFeed
13 from zeroinstall
.injector
import model
, sat
, selections
, arch
16 def __init__(self
, name
, command
, impl
, arch
):
18 self
.command
= command
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('.', '_')
29 def __init__(self
, iface
, impl
, arch
, dummy
= False):
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):
49 feed
= property(lambda self
: self
)
51 def get_version(self
):
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
67 """Chooses a set of implementations to satisfy the requirements of a program and its user.
69 1. Create a Solver object and configure it
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)]}
84 __slots__
= ['selections', 'requires', 'feeds_used', 'details', 'record_details', 'ready']
87 self
.selections
= self
.requires
= self
.feeds_used
= self
.details
= None
88 self
.record_details
= 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
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}
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"])
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.
142 __slots__
= ['_failure_reason', 'config', 'extra_restrictions', '_lang_ranks', '_langs']
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!"
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
168 # _lang_ranks is a map from locale string to score (higher is better)
172 # (is there are duplicates, the first occurance takes precedence)
175 lang
= langs
[i
].replace('_', '-')
176 _lang_ranks
[lang
.split('-')[0]] = score
177 _lang_ranks
[lang
] = score
+ 1
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
)
192 stab_policy
= interface
.stability_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
201 stability_limited
= stability
203 # Note: this list must match _ranking_component_reason above
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
222 # Newer versions come before older ones (ignoring modifiers)
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).
233 -arch
.os_ranks
.get(impl
.os
, 999),
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
245 # Order by ID so the order isn't random
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
271 iface_cache
= self
.config
.iface_cache
273 problem
= sat
.SATProblem()
275 impl_to_var
= {} # Impl -> sat var
276 self
.feeds_used
= set()
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
:
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)
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
):
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
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)
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
347 problem
.add_clause(dep_union
)
349 def is_unusable(impl
, restrictions
, arch
):
350 """@return: whether this implementation is unusable.
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.
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")
382 def usable_feeds(iface
, arch
):
383 """Return all feeds for iface that support arch.
384 @rtype: generator(ZeroInstallFeed)"""
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
):
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
)
423 for f
in usable_feeds(iface
, arch
):
424 self
.feeds_used
.add(f
)
425 debug(_("Processing feed %s"), f
)
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()
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
})
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
]
461 if is_unusable(impl
, my_extra_restrictions
, arch
):
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
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
)
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
:
486 clause
= problem
.at_most_one(var_names
)
487 problem
.add_clause(var_names
) # at least one
492 clause
= problem
.at_most_one(var_names
)
494 # Don't need to add to group_clause_for because we should
495 # never get a possible selection involving this.
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:
514 # First ensure that the interface itself has been processed
515 # We'll reuse the ordering of the implementations to order
519 iface
= iface_cache
.get_interface(uri
)
520 filtered_impls
= impls_for_iface
[iface
]
523 for impl
in filtered_impls
:
524 command
= impl
.commands
.get(command_name
, None)
526 if not isinstance(impl
, _DummyImpl
):
527 # Mark implementation as unselectable
528 problem
.add_clause([sat
.neg(impl_to_var
[impl
])])
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
:
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
]]
549 dummy_command
= problem
.add_variable(None)
550 var_names
.append(dummy_command
)
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
)
559 if command_name
is None:
560 add_iface(root_interface
, root_arch
)
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
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
576 self
._failure
_reason
= _("Interface '%s' has no usable implementations") % root_interface
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
)
584 # Require m<group> to be true if we select an implementation in that group
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
)
591 problem
.add_clause([group_var
, sat
.neg(impl
)])
592 m_groups
.append(group_var
)
594 m_groups_clause
= problem
.at_most_one(m_groups
)
596 m_groups_clause
= None
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:
615 dep_lit
= find_undecided(dep
.interface
)
616 if dep_lit
is not None:
621 def find_undecided(uri
):
623 return # Break cycles
626 group
= group_clause_for
.get(uri
, None)
629 # (can be None if the selected impl has an optional dependency on
630 # a feed with no implementations)
633 #print "Group for %s = %s" % (uri, group)
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
)]
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
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
)
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()
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)
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
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
699 # We selected an implementation for interface 'uri'
702 for b
in impl
.bindings
:
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
):
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)
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
:
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(
776 return _("There is no possible selection using {wanted}.\n{reason}").format(
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
:
814 return " (" + i
.id + ")"
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(
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.
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)
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))
844 changes_text
= '\n\n' + _('The changes would be:') + '\n\n' + '\n'.join(changes
)
849 return _("{wanted} is selectable, but using it would produce a less optimal solution overall.").format(wanted
= wanted
) + changes_text
851 return _("If {wanted} were the only option, the best available solution wouldn't use it.").format(wanted
= wanted
) + changes_text
853 DefaultSolver
= SATSolver