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 root_arch
= arch
.get_architecture(requirements
.os
, requirements
.cpu
)
98 if requirements
.source
:
99 root_arch
= arch
.SourceArchitecture(root_arch
)
100 if requirements
.command
== 'test':
101 # This is for old feeds that have use='testing' instead of the newer
102 # 'test' command for giving test-only dependencies.
103 root_arch
= arch
.Architecture(root_arch
.os_ranks
, root_arch
.machine_ranks
)
104 root_arch
.use
= frozenset([None, "testing"])
105 return self
.solve(requirements
.interface_uri
, root_arch
, requirements
.command
)
107 def solve(self
, root_interface
, root_arch
, command_name
= 'run'):
108 """Get the best implementation of root_interface and all of its dependencies.
109 @param root_interface: the URI of the program to be solved
110 @type root_interface: str
111 @param root_arch: the desired target architecture
112 @type root_arch: L{arch.Architecture}
113 @param command_name: which <command> element to select
114 @type command_name: str | None
115 @postcondition: self.ready, self.selections and self.feeds_used are updated"""
116 raise NotImplementedError("Abstract")
118 class SATSolver(Solver
):
119 """Converts the problem to a set of pseudo-boolean constraints and uses a PB solver to solve them.
120 @ivar langs: the preferred languages (e.g. ["es_ES", "en"]). Initialised to the current locale.
123 __slots__
= ['_failure_reason', 'config', 'extra_restrictions', '_lang_ranks', '_langs']
126 def iface_cache(self
):
127 return self
.config
.iface_cache
# (deprecated; used by 0compile)
129 def __init__(self
, config
, extra_restrictions
= None):
131 @param config: policy preferences (e.g. stability), the iface_cache and the stores to use
132 @type config: L{policy.Config}
133 @param extra_restrictions: extra restrictions on the chosen implementations
134 @type extra_restrictions: {L{model.Interface}: [L{model.Restriction}]}
136 Solver
.__init
__(self
)
137 assert not isinstance(config
, str), "API change!"
139 self
.extra_restrictions
= extra_restrictions
or {}
141 # By default, prefer the current locale's language first and English second
142 self
.langs
= [locale
.getlocale()[0] or 'en', 'en']
144 def set_langs(self
, langs
):
145 """Set the preferred languages.
146 @param langs: languages (and regions), first choice first
149 # _lang_ranks is a map from locale string to score (higher is better)
153 # (is there are duplicates, the first occurance takes precedence)
156 lang
= langs
[i
].replace('_', '-')
157 _lang_ranks
[lang
.split('-')[0]] = score
158 _lang_ranks
[lang
] = score
+ 1
161 self
._lang
_ranks
= _lang_ranks
163 langs
= property(lambda self
: self
._langs
, set_langs
)
165 def get_rating(self
, interface
, impl
, arch
):
166 impl_langs
= (impl
.langs
or 'en').split()
167 my_langs
= self
._lang
_ranks
169 stores
= self
.config
.stores
170 is_available
= impl
.is_available(stores
)
173 stab_policy
= interface
.stability_policy
175 if self
.config
.help_with_testing
: stab_policy
= model
.testing
176 else: stab_policy
= model
.stable
178 stability
= impl
.get_stability()
179 if stability
>= stab_policy
:
180 stability_limited
= model
.preferred
182 stability_limited
= stability
184 # Note: this list must match _ranking_component_reason above
186 # Languages we understand come first
187 max(my_langs
.get(l
.split('-')[0], -1) for l
in impl_langs
),
189 # Preferred versions come first
190 stability
== model
.preferred
,
192 # Prefer available implementations next if we have limited network access
193 self
.config
.network_use
!= model
.network_full
and is_available
,
195 # Packages that require admin access to install come last
196 not impl
.requires_root_install
,
198 # Prefer more stable versions, but treat everything over stab_policy the same
199 # (so we prefer stable over testing if the policy is to prefer "stable", otherwise
203 # Newer versions come before older ones (ignoring modifiers)
206 # Prefer native packages if the main part of the versions are the same
207 impl
.id.startswith('package:'),
209 # Full version compare (after package check, since comparing modifiers between native and non-native
210 # packages doesn't make sense).
214 -arch
.os_ranks
.get(impl
.os
, 999),
217 -arch
.machine_ranks
.get(impl
.machine
, 999),
219 # Slightly prefer languages specialised to our country
220 # (we know a and b have the same base language at this point)
221 max(my_langs
.get(l
, -1) for l
in impl_langs
),
223 # Slightly prefer cached versions
226 # Order by ID so the order isn't random
230 def solve(self
, root_interface
, root_arch
, command_name
= 'run', closest_match
= False):
231 # closest_match is used internally. It adds a lowest-ranked
232 # by valid implementation to every interface, so we can always
233 # select something. Useful for diagnostics.
235 # The basic plan is this:
236 # 1. Scan the root interface and all dependencies recursively, building up a SAT problem.
237 # 2. Solve the SAT problem. Whenever there are multiple options, try the most preferred one first.
238 # 3. Create a Selections object from the results.
240 # All three involve recursively walking the tree in a similar way:
241 # 1) we follow every dependency of every implementation (order not important)
242 # 2) we follow every dependency of every selected implementation (better versions first)
243 # 3) we follow every dependency of every selected implementation (order doesn't matter)
245 # In all cases, a dependency may be on an <implementation> or on a specific <command>.
247 # TODO: We need some way to figure out which feeds to include.
248 # Currently, we include any feed referenced from anywhere but
249 # this is probably too much. We could insert a dummy optimial
250 # implementation in stale/uncached feeds and see whether it
252 iface_cache
= self
.config
.iface_cache
254 problem
= sat
.SATProblem()
256 impl_to_var
= {} # Impl -> sat var
257 self
.feeds_used
= set()
260 self
.details
= self
.record_details
and {}
262 self
.selections
= None
263 self
._failure
_reason
= None
265 ifaces_processed
= set()
267 machine_groups
= arch
.machine_groups
268 impls_for_machine_group
= {0 : []} # Machine group (e.g. "64") to [impl] in that group
269 for machine_group
in machine_groups
.values():
270 impls_for_machine_group
[machine_group
] = []
272 impls_for_iface
= {} # Iface -> [impl]
274 group_clause_for
= {} # Iface URI -> AtMostOneClause | bool
275 group_clause_for_command
= {} # (Iface URI, command name) -> AtMostOneClause | bool
277 # Return the dependencies of impl that we should consider.
278 # Skips dependencies if the use flag isn't what we need.
279 # (note: impl may also be a model.Command)
280 def deps_in_use(impl
, arch
):
281 for dep
in impl
.requires
:
282 use
= dep
.metadata
.get("use", None)
283 if use
not in arch
.use
:
287 # Must have already done add_iface on dependency.interface.
288 # If dependency is essential:
289 # Add a clause so that if requiring_impl_var is True then an implementation
290 # matching 'dependency' must also be selected.
291 # If dependency is optional:
292 # Require that no incompatible version is selected.
293 # This ignores any 'command' required. Handle that separately.
294 def find_dependency_candidates(requiring_impl_var
, dependency
):
295 def meets_restrictions(candidate
):
296 for r
in dependency
.restrictions
:
297 if not r
.meets_restriction(candidate
):
298 #warn("%s rejected due to %s", candidate.get_version(), r)
302 essential
= dependency
.importance
== model
.Dependency
.Essential
304 dep_iface
= iface_cache
.get_interface(dependency
.interface
)
305 dep_union
= [sat
.neg(requiring_impl_var
)] # Either requiring_impl_var is False, or ...
306 for candidate
in impls_for_iface
[dep_iface
]:
307 if (candidate
.__class
__ is _DummyImpl
) or meets_restrictions(candidate
):
309 c_var
= impl_to_var
.get(candidate
, None)
310 if c_var
is not None:
311 dep_union
.append(c_var
)
312 # else we filtered that version out, so ignore it
314 # Candidate doesn't meet our requirements
315 # If the dependency is optional add a rule to make sure we don't
316 # select this candidate.
317 # (for essential dependencies this isn't necessary because we must
318 # select a good version and we can't select two)
320 c_var
= impl_to_var
.get(candidate
, None)
321 if c_var
is not None:
322 problem
.add_clause(dep_union
+ [sat
.neg(c_var
)])
323 # else we filtered that version out, so ignore it
326 problem
.add_clause(dep_union
)
328 def is_unusable(impl
, restrictions
, arch
):
329 """@return: whether this implementation is unusable.
331 return get_unusable_reason(impl
, restrictions
, arch
) != None
333 def get_unusable_reason(impl
, restrictions
, arch
):
335 @param impl: Implementation to test.
336 @type restrictions: [L{model.Restriction}]
337 @return: The reason why this impl is unusable, or None if it's OK.
339 @note: The restrictions are for the interface being requested, not the feed
340 of the implementation; they may be different when feeds are being used."""
341 for r
in restrictions
:
342 if not r
.meets_restriction(impl
):
343 return _("Incompatible with another selected implementation")
344 stability
= impl
.get_stability()
345 if stability
<= model
.buggy
:
346 return stability
.name
347 if (self
.config
.network_use
== model
.network_offline
or not impl
.download_sources
) and not impl
.is_available(self
.config
.stores
):
348 if not impl
.download_sources
:
349 return _("No retrieval methods")
350 return _("Not cached and we are off-line")
351 if impl
.os
not in arch
.os_ranks
:
352 return _("Unsupported OS")
353 if impl
.machine
not in arch
.machine_ranks
:
354 if impl
.machine
== 'src':
355 return _("Source code")
356 elif 'src' in arch
.machine_ranks
:
357 return _("Not source code")
358 return _("Unsupported machine type")
361 def usable_feeds(iface
, arch
):
362 """Return all feeds for iface that support arch.
363 @rtype: generator(ZeroInstallFeed)"""
366 for f
in iface_cache
.get_feed_imports(iface
):
367 # Note: when searching for src, None is not in machine_ranks
368 if f
.os
in arch
.os_ranks
and \
369 (f
.machine
is None or f
.machine
in arch
.machine_ranks
):
372 debug(_("Skipping '%(feed)s'; unsupported architecture %(os)s-%(machine)s"),
373 {'feed': f
, 'os': f
.os
, 'machine': f
.machine
})
375 # If requiring_var is True then all of requirer's dependencies must be satisfied.
376 # requirer can be a <command> or an <implementation>
377 def process_dependencies(requiring_var
, requirer
, arch
):
378 for d
in deps_in_use(requirer
, arch
):
379 debug(_("Considering command dependency %s"), d
)
381 add_iface(d
.interface
, arch
.child_arch
)
383 for c
in d
.get_required_commands():
384 # We depend on a specific command within the implementation.
385 command_vars
= add_command_iface(d
.interface
, arch
.child_arch
, c
)
387 # If the parent command/impl is chosen, one of the candidate commands
388 # must be too. If there aren't any, then this command is unselectable.
389 problem
.add_clause([sat
.neg(requiring_var
)] + command_vars
)
391 # Must choose one version of d if impl is selected
392 find_dependency_candidates(requiring_var
, d
)
394 def add_iface(uri
, arch
):
395 """Name implementations from feed and assert that only one can be selected."""
396 if uri
in ifaces_processed
: return
397 ifaces_processed
.add(uri
)
399 iface
= iface_cache
.get_interface(uri
)
402 for f
in usable_feeds(iface
, arch
):
403 self
.feeds_used
.add(f
)
404 debug(_("Processing feed %s"), f
)
407 feed
= iface_cache
.get_feed(f
)
408 if feed
is None: continue
409 #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for:
410 # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f})
412 if feed
.implementations
:
413 impls
.extend(feed
.implementations
.values())
415 distro_feed_url
= feed
.get_distro_feed()
417 self
.feeds_used
.add(distro_feed_url
)
418 distro_feed
= iface_cache
.get_feed(distro_feed_url
)
419 if distro_feed
.implementations
:
420 impls
.extend(distro_feed
.implementations
.values())
421 except MissingLocalFeed
as ex
:
422 warn(_("Missing local feed; if it's no longer required, remove it with:") +
423 '\n0install remove-feed ' + iface
.uri
+ ' ' + f
,
424 {'feed': f
, 'interface': iface
, 'exception': ex
})
425 except Exception as ex
:
426 warn(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f
, 'interface': iface
, 'exception': ex
})
429 impls
.sort(key
= lambda impl
: self
.get_rating(iface
, impl
, arch
), reverse
= True)
431 impls_for_iface
[iface
] = filtered_impls
= []
433 my_extra_restrictions
= self
.extra_restrictions
.get(iface
, [])
435 if self
.record_details
:
436 self
.details
[iface
] = [(impl
, get_unusable_reason(impl
, my_extra_restrictions
, arch
)) for impl
in impls
]
440 if is_unusable(impl
, my_extra_restrictions
, arch
):
443 filtered_impls
.append(impl
)
445 assert impl
not in impl_to_var
446 v
= problem
.add_variable(ImplInfo(iface
, impl
, arch
))
447 impl_to_var
[impl
] = v
450 if impl
.machine
and impl
.machine
!= 'src':
451 impls_for_machine_group
[machine_groups
.get(impl
.machine
, 0)].append(v
)
453 process_dependencies(v
, impl
, arch
)
456 dummy_impl
= _DummyImpl()
457 dummy_var
= problem
.add_variable(ImplInfo(iface
, dummy_impl
, arch
, dummy
= True))
458 var_names
.append(dummy_var
)
459 impl_to_var
[dummy_impl
] = dummy_var
460 filtered_impls
.append(dummy_impl
)
462 # Only one implementation of this interface can be selected
463 if uri
== root_interface
:
465 clause
= problem
.at_most_one(var_names
)
466 problem
.add_clause(var_names
) # at least one
471 clause
= problem
.at_most_one(var_names
)
473 # Don't need to add to group_clause_for because we should
474 # never get a possible selection involving this.
477 assert clause
is not True
478 assert clause
is not None
479 if clause
is not False:
480 group_clause_for
[uri
] = clause
482 def add_command_iface(uri
, arch
, command_name
):
483 """Add every <command> in interface 'uri' with this name.
484 Each one depends on the corresponding implementation and only
485 one can be selected. If closest_match is on, include a dummy
486 command that can always be selected."""
488 # Check whether we've already processed this (interface,command) pair
489 existing
= group_clause_for_command
.get((uri
, command_name
), None)
490 if existing
is not None:
493 # First ensure that the interface itself has been processed
494 # We'll reuse the ordering of the implementations to order
498 iface
= iface_cache
.get_interface(uri
)
499 filtered_impls
= impls_for_iface
[iface
]
502 for impl
in filtered_impls
:
503 command
= impl
.commands
.get(command_name
, None)
505 if not isinstance(impl
, _DummyImpl
):
506 # Mark implementation as unselectable
507 problem
.add_clause([sat
.neg(impl_to_var
[impl
])])
510 # We have a candidate <command>. Require that if it's selected
511 # then we select the corresponding <implementation> too.
512 command_var
= problem
.add_variable(CommandInfo(command_name
, command
, impl
, arch
))
513 problem
.add_clause([impl_to_var
[impl
], sat
.neg(command_var
)])
515 var_names
.append(command_var
)
517 process_dependencies(command_var
, command
, arch
)
519 # Tell the user why we couldn't use this version
520 if self
.record_details
:
521 def new_reason(impl
, old_reason
):
522 if command_name
in impl
.commands
:
524 return old_reason
or (_('No %s command') % command_name
)
525 self
.details
[iface
] = [(impl
, new_reason(impl
, reason
)) for (impl
, reason
) in self
.details
[iface
]]
528 dummy_command
= problem
.add_variable(None)
529 var_names
.append(dummy_command
)
532 # Can't select more than one of them.
533 assert (uri
, command_name
) not in group_clause_for_command
534 group_clause_for_command
[(uri
, command_name
)] = problem
.at_most_one(var_names
)
538 if command_name
is None:
539 add_iface(root_interface
, root_arch
)
541 commands
= add_command_iface(root_interface
, root_arch
, command_name
)
542 if len(commands
) > int(closest_match
):
543 # (we have at least one non-dummy command)
544 problem
.add_clause(commands
) # At least one
546 # (note: might be because we haven't cached it yet)
547 info("No %s <command> in %s", command_name
, root_interface
)
549 impls
= impls_for_iface
[iface_cache
.get_interface(root_interface
)]
550 if impls
== [] or (len(impls
) == 1 and isinstance(impls
[0], _DummyImpl
)):
551 # There were no candidates at all.
552 if self
.config
.network_use
== model
.network_offline
:
553 self
._failure
_reason
= _("Interface '%s' has no usable implementations in the cache (and 0install is in off-line mode)") % root_interface
555 self
._failure
_reason
= _("Interface '%s' has no usable implementations") % root_interface
557 # We had some candidates implementations, but none for the command we need
558 self
._failure
_reason
= _("Interface '%s' cannot be executed directly; it is just a library "
559 "to be used by other programs (or missing '%s' command)") % (root_interface
, command_name
)
563 # Require m<group> to be true if we select an implementation in that group
565 for machine_group
, impls
in impls_for_machine_group
.iteritems():
566 m_group
= 'm%d' % machine_group
567 group_var
= problem
.add_variable(m_group
)
570 problem
.add_clause([group_var
, sat
.neg(impl
)])
571 m_groups
.append(group_var
)
573 m_groups_clause
= problem
.at_most_one(m_groups
)
575 m_groups_clause
= None
578 """Recurse through the current selections until we get to an interface with
579 no chosen version, then tell the solver to try the best version from that."""
581 def find_undecided_dep(impl_or_command
, arch
):
582 # Check for undecided dependencies of impl_or_command
583 for dep
in deps_in_use(impl_or_command
, arch
):
584 for c
in dep
.get_required_commands():
585 dep_lit
= find_undecided_command(dep
.interface
, c
)
586 if dep_lit
is not None:
588 dep_lit
= find_undecided(dep
.interface
)
589 if dep_lit
is not None:
594 def find_undecided(uri
):
596 return # Break cycles
599 group
= group_clause_for
.get(uri
, None)
602 # (can be None if the selected impl has an optional dependency on
603 # a feed with no implementations)
606 #print "Group for %s = %s" % (uri, group)
609 return group
.best_undecided()
610 # else there was only one choice anyway
612 # Check for undecided dependencies
613 lit_info
= problem
.get_varinfo_for_lit(lit
).obj
614 return find_undecided_dep(lit_info
.impl
, lit_info
.arch
)
616 def find_undecided_command(uri
, name
):
617 if name
is None: return find_undecided(uri
)
619 group
= group_clause_for_command
[(uri
, name
)]
622 return group
.best_undecided()
623 # else we've already chosen which <command> to use
625 # Check for undecided command-specific dependencies, and then for
626 # implementation dependencies.
627 lit_info
= problem
.get_varinfo_for_lit(lit
).obj
630 return None # (a dummy command added for better diagnostics; has no dependencies)
631 return find_undecided_dep(lit_info
.command
, lit_info
.arch
) or \
632 find_undecided_dep(lit_info
.impl
, lit_info
.arch
)
634 best
= find_undecided_command(root_interface
, command_name
)
638 # If we're chosen everything we need, we can probably
639 # set everything else to False.
640 for group
in group_clause_for
.values() + group_clause_for_command
.values() + [m_groups_clause
]:
641 if group
.current
is None:
642 best
= group
.best_undecided()
646 return None # Failed to find any valid combination
648 ready
= problem
.run_solver(decide
) is True
650 if not ready
and not closest_match
:
651 # We failed while trying to do a real solve.
652 # Try a closest match solve to get a better
653 # error report for the user.
654 self
.solve(root_interface
, root_arch
, command_name
= command_name
, closest_match
= True)
656 self
.ready
= ready
and not closest_match
657 self
.selections
= selections
.Selections(None)
658 self
.selections
.interface
= root_interface
660 sels
= self
.selections
.selections
664 # Populate sels with the selected implementations.
665 # Also, note down all the commands we need.
666 for uri
, group
in group_clause_for
.iteritems():
667 if group
.current
is not None:
668 lit_info
= problem
.get_varinfo_for_lit(group
.current
).obj
669 if lit_info
.is_dummy
:
670 sels
[lit_info
.iface
.uri
] = None
672 # We selected an implementation for interface 'uri'
675 for b
in impl
.bindings
:
678 commands
.append((uri
, c
))
680 deps
= self
.requires
[lit_info
.iface
] = []
681 for dep
in deps_in_use(lit_info
.impl
, lit_info
.arch
):
683 for c
in dep
.get_required_commands():
684 commands_needed
.append((dep
.interface
, c
))
686 sels
[lit_info
.iface
.uri
] = selections
.ImplSelection(lit_info
.iface
.uri
, impl
, deps
)
688 # Now all all the commands in too.
689 def add_command(iface
, name
):
690 sel
= sels
.get(iface
, None)
692 command
= sel
.impl
.commands
[name
]
693 if name
in sel
._used
_commands
:
694 return # Already added
695 sel
._used
_commands
.add(name
)
697 for dep
in command
.requires
:
698 for dep_command_name
in dep
.get_required_commands():
699 add_command(dep
.interface
, dep_command_name
)
701 # A <command> can depend on another <command> in the same interface
702 # (e.g. the tests depending on the main program)
703 for b
in command
.bindings
:
706 add_command(iface
, c
)
708 for iface
, command
in commands_needed
:
709 add_command(iface
, command
)
711 if command_name
is not None:
712 self
.selections
.command
= command_name
713 add_command(root_interface
, command_name
)
715 def get_failure_reason(self
):
716 """Return an exception explaining why the solve failed."""
717 assert not self
.ready
719 if self
._failure
_reason
:
720 return model
.SafeException(self
._failure
_reason
)
722 return model
.SafeException(_("Can't find all required implementations:") + '\n' +
723 '\n'.join(["- %s -> %s" % (iface
, self
.selections
[iface
])
724 for iface
in self
.selections
]))
726 def justify_decision(self
, requirements
, iface
, impl
):
727 """Run a solve with impl_id forced to be selected, and explain why it wasn't (or was)
728 selected in the normal case."""
729 assert isinstance(iface
, model
.Interface
), iface
731 restrictions
= self
.extra_restrictions
.copy()
732 restrictions
[iface
] = restrictions
.get(iface
, []) + [_ForceImpl(impl
.id)]
733 s
= SATSolver(self
.config
, restrictions
)
734 s
.record_details
= True
735 s
.solve_for(requirements
)
737 wanted
= "{iface} {version}".format(iface
= iface
.get_name(), version
= impl
.get_version())
739 # Could a selection involving impl even be valid?
740 if not s
.ready
or iface
.uri
not in s
.selections
.selections
:
741 reasons
= s
.details
.get(iface
, [])
742 for (rid
, rstr
) in reasons
:
743 if rid
.id == impl
.id and rstr
is not None:
744 return _("{wanted} cannot be used (regardless of other components): {reason}").format(
749 return _("There is no possible selection using {wanted}.\n{reason}").format(
751 reason
= s
.get_failure_reason())
753 actual_selection
= self
.selections
.get(iface
, None)
754 if actual_selection
is not None:
755 # Was impl actually selected anyway?
756 if actual_selection
.id == impl
.id:
757 return _("{wanted} was selected as the preferred version.").format(wanted
= wanted
)
759 # Was impl ranked below the selected version?
760 iface_arch
= arch
.get_architecture(requirements
.os
, requirements
.cpu
)
761 if requirements
.source
and iface
.uri
== requirements
.interface_uri
:
762 iface_arch
= arch
.SourceArchitecture(iface_arch
)
763 wanted_rating
= self
.get_rating(iface
, impl
, arch
)
764 selected_rating
= self
.get_rating(iface
, actual_selection
, arch
)
766 if wanted_rating
< selected_rating
:
767 _ranking_component_reason
= [
768 _("natural languages we understand are preferred"),
769 _("preferred versions come first"),
770 _("locally-available versions are preferred when network use is limited"),
771 _("packages that don't require admin access to install are preferred"),
772 _("more stable versions preferred"),
773 _("newer versions are preferred"),
774 _("native packages are preferred"),
775 _("newer versions are preferred"),
776 _("better OS match"),
777 _("better CPU match"),
778 _("better locale match"),
779 _("is locally available"),
780 _("better ID (tie-breaker)"),
783 actual
= actual_selection
.get_version()
784 if impl
.get_version() == actual
:
787 return " (" + i
.id + ")"
789 return " (" + i
.id[:16] + "...)"
791 wanted
+= detail(impl
)
792 actual
+= detail(actual_selection
)
794 for i
in range(len(wanted_rating
)):
795 if wanted_rating
[i
] < selected_rating
[i
]:
796 return _("{wanted} is ranked lower than {actual}: {why}").format(
799 why
= _ranking_component_reason
[i
])
801 used_impl
= iface
.uri
in s
.selections
.selections
803 # Impl is selectable and ranked higher than the selected version. Selecting it would cause
804 # a problem elsewhere.
806 for old_iface
, old_sel
in self
.selections
.selections
.iteritems():
807 if old_iface
== iface
.uri
and used_impl
: continue
808 new_sel
= s
.selections
.selections
.get(old_iface
, None)
810 changes
.append(_("{interface}: no longer used").format(interface
= old_iface
))
811 elif old_sel
.version
!= new_sel
.version
:
812 changes
.append(_("{interface}: {old} to {new}").format(interface
= old_iface
, old
= old_sel
.version
, new
= new_sel
.version
))
813 elif old_sel
.id != new_sel
.id:
814 changes
.append(_("{interface}: {old} to {new}").format(interface
= old_iface
, old
= old_sel
.id, new
= new_sel
.id))
817 changes_text
= '\n\n' + _('The changes would be:') + '\n\n' + '\n'.join(changes
)
822 return _("{wanted} is selectable, but using it would produce a less optimal solution overall.").format(wanted
= wanted
) + changes_text
824 return _("If {wanted} were the only option, the best available solution wouldn't use it.").format(wanted
= wanted
) + changes_text
826 DefaultSolver
= SATSolver