Move construction of pool from repo set into a pool builder
Pool construction depends on the install request now, so only required packages get loaded, add some structure for future asynchronously loading composer repositoriespull/7625/head
parent
4c7d271a36
commit
c0f19f6c57
|
@ -57,11 +57,6 @@ class DefaultPolicy implements PolicyInterface
|
|||
return $packages;
|
||||
}
|
||||
|
||||
public function getPriority(Pool $pool, PackageInterface $package)
|
||||
{
|
||||
return $pool->getPriority($package->getRepository());
|
||||
}
|
||||
|
||||
public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null)
|
||||
{
|
||||
$packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);
|
||||
|
@ -168,7 +163,7 @@ class DefaultPolicy implements PolicyInterface
|
|||
return 1;
|
||||
}
|
||||
|
||||
return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1;
|
||||
return ($pool->getPriority($a->id) > $pool->getPriority($b->id)) ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,10 +231,10 @@ class DefaultPolicy implements PolicyInterface
|
|||
}
|
||||
|
||||
if (null === $priority) {
|
||||
$priority = $this->getPriority($pool, $package);
|
||||
$priority = $pool->getPriority($package->id);
|
||||
}
|
||||
|
||||
if ($this->getPriority($pool, $package) != $priority) {
|
||||
if ($pool->getPriority($package->id) != $priority) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ use Composer\Repository\PlatformRepository;
|
|||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* A package pool contains repositories that provide packages.
|
||||
* A package pool contains all packages for dependency resolution
|
||||
*
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
|
@ -41,23 +41,18 @@ class Pool implements \Countable
|
|||
const MATCH_REPLACE = 3;
|
||||
const MATCH_FILTERED = 4;
|
||||
|
||||
protected $repositories = array();
|
||||
protected $providerRepos = array();
|
||||
protected $packages = array();
|
||||
protected $packageByName = array();
|
||||
protected $packageByExactName = array();
|
||||
protected $acceptableStabilities;
|
||||
protected $stabilityFlags;
|
||||
protected $priorities = array();
|
||||
protected $versionParser;
|
||||
protected $providerCache = array();
|
||||
protected $filterRequires;
|
||||
protected $whitelist = null;
|
||||
protected $id = 1;
|
||||
|
||||
public function __construct(array $acceptableStabilities, array $stabilityFlags = array(), array $filterRequires = array())
|
||||
public function __construct(array $filterRequires = array())
|
||||
{
|
||||
$this->acceptableStabilities = $acceptableStabilities;
|
||||
$this->stabilityFlags = $stabilityFlags;
|
||||
$this->filterRequires = $filterRequires;
|
||||
$this->versionParser = new VersionParser;
|
||||
}
|
||||
|
@ -68,76 +63,24 @@ class Pool implements \Countable
|
|||
$this->providerCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a repository and its packages to this package pool
|
||||
*
|
||||
* @param RepositoryInterface $repo A package repository
|
||||
* @param array $rootAliases
|
||||
*/
|
||||
public function addRepository(RepositoryInterface $repo, $rootAliases = array())
|
||||
public function setPackages(array $packages, array $priorities = array())
|
||||
{
|
||||
if ($repo instanceof CompositeRepository) {
|
||||
$repos = $repo->getRepositories();
|
||||
} else {
|
||||
$repos = array($repo);
|
||||
}
|
||||
$this->priorities = $priorities;
|
||||
$this->packages = $packages;
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
$this->repositories[] = $repo;
|
||||
foreach ($this->packages as $package) {
|
||||
$names = $package->getNames();
|
||||
$this->packageByExactName[$package->getName()][$package->id] = $package;
|
||||
|
||||
$exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
|
||||
|
||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
||||
$this->providerRepos[] = $repo;
|
||||
$repo->setRootAliases($rootAliases);
|
||||
$repo->resetPackageIds();
|
||||
} else {
|
||||
foreach ($repo->getPackages() as $package) {
|
||||
$names = $package->getNames();
|
||||
$stability = $package->getStability();
|
||||
if ($exempt || $this->isPackageAcceptable($names, $stability)) {
|
||||
$package->setId($this->id++);
|
||||
$this->packages[] = $package;
|
||||
$this->packageByExactName[$package->getName()][$package->id] = $package;
|
||||
|
||||
foreach ($names as $provided) {
|
||||
$this->packageByName[$provided][] = $package;
|
||||
}
|
||||
|
||||
// handle root package aliases
|
||||
$name = $package->getName();
|
||||
if (isset($rootAliases[$name][$package->getVersion()])) {
|
||||
$alias = $rootAliases[$name][$package->getVersion()];
|
||||
if ($package instanceof AliasPackage) {
|
||||
$package = $package->getAliasOf();
|
||||
}
|
||||
$aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
|
||||
$aliasPackage->setRootPackageAlias(true);
|
||||
$aliasPackage->setId($this->id++);
|
||||
|
||||
$package->getRepository()->addPackage($aliasPackage);
|
||||
$this->packages[] = $aliasPackage;
|
||||
$this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;
|
||||
|
||||
foreach ($aliasPackage->getNames() as $name) {
|
||||
$this->packageByName[$name][] = $aliasPackage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($names as $provided) {
|
||||
$this->packageByName[$provided][] = $package;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPriority(RepositoryInterface $repo)
|
||||
public function getPriority($id)
|
||||
{
|
||||
$priority = array_search($repo, $this->repositories, true);
|
||||
|
||||
if (false === $priority) {
|
||||
throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool.");
|
||||
}
|
||||
|
||||
return -$priority;
|
||||
return $this->priorities[$id - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,25 +134,12 @@ class Pool implements \Countable
|
|||
{
|
||||
$candidates = array();
|
||||
|
||||
foreach ($this->providerRepos as $repo) {
|
||||
foreach ($repo->whatProvides($name, $bypassFilters, array($this, 'isPackageAcceptable')) as $candidate) {
|
||||
$candidates[] = $candidate;
|
||||
if ($candidate->id < 1) {
|
||||
$candidate->setId($this->id++);
|
||||
$this->packages[$this->id - 2] = $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($mustMatchName) {
|
||||
$candidates = array_filter($candidates, function ($candidate) use ($name) {
|
||||
return $candidate->getName() == $name;
|
||||
});
|
||||
if (isset($this->packageByExactName[$name])) {
|
||||
$candidates = array_merge($candidates, $this->packageByExactName[$name]);
|
||||
$candidates = $this->packageByExactName[$name];
|
||||
}
|
||||
} elseif (isset($this->packageByName[$name])) {
|
||||
$candidates = array_merge($candidates, $this->packageByName[$name]);
|
||||
$candidates = $this->packageByName[$name];
|
||||
}
|
||||
|
||||
$matches = $provideMatches = array();
|
||||
|
@ -287,23 +217,6 @@ class Pool implements \Countable
|
|||
return $prefix.' '.$package->getPrettyString();
|
||||
}
|
||||
|
||||
public function isPackageAcceptable($name, $stability)
|
||||
{
|
||||
foreach ((array) $name as $n) {
|
||||
// allow if package matches the global stability requirement and has no exception
|
||||
if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow if package matches the package-specific stability flag
|
||||
if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the package matches the given constraint directly or through
|
||||
* provided or replaced packages
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
<?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\AliasPackage;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\AsyncRepositoryInterface;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use Composer\Repository\LockArrayRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
*/
|
||||
class PoolBuilder
|
||||
{
|
||||
private $isPackageAcceptableCallable;
|
||||
private $filterRequires;
|
||||
private $rootAliases;
|
||||
|
||||
private $loadedNames = array();
|
||||
|
||||
private $id = 1;
|
||||
private $packages = array();
|
||||
private $priorities = array();
|
||||
|
||||
public function __construct($isPackageAcceptableCallable, array $filterRequires = array())
|
||||
{
|
||||
$this->isPackageAcceptableCallable = $isPackageAcceptableCallable;
|
||||
$this->filterRequires = $filterRequires;
|
||||
}
|
||||
|
||||
public function buildPool(array $repositories, array $rootAliases, Request $request)
|
||||
{
|
||||
$this->pool = new Pool($this->filterRequires);
|
||||
$this->rootAliases = $rootAliases;
|
||||
|
||||
// TODO do we really want the request here? kind of want a root requirements thingy instead
|
||||
$loadNames = array();
|
||||
foreach ($request->getJobs() as $job) {
|
||||
switch ($job['cmd']) {
|
||||
case 'install':
|
||||
$loadNames[$job['packageName']] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
if ($repository instanceof ComposerRepository && $repository->hasProviders()) {
|
||||
$this->providerRepos[] = $repository;
|
||||
$repository->setRootAliases($this->rootAliases);
|
||||
$repository->resetPackageIds();
|
||||
}
|
||||
}
|
||||
|
||||
while (!empty($loadNames)) {
|
||||
$loadIds = array();
|
||||
foreach ($repositories as $key => $repository) {
|
||||
if ($repository instanceof AsyncRepositoryInterface) {
|
||||
$loadIds[$key] = $repository->requestPackages($loadNames);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($loadNames as $name => $void) {
|
||||
$this->loadedNames[$name] = true;
|
||||
}
|
||||
|
||||
$newLoadNames = array();
|
||||
foreach ($repositories as $key => $repository) {
|
||||
if ($repository instanceof PlatformRepository || $repository instanceof InstalledRepositoryInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($repository instanceof AsyncRepositoryInterface) {
|
||||
$packages = $repository->returnPackages($loadIds[$key]);
|
||||
} else {
|
||||
$packages = $repository->loadPackages($loadNames);
|
||||
}
|
||||
|
||||
foreach ($packages as $package) {
|
||||
if (call_user_func($this->isPackageAcceptableCallable, $package->getNames(), $package->getStability())) {
|
||||
$newLoadNames += $this->loadPackage($package, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$loadNames = $newLoadNames;
|
||||
}
|
||||
|
||||
foreach ($repositories as $key => $repository) {
|
||||
if ($repository instanceof PlatformRepository ||
|
||||
$repository instanceof InstalledRepositoryInterface) {
|
||||
foreach ($repository->getPackages() as $package) {
|
||||
$this->loadPackage($package, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->pool->setPackages($this->packages, $this->priorities);
|
||||
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
private function loadPackage(PackageInterface $package, $repoIndex)
|
||||
{
|
||||
$package->setId($this->id++);
|
||||
$this->packages[] = $package;
|
||||
$this->priorities[$this->id - 2] = -$repoIndex;
|
||||
|
||||
// handle root package aliases
|
||||
$name = $package->getName();
|
||||
if (isset($this->rootAliases[$name][$package->getVersion()])) {
|
||||
$alias = $this->rootAliases[$name][$package->getVersion()];
|
||||
if ($package instanceof AliasPackage) {
|
||||
$package = $package->getAliasOf();
|
||||
}
|
||||
$aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
|
||||
$aliasPackage->setRootPackageAlias(true);
|
||||
$aliasPackage->setId($this->id++);
|
||||
|
||||
$package->getRepository()->addPackage($aliasPackage); // TODO do we need this?
|
||||
$this->packages[] = $aliasPackage;
|
||||
}
|
||||
|
||||
$loadNames = array();
|
||||
foreach ($package->getRequires() as $link) {
|
||||
$require = $link->getTarget();
|
||||
if (!isset($this->loadedNames[$require])) {
|
||||
$loadNames[$require] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $loadNames;
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ class Problem
|
|||
* @param array $installedMap A map of all installed packages
|
||||
* @return string
|
||||
*/
|
||||
public function getPrettyString(array $installedMap = array())
|
||||
public function getPrettyString(array $installedMap = array(), array $learnedPool = array())
|
||||
{
|
||||
$reasons = call_user_func_array('array_merge', array_reverse($this->reasons));
|
||||
|
||||
|
@ -168,7 +168,7 @@ class Problem
|
|||
$messages[] = $this->jobToText($job);
|
||||
} elseif ($rule) {
|
||||
if ($rule instanceof Rule) {
|
||||
$messages[] = $rule->getPrettyString($this->pool, $installedMap);
|
||||
$messages[] = $rule->getPrettyString($this->pool, $installedMap, $learnedPool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ abstract class Rule
|
|||
|
||||
abstract public function isAssertion();
|
||||
|
||||
public function getPrettyString(Pool $pool, array $installedMap = array())
|
||||
public function getPrettyString(Pool $pool, array $installedMap = array(), array $learnedPool = array())
|
||||
{
|
||||
$literals = $this->getLiterals();
|
||||
|
||||
|
@ -230,7 +230,18 @@ abstract class Rule
|
|||
case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
|
||||
return $ruleText;
|
||||
case self::RULE_LEARNED:
|
||||
return 'Conclusion: '.$ruleText;
|
||||
// TODO not sure this is a good idea, most of these rules should be listed in the problem anyway
|
||||
$learnedString = '(learned rule, ';
|
||||
if (isset($learnedPool[$this->reasonData])) {
|
||||
foreach ($learnedPool[$this->reasonData] as $learnedRule) {
|
||||
$learnedString .= $learnedRule->getPrettyString($pool, $installedMap, $learnedPool);
|
||||
}
|
||||
} else {
|
||||
$learnedString .= 'reasoning unavailable';
|
||||
}
|
||||
$learnedString .= ')';
|
||||
|
||||
return 'Conclusion: '.$ruleText.' '.$learnedString;
|
||||
case self::RULE_PACKAGE_ALIAS:
|
||||
return $ruleText;
|
||||
default:
|
||||
|
|
|
@ -244,7 +244,7 @@ class Solver
|
|||
}
|
||||
|
||||
if ($this->problems) {
|
||||
throw new SolverProblemsException($this->problems, $this->installedMap);
|
||||
throw new SolverProblemsException($this->problems, $this->installedMap, $this->learnedPool);
|
||||
}
|
||||
|
||||
$transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);
|
||||
|
|
|
@ -21,11 +21,13 @@ class SolverProblemsException extends \RuntimeException
|
|||
{
|
||||
protected $problems;
|
||||
protected $installedMap;
|
||||
protected $learnedPool;
|
||||
|
||||
public function __construct(array $problems, array $installedMap)
|
||||
public function __construct(array $problems, array $installedMap, array $learnedPool)
|
||||
{
|
||||
$this->problems = $problems;
|
||||
$this->installedMap = $installedMap;
|
||||
$this->learnedPool = $learnedPool;
|
||||
|
||||
parent::__construct($this->createMessage(), 2);
|
||||
}
|
||||
|
@ -35,7 +37,7 @@ class SolverProblemsException extends \RuntimeException
|
|||
$text = "\n";
|
||||
$hasExtensionProblems = false;
|
||||
foreach ($this->problems as $i => $problem) {
|
||||
$text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n";
|
||||
$text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap, $this->learnedPool)."\n";
|
||||
|
||||
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
|
||||
$hasExtensionProblems = true;
|
||||
|
|
|
@ -467,7 +467,7 @@ class Installer
|
|||
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request);
|
||||
|
||||
$pool = $repositorySet->createPool();
|
||||
$pool = $repositorySet->createPool($request);
|
||||
|
||||
// force dev packages to have the latest links if we update or install from a (potentially new) lock
|
||||
$this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links');
|
||||
|
@ -701,7 +701,7 @@ class Installer
|
|||
|
||||
// solve deps to see which get removed
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request);
|
||||
$solver = new Solver($policy, $repositorySet->createPool(), $installedRepo, $this->io);
|
||||
$solver = new Solver($policy, $repositorySet->createPool($request), $installedRepo, $this->io);
|
||||
$ops = $solver->solve($request, $this->ignorePlatformReqs);
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request, $ops);
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@ namespace Composer\Package;
|
|||
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Installer\InstallationManager;
|
||||
use Composer\Repository\LockArrayRepository;
|
||||
use Composer\Repository\RepositoryManager;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Package\Dumper\ArrayDumper;
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Util\Git as GitUtil;
|
||||
|
@ -150,7 +150,7 @@ class Locker
|
|||
public function getLockedRepository($withDevReqs = false)
|
||||
{
|
||||
$lockData = $this->getLockData();
|
||||
$packages = new ArrayRepository();
|
||||
$packages = new LockArrayRepository();
|
||||
|
||||
$lockedPackages = $lockData['packages'];
|
||||
if ($withDevReqs) {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?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\Repository;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Repository interface.
|
||||
*
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
interface AsyncRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @param array $names Names of packages to retrieve data for
|
||||
* @return scalar Id to be passed to later loadPackages call
|
||||
*/
|
||||
public function requestPackages(array $names);
|
||||
|
||||
/**
|
||||
* @param array $names
|
||||
* @return scalar id for load call
|
||||
*/
|
||||
public function returnPackages($loadId);
|
||||
}
|
||||
|
|
@ -24,6 +24,21 @@ use Composer\Package\Link;
|
|||
*/
|
||||
abstract class BaseRepository implements RepositoryInterface
|
||||
{
|
||||
// TODO should this stay here? some repos need a better implementation
|
||||
public function loadPackages(array $packageNameMap)
|
||||
{
|
||||
$packages = $this->getPackages();
|
||||
|
||||
$result = array();
|
||||
foreach ($packages as $package) {
|
||||
if (isset($packageNameMap[$package->getName()])) {
|
||||
$result[] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of links causing the requested needle packages to be installed, as an associative array with the
|
||||
* dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?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\Repository;
|
||||
|
||||
/**
|
||||
* Lock array repository.
|
||||
*
|
||||
* Regular array repository, only uses a different type to identify the lock file as the source of info
|
||||
*
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
*/
|
||||
class LockArrayRepository extends ArrayRepository implements RepositoryInterface
|
||||
{
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ interface RepositoryInterface extends \Countable
|
|||
*/
|
||||
public function findPackages($name, $constraint = null);
|
||||
|
||||
// TODO this should really not be in this generic interface anymore
|
||||
/**
|
||||
* Returns list of registered packages.
|
||||
*
|
||||
|
@ -62,6 +63,14 @@ interface RepositoryInterface extends \Countable
|
|||
*/
|
||||
public function getPackages();
|
||||
|
||||
/**
|
||||
* Returns list of registered packages with the supplied name
|
||||
*
|
||||
* @param bool[] $packageNameMap
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
public function loadPackages(array $packageNameMap);
|
||||
|
||||
/**
|
||||
* Searches the repository for packages containing the query
|
||||
*
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
namespace Composer\Repository;
|
||||
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\PoolBuilder;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
|
@ -31,9 +33,6 @@ class RepositorySet
|
|||
/** @var RepositoryInterface[] */
|
||||
private $repositories = array();
|
||||
|
||||
/** @var ComposerRepository[] */
|
||||
private $providerRepos = array();
|
||||
|
||||
private $acceptableStabilities;
|
||||
private $stabilityFlags;
|
||||
protected $filterRequires;
|
||||
|
@ -79,9 +78,6 @@ class RepositorySet
|
|||
|
||||
foreach ($repos as $repo) {
|
||||
$this->repositories[] = $repo;
|
||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
||||
$this->providerRepos[] = $repo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,20 +129,43 @@ class RepositorySet
|
|||
return $candidates;
|
||||
}
|
||||
|
||||
public function getPriority(RepositoryInterface $repo)
|
||||
{
|
||||
$priority = array_search($repo, $this->repositories, true);
|
||||
|
||||
if (false === $priority) {
|
||||
throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool.");
|
||||
}
|
||||
|
||||
return -$priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pool for dependency resolution from the packages in this repository set.
|
||||
*
|
||||
* @return Pool
|
||||
*/
|
||||
public function createPool()
|
||||
public function createPool(Request $request)
|
||||
{
|
||||
$this->pool = new Pool($this->acceptableStabilities, $this->stabilityFlags, $this->filterRequires);
|
||||
$poolBuilder = new PoolBuilder(array($this, 'isPackageAcceptable'), $this->filterRequires);
|
||||
|
||||
foreach ($this->repositories as $repository) {
|
||||
$this->pool->addRepository($repository, $this->rootAliases);
|
||||
return $this->pool = $poolBuilder->buildPool($this->repositories, $this->rootAliases, $request);
|
||||
}
|
||||
|
||||
// TODO unify this with above in some simpler version without "request"?
|
||||
public function createPoolForPackage($packageName)
|
||||
{
|
||||
return $this->createPoolForPackages(array($packageName));
|
||||
}
|
||||
|
||||
public function createPoolForPackages($packageNames)
|
||||
{
|
||||
$request = new Request();
|
||||
foreach ($packageNames as $packageName) {
|
||||
$request->install($packageName);
|
||||
}
|
||||
|
||||
return $this->pool;
|
||||
return $this->createPool($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@ use Composer\Package\AliasPackage;
|
|||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\TestCase;
|
||||
use http\Env\Request;
|
||||
|
||||
class DefaultPolicyTest extends TestCase
|
||||
{
|
||||
|
@ -47,7 +48,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA->getId());
|
||||
$expected = array($packageA->getId());
|
||||
|
@ -63,7 +64,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA1->getId(), $packageA2->getId());
|
||||
$expected = array($packageA2->getId());
|
||||
|
@ -79,7 +80,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha'));
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA1->getId(), $packageA2->getId());
|
||||
$expected = array($packageA2->getId());
|
||||
|
@ -95,7 +96,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha'));
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA1->getId(), $packageA2->getId());
|
||||
$expected = array($packageA1->getId());
|
||||
|
@ -112,7 +113,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.0'));
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA1->getId(), $packageA2->getId());
|
||||
$expected = array($packageA2->getId());
|
||||
|
@ -129,7 +130,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repositorySet->addRepository($this->repoInstalled);
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA->getId(), $packageAInstalled->getId());
|
||||
$expected = array($packageA->getId());
|
||||
|
@ -150,7 +151,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repositorySet->addRepository($otherRepository);
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA->getId(), $packageAImportant->getId());
|
||||
$expected = array($packageAImportant->getId());
|
||||
|
@ -173,7 +174,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repositorySet->addRepository($repo1);
|
||||
$this->repositorySet->addRepository($repo2);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($package1->getId(), $package2->getId(), $package3->getId(), $package4->getId());
|
||||
$expected = array($package2->getId());
|
||||
|
@ -185,7 +186,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repositorySet->addRepository($repo2);
|
||||
$this->repositorySet->addRepository($repo1);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$expected = array($package4->getId());
|
||||
$selected = $this->policy->selectPreferredPackages($pool, array(), $literals);
|
||||
|
@ -209,7 +210,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repositorySet->addRepository($repoImportant);
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$packages = $pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev'));
|
||||
$literals = array();
|
||||
|
@ -234,7 +235,7 @@ class DefaultPolicyTest extends TestCase
|
|||
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackages(array('A', 'B'));
|
||||
|
||||
$literals = array($packageA->getId(), $packageB->getId());
|
||||
$expected = $literals;
|
||||
|
@ -253,7 +254,7 @@ class DefaultPolicyTest extends TestCase
|
|||
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackages(array('A', 'B'));
|
||||
|
||||
$literals = array($packageA->getId(), $packageB->getId());
|
||||
$expected = $literals;
|
||||
|
@ -274,7 +275,7 @@ class DefaultPolicyTest extends TestCase
|
|||
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackages(array('vendor-a/replacer', 'vendor-b/replacer'));
|
||||
|
||||
$literals = array($packageA->getId(), $packageB->getId());
|
||||
$expected = $literals;
|
||||
|
@ -290,7 +291,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$repositorySet = new RepositorySet(array(), 'dev');
|
||||
$repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackages(array('vendor-a/replacer', 'vendor-b/replacer'));
|
||||
|
||||
$literals = array($packageA->getId(), $packageB->getId());
|
||||
$expected = $literals;
|
||||
|
@ -317,7 +318,7 @@ class DefaultPolicyTest extends TestCase
|
|||
$this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
|
||||
$this->repositorySet->addRepository($this->repo);
|
||||
|
||||
$pool = $this->repositorySet->createPool();
|
||||
$pool = $this->repositorySet->createPoolForPackage('A');
|
||||
|
||||
$literals = array($packageA1->getId(), $packageA2->getId());
|
||||
$expected = array($packageA1->getId());
|
||||
|
|
|
@ -22,90 +22,25 @@ class PoolTest extends TestCase
|
|||
public function testPool()
|
||||
{
|
||||
$pool = $this->createPool();
|
||||
$repo = new ArrayRepository;
|
||||
$package = $this->getPackage('foo', '1');
|
||||
|
||||
$repo->addPackage($package);
|
||||
$pool->addRepository($repo);
|
||||
$pool->setPackages(array($package));
|
||||
|
||||
$this->assertEquals(array($package), $pool->whatProvides('foo'));
|
||||
$this->assertEquals(array($package), $pool->whatProvides('foo'));
|
||||
}
|
||||
|
||||
public function testPoolIgnoresIrrelevantPackages()
|
||||
{
|
||||
$pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE), array('bar' => BasePackage::STABILITY_BETA));
|
||||
$repo = new ArrayRepository;
|
||||
$repo->addPackage($package = $this->getPackage('bar', '1'));
|
||||
$repo->addPackage($betaPackage = $this->getPackage('bar', '1-beta'));
|
||||
$repo->addPackage($alphaPackage = $this->getPackage('bar', '1-alpha'));
|
||||
$repo->addPackage($package2 = $this->getPackage('foo', '1'));
|
||||
$repo->addPackage($rcPackage2 = $this->getPackage('foo', '1rc'));
|
||||
|
||||
$pool->addRepository($repo);
|
||||
|
||||
$this->assertEquals(array($package, $betaPackage), $pool->whatProvides('bar'));
|
||||
$this->assertEquals(array($package2), $pool->whatProvides('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testGetPriorityForNotRegisteredRepository()
|
||||
{
|
||||
$pool = $this->createPool();
|
||||
$repository = new ArrayRepository;
|
||||
|
||||
$pool->getPriority($repository);
|
||||
}
|
||||
|
||||
public function testGetPriorityWhenRepositoryIsRegistered()
|
||||
{
|
||||
$pool = $this->createPool();
|
||||
$firstRepository = new ArrayRepository;
|
||||
$pool->addRepository($firstRepository);
|
||||
$secondRepository = new ArrayRepository;
|
||||
$pool->addRepository($secondRepository);
|
||||
|
||||
$firstPriority = $pool->getPriority($firstRepository);
|
||||
$secondPriority = $pool->getPriority($secondRepository);
|
||||
|
||||
$this->assertEquals(0, $firstPriority);
|
||||
$this->assertEquals(-1, $secondPriority);
|
||||
}
|
||||
|
||||
public function testWhatProvidesSamePackageForDifferentRepositories()
|
||||
{
|
||||
$pool = $this->createPool();
|
||||
$firstRepository = new ArrayRepository;
|
||||
$secondRepository = new ArrayRepository;
|
||||
|
||||
$firstPackage = $this->getPackage('foo', '1');
|
||||
$secondPackage = $this->getPackage('foo', '1');
|
||||
$thirdPackage = $this->getPackage('foo', '2');
|
||||
|
||||
$firstRepository->addPackage($firstPackage);
|
||||
$secondRepository->addPackage($secondPackage);
|
||||
$secondRepository->addPackage($thirdPackage);
|
||||
|
||||
$pool->addRepository($firstRepository);
|
||||
$pool->addRepository($secondRepository);
|
||||
|
||||
$this->assertEquals(array($firstPackage, $secondPackage, $thirdPackage), $pool->whatProvides('foo'));
|
||||
}
|
||||
|
||||
public function testWhatProvidesPackageWithConstraint()
|
||||
{
|
||||
$pool = $this->createPool();
|
||||
$repository = new ArrayRepository;
|
||||
|
||||
$firstPackage = $this->getPackage('foo', '1');
|
||||
$secondPackage = $this->getPackage('foo', '2');
|
||||
|
||||
$repository->addPackage($firstPackage);
|
||||
$repository->addPackage($secondPackage);
|
||||
|
||||
$pool->addRepository($repository);
|
||||
$pool->setPackages(array(
|
||||
$firstPackage,
|
||||
$secondPackage,
|
||||
));
|
||||
|
||||
$this->assertEquals(array($firstPackage, $secondPackage), $pool->whatProvides('foo'));
|
||||
$this->assertEquals(array($secondPackage), $pool->whatProvides('foo', $this->getVersionConstraint('==', '2')));
|
||||
|
@ -114,11 +49,9 @@ class PoolTest extends TestCase
|
|||
public function testPackageById()
|
||||
{
|
||||
$pool = $this->createPool();
|
||||
$repository = new ArrayRepository;
|
||||
$package = $this->getPackage('foo', '1');
|
||||
|
||||
$repository->addPackage($package);
|
||||
$pool->addRepository($repository);
|
||||
$pool->setPackages(array($package));
|
||||
|
||||
$this->assertSame($package, $pool->packageById(1));
|
||||
}
|
||||
|
|
|
@ -22,13 +22,6 @@ use Composer\TestCase;
|
|||
|
||||
class RuleSetTest extends TestCase
|
||||
{
|
||||
protected $pool;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE));
|
||||
}
|
||||
|
||||
public function testAdd()
|
||||
{
|
||||
$rules = array(
|
||||
|
@ -146,9 +139,11 @@ class RuleSetTest extends TestCase
|
|||
|
||||
public function testPrettyString()
|
||||
{
|
||||
$repo = new ArrayRepository;
|
||||
$repo->addPackage($p = $this->getPackage('foo', '2.1'));
|
||||
$this->pool->addRepository($repo);
|
||||
$pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE));
|
||||
$pool->setPackages(array(
|
||||
$p = $this->getPackage('foo', '2.1'),
|
||||
));
|
||||
$p->setId(1);
|
||||
|
||||
$ruleSet = new RuleSet;
|
||||
$literal = $p->getId();
|
||||
|
@ -156,7 +151,7 @@ class RuleSetTest extends TestCase
|
|||
|
||||
$ruleSet->add($rule, RuleSet::TYPE_JOB);
|
||||
|
||||
$this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($this->pool));
|
||||
$this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($pool));
|
||||
}
|
||||
|
||||
private function getRuleMock()
|
||||
|
|
|
@ -22,13 +22,6 @@ use Composer\TestCase;
|
|||
|
||||
class RuleTest extends TestCase
|
||||
{
|
||||
protected $pool;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE));
|
||||
}
|
||||
|
||||
public function testGetHash()
|
||||
{
|
||||
$rule = new GenericRule(array(123), Rule::RULE_JOB_INSTALL, null);
|
||||
|
@ -100,13 +93,16 @@ class RuleTest extends TestCase
|
|||
|
||||
public function testPrettyString()
|
||||
{
|
||||
$repo = new ArrayRepository;
|
||||
$repo->addPackage($p1 = $this->getPackage('foo', '2.1'));
|
||||
$repo->addPackage($p2 = $this->getPackage('baz', '1.1'));
|
||||
$this->pool->addRepository($repo);
|
||||
$pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE));
|
||||
$pool->setPackages(array(
|
||||
$p1 = $this->getPackage('foo', '2.1'),
|
||||
$p2 = $this->getPackage('baz', '1.1'),
|
||||
));
|
||||
$p1->setId(1);
|
||||
$p2->setId(2);
|
||||
|
||||
$rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_JOB_INSTALL, null);
|
||||
|
||||
$this->assertEquals('Install command rule (don\'t install baz 1.1|install foo 2.1)', $rule->getPrettyString($this->pool));
|
||||
$this->assertEquals('Install command rule (don\'t install baz 1.1|install foo 2.1)', $rule->getPrettyString($pool));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\DependencyResolver\Request;
|
|||
use Composer\DependencyResolver\Solver;
|
||||
use Composer\DependencyResolver\SolverProblemsException;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Repository\InstalledArrayRepository;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\TestCase;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
|
@ -31,12 +32,13 @@ class SolverTest extends TestCase
|
|||
protected $repoInstalled;
|
||||
protected $request;
|
||||
protected $policy;
|
||||
protected $solver;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->repoSet = new RepositorySet(array());
|
||||
$this->repo = new ArrayRepository;
|
||||
$this->repoInstalled = new ArrayRepository;
|
||||
$this->repoInstalled = new InstalledArrayRepository;
|
||||
|
||||
$this->request = new Request($this->repoSet);
|
||||
$this->policy = new DefaultPolicy;
|
||||
|
@ -71,6 +73,7 @@ class SolverTest extends TestCase
|
|||
|
||||
$this->request->install('B', $this->getVersionConstraint('==', '1'));
|
||||
|
||||
$this->createSolver();
|
||||
try {
|
||||
$transaction = $this->solver->solve($this->request);
|
||||
$this->fail('Unsolvable conflict did not result in exception.');
|
||||
|
@ -94,8 +97,6 @@ class SolverTest extends TestCase
|
|||
$this->repoSet->addRepository($repo1);
|
||||
$this->repoSet->addRepository($repo2);
|
||||
|
||||
$this->solver = new Solver($this->policy, $this->repoSet->createPool(), $this->repoInstalled, new NullIO());
|
||||
|
||||
$this->request->install('foo');
|
||||
|
||||
$this->checkSolverResult(array(
|
||||
|
@ -447,6 +448,7 @@ class SolverTest extends TestCase
|
|||
|
||||
// must explicitly pick the provider, so error in this case
|
||||
$this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
|
||||
$this->createSolver();
|
||||
$this->solver->solve($this->request);
|
||||
}
|
||||
|
||||
|
@ -480,6 +482,7 @@ class SolverTest extends TestCase
|
|||
$this->request->install('A');
|
||||
|
||||
$this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
|
||||
$this->createSolver();
|
||||
$this->solver->solve($this->request);
|
||||
}
|
||||
|
||||
|
@ -652,6 +655,7 @@ class SolverTest extends TestCase
|
|||
|
||||
$this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
|
||||
|
||||
$this->createSolver();
|
||||
$this->solver->solve($this->request);
|
||||
}
|
||||
|
||||
|
@ -668,6 +672,7 @@ class SolverTest extends TestCase
|
|||
$this->request->install('A');
|
||||
$this->request->install('B');
|
||||
|
||||
$this->createSolver();
|
||||
try {
|
||||
$transaction = $this->solver->solve($this->request);
|
||||
$this->fail('Unsolvable conflict did not result in exception.');
|
||||
|
@ -697,6 +702,7 @@ class SolverTest extends TestCase
|
|||
|
||||
$this->request->install('A');
|
||||
|
||||
$this->createSolver();
|
||||
try {
|
||||
$transaction = $this->solver->solve($this->request);
|
||||
$this->fail('Unsolvable conflict did not result in exception.');
|
||||
|
@ -744,6 +750,7 @@ class SolverTest extends TestCase
|
|||
|
||||
$this->request->install('A');
|
||||
|
||||
$this->createSolver();
|
||||
try {
|
||||
$transaction = $this->solver->solve($this->request);
|
||||
$this->fail('Unsolvable conflict did not result in exception.');
|
||||
|
@ -843,12 +850,16 @@ class SolverTest extends TestCase
|
|||
{
|
||||
$this->repoSet->addRepository($this->repoInstalled);
|
||||
$this->repoSet->addRepository($this->repo);
|
||||
}
|
||||
|
||||
$this->solver = new Solver($this->policy, $this->repoSet->createPool(), $this->repoInstalled, new NullIO());
|
||||
protected function createSolver()
|
||||
{
|
||||
$this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), $this->repoInstalled, new NullIO());
|
||||
}
|
||||
|
||||
protected function checkSolverResult(array $expected)
|
||||
{
|
||||
$this->createSolver();
|
||||
$transaction = $this->solver->solve($this->request);
|
||||
|
||||
$result = array();
|
||||
|
|
|
@ -30,7 +30,6 @@ use Symfony\Component\Console\Output\StreamOutput;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Composer\TestCase;
|
||||
use Composer\IO\BufferIO;
|
||||
|
||||
class InstallerTest extends TestCase
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue