Эх сурвалжийг харах

New Multi Conflict Rule for transitive conflicts, to reduce memory

Nils Adermann 6 жил өмнө
parent
commit
ed300b9f22

+ 99 - 0
src/Composer/DependencyResolver/MultiConflictRule.php

@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+use Composer\Package\PackageInterface;
+use Composer\Package\Link;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ *
+ * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C])
+ */
+class MultiConflictRule extends Rule
+{
+    protected $literals;
+
+    /**
+     * @param array                 $literals
+     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
+     * @param Link|PackageInterface $reasonData
+     * @param array                 $job        The job this rule was created from
+     */
+    public function __construct(array $literals, $reason, $reasonData, $job = null)
+    {
+        parent::__construct($reason, $reasonData, $job);
+
+        // sort all packages ascending by id
+        sort($literals);
+
+        $this->literals = $literals;
+    }
+
+    public function getLiterals()
+    {
+        return $this->literals;
+    }
+
+    public function getHash()
+    {
+        $data = unpack('ihash', md5('c:'.implode(',', $this->literals), true));
+
+        return $data['hash'];
+    }
+
+    /**
+     * Checks if this rule is equal to another one
+     *
+     * Ignores whether either of the rules is disabled.
+     *
+     * @param  Rule $rule The rule to check against
+     * @return bool Whether the rules are equal
+     */
+    public function equals(Rule $rule)
+    {
+        return $this->literals === $rule->getLiterals();
+    }
+
+    public function isAssertion()
+    {
+        return false;
+    }
+
+    public function disable()
+    {
+        throw new \RuntimeException("can't disable conflict rule");
+    }
+
+    /**
+     * Formats a rule as a string of the format (Literal1|Literal2|...)
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        // TODO multi conflict?
+        $result = $this->isDisabled() ? 'disabled(multi(' : '(multi(';
+
+        foreach ($this->literals as $i => $literal) {
+            if ($i != 0) {
+                $result .= '|';
+            }
+            $result .= $literal;
+        }
+
+        $result .= '))';
+
+        return $result;
+    }
+}

+ 29 - 3
src/Composer/DependencyResolver/RuleSetGenerator.php

@@ -30,6 +30,7 @@ class RuleSetGenerator
     protected $conflictAddedMap;
     protected $addedPackages;
     protected $addedPackagesByNames;
+    protected $conflictsForName;
 
     public function __construct(PolicyInterface $policy, Pool $pool)
     {
@@ -128,6 +129,16 @@ class RuleSetGenerator
         return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
     }
 
+    protected function createMultiConflictRule(array $packages, $reason, $reasonData = null)
+    {
+        $literals = array();
+        foreach ($packages as $package) {
+            $literals[] = -$package->id;
+        }
+
+        return new MultiConflictRule($literals, $reason, $reasonData);
+    }
+
     /**
      * Adds a rule unless it duplicates an existing one of any type
      *
@@ -189,9 +200,16 @@ class RuleSetGenerator
 
                 if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
                     $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
-                } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
-                    $reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
-                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
+                } else {
+                    if (!isset($this->conflictsForName[$packageName])) {
+                        $this->conflictsForName[$packageName] = array();
+                    }
+                    if (!$package instanceof AliasPackage) {
+                        $this->conflictsForName[$packageName][$package->id] = $package;
+                    }
+                    if (!$provider instanceof AliasPackage) {
+                        $this->conflictsForName[$packageName][$provider->id] = $provider;
+                    }
                 }
             }
         }
@@ -240,6 +258,13 @@ class RuleSetGenerator
                 }
             }
         }
+
+        foreach ($this->conflictsForName as $name => $packages) {
+            if (count($packages) > 1) {
+                $reason = Rule::RULE_PACKAGE_SAME_NAME;
+                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, null));
+            }
+        }
     }
 
     protected function obsoleteImpossibleForAlias($package, $provider)
@@ -316,6 +341,7 @@ class RuleSetGenerator
         $this->conflictAddedMap = array();
         $this->addedPackages = array();
         $this->addedPackagesByNames = array();
+        $this->conflictsForName = array();
 
         $this->addRulesForRequest($request, $ignorePlatformReqs);
 

+ 45 - 21
src/Composer/DependencyResolver/RuleWatchGraph.php

@@ -44,13 +44,24 @@ class RuleWatchGraph
             return;
         }
 
-        foreach (array($node->watch1, $node->watch2) as $literal) {
-            if (!isset($this->watchChains[$literal])) {
-                $this->watchChains[$literal] = new RuleWatchChain;
+        if (!$node->getRule() instanceof MultiConflictRule) {
+            foreach (array($node->watch1, $node->watch2) as $literal) {
+                if (!isset($this->watchChains[$literal])) {
+                    $this->watchChains[$literal] = new RuleWatchChain;
+                }
+
+                $this->watchChains[$literal]->unshift($node);
             }
+        } else {
+            foreach ($node->getRule()->getLiterals() as $literal) {
+                if (!isset($this->watchChains[$literal])) {
+                    $this->watchChains[$literal] = new RuleWatchChain;
+                }
 
-            $this->watchChains[$literal]->unshift($node);
+                $this->watchChains[$literal]->unshift($node);
+            }
         }
+
     }
 
     /**
@@ -92,28 +103,41 @@ class RuleWatchGraph
         $chain->rewind();
         while ($chain->valid()) {
             $node = $chain->current();
-            $otherWatch = $node->getOtherWatch($literal);
+            if (!$node->getRule() instanceof MultiConflictRule) {
+                $otherWatch = $node->getOtherWatch($literal);
 
-            if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
-                $ruleLiterals = $node->getRule()->getLiterals();
+                if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
+                    $ruleLiterals = $node->getRule()->getLiterals();
 
-                $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
-                    return $literal !== $ruleLiteral &&
-                        $otherWatch !== $ruleLiteral &&
-                        !$decisions->conflict($ruleLiteral);
-                });
+                    $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
+                        return $literal !== $ruleLiteral &&
+                            $otherWatch !== $ruleLiteral &&
+                            !$decisions->conflict($ruleLiteral);
+                    });
 
-                if ($alternativeLiterals) {
-                    reset($alternativeLiterals);
-                    $this->moveWatch($literal, current($alternativeLiterals), $node);
-                    continue;
-                }
+                    if ($alternativeLiterals) {
+                        reset($alternativeLiterals);
+                        $this->moveWatch($literal, current($alternativeLiterals), $node);
+                        continue;
+                    }
 
-                if ($decisions->conflict($otherWatch)) {
-                    return $node->getRule();
-                }
+                    if ($decisions->conflict($otherWatch)) {
+                        return $node->getRule();
+                    }
 
-                $decisions->decide($otherWatch, $level, $node->getRule());
+                    $decisions->decide($otherWatch, $level, $node->getRule());
+                }
+            } else {
+                // check isDisabled?
+                foreach ($node->getRule()->getLiterals() as $otherLiteral) {
+                    if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) {
+                        if ($decisions->conflict($otherLiteral)) {
+                            return $node->getRule();
+                        }
+
+                        $decisions->decide($otherLiteral, $level, $node->getRule());
+                    }
+                }
             }
 
             $chain->next();

+ 1 - 1
src/Composer/DependencyResolver/RuleWatchNode.php

@@ -55,7 +55,7 @@ class RuleWatchNode
         $literals = $this->rule->getLiterals();
 
         // if there are only 2 elements, both are being watched anyway
-        if (count($literals) < 3) {
+        if (count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
             return;
         }