Solver.php 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
  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. /**
  15. * @author Nils Adermann <naderman@naderman.de>
  16. */
  17. class Solver
  18. {
  19. const RULE_INTERNAL_ALLOW_UPDATE = 1;
  20. const RULE_JOB_INSTALL = 2;
  21. const RULE_JOB_REMOVE = 3;
  22. const RULE_JOB_LOCK = 4;
  23. const RULE_NOT_INSTALLABLE = 5;
  24. const RULE_NOTHING_PROVIDES_DEP = 6;
  25. const RULE_PACKAGE_CONFLICT = 7;
  26. const RULE_PACKAGE_NOT_EXIST = 8;
  27. const RULE_PACKAGE_REQUIRES = 9;
  28. const TYPE_PACKAGE = 0;
  29. const TYPE_FEATURE = 1;
  30. const TYPE_UPDATE = 2;
  31. const TYPE_JOB = 3;
  32. const TYPE_WEAK = 4;
  33. const TYPE_LEARNED = 5;
  34. protected static $types = array(
  35. -1 => 'UNKNOWN',
  36. self::TYPE_PACKAGE => 'PACKAGE',
  37. self::TYPE_FEATURE => 'FEATURE',
  38. self::TYPE_UPDATE => 'UPDATE',
  39. self::TYPE_JOB => 'JOB',
  40. self::TYPE_WEAK => 'WEAK',
  41. self::TYPE_LEARNED => 'LEARNED',
  42. );
  43. protected $policy;
  44. protected $pool;
  45. protected $installed;
  46. protected $rules;
  47. protected $updateAll;
  48. protected $addedMap = array();
  49. protected $fixMap = array();
  50. protected $updateMap = array();
  51. protected $watches = array();
  52. protected $removeWatches = array();
  53. public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
  54. {
  55. $this->policy = $policy;
  56. $this->pool = $pool;
  57. $this->installed = $installed;
  58. $this->rules = array(
  59. // order matters here! further down => higher priority
  60. self::TYPE_LEARNED => array(),
  61. self::TYPE_WEAK => array(),
  62. self::TYPE_FEATURE => array(),
  63. self::TYPE_UPDATE => array(),
  64. self::TYPE_JOB => array(),
  65. self::TYPE_PACKAGE => array(),
  66. );
  67. }
  68. /**
  69. * Creates a new rule for the requirements of a package
  70. *
  71. * This rule is of the form (-A|B|C), where B and C are the providers of
  72. * one requirement of the package A.
  73. *
  74. * @param PackageInterface $package The package with a requirement
  75. * @param array $providers The providers of the requirement
  76. * @param int $reason A RULE_* constant describing the
  77. * reason for generating this rule
  78. * @param mixed $reasonData Any data, e.g. the requirement name,
  79. * that goes with the reason
  80. * @return Rule The generated rule or null if tautological
  81. */
  82. public function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
  83. {
  84. $literals = array(new Literal($package, false));
  85. foreach ($providers as $provider) {
  86. // self fulfilling rule?
  87. if ($provider === $package) {
  88. return null;
  89. }
  90. $literals[] = new Literal($provider, true);
  91. }
  92. return new Rule($literals, $reason, $reasonData);
  93. }
  94. /**
  95. * Create a new rule for updating a package
  96. *
  97. * If package A1 can be updated to A2 or A3 the rule is (A1|A2|A3).
  98. *
  99. * @param PackageInterface $package The package to be updated
  100. * @param array $updates An array of update candidate packages
  101. * @param int $reason A RULE_* constant describing the
  102. * reason for generating this rule
  103. * @param mixed $reasonData Any data, e.g. the package name, that
  104. * goes with the reason
  105. * @return Rule The generated rule or null if tautology
  106. */
  107. protected function createUpdateRule(PackageInterface $package, array $updates, $reason, $reasonData = null)
  108. {
  109. $literals = array(new Literal($package, true));
  110. foreach ($updates as $update) {
  111. $literals[] = new Literal($update, true);
  112. }
  113. return new Rule($literals, $reason, $reasonData);
  114. }
  115. /**
  116. * Creates a new rule for installing a package
  117. *
  118. * The rule is simply (A) for a package A to be installed.
  119. *
  120. * @param PackageInterface $package The package to be installed
  121. * @param int $reason A RULE_* constant describing the
  122. * reason for generating this rule
  123. * @param mixed $reasonData Any data, e.g. the package name, that
  124. * goes with the reason
  125. * @return Rule The generated rule
  126. */
  127. public function createInstallRule(PackageInterface $package, $reason, $reasonData = null)
  128. {
  129. return new Rule(new Literal($package, true));
  130. }
  131. /**
  132. * Creates a rule to install at least one of a set of packages
  133. *
  134. * The rule is (A|B|C) with A, B and C different packages. If the given
  135. * set of packages is empty an impossible rule is generated.
  136. *
  137. * @param array $packages The set of packages to choose from
  138. * @param int $reason A RULE_* constant describing the reason for
  139. * generating this rule
  140. * @param mixed $reasonData Any data, e.g. the package name, that goes with
  141. * the reason
  142. * @return Rule The generated rule
  143. */
  144. public function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
  145. {
  146. if (empty($packages)) {
  147. return $this->createImpossibleRule($reason, $reasonData);
  148. }
  149. $literals = array();
  150. foreach ($packages as $package) {
  151. $literals[] = new Literal($package, true);
  152. }
  153. return new Rule($literals, $reason, $reasonData);
  154. }
  155. /**
  156. * Creates a rule to remove a package
  157. *
  158. * The rule for a package A is (-A).
  159. *
  160. * @param PackageInterface $package The package to be removed
  161. * @param int $reason A RULE_* constant describing the
  162. * reason for generating this rule
  163. * @param mixed $reasonData Any data, e.g. the package name, that
  164. * goes with the reason
  165. * @return Rule The generated rule
  166. */
  167. public function createRemoveRule(PackageInterface $package, $reason, $reasonData = null)
  168. {
  169. return new Rule(array(new Literal($package, false)), $reason, $reasonData);
  170. }
  171. /**
  172. * Creates a rule for two conflicting packages
  173. *
  174. * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
  175. * and B the provider.
  176. *
  177. * @param PackageInterface $issuer The package declaring the conflict
  178. * @param Package $provider The package causing the conflict
  179. * @param int $reason A RULE_* constant describing the
  180. * reason for generating this rule
  181. * @param mixed $reasonData Any data, e.g. the package name, that
  182. * goes with the reason
  183. * @return Rule The generated rule
  184. */
  185. public function createConflictRule(PackageInterface $issuer, Package $provider, $reason, $reasonData = null)
  186. {
  187. // ignore self conflict
  188. if ($issuer === $provider) {
  189. return null;
  190. }
  191. return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData);
  192. }
  193. /**
  194. * Intentionally creates a rule impossible to solve
  195. *
  196. * The rule is an empty one so it can never be satisfied.
  197. *
  198. * @param int $reason A RULE_* constant describing the reason for
  199. * generating this rule
  200. * @param mixed $reasonData Any data, e.g. the package name, that goes with
  201. * the reason
  202. * @return Rule An empty rule
  203. */
  204. public function createImpossibleRule($reason, $reasonData = null)
  205. {
  206. return new Rule(array(), $reason, $reasonData);
  207. }
  208. /**
  209. * Adds a rule unless it duplicates an existing one of any type
  210. *
  211. * To be able to directly pass in the result of one of the rule creation
  212. * methods the rule may also be null to indicate that no rule should be
  213. * added.
  214. *
  215. * @param int $type A TYPE_* constant defining the rule type
  216. * @param Rule $newRule The rule about to be added
  217. */
  218. private function addRule($type, Rule $newRule = null) {
  219. if ($newRule) {
  220. foreach ($this->rules as $rules) {
  221. foreach ($rules as $rule) {
  222. if ($rule->equals($newRule)) {
  223. return;
  224. }
  225. }
  226. }
  227. $newRule->setType($type);
  228. $this->rules[$type][] = $newRule;
  229. }
  230. }
  231. public function addRulesForPackage(PackageInterface $package)
  232. {
  233. $workQueue = new \SPLQueue;
  234. $workQueue->enqueue($package);
  235. while (!$workQueue->isEmpty()) {
  236. $package = $workQueue->dequeue();
  237. if (isset($this->addedMap[$package->getId()])) {
  238. continue;
  239. }
  240. $this->addedMap[$package->getId()] = true;
  241. $dontFix = 0;
  242. if ($this->installed === $package->getRepository() && !isset($this->fixMap[$package->getId()])) {
  243. $dontFix = 1;
  244. }
  245. if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installed, $package)) {
  246. $this->addRule(self::TYPE_PACKAGE, $this->createRemoveRule($package, self::RULE_NOT_INSTALLABLE, (string) $package));
  247. continue;
  248. }
  249. foreach ($package->getRequires() as $link) {
  250. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  251. // the strategy here is to not insist on dependencies
  252. // that are already broken. so if we find one provider
  253. // that was already installed, we know that the
  254. // dependency was not broken before so we enforce it
  255. if ($dontFix) {
  256. $foundInstalled = false;
  257. foreach ($possibleRequires as $require) {
  258. if ($this->installed === $require->getRepository()) {
  259. $foundInstalled = true;
  260. break;
  261. }
  262. }
  263. // no installed provider found: previously broken dependency => don't add rule
  264. if (!$foundInstalled) {
  265. continue;
  266. }
  267. }
  268. $this->addRule(self::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, self::RULE_PACKAGE_REQUIRES, (string) $link));
  269. foreach ($possibleRequires as $require) {
  270. $workQueue->enqueue($require);
  271. }
  272. }
  273. foreach ($package->getConflicts() as $link) {
  274. $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  275. foreach ($possibleConflicts as $conflict) {
  276. if ($dontfix && $this->installed === $conflict->getRepository()) {
  277. continue;
  278. }
  279. $this->addRule(self::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, self::RULE_PACKAGE_CONFLICT, (string) $link));
  280. }
  281. }
  282. foreach ($package->getRecommends() as $link) {
  283. foreach ($this->pool->whatProvides($link->getTarget(), $link->getConstraint()) as $recommend) {
  284. $workQueue->enqueue($recommend);
  285. }
  286. }
  287. foreach ($package->getSuggests() as $link) {
  288. foreach ($this->pool->whatProvides($link->getTarget(), $link->getConstraint()) as $suggest) {
  289. $workQueue->enqueue($suggest);
  290. }
  291. }
  292. }
  293. }
  294. /**
  295. * Adds all rules for all update packages of a given package
  296. *
  297. * @param PackageInterface $package Rules for this package's updates are to
  298. * be added
  299. * @param bool $allowAll Whether downgrades are allowed
  300. */
  301. private function addRulesForUpdatePackages(PackageInterface $package, $allowAll)
  302. {
  303. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, $allowAll);
  304. $this->addRulesForPackage($package);
  305. foreach ($updates as $update) {
  306. $this->addRulesForPackage($update);
  307. }
  308. }
  309. /**
  310. * Sets up watch chains for all rules.
  311. *
  312. * Next1/2 always points to the next rule that is watching the same package.
  313. * The watches array contains rules to start from for each package
  314. *
  315. */
  316. private function makeWatches()
  317. {
  318. foreach ($this->rules as $type => $rules) {
  319. foreach ($rules as $i => $rule) {
  320. // skip simple assertions of the form (A) or (-A)
  321. if ($rule->isAssertion()) {
  322. continue;
  323. }
  324. if (!isset($this->watches[$rule->watch1])) {
  325. $this->watches[$rule->watch1] = null;
  326. }
  327. $rule->next1 = $this->watches[$rule->watch1];
  328. $this->watches[$rule->watch1] = $rule;
  329. if (!isset($this->watches[$rule->watch2])) {
  330. $this->watches[$rule->watch2] = null;
  331. }
  332. $rule->next2 = $this->watches[$rule->watch2];
  333. $this->watches[$rule->watch2] = $rule;
  334. }
  335. }
  336. }
  337. private function findDecisionRule(PackageInterface $package)
  338. {
  339. foreach ($this->decisionQueue as $i => $literal) {
  340. if ($package === $literal->getPackage()) {
  341. return $this->decisionQueueWhy[$i];
  342. }
  343. }
  344. return null;
  345. }
  346. private function makeAssertionRuleDecisions()
  347. {
  348. // do we need to decide a SYSTEMSOLVABLE at level 1?
  349. foreach ($this->rules as $type => $rules) {
  350. if (self::TYPE_WEAK === $type) {
  351. continue;
  352. }
  353. foreach ($rules as $rule) {
  354. if (!$rule->isAssertion() || $rule->isDisabled()) {
  355. continue;
  356. }
  357. $literals = $rule->getLiterals();
  358. $literal = $literals[0];
  359. if (!$this->decided($literal->getPackage())) {
  360. $this->decisionQueue[] = $literal;
  361. $this->decisionQueueWhy[] = $rule;
  362. $this->addDecision($literal, 1);
  363. continue;
  364. }
  365. if ($this->decisionsSatisfy($literal)) {
  366. continue;
  367. }
  368. // found a conflict
  369. if (self::TYPE_LEARNED === $type) {
  370. $rule->disable();
  371. }
  372. $conflict = $this->findDecisionRule($literal->getPackage());
  373. // todo: handle conflict with systemsolvable?
  374. if ($conflict && self::TYPE_PACKAGE === $conflict->getType()) {
  375. }
  376. }
  377. }
  378. foreach ($this->rules[self::TYPE_WEAK] as $rule) {
  379. if (!$rule->isAssertion() || $rule->isDisabled()) {
  380. continue;
  381. }
  382. if ($this->decisionsSatisfy($literals[0])) {
  383. continue;
  384. }
  385. // conflict, but this is a weak rule => disable
  386. $rule->disable();
  387. }
  388. }
  389. public function solve(Request $request)
  390. {
  391. $this->jobs = $request->getJobs();
  392. $installedPackages = $this->installed->getPackages();
  393. foreach ($this->jobs as $job) {
  394. switch ($job['cmd']) {
  395. case 'update-all':
  396. foreach ($installedPackages as $package) {
  397. $this->updateMap[$package->getId()] = true;
  398. }
  399. break;
  400. case 'fix-all':
  401. foreach ($installedPackages as $package) {
  402. $this->fixMap[$package->getId()] = true;
  403. }
  404. break;
  405. }
  406. foreach ($job['packages'] as $package) {
  407. switch ($job['cmd']) {
  408. case 'fix':
  409. if ($this->installed === $package->getRepository()) {
  410. $this->fixMap[$package->getId()] = true;
  411. }
  412. break;
  413. case 'update':
  414. if ($this->installed === $package->getRepository()) {
  415. $this->updateMap[$package->getId()] = true;
  416. }
  417. break;
  418. }
  419. }
  420. }
  421. foreach ($installedPackages as $package) {
  422. $this->addRulesForPackage($package);
  423. }
  424. foreach ($installedPackages as $package) {
  425. $this->addRulesForUpdatePackages($package, true);
  426. }
  427. foreach ($this->jobs as $job) {
  428. foreach ($job['packages'] as $package) {
  429. switch ($job['cmd']) {
  430. case 'install':
  431. $this->installCandidateMap[$package->getId()] = true;
  432. $this->addRulesForPackage($package);
  433. break;
  434. }
  435. }
  436. }
  437. // solver_addrpmrulesforweak(solv, &addedmap);
  438. foreach ($installedPackages as $package) {
  439. // create a feature rule which allows downgrades
  440. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, true);
  441. $featureRule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
  442. // create an update rule which does not allow downgrades
  443. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, false);
  444. $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
  445. if ($rule->equals($featureRule)) {
  446. if ($this->policy->allowUninstall()) {
  447. $this->addRule(self::TYPE_WEAK, $featureRule);
  448. } else {
  449. $this->addRule(self::TYPE_UPDATE, $rule);
  450. }
  451. } else if ($this->policy->allowUninstall()) {
  452. $this->addRule(self::TYPE_WEAK, $featureRule);
  453. $this->addRule(self::TYPE_WEAK, $rule);
  454. }
  455. }
  456. foreach ($this->jobs as $job) {
  457. switch ($job['cmd']) {
  458. case 'install':
  459. $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']);
  460. $this->addRule(self::TYPE_JOB, $rule);
  461. //$this->ruleToJob[$rule] = $job;
  462. break;
  463. case 'remove':
  464. // remove all packages with this name including uninstalled
  465. // ones to make sure none of them are picked as replacements
  466. // todo: cleandeps
  467. foreach ($job['packages'] as $package) {
  468. $rule = $this->createRemoveRule($package, self::RULE_JOB_REMOVE);
  469. $this->addRule(self::TYPE_JOB, $rule);
  470. //$this->ruleToJob[$rule] = $job;
  471. }
  472. break;
  473. case 'lock':
  474. foreach ($job['packages'] as $package) {
  475. if ($this->installed === $package->getRepository()) {
  476. $rule = $this->createInstallRule($package, self::RULE_JOB_LOCK);
  477. } else {
  478. $rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK);
  479. }
  480. $this->addRule(self::TYPE_JOB, $rule);
  481. //$this->ruleToJob[$rule] = $job;
  482. }
  483. break;
  484. }
  485. }
  486. // solver_addchoicerules(solv);
  487. $this->makeWatches();
  488. /* disable update rules that conflict with our job */
  489. //solver_disablepolicyrules(solv);
  490. /* make decisions based on job/update assertions */
  491. $this->makeAssertionRuleDecisions();
  492. $installRecommended = 0;
  493. $this->runSat(true, $installRecommended);
  494. //findrecommendedsuggested(solv);
  495. //solver_prepare_solutions(solv);
  496. $transaction = array();
  497. foreach ($this->decisionQueue as $literal) {
  498. $package = $literal->getPackage();
  499. // wanted & installed || !wanted & !installed
  500. if ($literal->isWanted() == ($this->installed == $package->getRepository())) {
  501. continue;
  502. }
  503. $transaction[] = array(
  504. 'job' => ($literal->isWanted()) ? 'install' : 'remove',
  505. 'package' => $package,
  506. );
  507. }
  508. return $transaction;
  509. }
  510. public function printRules()
  511. {
  512. print "\n";
  513. foreach ($this->rules as $type => $rules) {
  514. print str_pad(self::$types[$type], 8, ' ') . ": ";
  515. foreach ($rules as $rule) {
  516. print $rule;
  517. }
  518. print "\n";
  519. }
  520. }
  521. protected $decisionQueue = array();
  522. protected $propagateIndex;
  523. protected $decisionMap = array();
  524. protected $branches = array();
  525. protected function literalFromId($id)
  526. {
  527. $package = $this->pool->packageById($id);
  528. return new Literal($package, $id > 0);
  529. }
  530. protected function addDecision(Literal $l, $level)
  531. {
  532. if ($l->isWanted()) {
  533. $this->decisionMap[$l->getPackageId()] = $level;
  534. } else {
  535. $this->decisionMap[$l->getPackageId()] = -$level;
  536. }
  537. }
  538. protected function addDecisionId($literalId, $level)
  539. {
  540. $packageId = abs($literalId);
  541. if ($literalId > 0) {
  542. $this->decisionMap[$packageId] = $level;
  543. } else {
  544. $this->decisionMap[$packageId] = -$level;
  545. }
  546. }
  547. protected function decisionsContain(Literal $l)
  548. {
  549. return (isset($this->decisionMap[$l->getPackageId()]) && (
  550. $this->decisionMap[$l->getPackageId()] > 0 && $l->isWanted() ||
  551. $this->decisionMap[$l->getPackageId()] < 0 && !$l->isWanted()
  552. ));
  553. }
  554. protected function decisionsContainId($literalId)
  555. {
  556. $packageId = abs($literalId);
  557. return (isset($this->decisionMap[$packageId]) && (
  558. $this->decisionMap[$packageId] > 0 && $literalId > 0 ||
  559. $this->decisionMap[$packageId] < 0 && $literalId < 0
  560. ));
  561. }
  562. protected function decisionsSatisfy(Literal $l)
  563. {
  564. return ($l->isWanted() && isset($this->decisionMap[$l->getPackageId()]) && $this->decisionMap[$l->getPackageId()] > 0) ||
  565. (!$l->isWanted() && (!isset($this->decisionMap[$l->getPackageId()]) || $this->decisionMap[$l->getPackageId()] < 0));
  566. }
  567. protected function decisionsConflict(Literal $l)
  568. {
  569. return (isset($this->decisionMap[$l->getPackageId()]) && (
  570. $this->decisionMap[$l->getPackageId()] > 0 && !$l->isWanted() ||
  571. $this->decisionMap[$l->getPackageId()] < 0 && $l->isWanted()
  572. ));
  573. }
  574. protected function decisionsConflictId($literalId)
  575. {
  576. $packageId = abs($literalId);
  577. return (isset($this->decisionMap[$packageId]) && (
  578. $this->decisionMap[$packageId] > 0 && !$literalId < 0 ||
  579. $this->decisionMap[$packageId] < 0 && $literalId > 0
  580. ));
  581. }
  582. protected function decided(PackageInterface $p)
  583. {
  584. return isset($this->decisionMap[$p->getId()]);
  585. }
  586. protected function undecided(PackageInterface $p)
  587. {
  588. return !isset($this->decisionMap[$p->getId()]);
  589. }
  590. protected function decidedInstall(PackageInterface $p) {
  591. return isset($this->decisionMap[$p->getId()]) && $this->decisionMap[$p->getId()] > 0;
  592. }
  593. protected function decidedRemove(PackageInterface $p) {
  594. return isset($this->decisionMap[$p->getId()]) && $this->decisionMap[$p->getId()] < 0;
  595. }
  596. /**
  597. * Makes a decision and propagates it to all rules.
  598. *
  599. * Evaluates each term affected by the decision (linked through watches)
  600. * If we find unit rules we make new decisions based on them
  601. *
  602. * @return Rule|null A rule on conflict, otherwise null.
  603. */
  604. protected function propagate($level)
  605. {
  606. while ($this->propagateIndex < count($this->decisionQueue)) {
  607. // we invert the decided literal here, example:
  608. // A was decided => (-A|B) now requires B to be true, so we look for
  609. // rules which are fulfilled by -A, rather than A.
  610. $literal = $this->decisionQueue[$this->propagateIndex]->inverted();
  611. $this->propagateIndex++;
  612. // /* foreach rule where 'pkg' is now FALSE */
  613. //for (rp = watches + pkg; *rp; rp = next_rp)
  614. for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) {
  615. $nextRule = $rule->getNext($literal);
  616. if ($rule->isDisabled()) {
  617. continue;
  618. }
  619. $otherWatch = $rule->getOtherWatch($literal);
  620. if ($this->decisionsContainId($otherWatch)) {
  621. continue;
  622. }
  623. $ruleLiterals = $rule->getLiterals();
  624. if (sizeof($ruleLiterals) > 2) {
  625. foreach ($ruleLiterals as $ruleLiteral) {
  626. if (!$otherWatch->equals($ruleLiteral) &&
  627. !$this->decisionsConflict($ruleLiteral)) {
  628. if ($literal->equals($rule->getWatch1())) {
  629. $rule->setWatch1($ruleLiteral);
  630. $rule->setNext1($rule);
  631. } else {
  632. $rule->setWatch2($ruleLiteral);
  633. $rule->setNext2($rule);
  634. }
  635. $this->watches[$ruleLiteral->getId()] = $rule;
  636. continue 2;
  637. }
  638. }
  639. }
  640. // yay, we found a unit clause! try setting it to true
  641. if ($this->decisionsConflictId($otherWatch)) {
  642. return $rule;
  643. }
  644. $this->addDecisionId($otherWatch, $level);
  645. $this->decisionQueue[] = $this->literalFromId($otherWatch);
  646. $this->decisionQueueWhy[] = $rule;
  647. }
  648. }
  649. return null;
  650. }
  651. private function setPropagateLearn($level, Literal $literal, $disableRules, Rule $rule)
  652. {
  653. return 0;
  654. }
  655. private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
  656. {
  657. // choose best package to install from decisionQueue
  658. $literals = $this->policy->selectPreferedPackages($decisionQueue);
  659. // if there are multiple candidates, then branch
  660. if (count($literals) > 1) {
  661. foreach ($literals as $i => $literal) {
  662. if (0 !== $i) {
  663. $this->branches[] = array($literal, $level);
  664. }
  665. }
  666. }
  667. return $this->setPropagateLearn($level, $literals[0], $disableRules, $rule);
  668. }
  669. private function analyzeUnsolvable($conflictRule, $disableRules)
  670. {
  671. return false;
  672. }
  673. private function runSat($disableRules = true, $installRecommended = false)
  674. {
  675. $this->propagateIndex = 0;
  676. // /*
  677. // * here's the main loop:
  678. // * 1) propagate new decisions (only needed once)
  679. // * 2) fulfill jobs
  680. // * 3) try to keep installed packages
  681. // * 4) fulfill all unresolved rules
  682. // * 5) install recommended packages
  683. // * 6) minimalize solution if we had choices
  684. // * if we encounter a problem, we rewind to a safe level and restart
  685. // * with step 1
  686. // */
  687. $decisionQueue = array();
  688. $decisionSupplementQueue = array();
  689. $disableRules = array();
  690. $level = 1;
  691. $systemLevel = $level + 1;
  692. $minimizationsteps = 0;
  693. $installedPos = 0;
  694. $this->installedPackages = $this->installed->getPackages();
  695. while (true) {
  696. $conflictRule = $this->propagate($level);
  697. if ($conflictRule !== null) {
  698. // if (analyze_unsolvable(solv, r, disablerules))
  699. if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
  700. continue;
  701. } else {
  702. return;
  703. }
  704. }
  705. // handle job rules
  706. if ($level < $systemLevel) {
  707. $ruleIndex = 0;
  708. foreach ($this->rules[self::TYPE_JOB] as $ruleIndex => $rule) {
  709. if ($rule->isEnabled()) {
  710. $decisionQueue = array();
  711. $noneSatisfied = true;
  712. foreach ($rule->getLiterals() as $literal) {
  713. if ($this->decisionsSatisfy($literal)) {
  714. $noneSatisfied = false;
  715. break;
  716. }
  717. $decisionQueue[] = $literal;
  718. }
  719. if ($noneSatisfied && count($decisionQueue)) {
  720. // prune all update packages until installed version
  721. // except for requested updates
  722. if (count($this->installed) != count($this->updateMap)) {
  723. $prunedQueue = array();
  724. foreach ($decisionQueue as $literal) {
  725. if ($this->installed === $literal->getPackage()->getRepository()) {
  726. $prunedQueue[] = $literal;
  727. if (isset($this->updateMap[$literal->getPackageId()])) {
  728. $prunedQueue = $decisionQueue;
  729. break;
  730. }
  731. }
  732. }
  733. $decisionQueue = $prunedQueue;
  734. }
  735. }
  736. if ($noneSatisfied && count($decisionQueue)) {
  737. $oLevel = $level;
  738. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  739. if (0 === $level) {
  740. return;
  741. }
  742. if ($level <= $oLevel) {
  743. break;
  744. }
  745. }
  746. }
  747. }
  748. $systemLevel = $level + 1;
  749. // jobs left
  750. if ($ruleIndex + 1 < count($this->rules[Solver::TYPE_JOB])) {
  751. continue;
  752. }
  753. }
  754. // handle installed packages
  755. if ($level < $systemLevel) {
  756. // use two passes if any packages are being updated
  757. // -> better user experience
  758. for ($pass = (count($this->updateMap)) ? 0 : 1; $pass < 2; $pass++) {
  759. $passLevel = $level;
  760. for ($i = $installedPos, $n = 0; $n < count($this->installedPackages); $i++, $n++) {
  761. $repeat = false;
  762. if ($i == count($this->installedPackages)) {
  763. $i = 0;
  764. }
  765. $literal = new Literal($this->installedPackages[$i], true);
  766. if ($this->decisionsContain($literal)) {
  767. continue;
  768. }
  769. // only process updates in first pass
  770. /** TODO: && or || ? **/
  771. if (0 === $pass || !isset($this->updateMap[$literal->getPackageId()])) {
  772. continue;
  773. }
  774. $rule = null;
  775. if (isset($this->rules[Solver::TYPE_UPDATE][$literal->getPackageId()])) {
  776. $rule = $this->rules[Solver::TYPE_UPDATE][$literal->getPackageId()];
  777. }
  778. if ((!$rule || $rule->isDisabled()) && isset($this->rules[Solver::TYPE_FEATURE][$literal->getPackageId()])) {
  779. $rule = $this->rules[Solver::TYPE_FEATURE][$literal->getPackageId()];
  780. }
  781. if (!$rule || $rule->isDisabled()) {
  782. continue;
  783. }
  784. $decisionQueue = array();
  785. if (!isset($this->noUpdate[$literal->getPackageId()]) && (
  786. $this->decidedRemove($literal->getPackage()) ||
  787. isset($this->updateMap[$literal->getPackageId()]) ||
  788. !$literal->equals($rule->getFirstLiteral())
  789. )) {
  790. foreach ($rule->getLiterals() as $ruleLiteral) {
  791. if ($this->decidedInstall($ruleLiteral->getPackage())) {
  792. // already fulfilled
  793. break;
  794. }
  795. if ($this->undecided($ruleLiteral->getPackage())) {
  796. $decisionQueue[] = $ruleLiteral;
  797. }
  798. }
  799. }
  800. if (sizeof($decisionQueue)) {
  801. $oLevel = $level;
  802. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  803. if (0 === $level) {
  804. return;
  805. }
  806. if ($level <= $oLevel) {
  807. $repeat = true;
  808. }
  809. }
  810. // still undecided? keep package.
  811. if (!$repeat && $this->undecided($literal->getPackage())) {
  812. $oLevel = $level;
  813. if (isset($this->cleanDepsMap[$literal->getPackageId()])) {
  814. // clean deps removes package
  815. $level = $this->setPropagateLearn($level, $literal->invert(), $disableRules, null);
  816. } else {
  817. // ckeeping package
  818. $level = $this->setPropagateLearn($level, $literal, $disableRules, $rule);
  819. }
  820. if (0 === $level) {
  821. return;
  822. }
  823. if ($level <= $oLevel) {
  824. $repeat = true;
  825. }
  826. }
  827. if ($repeat) {
  828. if (1 === $level || $level < $passLevel) {
  829. // trouble
  830. break;
  831. }
  832. if ($level < $oLevel) {
  833. // redo all
  834. $n = 0;
  835. }
  836. // repeat
  837. $i--;
  838. $n--;
  839. continue;
  840. }
  841. }
  842. if ($n < count($this->installedPackages)) {
  843. $installedPos = $i; // retry this problem next time
  844. break;
  845. }
  846. $installedPos = 0;
  847. }
  848. $systemlevel = $level + 1;
  849. if ($pass < 2) {
  850. // had trouble => retry
  851. continue;
  852. }
  853. }
  854. if ($level < $systemLevel) {
  855. $systemLevel = $level;
  856. }
  857. foreach ($this->rules as $ruleType => $rules) {
  858. foreach ($rules as $rule) {
  859. if ($rule->isEnabled()) {
  860. $decisionQueue = array();
  861. }
  862. }
  863. }
  864. //
  865. // /*
  866. // * decide
  867. // */
  868. // POOL_DEBUG(SAT_DEBUG_POLICY, "deciding unresolved rules\n");
  869. // for (i = 1, n = 1; n < solv->nrules; i++, n++)
  870. // {
  871. // if (i == solv->nrules)
  872. // i = 1;
  873. // r = solv->rules + i;
  874. // if (r->d < 0) /* ignore disabled rules */
  875. // continue;
  876. // queue_empty(&dq);
  877. // if (r->d == 0)
  878. // {
  879. // /* binary or unary rule */
  880. // /* need two positive undecided literals */
  881. // if (r->p < 0 || r->w2 <= 0)
  882. // continue;
  883. // if (solv->decisionmap[r->p] || solv->decisionmap[r->w2])
  884. // continue;
  885. // queue_push(&dq, r->p);
  886. // queue_push(&dq, r->w2);
  887. // }
  888. // else
  889. // {
  890. // /* make sure that
  891. // * all negative literals are installed
  892. // * no positive literal is installed
  893. // * i.e. the rule is not fulfilled and we
  894. // * just need to decide on the positive literals
  895. // */
  896. // if (r->p < 0)
  897. // {
  898. // if (solv->decisionmap[-r->p] <= 0)
  899. // continue;
  900. // }
  901. // else
  902. // {
  903. // if (solv->decisionmap[r->p] > 0)
  904. // continue;
  905. // if (solv->decisionmap[r->p] == 0)
  906. // queue_push(&dq, r->p);
  907. // }
  908. // dp = pool->whatprovidesdata + r->d;
  909. // while ((p = *dp++) != 0)
  910. // {
  911. // if (p < 0)
  912. // {
  913. // if (solv->decisionmap[-p] <= 0)
  914. // break;
  915. // }
  916. // else
  917. // {
  918. // if (solv->decisionmap[p] > 0)
  919. // break;
  920. // if (solv->decisionmap[p] == 0)
  921. // queue_push(&dq, p);
  922. // }
  923. // }
  924. // if (p)
  925. // continue;
  926. // }
  927. // IF_POOLDEBUG (SAT_DEBUG_PROPAGATE)
  928. // {
  929. // POOL_DEBUG(SAT_DEBUG_PROPAGATE, "unfulfilled ");
  930. // solver_printruleclass(solv, SAT_DEBUG_PROPAGATE, r);
  931. // }
  932. // /* dq.count < 2 cannot happen as this means that
  933. // * the rule is unit */
  934. // assert(dq.count > 1);
  935. //
  936. // olevel = level;
  937. // level = selectandinstall(solv, level, &dq, disablerules, r - solv->rules);
  938. // if (level == 0)
  939. // {
  940. // queue_free(&dq);
  941. // queue_free(&dqs);
  942. // return;
  943. // }
  944. // if (level < systemlevel || level == 1)
  945. // break; /* trouble */
  946. // /* something changed, so look at all rules again */
  947. // n = 0;
  948. // }
  949. //
  950. // if (n != solv->nrules) /* ran into trouble, restart */
  951. // continue;
  952. //
  953. // /* at this point we have a consistent system. now do the extras... */
  954. //
  955. }
  956. }
  957. }