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