123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439(* Copyright (C) 2013, Thomas Leonard
* See the README file for details, or visit http://0install.net.
*)(** Explaining why a solve failed or gave an unexpected answer. *)moduleList=Solver_core.Listletpf=Format.fprintfmoduleMake(Results:S.SOLVER_RESULT)=structmoduleModel=Results.InputmoduleRoleMap=Results.RoleMapletformat_role=Model.Role.ppletformat_restrictionsr=String.concat", "(List.mapModel.string_of_restrictionr)moduleNote=structtypet=|UserRequestedofModel.restriction|ReplacesConflictofModel.Role.t|ReplacedByConflictofModel.Role.t|RestrictsofModel.Role.t*Model.impl*Model.restrictionlist|RequiresCommandofModel.Role.t*Model.impl*Model.command_name|Feed_problemofstringletppf=function|UserRequestedr->pff"User requested %s"(format_restrictions[r])|ReplacesConflictold->pff"Replaces (and therefore conflicts with) %a"format_roleold|ReplacedByConflictreplacement->pff"Replaced by (and therefore conflicts with) %a"format_rolereplacement|Restricts(other_role,impl,r)->pff"%a %a requires %s"format_roleother_roleModel.pp_versionimpl(format_restrictionsr)|RequiresCommand(other_role,impl,command)->pff"%a %a requires '%s' command"format_roleother_roleModel.pp_versionimpl(command:>string)|Feed_problemmsg->pff"%s"msgend(** Represents a single interface in the example (failed) selections produced by the solver.
It partitions the implementations into good and bad based (initially) on the split from the
impl_provider. As we explore the example selections, we further filter the candidates. *)moduleComponent=structtyperejection_reason=[|`Model_rejectionofModel.rejection|`FailsRestrictionofModel.restriction|`DepFailsRestrictionofModel.dependency*Model.restriction|`MachineGroupConflictofModel.Role.t*Model.impl|`ClassConflictofModel.Role.t*Model.conflict_class|`ConflictsRoleofModel.Role.t|`MissingCommandofModel.command_name|`DiagnosticsFailureofstring](* Why a particular implementation was rejected. This could be because the model rejected it,
or because it conflicts with something else in the example (partial) solution. *)typereject=Model.impl*rejection_reasontypet={role:Model.Role.t;replacement:Model.Role.toption;diagnostics:stringLazy.t;selected_impl:Model.imploption;selected_commands:Model.command_namelist;(* orig_good is all the implementations passed to the SAT solver (these are the
ones with a compatible OS, CPU, etc). They are sorted most desirable first. *)orig_good:Model.impllist;orig_bad:(Model.impl*Model.rejection)list;mutablegood:Model.impllist;mutablebad:(Model.impl*rejection_reason)list;mutablenotes:Note.tlist;}(* Initialise a new component.
@param candidates is the result from the impl_provider.
@param selected_impl is the selected implementation, or [None] if we chose [dummy_impl].
@param diagnostics can be used to produce diagnostics as a last resort. *)letcreate~role(candidates,orig_bad,feed_problems)(diagnostics:stringLazy.t)(selected_impl:Model.imploption)(selected_commands:Model.command_namelist)=let{Model.impls;Model.replacement}=candidatesinletnotes=List.map(funx->Note.Feed_problemx)feed_problemsin{role;replacement;orig_good=impls;orig_bad;good=impls;bad=List.map(fun(impl,reason)->(impl,`Model_rejectionreason))orig_bad;notes;diagnostics;selected_impl;selected_commands}letnotetnote=t.notes<-note::t.notesletnotest=List.revt.notes(* Did rejecting [impl] make any difference?
If [t] selected a better version anyway then we don't need to report this rejection. *)letaffected_selectiontimpl=matcht.selected_implwith|SomeselectedwhenModel.compare_versionselectedimpl>0->false|_->true(* Call [get_problem impl] on each good impl. If a problem is returned, move [impl] to [bad_impls].
If anything changes and [!note] is not None, report it and clear the pending note. *)letfilter_impls_ref~note:ntget_problem=letold_good=List.revt.goodint.good<-[];old_good|>List.iter(funimpl->matchget_problemimplwith|None->t.good<-impl::t.good|Someproblem->!n|>Option.iter(funinfo->ifaffected_selectiontimplthen(notetinfo;n:=None;));t.bad<-(impl,problem)::t.bad)letfilter_impls?notetget_problem=letnote=refnoteinfilter_impls_ref~notetget_problem(* Remove from [good_impls] anything that fails to meet these restrictions.
Add removed items to [bad_impls], along with the cause. *)letapply_restrictions~notetrestrictions=letnote=ref(Somenote)inrestrictions|>List.iter(funr->filter_impls_ref~notet(funimpl->ifModel.meets_restrictionimplrthenNoneelseSome(`FailsRestrictionr)))letapply_user_restrictiontr=notet(UserRequestedr);(* User restrictions should be applied before reaching the solver, but just in case: *)filter_implst(funimpl->ifModel.meets_restrictionimplrthenNoneelseSome(`FailsRestrictionr));(* Completely remove non-matching impls.
The user will only want to see the version they asked for. *)letnew_bad=t.bad|>List.filter(fun(impl,_)->ifModel.meets_restrictionimplrthentrueelsefalse)inifnew_bad<>[]||t.good<>[]thent.bad<-new_badletreject_alltreason=t.bad<-List.map(funimpl->(impl,reason))t.good@t.bad;t.good<-[]letreplacementt=t.replacementletselected_implt=t.selected_implletselected_commandst=t.selected_commands(* When something conflicts with itself then our usual trick of selecting
the main implementation and failing the dependency doesn't work, so
special-case that here. *)letreject_self_conflictst=filter_implst(funimpl->letdeps,_=Model.requirest.roleimplindeps|>List.find_map(fundep->let{Model.dep_role;_}=Model.dep_infodepinifModel.Role.comparedep_rolet.role<>0thenNoneelse((* It depends on itself. *)Model.restrictionsdep|>List.find_map(funr->ifModel.meets_restrictionimplrthenNoneelseSome(`DepFailsRestriction(dep,r))))))letfinaliset=ift.selected_impl=Nonethen(reject_self_conflictst;reject_allt(`DiagnosticsFailure(Lazy.forcet.diagnostics)))letpp_rejectf((impl,reason):reject)=matchreasonwith|`Model_rejectionr->Format.pp_print_stringf(Model.describe_problemimplr)|`FailsRestrictionr->pff"Incompatible with restriction: %s"(Model.string_of_restrictionr)|`DepFailsRestriction(dep,restriction)->letdep_info=Model.dep_infodepinpff"Requires %a %s"format_roledep_info.Model.dep_role(format_restrictions[restriction])|`MachineGroupConflict(other_role,other_impl)->pff"Can't use %s with selection of %a (%s)"(Model.format_machineimpl)format_roleother_role(Model.format_machineother_impl)|`ClassConflict(other_role,cl)->pff"In same conflict class (%s) as %a"(cl:>string)format_roleother_role|`ConflictsRoleother_role->pff"Conflicts with %a"format_roleother_role|`MissingCommandcommand->pff"No %s command"(command:Model.command_name:>string)|`DiagnosticsFailuremsg->pff"Reason for rejection unknown: %s"msgletshow_rejections~verbosefrejected=letby_version(a,_)(b,_)=Model.compare_versionbainletrejected=List.sortby_versionrejectedinletrecauxi=function|[]->()|_wheni=5&¬verbose->pff"@,..."|(impl,problem)::xs->pff"@,%a: %a"Model.pp_impl_longimplpp_reject(impl,problem);aux(i+1)xsinaux0rejectedletrejectst=letsummary=ift.orig_good=[]then(ift.orig_bad=[]then`No_candidateselse`All_unusable)else`Conflictsint.bad,summaryletpp_candidates~verboseft=ift.selected_impl=Nonethen(matchrejectstwith|_,`No_candidates->pff"@,No known implementations at all"|bad,`All_unusable->pff"@,@[<v2>No usable implementations:%a@]"(show_rejections~verbose)bad|bad,`Conflicts->pff"@,@[<v2>Rejected candidates:%a@]"(show_rejections~verbose)bad)letpp_notesft=matchnotestwith|[]->()|notes->pff"@,%a"Format.(pp_print_list~pp_sep:pp_print_cutNote.pp)notesletpp_outcomeft=matcht.selected_implwith|Somesel->Model.pp_impl_longfsel|None->Format.pp_print_stringf"(problem)"(* Format a textual description of this component's report. *)letpp~verboseft=pff"@[<v2>%a -> %a%a%a@]"format_rolet.rolepp_outcometpp_notest(pp_candidates~verbose)tendtypet=Component.tRoleMap.tletfind_component_exrolereport=matchRoleMap.find_optrolereportwith|Somec->c|None->failwith(Format.asprintf"Can't find component %a!"format_rolerole)(* Did any dependency of [impl] prevent it being selected?
This can only happen if a component conflicts with something more important
than itself (otherwise, we'd select something in [impl]'s interface and
complain about the dependency instead).
e.g. A depends on B and C. B and C both depend on D.
C1 conflicts with D1. The depth-first priority order means we give priority
to {A1, B1, D1}. Then we can't choose C1 because we prefer to keep D1. *)letget_dependency_problemrolereportimpl=letcheck_depdep=letdep_info=Model.dep_infodepinmatchRoleMap.find_optdep_info.Model.dep_rolereportwith|None->None(* Not in the selections => can't be part of a conflict *)|Somerequired_component->matchComponent.selected_implrequired_componentwith|None->None(* Dummy selection can't cause a conflict *)|Somedep_impl->letcheck_restrictionr=ifModel.meets_restrictiondep_implrthenNoneelseSome(`DepFailsRestriction(dep,r))inList.find_mapcheck_restriction(Model.restrictionsdep)inletdeps,commands_needed=Model.requiresroleimplincommands_needed|>List.find_map(funcommand->ifModel.get_commandimplcommand<>NonethenNoneelseSome(`MissingCommandcommand:Component.rejection_reason))|>function|Some_asr->r|None->List.find_mapcheck_depdeps(** A selected component has [dep] as a dependency. Use this to explain why some implementations
of the required interface were rejected. *)letexamine_deprequiring_rolerequiring_implreportdep=let{Model.dep_role=other_role;dep_importance=_;dep_required_commands}=Model.dep_infodepinmatchRoleMap.find_optother_rolereportwith|None->()|Somerequired_component->letdep_restrictions=Model.restrictionsdepinifdep_restrictions<>[]then((* Remove implementations incompatible with the other selections *)Component.apply_restrictionsrequired_componentdep_restrictions~note:(Restricts(requiring_role,requiring_impl,dep_restrictions)));dep_required_commands|>List.iter(funcommand->letnote=Note.RequiresCommand(requiring_role,requiring_impl,command)inComponent.filter_impls~noterequired_component(funimpl->ifModel.get_commandimplcommand<>NonethenNoneelseSome(`MissingCommandcommand)))(* Find all restrictions that are in play and affect this interface *)letexamine_selectionreportrolecomponent=(* Note any conflicts caused by <replaced-by> elements *)let()=matchComponent.replacementcomponentwith|SomereplacementwhenRoleMap.memreplacementreport->(Component.notecomponent(ReplacedByConflictreplacement);Component.reject_allcomponent(`ConflictsRolereplacement);matchRoleMap.find_optreplacementreportwith|Somereplacement_component->Component.notereplacement_component(ReplacesConflictrole);Component.reject_allreplacement_component(`ConflictsRolerole)|None->())|_->()inmatchComponent.selected_implcomponentwith|Someour_impl->(* For each dependency of our selected impl, explain why it rejected impls in the dependency's interface. *)letdeps,_commands_needed=Model.requiresroleour_implin(* We can ignore [commands_needed] here because we obviously were selected. *)List.iter(examine_deproleour_implreport)deps;Component.selected_commandscomponent|>List.iter(funname->matchModel.get_commandour_implnamewith|None->failwith"BUG: missing command!"(* Can't happen - it's a "selected" command *)|Somecommand->letdeps,_commands_needed=Model.command_requiresrolecommandinList.iter(examine_deproleour_implreport)deps;)|None->(* For each of our remaining unrejected impls, check whether a dependency prevented its selection. *)Component.filter_implscomponent(get_dependency_problemrolereport)(* Check for user-supplied restrictions *)letexamine_extra_restrictionsreport=report|>RoleMap.iter(funrolecomponent->Model.user_restrictionsrole|>Option.iter(funrestriction->Component.apply_user_restrictioncomponentrestriction))(** If we wanted a command on the root, add that as a restriction. *)letprocess_root_reqreport=function|{Model.command=Someroot_command;role=root_role}->letcomponent=find_component_exroot_rolereportinComponent.filter_implscomponent(funimpl->ifModel.get_commandimplroot_command<>NonethenNoneelseSome(`MissingCommandroot_command))|_->()(** Find an implementation which requires a machine group. Use this to
explain the rejection of all implementations requiring other groups. *)exceptionFoundof(Model.Role.t*Model.impl*Model.machine_group)letcheck_machine_groupsreport=letcheckrolecompoment=matchComponent.selected_implcompomentwith|None->()|Someimpl->matchModel.machine_groupimplwith|None->()|Somegroup->raise(Found(role,impl,group))intryRoleMap.itercheckreportwithFound(example_role,example_impl,example_group)->letfilter_keycomponent=Component.filter_implscomponent(funimpl->matchModel.machine_groupimplwith|Somegroupwhengroup<>example_group->Some(`MachineGroupConflict(example_role,example_impl))|_->None)inRoleMap.iterfilterreportmoduleClasses=Map.Make(structtypet=Model.conflict_classletcompare=compareend)(** For each selected implementation with a conflict class, reject all candidates
with the same class. *)letcheck_conflict_classesreport=letclasses=RoleMap.fold(funrolecomponentacc->matchComponent.selected_implcomponentwith|None->acc|Someimpl->Model.conflict_classimpl|>List.fold_left(funaccx->Classes.addxroleacc)acc)reportClasses.emptyinreport|>RoleMap.iter@@funrolecomponent->Component.filter_implscomponent@@funimpl->letrecaux=function|[]->None|cl::cls->matchClasses.find_optclclasseswith|Someother_rolewhenModel.Role.compareroleother_role<>0->Some(`ClassConflict(other_role,cl))|_->auxclsinaux(Model.conflict_classimpl)letof_resultresult=letimpls=Results.to_mapresultinletroot_req=Results.requirementsresultinletreport=letget_selectedrolesel=letimpl=Results.unwrapselinletdiagnostics=lazy(Results.explainresultrole)inletimpl=ifimpl==Model.dummy_implthenNoneelseSomeimplinletimpl_candidates=Model.implementationsroleinletrejects,feed_problems=Model.rejectsroleinletselected_commands=Results.selected_commandsselinComponent.create~role(impl_candidates,rejects,feed_problems)diagnosticsimplselected_commandsinRoleMap.mapiget_selectedimplsinprocess_root_reqreportroot_req;examine_extra_restrictionsreport;check_machine_groupsreport;check_conflict_classesreport;RoleMap.iter(examine_selectionreport)report;RoleMap.iter(fun_c->Component.finalisec)report;reportletpp_rolemap~verbosefreasons=letpp_itemf(_,c)=pff"- @[%a@]"(Component.pp~verbose)cinFormat.(pp_print_list~pp_sep:pp_print_cut)pp_itemf(RoleMap.bindingsreasons)(** Return a message explaining why the solve failed. *)letget_failure_reason?(verbose=false)result=letreasons=of_resultresultinFormat.asprintf"Can't find all required implementations:@\n@[<v0>%a@]"(pp_rolemap~verbose)reasonsend