123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617(* Copyright (C) 2013, Thomas Leonard
* See the README file for details, or visit http://0install.net.
*)(** Select a compatible set of components to run a program. *)moduleList=structincludeListletrecfind_mapf=function|[]->None|(x::xs)->matchfxwith|Some_asresult->result|None->find_mapfxsendtype('a,'b)partition_result=|Leftof'a|Rightof'bletpartitionfnlst=letpass=ref[]inletfail=ref[]inListLabels.iterlst~f:(funitem->matchfnitemwith|Leftx->pass:=x::!pass|Rightx->fail:=x::!fail);(List.rev!pass,List.rev!fail)moduletypeCACHE_ENTRY=sigtypettypevaluevalcompare:t->t->intendmoduleCache(CacheEntry:CACHE_ENTRY):sig(** The cache is used in [build_problem], while the clauses are still being added. *)typetmoduleM:Map.Swithtypekey=CacheEntry.t(** Once the problem is built, an immutable snapshot is taken. *)typesnapshot=CacheEntry.valueM.tvalcreate:unit->t(** [lookup cache make key] will look up [key] in [cache].
* If not found, create it with [value, process = make key], add [value] to the cache,
* and then call [process ()] on it.
* [make] must not be recursive (since the key hasn't been added yet),
* but [process] can be. In other words, [make] does whatever setup *must*
* be done before anyone can use this cache entry, while [process] does
* setup that can be done afterwards. *)vallookup:t->(CacheEntry.t->(CacheEntry.value*(unit->unit)))->CacheEntry.t->CacheEntry.valuevalsnapshot:t->snapshotvalget:CacheEntry.t->snapshot->CacheEntry.valueoptionvalget_exn:CacheEntry.t->snapshot->CacheEntry.valuevalfilter_map:(CacheEntry.t->'a->'boption)->'aM.t->'bM.tend=structmoduleM=Map.Make(CacheEntry)typesnapshot=CacheEntry.valueM.ttypet=snapshotrefletcreate()=refM.emptyletlookuptablemakekey=matchM.find_optkey!tablewith|Somex->x|None->letvalue,process=makekeyintable:=M.addkeyvalue!table;process();valueletsnapshottable=!tableletget=M.find_optletget_exn=M.findletfilter_mapfm=M.merge(funkeyao_bo->matchaowith|Somex->fkeyx|None->assertfalse)mM.emptyendmoduleMake(Model:S.SOLVER_INPUT)=struct(** We attach this data to each SAT variable. *)moduleSolverData=structtypet=(* If the SAT variable is True then we selected this... *)|ImplElemofModel.impl|CommandElemofModel.command|MachineGroupofstring|RoleofModel.Role.tletppf=function|ImplElemimpl->Model.pp_implfimpl|CommandElemcommand->Model.pp_commandfcommand|MachineGroupname->Format.pp_print_stringfname|Rolerole->Model.Role.ppfroleendmoduleS=Sat.Make(SolverData)typedecision_state=(* The next candidate to try *)|UndecidedofS.lit(* The dependencies to check next *)|Selectedof(Model.dependencylist*Model.command_namelist)|Unselectedclasstypecandidates=objectmethodget_clause:S.at_most_one_clauseoptionmethodget_vars:S.litlistmethodget_state:decision_stateendclassimpl_candidatesrole(clause:S.at_most_one_clauseoption)(vars:(S.lit*Model.impl)list)dummy_impl=letis_dummy=matchdummy_implwith|None->fun_->false|Somedummy_impl->(==)dummy_implinobject(_:#candidates)methodget_clause=clause(** Get just those implementations that have a command with this name. *)methodget_commandsname=letmatch_command(impl_var,impl)=matchModel.get_commandimplnamewith|Somecommand->Some(impl_var,command)|None->Noneinvars|>List.filter_mapmatch_command(** Get all variables, except dummy_impl (if present) *)methodget_real_vars=vars|>List.filter_map(fun(var,impl)->ifis_dummyimplthenNoneelseSomevar)methodget_vars=List.map(fun(var,_impl)->var)varsmethodget_selected=matchclausewith|None->None(* There were never any candidates *)|Someclause->matchS.get_selectedclausewith|None->None|Somelit->matchS.get_user_data_for_litlitwith|SolverData.ImplElemimpl->Some(lit,impl)|_->assertfalsemethodget_state=matchclausewith|None->Unselected(* There were never any candidates *)|Someclause->matchS.get_selectedclausewith|Somelit->(* We've already chosen which <implementation> to use. Follow dependencies. *)letimpl=matchS.get_user_data_for_litlitwith|SolverData.ImplElemimpl->impl|_->assertfalseinSelected(Model.requiresroleimpl)|None->matchS.get_best_undecidedclausewith|Somelit->Undecidedlit|None->Unselected(* No remaining candidates, and none was chosen. *)(** Apply [test impl] to each implementation, partitioning the vars into two lists.
Only defined for [impl_candidates]. *)methodpartitiontest=partition(fun(var,impl)->iftestimplthenLeftvarelseRightvar)varsend(** Holds all the commands with a given name within an interface. *)classcommand_candidatesrole(clause:S.at_most_one_clauseoption)(vars:(S.lit*Model.command)list)=object(_:#candidates)methodget_clause=clausemethodget_vars=List.map(fun(var,_command)->var)varsmethodget_state=matchclausewith|None->Unselected(* There were never any candidates *)|Someclause->matchS.get_selectedclausewith|Somelit->(* We've already chosen which <command> to use. Follow dependencies. *)letcommand=matchS.get_user_data_for_litlitwith|SolverData.CommandElemcommand->command|_->assertfalseinSelected(Model.command_requiresrolecommand)|None->matchS.get_best_undecidedclausewith|Somelit->Undecidedlit|None->Unselected(* No remaining candidates, and none was chosen. *)endmoduleCommandRoleEntry=structtypet=(Model.command_name*Model.Role.t)typevalue=command_candidatesletcompare((an,ar):t)((bn,br):t)=matchString.compare(an:>string)(bn:>string)with|0->Model.Role.comparearbr|r->rendmoduleRoleEntry=structincludeModel.Roletypevalue=impl_candidatesendmoduleImplCache=Cache(RoleEntry)moduleCommandCache=Cache(CommandRoleEntry)moduleRoleMap=ImplCache.Mtypediagnostics=S.litletexplain=S.explain_reasontypeselection={impl:Model.impl;(** The implementation chosen to fill the role *)commands:Model.command_namelist;(** The commands required *)diagnostics:diagnostics;(** Extra information useful for diagnostics *)}(* Make each interface conflict with its replacement (if any).
* We do this at the end because if we didn't use the replacement feed, there's no need to conflict
* (avoids getting it added to feeds_used). *)letadd_replaced_by_conflictssatimpl_clauses=List.iter(fun(clause,replacement)->ImplCache.getreplacementimpl_clauses|>Option.iter(funreplacement_candidates->(* Our replacement was also added to [sat], so conflict with it. *)letour_vars=clause#get_real_varsinletreplacements=replacement_candidates#get_real_varsinif(our_vars<>[]&&replacements<>[])then((* Must select one implementation out of all candidates from both interfaces.
Dummy implementations don't conflict, though. *)S.at_most_onesat(our_vars@replacements)|>ignore)))(** On multi-arch systems, we can select 32-bit or 64-bit implementations,
but not both in the same set of selections. *)moduleMachine_group=structmoduleMap=Map.Make(structtypet=Model.machine_groupletcompare=compareend)typet={sat:S.t;mutablegroups:S.litMap.t;}letcreatesat={sat;groups=Map.empty}letvartname=matchMap.find_optnamet.groupswith|Somev->v|None->letv=S.add_variablet.sat@@SolverData.MachineGroup("m."^(name:>string))int.groups<-Map.addnamevt.groups;v(* If [impl] requires a particular machine group, add a constraint to the problem. *)letprocesstimpl_varimpl=Model.machine_groupimpl|>Option.iter(fungroup->S.impliest.sat~reason:"machine group"impl_var[vartgroup])(* Call this at the end to add the final clause with all discovered groups.
[t] must not be used after this. *)letsealt=letxs=Map.bindingst.groupsinifList.lengthxs>1then((* If we get to the end of the solve without deciding then nothing we selected cares about the
type of CPU. The solver will set them all to false at the end. *)S.at_most_onet.sat(List.mapsndxs)|>ignore)endmoduleConflict_classes=structmoduleMap=Map.Make(structtypet=Model.conflict_classletcompare=compareend)typet={sat:S.t;mutablegroups:S.litlistrefMap.t;}letcreatesat={sat;groups=Map.empty}letvartname=matchMap.find_optnamet.groupswith|Somev->v|None->letv=ref[]int.groups<-Map.addnamevt.groups;v(* Add [impl] to its conflict groups, if any. *)letprocesstimpl_varimpl=Model.conflict_classimpl|>List.iter(funname->letimpls=vartnameinimpls:=impl_var::!impls)(* Call this at the end to add the final clause with all discovered groups.
[t] must not be used after this. *)letsealt=t.groups|>Map.iter@@fun_nameimpls->letimpls=!implsinifList.lengthimpls>1then(S.at_most_onet.satimpls|>ignore)end(** If this binding depends on a command (<executable-in-*>), add that to the problem.
@param user_var indicates when this binding is used
@param dep_iface the required interface this binding targets *)letprocess_self_commandsatlookup_commanduser_vardep_rolename=(* Note: we only call this for self-bindings, so we could be efficient by selecting the exact command here... *)letcandidates=lookup_command(name,dep_role)inS.impliessat~reason:"binding on command"user_varcandidates#get_vars(* Process a dependency of [user_var]:
- find the candidate implementations/commands to satisfy it
- take just those that satisfy any restrictions in the dependency
- ensure that we don't pick an incompatbile version if we select [user_var]
- ensure that we do pick a compatible version if we select [user_var] (for "essential" dependencies only) *)letprocess_depsatlookup_impllookup_commanduser_vardep=let{Model.dep_role;dep_importance;dep_required_commands}=Model.dep_infodepinletdep_restrictions=Model.restrictionsdepin(* Restrictions on the candidates *)letmeets_restrictionsimpl=List.for_all(Model.meets_restrictionimpl)dep_restrictionsinletcandidates=lookup_impldep_roleinletpass,fail=candidates#partitionmeets_restrictionsin(* Dependencies on commands *)dep_required_commands|>List.iter(funname->letcandidates=lookup_command(name,dep_role)inifdep_importance=`Essentialthen(S.impliessat~reason:"dep on command"user_varcandidates#get_vars)else((* An optional dependency is selected when any implementation of the target interface
* is selected. Force [dep_iface_selected] to be true in that case. We only need to test
* [pass] here, because we always avoid [fail] anyway. *)letdep_iface_selected=S.add_variablesat(SolverData.Roledep_role)inS.at_most_onesat(S.negdep_iface_selected::pass)|>ignore;(* If user_var is selected, then either we don't select this interface, or we select
* a suitable command. *)S.impliessat~reason:"opt dep on command"user_var(S.negdep_iface_selected::candidates#get_vars)););ifdep_importance=`Essentialthen(S.impliessat~reason:"essential dep"user_varpass(* Must choose a suitable candidate *))else((* If [user_var] is selected, don't select an incompatible version of the optional dependency.
We don't need to do this explicitly in the [essential] case, because we must select a good
version and we can't select two. *)tryS.at_most_onesat(user_var::fail)|>ignore;withInvalid_argumentreason->(* Explicitly conflicts with itself! *)S.at_least_onesat[S.neguser_var]~reason)(* Add the implementations of an interface to the ImplCache (called the first time we visit it). *)letmake_impl_clausesat~dummy_implreplacementsrole=let{Model.replacement;impls}=Model.implementationsrolein(* Insert dummy_impl (last) if we're trying to diagnose a problem. *)letimpls=matchdummy_implwith|None->impls|Somedummy_impl->impls@[dummy_impl]inletimpls=impls|>List.map(funimpl->letvar=S.add_variablesat(SolverData.ImplElemimpl)in(var,impl))inletimpl_clause=ifimpls<>[]thenSome(S.at_most_onesat(List.mapfstimpls))elseNoneinletclause=newimpl_candidatesroleimpl_clauseimplsdummy_implin(* If we have a <replaced-by>, remember to add a conflict with our replacement *)replacement|>Option.iter(funreplacement->replacements:=(clause,replacement)::!replacements;);clause,impls(* Create a new CommandCache entry (called the first time we request this key). *)letmake_commands_clausesatlookup_implprocess_self_commandsprocess_depskey=let(command,role)=keyinletimpls=lookup_implroleinletcommands=impls#get_commandscommandinletmake_provides_command(_impl,elem)=(* [var] will be true iff this <command> is selected. *)letvar=S.add_variablesat(SolverData.CommandElemelem)in(var,elem)inletvars=List.mapmake_provides_commandcommandsinletcommand_clause=ifvars<>[]thenSome(S.at_most_onesat@@List.mapfstvars)elseNoneinletdata=newcommand_candidatesrolecommand_clausevarsin(data,fun()->letdepend_on_impl(command_var,command)(impl_var,_command)=(* For each command, require that we select the corresponding implementation. *)S.impliessat~reason:"impl for command"command_var[impl_var];letdeps,self_commands=Model.command_requiresrolecommandin(* Commands can depend on other commands in the same implementation *)process_self_commandscommand_varroleself_commands;(* Process command-specific dependencies *)process_depscommand_vardepsinList.iter2depend_on_implvarscommands)(** Starting from [root_req], explore all the feeds, commands and implementations we might need, adding
* all of them to [sat_problem]. *)letbuild_problemroot_reqsat~dummy_impl=(* For each (iface, command, source) we have a list of implementations (or commands). *)letimpl_cache=ImplCache.create()inletcommand_cache=CommandCache.create()inletmachine_groups=Machine_group.createsatinletconflict_classes=Conflict_classes.createsatin(* Handle <replaced-by> conflicts after building the problem. *)letreplacements=ref[]inletrecadd_impls_to_cacherole=letclause,impls=make_impl_clausesat~dummy_implreplacementsrolein(clause,fun()->impls|>List.iter(fun(impl_var,impl)->Machine_group.processmachine_groupsimpl_varimpl;Conflict_classes.processconflict_classesimpl_varimpl;letdeps,self_commands=Model.requiresroleimplinprocess_self_commandsimpl_varroleself_commands;process_depsimpl_vardeps))andadd_commands_to_cachekey=make_commands_clausesatlookup_implprocess_self_commandsprocess_depskeyandlookup_implkey=ImplCache.lookupimpl_cacheadd_impls_to_cachekeyandlookup_commandkey=CommandCache.lookupcommand_cacheadd_commands_to_cachekeyandprocess_self_commandsuser_vardep_role=List.iter(process_self_commandsatlookup_commanduser_vardep_role)andprocess_depsuser_var=List.iter(process_depsatlookup_impllookup_commanduser_var)in(* This recursively builds the whole problem up. *)beginmatchroot_reqwith|{Model.role;command=None}->(lookup_implrole)#get_vars|{Model.role;command=Somecommand}->(lookup_command(command,role))#get_varsend|>S.at_least_onesat~reason:"need root";(* Must get what we came for! *)(* All impl_candidates and command_candidates have now been added, so snapshot the cache. *)letimpl_clauses,command_clauses=ImplCache.snapshotimpl_cache,CommandCache.snapshotcommand_cacheinadd_replaced_by_conflictssatimpl_clauses!replacements;Machine_group.sealmachine_groups;Conflict_classes.sealconflict_classes;impl_clauses,command_clausesmoduleOutput=structmoduleInput=ModelmoduleRole=Input.RolemoduleRoleMap=RoleMaptypeimpl=selectiontypecommand=Model.commandtypecommand_name=Model.command_nametypedependency=Model.dependencytypedep_info=Model.dep_info={dep_role:Role.t;dep_importance:[`Essential|`Recommended|`Restricts];dep_required_commands:command_namelist;}typerequirements=Model.requirements={role:Role.t;command:command_nameoption;}letdep_info=Model.dep_infoletrequiresroleimpl=Model.requiresroleimpl.implletcommand_requiresrolecmd=Model.command_requiresrolecmdletget_commandimplname=Model.get_commandimpl.implnametypet={root_req:requirements;selections:selectionRoleMap.t;}letto_mapt=t.selectionsletrequirementst=t.root_reqletexplaintrole=matchRoleMap.find_optrolet.selectionswith|Somesel->explainsel.diagnostics|None->"Role not used!"letget_selectedrolet=matchRoleMap.find_optrolet.selectionswith|Someselectionwhenselection.impl==Model.dummy_impl->None|x->xletselected_commandssel=sel.commandsletunwrapsel=sel.implendletdo_solve~closest_matchroot_req=(* The basic plan is this:
1. Scan the root interface and all dependencies recursively, building up a SAT problem.
2. Solve the SAT problem. Whenever there are multiple options, try the most preferred one first.
3. Create the selections XML from the results.
All three involve recursively walking the tree in a similar way:
1) we follow every dependency of every implementation (order not important)
2) we follow every dependency of every selected implementation (better versions first)
3) we follow every dependency of every selected implementation
In all cases, a dependency may be on an <implementation> or on a specific <command>.
*)letsat=S.create()inletdummy_impl=ifclosest_matchthenSomeModel.dummy_implelseNoneinletimpl_clauses,command_clauses=build_problemroot_reqsat~dummy_implinletlookup=function|{Model.role;command=None}->(ImplCache.get_exnroleimpl_clauses:>candidates)|{Model.role;command=Somecommand}->(CommandCache.get_exn(command,role)command_clauses)in(* Run the solve *)letdecider()=(* Walk the current solution, depth-first, looking for the first undecided interface.
Then try the most preferred implementation of it that hasn't been ruled out. *)letseen=Hashtbl.create100inletrecfind_undecidedreq=ifHashtbl.memseenreqthenNone(* Break cycles *)else(Hashtbl.addseenreqtrue;letcandidates=lookupreqinmatchcandidates#get_statewith|Unselected->None|Undecidedlit->Somelit|Selected(deps,self_commands)->(* We've already selected a candidate for this component. Now check its dependencies. *)letcheck_self_commandname=find_undecided{reqwithModel.command=Somename}inmatchList.find_mapcheck_self_commandself_commandswith|Some_asr->r|None->(* Self-commands already done; now try the dependencies *)letcheck_depdep=let{Model.dep_role;dep_importance;dep_required_commands}=Model.dep_infodepinifdep_importance=`Restrictsthen((* Restrictions don't express that we do or don't want the
dependency, so skip them here. If someone else needs this,
we'll handle it when we get to them.
If noone wants it, it will be set to unselected at the end. *)None)else(matchfind_undecided{Model.role=dep_role;command=None}with|Somelit->Somelit|None->(* Command dependencies next *)letcheck_command_depname=find_undecided{Model.command=Somename;role=dep_role}inList.find_mapcheck_command_depdep_required_commands)inmatchList.find_mapcheck_depdepswith|Some_asr->r|None->(* All dependencies checked; now to the impl (if we're a <command>) *)Option.bindreq.Model.command(fun_command->find_undecided{reqwithModel.command=None}))infind_undecidedroot_reqinmatchS.run_solversatdeciderwith|None->None|Some_solution->(* Build the results object *)(* For each implementation, remember which commands we need. *)letcommands_needed=Hashtbl.create10incommand_clauses|>CommandCache.M.iter(fun(command_name,role)candidates->candidates#get_clause|>Option.iter(funclause->ifS.get_selectedclause<>NonethenHashtbl.addcommands_neededrolecommand_name));letselections=impl_clauses|>ImplCache.filter_map(funrolecandidates->candidates#get_selected|>Option.map(fun(lit,impl)->letcommands=Hashtbl.find_allcommands_neededrolein{impl;commands;diagnostics=lit}))inSome{Output.root_req;selections}end