Solver.php 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\DependencyResolver;
  12. use Composer\Repository\RepositoryInterface;
  13. use Composer\Package\PackageInterface;
  14. use Composer\DependencyResolver\Operation;
  15. /**
  16. * @author Nils Adermann <naderman@naderman.de>
  17. */
  18. class Solver
  19. {
  20. protected $policy;
  21. protected $pool;
  22. protected $installed;
  23. protected $rules;
  24. protected $updateAll;
  25. protected $ruleToJob = array();
  26. protected $addedMap = array();
  27. protected $updateMap = array();
  28. protected $watches = array();
  29. protected $removeWatches = array();
  30. protected $decisionMap;
  31. protected $installedMap;
  32. protected $installedPackages;
  33. protected $packageToFeatureRule = array();
  34. public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
  35. {
  36. $this->policy = $policy;
  37. $this->pool = $pool;
  38. $this->installed = $installed;
  39. $this->rules = new RuleSet;
  40. }
  41. /**
  42. * Creates a new rule for the requirements of a package
  43. *
  44. * This rule is of the form (-A|B|C), where B and C are the providers of
  45. * one requirement of the package A.
  46. *
  47. * @param PackageInterface $package The package with a requirement
  48. * @param array $providers The providers of the requirement
  49. * @param int $reason A RULE_* constant describing the
  50. * reason for generating this rule
  51. * @param mixed $reasonData Any data, e.g. the requirement name,
  52. * that goes with the reason
  53. * @return Rule The generated rule or null if tautological
  54. */
  55. protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
  56. {
  57. $literals = array(new Literal($package, false));
  58. foreach ($providers as $provider) {
  59. // self fulfilling rule?
  60. if ($provider === $package) {
  61. return null;
  62. }
  63. $literals[] = new Literal($provider, true);
  64. }
  65. return new Rule($literals, $reason, $reasonData);
  66. }
  67. /**
  68. * Create a new rule for updating a package
  69. *
  70. * If package A1 can be updated to A2 or A3 the rule is (A1|A2|A3).
  71. *
  72. * @param PackageInterface $package The package to be updated
  73. * @param array $updates An array of update candidate packages
  74. * @param int $reason A RULE_* constant describing the
  75. * reason for generating this rule
  76. * @param mixed $reasonData Any data, e.g. the package name, that
  77. * goes with the reason
  78. * @return Rule The generated rule or null if tautology
  79. */
  80. protected function createUpdateRule(PackageInterface $package, array $updates, $reason, $reasonData = null)
  81. {
  82. $literals = array(new Literal($package, true));
  83. foreach ($updates as $update) {
  84. $literals[] = new Literal($update, true);
  85. }
  86. return new Rule($literals, $reason, $reasonData);
  87. }
  88. /**
  89. * Creates a new rule for installing a package
  90. *
  91. * The rule is simply (A) for a package A to be installed.
  92. *
  93. * @param PackageInterface $package The package to be installed
  94. * @param int $reason A RULE_* constant describing the
  95. * reason for generating this rule
  96. * @param mixed $reasonData Any data, e.g. the package name, that
  97. * goes with the reason
  98. * @return Rule The generated rule
  99. */
  100. protected function createInstallRule(PackageInterface $package, $reason, $reasonData = null)
  101. {
  102. return new Rule(new Literal($package, true));
  103. }
  104. /**
  105. * Creates a rule to install at least one of a set of packages
  106. *
  107. * The rule is (A|B|C) with A, B and C different packages. If the given
  108. * set of packages is empty an impossible rule is generated.
  109. *
  110. * @param array $packages The set of packages to choose from
  111. * @param int $reason A RULE_* constant describing the reason for
  112. * generating this rule
  113. * @param mixed $reasonData Any data, e.g. the package name, that goes with
  114. * the reason
  115. * @return Rule The generated rule
  116. */
  117. protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
  118. {
  119. $literals = array();
  120. foreach ($packages as $package) {
  121. $literals[] = new Literal($package, true);
  122. }
  123. return new Rule($literals, $reason, $reasonData);
  124. }
  125. /**
  126. * Creates a rule to remove a package
  127. *
  128. * The rule for a package A is (-A).
  129. *
  130. * @param PackageInterface $package The package to be removed
  131. * @param int $reason A RULE_* constant describing the
  132. * reason for generating this rule
  133. * @param mixed $reasonData Any data, e.g. the package name, that
  134. * goes with the reason
  135. * @return Rule The generated rule
  136. */
  137. protected function createRemoveRule(PackageInterface $package, $reason, $reasonData = null)
  138. {
  139. return new Rule(array(new Literal($package, false)), $reason, $reasonData);
  140. }
  141. /**
  142. * Creates a rule for two conflicting packages
  143. *
  144. * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
  145. * and B the provider.
  146. *
  147. * @param PackageInterface $issuer The package declaring the conflict
  148. * @param Package $provider The package causing the conflict
  149. * @param int $reason A RULE_* constant describing the
  150. * reason for generating this rule
  151. * @param mixed $reasonData Any data, e.g. the package name, that
  152. * goes with the reason
  153. * @return Rule The generated rule
  154. */
  155. protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
  156. {
  157. // ignore self conflict
  158. if ($issuer === $provider) {
  159. return null;
  160. }
  161. return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData);
  162. }
  163. /**
  164. * Adds a rule unless it duplicates an existing one of any type
  165. *
  166. * To be able to directly pass in the result of one of the rule creation
  167. * methods the rule may also be null to indicate that no rule should be
  168. * added.
  169. *
  170. * @param int $type A TYPE_* constant defining the rule type
  171. * @param Rule $newRule The rule about to be added
  172. */
  173. private function addRule($type, Rule $newRule = null) {
  174. if ($newRule) {
  175. if ($this->rules->containsEqual($newRule)) {
  176. return;
  177. }
  178. $this->rules->add($newRule, $type);
  179. }
  180. }
  181. protected function addRulesForPackage(PackageInterface $package)
  182. {
  183. $workQueue = new \SplQueue;
  184. $workQueue->enqueue($package);
  185. while (!$workQueue->isEmpty()) {
  186. $package = $workQueue->dequeue();
  187. if (isset($this->addedMap[$package->getId()])) {
  188. continue;
  189. }
  190. $this->addedMap[$package->getId()] = true;
  191. if (!$this->policy->installable($this, $this->pool, $this->installedMap, $package)) {
  192. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, Rule::RULE_NOT_INSTALLABLE, (string) $package));
  193. continue;
  194. }
  195. foreach ($package->getRequires() as $link) {
  196. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  197. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link));
  198. foreach ($possibleRequires as $require) {
  199. $workQueue->enqueue($require);
  200. }
  201. }
  202. foreach ($package->getConflicts() as $link) {
  203. $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  204. foreach ($possibleConflicts as $conflict) {
  205. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link));
  206. }
  207. }
  208. // check obsoletes and implicit obsoletes of a package
  209. $isInstalled = (isset($this->installedMap[$package->getId()]));
  210. foreach ($package->getReplaces() as $link) {
  211. $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  212. foreach ($obsoleteProviders as $provider) {
  213. if ($provider === $package) {
  214. continue;
  215. }
  216. $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
  217. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
  218. }
  219. }
  220. // check implicit obsoletes
  221. // for installed packages we only need to check installed/installed problems,
  222. // as the others are picked up when looking at the uninstalled package.
  223. if (!$isInstalled) {
  224. $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
  225. foreach ($obsoleteProviders as $provider) {
  226. if ($provider === $package) {
  227. continue;
  228. }
  229. if ($isInstalled && !isset($this->installedMap[$provider->getId()])) {
  230. continue;
  231. }
  232. $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
  233. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
  234. }
  235. }
  236. }
  237. }
  238. /**
  239. * Adds all rules for all update packages of a given package
  240. *
  241. * @param PackageInterface $package Rules for this package's updates are to
  242. * be added
  243. * @param bool $allowAll Whether downgrades are allowed
  244. */
  245. private function addRulesForUpdatePackages(PackageInterface $package)
  246. {
  247. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
  248. $this->addRulesForPackage($package);
  249. foreach ($updates as $update) {
  250. $this->addRulesForPackage($update);
  251. }
  252. }
  253. /**
  254. * Alters watch chains for a rule.
  255. *
  256. * Next1/2 always points to the next rule that is watching the same package.
  257. * The watches array contains rules to start from for each package
  258. *
  259. */
  260. private function addWatchesToRule(Rule $rule)
  261. {
  262. // skip simple assertions of the form (A) or (-A)
  263. if ($rule->isAssertion()) {
  264. return;
  265. }
  266. if (!isset($this->watches[$rule->watch1])) {
  267. $this->watches[$rule->watch1] = null;
  268. }
  269. $rule->next1 = $this->watches[$rule->watch1];
  270. $this->watches[$rule->watch1] = $rule;
  271. if (!isset($this->watches[$rule->watch2])) {
  272. $this->watches[$rule->watch2] = null;
  273. }
  274. $rule->next2 = $this->watches[$rule->watch2];
  275. $this->watches[$rule->watch2] = $rule;
  276. }
  277. /**
  278. * Put watch2 on rule's literal with highest level
  279. */
  280. private function watch2OnHighest(Rule $rule)
  281. {
  282. $literals = $rule->getLiterals();
  283. // if there are only 2 elements, both are being watched anyway
  284. if ($literals < 3) {
  285. return;
  286. }
  287. $watchLevel = 0;
  288. foreach ($literals as $literal) {
  289. $level = abs($this->decisionMap[$literal->getPackageId()]);
  290. if ($level > $watchLevel) {
  291. $rule->watch2 = $literal->getId();
  292. $watchLevel = $level;
  293. }
  294. }
  295. }
  296. private function findDecisionRule(PackageInterface $package)
  297. {
  298. foreach ($this->decisionQueue as $i => $literal) {
  299. if ($package === $literal->getPackage()) {
  300. return $this->decisionQueueWhy[$i];
  301. }
  302. }
  303. return null;
  304. }
  305. // aka solver_makeruledecisions
  306. private function makeAssertionRuleDecisions()
  307. {
  308. // do we need to decide a SYSTEMSOLVABLE at level 1?
  309. $decisionStart = count($this->decisionQueue);
  310. for ($ruleIndex = 0; $ruleIndex < count($this->rules); $ruleIndex++) {
  311. $rule = $this->rules->ruleById($ruleIndex);
  312. if ($rule->isWeak() || !$rule->isAssertion() || $rule->isDisabled()) {
  313. continue;
  314. }
  315. $literals = $rule->getLiterals();
  316. $literal = $literals[0];
  317. if (!$this->decided($literal->getPackage())) {
  318. $this->decisionQueue[] = $literal;
  319. $this->decisionQueueWhy[] = $rule;
  320. $this->addDecision($literal, 1);
  321. continue;
  322. }
  323. if ($this->decisionsSatisfy($literal)) {
  324. continue;
  325. }
  326. // found a conflict
  327. if (RuleSet::TYPE_LEARNED === $rule->getType()) {
  328. $rule->disable();
  329. continue;
  330. }
  331. $conflict = $this->findDecisionRule($literal->getPackage());
  332. /** TODO: handle conflict with systemsolvable? */
  333. if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
  334. $problem = new Problem;
  335. if ($rule->getType() == RuleSet::TYPE_JOB) {
  336. $job = $this->ruleToJob[$rule->getId()];
  337. $problem->addJobRule($job, $rule);
  338. $problem->addRule($conflict);
  339. $this->disableProblem($job);
  340. } else {
  341. $problem->addRule($rule);
  342. $problem->addRule($conflict);
  343. $this->disableProblem($rule);
  344. }
  345. $this->problems[] = $problem;
  346. continue;
  347. }
  348. // conflict with another job or update/feature rule
  349. $problem = new Problem;
  350. $problem->addRule($rule);
  351. $problem->addRule($conflict);
  352. // push all of our rules (can only be feature or job rules)
  353. // asserting this literal on the problem stack
  354. foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) {
  355. if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
  356. continue;
  357. }
  358. $assertRuleLiterals = $assertRule->getLiterals();
  359. $assertRuleLiteral = $assertRuleLiterals[0];
  360. if ($literal->getPackageId() !== $assertRuleLiteral->getPackageId()) {
  361. continue;
  362. }
  363. if ($assertRule->getType() === RuleSet::TYPE_JOB) {
  364. $job = $this->ruleToJob[$assertRule->getId()];
  365. $problem->addJobRule($job, $assertRule);
  366. $this->disableProblem($job);
  367. } else {
  368. $problem->addRule($assertRule);
  369. $this->disableProblem($assertRule);
  370. }
  371. }
  372. $this->problems[] = $problem;
  373. // start over
  374. while (count($this->decisionQueue) > $decisionStart) {
  375. $decisionLiteral = array_pop($this->decisionQueue);
  376. array_pop($this->decisionQueueWhy);
  377. unset($this->decisionQueueFree[count($this->decisionQueue)]);
  378. $this->decisionMap[$decisionLiteral->getPackageId()] = 0;
  379. }
  380. $ruleIndex = -1;
  381. }
  382. foreach ($this->rules as $rule) {
  383. if (!$rule->isWeak() || !$rule->isAssertion() || $rule->isDisabled()) {
  384. continue;
  385. }
  386. $literals = $rule->getLiterals();
  387. $literal = $literals[0];
  388. if ($this->decisionMap[$literal->getPackageId()] == 0) {
  389. $this->decisionQueue[] = $literal;
  390. $this->decisionQueueWhy[] = $rule;
  391. $this->addDecision($literal, 1);
  392. continue;
  393. }
  394. if ($this->decisionsSatisfy($literals[0])) {
  395. continue;
  396. }
  397. // conflict, but this is a weak rule => disable
  398. if ($rule->getType() == RuleSet::TYPE_JOB) {
  399. $why = $this->ruleToJob[$rule->getId()];
  400. } else {
  401. $why = $rule;
  402. }
  403. $this->disableProblem($why);
  404. /** TODO solver_reenablepolicyrules(solv, -(v + 1)); */
  405. }
  406. }
  407. /***********************************************************************
  408. ***
  409. *** Policy rule disabling/reenabling
  410. ***
  411. *** Disable all policy rules that conflict with our jobs. If a job
  412. *** gets disabled later on, reenable the involved policy rules again.
  413. ***
  414. *** /
  415. #define DISABLE_UPDATE 1
  416. #define DISABLE_INFARCH 2
  417. #define DISABLE_DUP 3
  418. */
  419. protected function jobToDisableQueue(array $job, array $disableQueue)
  420. {
  421. switch ($job['cmd']) {
  422. case 'install':
  423. foreach ($job['packages'] as $package) {
  424. if (isset($this->installedMap[$package->getId()])) {
  425. $disableQueue[] = array('type' => 'update', 'package' => $package);
  426. }
  427. /* all job packages obsolete * /
  428. qstart = q->count;
  429. pass = 0;
  430. memset(&omap, 0, sizeof(omap));
  431. FOR_JOB_SELECT(p, pp, select, what)
  432. {
  433. Id p2, pp2;
  434. if (pass == 1)
  435. map_grow(&omap, installed->end - installed->start);
  436. s = pool->solvables + p;
  437. if (s->obsoletes)
  438. {
  439. Id obs, *obsp;
  440. obsp = s->repo->idarraydata + s->obsoletes;
  441. while ((obs = *obsp++) != 0)
  442. FOR_PROVIDES(p2, pp2, obs)
  443. {
  444. Solvable *ps = pool->solvables + p2;
  445. if (ps->repo != installed)
  446. continue;
  447. if (!pool->obsoleteusesprovides && !pool_match_nevr(pool, ps, obs))
  448. continue;
  449. if (pool->obsoleteusescolors && !pool_colormatch(pool, s, ps))
  450. continue;
  451. if (pass)
  452. MAPSET(&omap, p2 - installed->start);
  453. else
  454. queue_push2(q, DISABLE_UPDATE, p2);
  455. }
  456. }
  457. FOR_PROVIDES(p2, pp2, s->name)
  458. {
  459. Solvable *ps = pool->solvables + p2;
  460. if (ps->repo != installed)
  461. continue;
  462. if (!pool->implicitobsoleteusesprovides && ps->name != s->name)
  463. continue;
  464. if (pool->obsoleteusescolors && !pool_colormatch(pool, s, ps))
  465. continue;
  466. if (pass)
  467. MAPSET(&omap, p2 - installed->start);
  468. else
  469. queue_push2(q, DISABLE_UPDATE, p2);
  470. }
  471. if (pass)
  472. {
  473. for (i = j = qstart; i < q->count; i += 2)
  474. {
  475. if (MAPTST(&omap, q->elements[i + 1] - installed->start))
  476. {
  477. MAPCLR(&omap, q->elements[i + 1] - installed->start);
  478. q->elements[j + 1] = q->elements[i + 1];
  479. j += 2;
  480. }
  481. }
  482. queue_truncate(q, j);
  483. }
  484. if (q->count == qstart)
  485. break;
  486. pass++;
  487. }
  488. if (omap.size)
  489. map_free(&omap);
  490. if (qstart == q->count)
  491. return; /* nothing to prune * /
  492. if ((set & (SOLVER_SETEVR | SOLVER_SETARCH | SOLVER_SETVENDOR)) == (SOLVER_SETEVR | SOLVER_SETARCH | SOLVER_SETVENDOR))
  493. return; /* all is set */
  494. /* now that we know which installed packages are obsoleted check each of them * /
  495. for (i = j = qstart; i < q->count; i += 2)
  496. {
  497. Solvable *is = pool->solvables + q->elements[i + 1];
  498. FOR_JOB_SELECT(p, pp, select, what)
  499. {
  500. int illegal = 0;
  501. s = pool->solvables + p;
  502. if ((set & SOLVER_SETEVR) != 0)
  503. illegal |= POLICY_ILLEGAL_DOWNGRADE; /* ignore * /
  504. if ((set & SOLVER_SETARCH) != 0)
  505. illegal |= POLICY_ILLEGAL_ARCHCHANGE; /* ignore * /
  506. if ((set & SOLVER_SETVENDOR) != 0)
  507. illegal |= POLICY_ILLEGAL_VENDORCHANGE; /* ignore * /
  508. illegal = policy_is_illegal(solv, is, s, illegal);
  509. if (illegal && illegal == POLICY_ILLEGAL_DOWNGRADE && (set & SOLVER_SETEV) != 0)
  510. {
  511. /* it's ok if the EV is different * /
  512. if (evrcmp(pool, is->evr, s->evr, EVRCMP_COMPARE_EVONLY) != 0)
  513. illegal = 0;
  514. }
  515. if (illegal)
  516. break;
  517. }
  518. if (!p)
  519. {
  520. /* no package conflicts with the update rule * /
  521. /* thus keep the DISABLE_UPDATE * /
  522. q->elements[j + 1] = q->elements[i + 1];
  523. j += 2;
  524. }
  525. }
  526. queue_truncate(q, j);
  527. return;*/
  528. }
  529. break;
  530. case 'remove':
  531. foreach ($job['packages'] as $package) {
  532. if (isset($this->installedMap[$package->getId()])) {
  533. $disableQueue[] = array('type' => 'update', 'package' => $package);
  534. }
  535. }
  536. break;
  537. }
  538. return $disableQueue;
  539. }
  540. protected function disableUpdateRule($package)
  541. {
  542. if (isset($this->packageToFeatureRule[$package->getId()])) {
  543. $this->packageToFeatureRule[$package->getId()]->disable();
  544. }
  545. }
  546. /**
  547. * Disables all policy rules that conflict with jobs
  548. */
  549. protected function disablePolicyRules()
  550. {
  551. $lastJob = null;
  552. $allQueue = array();
  553. $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
  554. foreach ($iterator as $rule) {
  555. if ($rule->isDisabled()) {
  556. continue;
  557. }
  558. $job = $this->ruleToJob[$rule->getId()];
  559. if ($job === $lastJob) {
  560. continue;
  561. }
  562. $lastJob = $job;
  563. $allQueue = $this->jobToDisableQueue($job, $allQueue);
  564. }
  565. foreach ($allQueue as $disable) {
  566. switch ($disable['type']) {
  567. case 'update':
  568. $this->disableUpdateRule($disable['package']);
  569. break;
  570. default:
  571. throw new \RuntimeException("Unsupported disable type: " . $disable['type']);
  572. }
  573. }
  574. }
  575. protected function setupInstalledMap()
  576. {
  577. $this->installedPackages = $this->installed->getPackages();
  578. $this->installedMap = array();
  579. foreach ($this->installedPackages as $package) {
  580. $this->installedMap[$package->getId()] = $package;
  581. }
  582. }
  583. public function solve(Request $request)
  584. {
  585. $this->jobs = $request->getJobs();
  586. $this->setupInstalledMap();
  587. if (version_compare(PHP_VERSION, '5.3.4', '>=')) {
  588. $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1);
  589. } else {
  590. $this->decisionMap = array_fill(0, $this->pool->getMaxId() + 1, 0);
  591. }
  592. foreach ($this->jobs as $job) {
  593. foreach ($job['packages'] as $package) {
  594. switch ($job['cmd']) {
  595. case 'update':
  596. if (isset($this->installedMap[$package->getId()])) {
  597. $this->updateMap[$package->getId()] = true;
  598. }
  599. break;
  600. }
  601. }
  602. switch ($job['cmd']) {
  603. case 'update-all':
  604. foreach ($this->installedMap as $package) {
  605. $this->updateMap[$package->getId()] = true;
  606. }
  607. break;
  608. }
  609. }
  610. foreach ($this->installedMap as $package) {
  611. $this->addRulesForPackage($package);
  612. }
  613. foreach ($this->installedMap as $package) {
  614. $this->addRulesForUpdatePackages($package);
  615. }
  616. foreach ($this->jobs as $job) {
  617. foreach ($job['packages'] as $package) {
  618. switch ($job['cmd']) {
  619. case 'install':
  620. $this->installCandidateMap[$package->getId()] = true;
  621. $this->addRulesForPackage($package);
  622. break;
  623. }
  624. }
  625. }
  626. // solver_addrpmrulesforweak(solv, &addedmap);
  627. foreach ($this->installedMap as $package) {
  628. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
  629. $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
  630. $rule->setWeak(true);
  631. $this->addRule(RuleSet::TYPE_FEATURE, $rule);
  632. $this->packageToFeatureRule[$package->getId()] = $rule;
  633. }
  634. foreach ($this->jobs as $job) {
  635. switch ($job['cmd']) {
  636. case 'install':
  637. if (empty($job['packages'])) {
  638. $problem = new Problem();
  639. $problem->addJobRule($job);
  640. $this->problems[] = $problem;
  641. } else {
  642. $rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job['packageName']);
  643. $this->addRule(RuleSet::TYPE_JOB, $rule);
  644. $this->ruleToJob[$rule->getId()] = $job;
  645. }
  646. break;
  647. case 'remove':
  648. // remove all packages with this name including uninstalled
  649. // ones to make sure none of them are picked as replacements
  650. // todo: cleandeps
  651. foreach ($job['packages'] as $package) {
  652. $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE);
  653. $this->addRule(RuleSet::TYPE_JOB, $rule);
  654. $this->ruleToJob[$rule->getId()] = $job;
  655. }
  656. break;
  657. case 'lock':
  658. foreach ($job['packages'] as $package) {
  659. if (isset($this->installedMap[$package->getId()])) {
  660. $rule = $this->createInstallRule($package, Rule::RULE_JOB_LOCK);
  661. } else {
  662. $rule = $this->createRemoveRule($package, Rule::RULE_JOB_LOCK);
  663. }
  664. $this->addRule(RuleSet::TYPE_JOB, $rule);
  665. $this->ruleToJob[$rule->getId()] = $job;
  666. }
  667. break;
  668. }
  669. }
  670. foreach ($this->rules as $rule) {
  671. $this->addWatchesToRule($rule);
  672. }
  673. /* disable update rules that conflict with our job */
  674. $this->disablePolicyRules();
  675. /* make decisions based on job/update assertions */
  676. $this->makeAssertionRuleDecisions();
  677. $installRecommended = 0;
  678. $this->runSat(true, $installRecommended);
  679. //$this->printDecisionMap();
  680. //findrecommendedsuggested(solv);
  681. //solver_prepare_solutions(solv);
  682. if ($this->problems) {
  683. throw new SolverProblemsException($this->problems);
  684. }
  685. return $this->createTransaction();
  686. }
  687. protected function createTransaction()
  688. {
  689. $transaction = array();
  690. $installMeansUpdateMap = array();
  691. foreach ($this->decisionQueue as $i => $literal) {
  692. $package = $literal->getPackage();
  693. // !wanted & installed
  694. if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
  695. $literals = array();
  696. if (isset($this->packageToFeatureRule[$package->getId()])) {
  697. $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
  698. }
  699. foreach ($literals as $updateLiteral) {
  700. if (!$updateLiteral->equals($literal)) {
  701. $installMeansUpdateMap[$updateLiteral->getPackageId()] = $package;
  702. }
  703. }
  704. }
  705. }
  706. foreach ($this->decisionQueue as $i => $literal) {
  707. $package = $literal->getPackage();
  708. // wanted & installed || !wanted & !installed
  709. if ($literal->isWanted() == (isset($this->installedMap[$package->getId()]))) {
  710. continue;
  711. }
  712. if ($literal->isWanted()) {
  713. if (isset($installMeansUpdateMap[$literal->getPackageId()])) {
  714. $source = $installMeansUpdateMap[$literal->getPackageId()];
  715. $transaction[] = new Operation\UpdateOperation(
  716. $source, $package, $this->decisionQueueWhy[$i]
  717. );
  718. // avoid updates to one package from multiple origins
  719. unset($installMeansUpdateMap[$literal->getPackageId()]);
  720. $ignoreRemove[$source->getId()] = true;
  721. } else {
  722. $transaction[] = new Operation\InstallOperation(
  723. $package, $this->decisionQueueWhy[$i]
  724. );
  725. }
  726. } else if (!isset($ignoreRemove[$package->getId()])) {
  727. $transaction[] = new Operation\UninstallOperation(
  728. $package, $this->decisionQueueWhy[$i]
  729. );
  730. }
  731. }
  732. return array_reverse($transaction);
  733. }
  734. protected $decisionQueue = array();
  735. protected $decisionQueueWhy = array();
  736. protected $decisionQueueFree = array();
  737. protected $propagateIndex;
  738. protected $branches = array();
  739. protected $problems = array();
  740. protected $learnedPool = array();
  741. protected $recommendsIndex;
  742. protected function literalFromId($id)
  743. {
  744. $package = $this->pool->packageById(abs($id));
  745. return new Literal($package, $id > 0);
  746. }
  747. protected function addDecision(Literal $l, $level)
  748. {
  749. assert($this->decisionMap[$l->getPackageId()] == 0);
  750. if ($l->isWanted()) {
  751. $this->decisionMap[$l->getPackageId()] = $level;
  752. } else {
  753. $this->decisionMap[$l->getPackageId()] = -$level;
  754. }
  755. }
  756. protected function addDecisionId($literalId, $level)
  757. {
  758. $packageId = abs($literalId);
  759. assert($this->decisionMap[$packageId] == 0);
  760. if ($literalId > 0) {
  761. $this->decisionMap[$packageId] = $level;
  762. } else {
  763. $this->decisionMap[$packageId] = -$level;
  764. }
  765. }
  766. protected function decisionsContain(Literal $l)
  767. {
  768. return (
  769. $this->decisionMap[$l->getPackageId()] > 0 && $l->isWanted() ||
  770. $this->decisionMap[$l->getPackageId()] < 0 && !$l->isWanted()
  771. );
  772. }
  773. protected function decisionsContainId($literalId)
  774. {
  775. $packageId = abs($literalId);
  776. return (
  777. $this->decisionMap[$packageId] > 0 && $literalId > 0 ||
  778. $this->decisionMap[$packageId] < 0 && $literalId < 0
  779. );
  780. }
  781. protected function decisionsSatisfy(Literal $l)
  782. {
  783. return ($l->isWanted() && $this->decisionMap[$l->getPackageId()] > 0) ||
  784. (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] <= 0);
  785. }
  786. protected function decisionsConflict(Literal $l)
  787. {
  788. return (
  789. $this->decisionMap[$l->getPackageId()] > 0 && !$l->isWanted() ||
  790. $this->decisionMap[$l->getPackageId()] < 0 && $l->isWanted()
  791. );
  792. }
  793. protected function decisionsConflictId($literalId)
  794. {
  795. $packageId = abs($literalId);
  796. return (
  797. ($this->decisionMap[$packageId] > 0 && $literalId < 0) ||
  798. ($this->decisionMap[$packageId] < 0 && $literalId > 0)
  799. );
  800. }
  801. protected function decided(PackageInterface $p)
  802. {
  803. return $this->decisionMap[$p->getId()] != 0;
  804. }
  805. protected function undecided(PackageInterface $p)
  806. {
  807. return $this->decisionMap[$p->getId()] == 0;
  808. }
  809. protected function decidedInstall(PackageInterface $p) {
  810. return $this->decisionMap[$p->getId()] > 0;
  811. }
  812. protected function decidedRemove(PackageInterface $p) {
  813. return $this->decisionMap[$p->getId()] < 0;
  814. }
  815. /**
  816. * Makes a decision and propagates it to all rules.
  817. *
  818. * Evaluates each term affected by the decision (linked through watches)
  819. * If we find unit rules we make new decisions based on them
  820. *
  821. * @return Rule|null A rule on conflict, otherwise null.
  822. */
  823. protected function propagate($level)
  824. {
  825. while ($this->propagateIndex < count($this->decisionQueue)) {
  826. // we invert the decided literal here, example:
  827. // A was decided => (-A|B) now requires B to be true, so we look for
  828. // rules which are fulfilled by -A, rather than A.
  829. $literal = $this->decisionQueue[$this->propagateIndex]->inverted();
  830. $this->propagateIndex++;
  831. // /* foreach rule where 'pkg' is now FALSE */
  832. //for (rp = watches + pkg; *rp; rp = next_rp)
  833. if (!isset($this->watches[$literal->getId()])) {
  834. continue;
  835. }
  836. $prevRule = null;
  837. for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
  838. $nextRule = $rule->getNext($literal);
  839. if ($rule->isDisabled()) {
  840. continue;
  841. }
  842. $otherWatch = $rule->getOtherWatch($literal);
  843. if ($this->decisionsContainId($otherWatch)) {
  844. continue;
  845. }
  846. $ruleLiterals = $rule->getLiterals();
  847. if (sizeof($ruleLiterals) > 2) {
  848. foreach ($ruleLiterals as $ruleLiteral) {
  849. if ($otherWatch !== $ruleLiteral->getId() &&
  850. !$this->decisionsConflict($ruleLiteral)) {
  851. if ($literal->getId() === $rule->watch1) {
  852. $rule->watch1 = $ruleLiteral->getId();
  853. $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
  854. } else {
  855. $rule->watch2 = $ruleLiteral->getId();
  856. $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
  857. }
  858. if ($prevRule) {
  859. if ($prevRule->next1 == $rule) {
  860. $prevRule->next1 = $nextRule;
  861. } else {
  862. $prevRule->next2 = $nextRule;
  863. }
  864. } else {
  865. $this->watches[$literal->getId()] = $nextRule;
  866. }
  867. $this->watches[$ruleLiteral->getId()] = $rule;
  868. $rule = $prevRule;
  869. continue 2;
  870. }
  871. }
  872. }
  873. // yay, we found a unit clause! try setting it to true
  874. if ($this->decisionsConflictId($otherWatch)) {
  875. return $rule;
  876. }
  877. $this->addDecisionId($otherWatch, $level);
  878. $this->decisionQueue[] = $this->literalFromId($otherWatch);
  879. $this->decisionQueueWhy[] = $rule;
  880. }
  881. }
  882. return null;
  883. }
  884. /**
  885. * Reverts a decision at the given level.
  886. */
  887. private function revert($level)
  888. {
  889. while (!empty($this->decisionQueue)) {
  890. $literal = $this->decisionQueue[count($this->decisionQueue) - 1];
  891. if (!$this->decisionMap[$literal->getPackageId()]) {
  892. break;
  893. }
  894. $decisionLevel = abs($this->decisionMap[$literal->getPackageId()]);
  895. if ($decisionLevel <= $level) {
  896. break;
  897. }
  898. /** TODO: implement recommendations
  899. *if (v > 0 && solv->recommendations.count && v == solv->recommendations.elements[solv->recommendations.count - 1])
  900. * solv->recommendations.count--;
  901. */
  902. $this->decisionMap[$literal->getPackageId()] = 0;
  903. array_pop($this->decisionQueue);
  904. array_pop($this->decisionQueueWhy);
  905. $this->propagateIndex = count($this->decisionQueue);
  906. }
  907. while (!empty($this->branches)) {
  908. list($literals, $branchLevel) = $this->branches[count($this->branches) - 1];
  909. if ($branchLevel >= $level) {
  910. break;
  911. }
  912. array_pop($this->branches);
  913. }
  914. $this->recommendsIndex = -1;
  915. }
  916. /**-------------------------------------------------------------------
  917. *
  918. * setpropagatelearn
  919. *
  920. * add free decision (solvable to install) to decisionq
  921. * increase level and propagate decision
  922. * return if no conflict.
  923. *
  924. * in conflict case, analyze conflict rule, add resulting
  925. * rule to learnt rule set, make decision from learnt
  926. * rule (always unit) and re-propagate.
  927. *
  928. * returns the new solver level or 0 if unsolvable
  929. *
  930. */
  931. private function setPropagateLearn($level, Literal $literal, $disableRules, Rule $rule)
  932. {
  933. assert($rule != null);
  934. assert($literal != null);
  935. $level++;
  936. $this->addDecision($literal, $level);
  937. $this->decisionQueue[] = $literal;
  938. $this->decisionQueueWhy[] = $rule;
  939. $this->decisionQueueFree[count($this->decisionQueueWhy) - 1] = true;
  940. while (true) {
  941. $rule = $this->propagate($level);
  942. if (!$rule) {
  943. break;
  944. }
  945. if ($level == 1) {
  946. return $this->analyzeUnsolvable($rule, $disableRules);
  947. }
  948. // conflict
  949. list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule);
  950. assert($newLevel > 0);
  951. assert($newLevel < $level);
  952. $level = $newLevel;
  953. $this->revert($level);
  954. assert($newRule != null);
  955. $this->addRule(RuleSet::TYPE_LEARNED, $newRule);
  956. $this->learnedWhy[$newRule->getId()] = $why;
  957. $this->watch2OnHighest($newRule);
  958. $this->addWatchesToRule($newRule);
  959. $this->addDecision($learnLiteral, $level);
  960. $this->decisionQueue[] = $learnLiteral;
  961. $this->decisionQueueWhy[] = $newRule;
  962. }
  963. return $level;
  964. }
  965. private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
  966. {
  967. // choose best package to install from decisionQueue
  968. $literals = $this->policy->selectPreferedPackages($this->pool, $this->installedMap, $decisionQueue);
  969. $selectedLiteral = array_shift($literals);
  970. // if there are multiple candidates, then branch
  971. if (count($literals)) {
  972. $this->branches[] = array($literals, $level);
  973. }
  974. return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
  975. }
  976. protected function analyze($level, $rule)
  977. {
  978. $ruleLevel = 1;
  979. $num = 0;
  980. $l1num = 0;
  981. $seen = array();
  982. $learnedLiterals = array(null);
  983. $decisionId = count($this->decisionQueue);
  984. $this->learnedPool[] = array();
  985. while(true) {
  986. $this->learnedPool[count($this->learnedPool) - 1][] = $rule;
  987. foreach ($rule->getLiterals() as $literal) {
  988. // skip the one true literal
  989. if ($this->decisionsSatisfy($literal)) {
  990. continue;
  991. }
  992. if (isset($seen[$literal->getPackageId()])) {
  993. continue;
  994. }
  995. $seen[$literal->getPackageId()] = true;
  996. $l = abs($this->decisionMap[$literal->getPackageId()]);
  997. if (1 === $l) {
  998. $l1num++;
  999. } else if ($level === $l) {
  1000. $num++;
  1001. } else {
  1002. // not level1 or conflict level, add to new rule
  1003. $learnedLiterals[] = $literal;
  1004. if ($l > $ruleLevel) {
  1005. $ruleLevel = $l;
  1006. }
  1007. }
  1008. }
  1009. $l1retry = true;
  1010. while ($l1retry) {
  1011. $l1retry = false;
  1012. if (!$num && !--$l1num) {
  1013. // all level 1 literals done
  1014. break 2;
  1015. }
  1016. while (true) {
  1017. assert($decisionId > 0);
  1018. $decisionId--;
  1019. $literal = $this->decisionQueue[$decisionId];
  1020. if (isset($seen[$literal->getPackageId()])) {
  1021. break;
  1022. }
  1023. }
  1024. unset($seen[$literal->getPackageId()]);
  1025. if ($num && 0 === --$num) {
  1026. $learnedLiterals[0] = $this->literalFromId(-$literal->getPackageId());
  1027. if (!$l1num) {
  1028. break 2;
  1029. }
  1030. foreach ($learnedLiterals as $i => $learnedLiteral) {
  1031. if ($i !== 0) {
  1032. unset($seen[$literal->getPackageId()]);
  1033. }
  1034. }
  1035. // only level 1 marks left
  1036. $l1num++;
  1037. $l1retry = true;
  1038. }
  1039. $rule = $this->decisionQueueWhy[$decisionId];
  1040. }
  1041. }
  1042. $why = count($this->learnedPool) - 1;
  1043. assert($learnedLiterals[0] !== null);
  1044. $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
  1045. return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
  1046. }
  1047. private function analyzeUnsolvableRule($problem, $conflictRule, &$lastWeakWhy)
  1048. {
  1049. $why = $conflictRule->getId();
  1050. if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
  1051. $learnedWhy = $this->learnedWhy[$why];
  1052. $problemRules = $this->learnedPool[$learnedWhy];
  1053. foreach ($problemRules as $problemRule) {
  1054. $this->analyzeUnsolvableRule($problem, $problemRule, $lastWeakWhy);
  1055. }
  1056. return;
  1057. }
  1058. if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) {
  1059. // package rules cannot be part of a problem
  1060. return;
  1061. }
  1062. if ($conflictRule->isWeak()) {
  1063. /** TODO why > or < lastWeakWhy? */
  1064. if (!$lastWeakWhy || $why > $lastWeakWhy->getId()) {
  1065. $lastWeakWhy = $conflictRule;
  1066. }
  1067. }
  1068. if ($conflictRule->getType() == RuleSet::TYPE_JOB) {
  1069. $job = $this->ruleToJob[$conflictRule->getId()];
  1070. $problem->addJobRule($job, $conflictRule);
  1071. } else {
  1072. $problem->addRule($conflictRule);
  1073. }
  1074. }
  1075. private function analyzeUnsolvable($conflictRule, $disableRules)
  1076. {
  1077. $lastWeakWhy = null;
  1078. $problem = new Problem;
  1079. $problem->addRule($conflictRule);
  1080. $this->analyzeUnsolvableRule($problem, $conflictRule, $lastWeakWhy);
  1081. $this->problems[] = $problem;
  1082. $seen = array();
  1083. $literals = $conflictRule->getLiterals();
  1084. /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
  1085. if (sizeof($literals) == 2) {
  1086. $literals[1] = $this->literalFromId($conflictRule->watch2);
  1087. }
  1088. */
  1089. foreach ($literals as $literal) {
  1090. // skip the one true literal
  1091. if ($this->decisionsSatisfy($literal)) {
  1092. continue;
  1093. }
  1094. $seen[$literal->getPackageId()] = true;
  1095. }
  1096. $decisionId = count($this->decisionQueue);
  1097. while ($decisionId > 0) {
  1098. $decisionId--;
  1099. $literal = $this->decisionQueue[$decisionId];
  1100. // skip literals that are not in this rule
  1101. if (!isset($seen[$literal->getPackageId()])) {
  1102. continue;
  1103. }
  1104. $why = $this->decisionQueueWhy[$decisionId];
  1105. $problem->addRule($why);
  1106. $this->analyzeUnsolvableRule($problem, $why, $lastWeakWhy);
  1107. $literals = $why->getLiterals();
  1108. /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
  1109. if (sizeof($literals) == 2) {
  1110. $literals[1] = $this->literalFromId($why->watch2);
  1111. }
  1112. */
  1113. foreach ($literals as $literal) {
  1114. // skip the one true literal
  1115. if ($this->decisionsSatisfy($literal)) {
  1116. continue;
  1117. }
  1118. $seen[$literal->getPackageId()] = true;
  1119. }
  1120. }
  1121. if ($lastWeakWhy) {
  1122. array_pop($this->problems);
  1123. if ($lastWeakWhy->getType() === RuleSet::TYPE_JOB) {
  1124. $why = $this->ruleToJob[$lastWeakWhy];
  1125. } else {
  1126. $why = $lastWeakWhy;
  1127. }
  1128. $this->disableProblem($why);
  1129. /**
  1130. @TODO what does v < 0 mean here? ($why == v)
  1131. if (v < 0)
  1132. solver_reenablepolicyrules(solv, -(v + 1));
  1133. */
  1134. $this->resetSolver();
  1135. return true;
  1136. }
  1137. if ($disableRules) {
  1138. foreach ($this->problems[count($this->problems) - 1] as $reason) {
  1139. if ($reason['job']) {
  1140. $this->disableProblem($reason['job']);
  1141. } else {
  1142. $this->disableProblem($reason['rule']);
  1143. }
  1144. }
  1145. $this->resetSolver();
  1146. return true;
  1147. }
  1148. return false;
  1149. }
  1150. private function disableProblem($why)
  1151. {
  1152. if ($why instanceof Rule) {
  1153. $why->disable();
  1154. } else if (is_array($why)) {
  1155. // disable all rules of this job
  1156. foreach ($this->ruleToJob as $ruleId => $job) {
  1157. if ($why === $job) {
  1158. $this->rules->ruleById($ruleId)->disable();
  1159. }
  1160. }
  1161. }
  1162. }
  1163. private function resetSolver()
  1164. {
  1165. while ($literal = array_pop($this->decisionQueue)) {
  1166. $this->decisionMap[$literal->getPackageId()] = 0;
  1167. }
  1168. $this->decisionQueueWhy = array();
  1169. $this->decisionQueueFree = array();
  1170. $this->recommendsIndex = -1;
  1171. $this->propagateIndex = 0;
  1172. $this->recommendations = array();
  1173. $this->branches = array();
  1174. $this->enableDisableLearnedRules();
  1175. $this->makeAssertionRuleDecisions();
  1176. }
  1177. /*-------------------------------------------------------------------
  1178. * enable/disable learnt rules
  1179. *
  1180. * we have enabled or disabled some of our rules. We now reenable all
  1181. * of our learnt rules except the ones that were learnt from rules that
  1182. * are now disabled.
  1183. */
  1184. private function enableDisableLearnedRules()
  1185. {
  1186. foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
  1187. $why = $this->learnedWhy[$rule->getId()];
  1188. $problemRules = $this->learnedPool[$why];
  1189. $foundDisabled = false;
  1190. foreach ($problemRules as $problemRule) {
  1191. if ($problemRule->isDisabled()) {
  1192. $foundDisabled = true;
  1193. break;
  1194. }
  1195. }
  1196. if ($foundDisabled && $rule->isEnabled()) {
  1197. $rule->disable();
  1198. } else if (!$foundDisabled && $rule->isDisabled()) {
  1199. $rule->enable();
  1200. }
  1201. }
  1202. }
  1203. private function runSat($disableRules = true, $installRecommended = false)
  1204. {
  1205. $this->propagateIndex = 0;
  1206. // /*
  1207. // * here's the main loop:
  1208. // * 1) propagate new decisions (only needed once)
  1209. // * 2) fulfill jobs
  1210. // * 3) try to keep installed packages
  1211. // * 4) fulfill all unresolved rules
  1212. // * 5) install recommended packages
  1213. // * 6) minimalize solution if we had choices
  1214. // * if we encounter a problem, we rewind to a safe level and restart
  1215. // * with step 1
  1216. // */
  1217. $decisionQueue = array();
  1218. $decisionSupplementQueue = array();
  1219. $disableRules = array();
  1220. $level = 1;
  1221. $systemLevel = $level + 1;
  1222. $minimizationSteps = 0;
  1223. $installedPos = 0;
  1224. while (true) {
  1225. if (1 === $level) {
  1226. $conflictRule = $this->propagate($level);
  1227. if ($conflictRule !== null) {
  1228. if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
  1229. continue;
  1230. } else {
  1231. return;
  1232. }
  1233. }
  1234. }
  1235. // handle job rules
  1236. if ($level < $systemLevel) {
  1237. $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
  1238. foreach ($iterator as $rule) {
  1239. if ($rule->isEnabled()) {
  1240. $decisionQueue = array();
  1241. $noneSatisfied = true;
  1242. foreach ($rule->getLiterals() as $literal) {
  1243. if ($this->decisionsSatisfy($literal)) {
  1244. $noneSatisfied = false;
  1245. break;
  1246. }
  1247. $decisionQueue[] = $literal;
  1248. }
  1249. if ($noneSatisfied && count($decisionQueue)) {
  1250. // prune all update packages until installed version
  1251. // except for requested updates
  1252. if (count($this->installed) != count($this->updateMap)) {
  1253. $prunedQueue = array();
  1254. foreach ($decisionQueue as $literal) {
  1255. if (isset($this->installedMap[$literal->getPackageId()])) {
  1256. $prunedQueue[] = $literal;
  1257. if (isset($this->updateMap[$literal->getPackageId()])) {
  1258. $prunedQueue = $decisionQueue;
  1259. break;
  1260. }
  1261. }
  1262. }
  1263. $decisionQueue = $prunedQueue;
  1264. }
  1265. }
  1266. if ($noneSatisfied && count($decisionQueue)) {
  1267. $oLevel = $level;
  1268. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  1269. if (0 === $level) {
  1270. return;
  1271. }
  1272. if ($level <= $oLevel) {
  1273. break;
  1274. }
  1275. }
  1276. }
  1277. }
  1278. $systemLevel = $level + 1;
  1279. // jobs left
  1280. $iterator->next();
  1281. if ($iterator->valid()) {
  1282. continue;
  1283. }
  1284. }
  1285. // handle installed packages
  1286. if ($level < $systemLevel) {
  1287. // use two passes if any packages are being updated
  1288. // -> better user experience
  1289. for ($pass = (count($this->updateMap)) ? 0 : 1; $pass < 2; $pass++) {
  1290. $passLevel = $level;
  1291. for ($i = $installedPos, $n = 0; $n < count($this->installedPackages); $i++, $n++) {
  1292. $repeat = false;
  1293. if ($i == count($this->installedPackages)) {
  1294. $i = 0;
  1295. }
  1296. $literal = new Literal($this->installedPackages[$i], true);
  1297. if ($this->decisionsContain($literal)) {
  1298. continue;
  1299. }
  1300. // only process updates in first pass
  1301. /** TODO: && or || ? **/
  1302. if (0 === $pass && !isset($this->updateMap[$literal->getPackageId()])) {
  1303. continue;
  1304. }
  1305. $rule = null;
  1306. if (isset($this->packageToFeatureRule[$literal->getPackageId()])) {
  1307. $rule = $this->packageToFeatureRule[$literal->getPackageId()];
  1308. }
  1309. if (!$rule || $rule->isDisabled()) {
  1310. continue;
  1311. }
  1312. $updateRuleLiterals = $rule->getLiterals();
  1313. $decisionQueue = array();
  1314. if (!isset($this->noUpdate[$literal->getPackageId()]) && (
  1315. $this->decidedRemove($literal->getPackage()) ||
  1316. isset($this->updateMap[$literal->getPackageId()]) ||
  1317. !$literal->equals($updateRuleLiterals[0])
  1318. )) {
  1319. foreach ($updateRuleLiterals as $ruleLiteral) {
  1320. if ($this->decidedInstall($ruleLiteral->getPackage())) {
  1321. // already fulfilled
  1322. $decisionQueue = array();
  1323. break;
  1324. }
  1325. if ($this->undecided($ruleLiteral->getPackage())) {
  1326. $decisionQueue[] = $ruleLiteral;
  1327. }
  1328. }
  1329. }
  1330. if (sizeof($decisionQueue)) {
  1331. $oLevel = $level;
  1332. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  1333. if (0 === $level) {
  1334. return;
  1335. }
  1336. if ($level <= $oLevel) {
  1337. $repeat = true;
  1338. }
  1339. } else if (!$repeat && $this->undecided($literal->getPackage())) {
  1340. // still undecided? keep package.
  1341. $oLevel = $level;
  1342. if (isset($this->cleanDepsMap[$literal->getPackageId()])) {
  1343. // clean deps removes package
  1344. $level = $this->setPropagateLearn($level, $literal->invert(), $disableRules, null);
  1345. } else {
  1346. // ckeeping package
  1347. $level = $this->setPropagateLearn($level, $literal, $disableRules, $rule);
  1348. }
  1349. if (0 === $level) {
  1350. return;
  1351. }
  1352. if ($level <= $oLevel) {
  1353. $repeat = true;
  1354. }
  1355. }
  1356. if ($repeat) {
  1357. if (1 === $level || $level < $passLevel) {
  1358. // trouble
  1359. break;
  1360. }
  1361. if ($level < $oLevel) {
  1362. // redo all
  1363. $n = 0;
  1364. }
  1365. // repeat
  1366. $i--;
  1367. $n--;
  1368. continue;
  1369. }
  1370. }
  1371. if ($n < count($this->installedPackages)) {
  1372. $installedPos = $i; // retry this problem next time
  1373. break;
  1374. }
  1375. $installedPos = 0;
  1376. }
  1377. $systemLevel = $level + 1;
  1378. if ($pass < 2) {
  1379. // had trouble => retry
  1380. continue;
  1381. }
  1382. }
  1383. if ($level < $systemLevel) {
  1384. $systemLevel = $level;
  1385. }
  1386. for ($i = 0, $n = 0; $n < count($this->rules); $i++, $n++) {
  1387. if ($i == count($this->rules)) {
  1388. $i = 0;
  1389. }
  1390. $rule = $this->rules->ruleById($i);
  1391. $literals = $rule->getLiterals();
  1392. if ($rule->isDisabled()) {
  1393. continue;
  1394. }
  1395. $decisionQueue = array();
  1396. // make sure that
  1397. // * all negative literals are installed
  1398. // * no positive literal is installed
  1399. // i.e. the rule is not fulfilled and we
  1400. // just need to decide on the positive literals
  1401. //
  1402. foreach ($literals as $literal) {
  1403. if (!$literal->isWanted()) {
  1404. if (!$this->decidedInstall($literal->getPackage())) {
  1405. continue 2; // next rule
  1406. }
  1407. } else {
  1408. if ($this->decidedInstall($literal->getPackage())) {
  1409. continue 2; // next rule
  1410. }
  1411. if ($this->undecided($literal->getPackage())) {
  1412. $decisionQueue[] = $literal;
  1413. }
  1414. }
  1415. }
  1416. // need to have at least 2 item to pick from
  1417. if (count($decisionQueue) < 2) {
  1418. continue;
  1419. }
  1420. $oLevel = $level;
  1421. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  1422. if (0 === $level) {
  1423. return;
  1424. }
  1425. // open suse sat-solver uses this, but why is $level == 1 trouble?
  1426. // SYSTEMSOLVABLE related? we don't have that, so should work
  1427. //if ($level < $systemLevel || $level == 1) {
  1428. if ($level < $systemLevel) {
  1429. break; // trouble
  1430. }
  1431. // something changed, so look at all rules again
  1432. $n = -1;
  1433. }
  1434. // minimization step
  1435. if (count($this->branches)) {
  1436. $lastLiteral = null;
  1437. $lastLevel = null;
  1438. $lastBranchIndex = 0;
  1439. $lastBranchOffset = 0;
  1440. for ($i = count($this->branches) - 1; $i >= 0; $i--) {
  1441. list($literals, $level) = $this->branches[$i];
  1442. foreach ($literals as $offset => $literal) {
  1443. if ($literal && $literal->isWanted() && $this->decisionMap[$literal->getPackageId()] > $level + 1) {
  1444. $lastLiteral = $literal;
  1445. $lastBranchIndex = $i;
  1446. $lastBranchOffset = $offset;
  1447. $lastLevel = $level;
  1448. }
  1449. }
  1450. }
  1451. if ($lastLiteral) {
  1452. $this->branches[$lastBranchIndex][$lastBranchOffset] = null;
  1453. $minimizationSteps++;
  1454. $level = $lastLevel;
  1455. $this->revert($level);
  1456. $why = $this->decisionQueueWhy[count($this->decisionQueueWhy) - 1];
  1457. $oLevel = $level;
  1458. $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);
  1459. if ($level == 0) {
  1460. return;
  1461. }
  1462. continue;
  1463. }
  1464. }
  1465. break;
  1466. }
  1467. }
  1468. private function printDecisionMap()
  1469. {
  1470. echo "\nDecisionMap: \n";
  1471. foreach ($this->decisionMap as $packageId => $level) {
  1472. if ($packageId === 0) {
  1473. continue;
  1474. }
  1475. if ($level > 0) {
  1476. echo ' +' . $this->pool->packageById($packageId)."\n";
  1477. } elseif ($level < 0) {
  1478. echo ' -' . $this->pool->packageById($packageId)."\n";
  1479. } else {
  1480. echo ' ?' . $this->pool->packageById($packageId)."\n";
  1481. }
  1482. }
  1483. echo "\n";
  1484. }
  1485. private function printDecisionQueue()
  1486. {
  1487. echo "DecisionQueue: \n";
  1488. foreach ($this->decisionQueue as $i => $literal) {
  1489. echo ' ' . $literal . ' ' . $this->decisionQueueWhy[$i]." level ".$this->decisionMap[$literal->getPackageId()]."\n";
  1490. }
  1491. echo "\n";
  1492. }
  1493. private function printWatches()
  1494. {
  1495. echo "\nWatches:\n";
  1496. foreach ($this->watches as $literalId => $watch) {
  1497. echo ' '.$this->literalFromId($literalId)."\n";
  1498. $queue = array(array(' ', $watch));
  1499. while (!empty($queue)) {
  1500. list($indent, $watch) = array_pop($queue);
  1501. echo $indent.$watch;
  1502. if ($watch) {
  1503. echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]";
  1504. }
  1505. echo "\n";
  1506. if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) {
  1507. if ($watch->next1 == $watch) {
  1508. echo $indent." 1 *RECURSION*";
  1509. }
  1510. if ($watch->next2 == $watch) {
  1511. echo $indent." 2 *RECURSION*";
  1512. }
  1513. } elseif ($watch && ($watch->next1 || $watch->next2)) {
  1514. $indent = str_replace(array('1', '2'), ' ', $indent);
  1515. array_push($queue, array($indent.' 2 ', $watch->next2));
  1516. array_push($queue, array($indent.' 1 ', $watch->next1));
  1517. }
  1518. }
  1519. echo "\n";
  1520. }
  1521. }
  1522. }