1
0
Fork 0

Deduplicate link instances between versions of a given package

pull/7904/head
Jordi Boggiano 2018-12-04 11:20:35 +01:00
parent e753bf08b1
commit e8c6948770
3 changed files with 230 additions and 112 deletions

View File

@ -38,6 +38,70 @@ class ArrayLoader implements LoaderInterface
}
public function load(array $config, $class = 'Composer\Package\CompletePackage')
{
$package = $this->createObject($config, $class);
foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
if (isset($config[$type])) {
$method = 'set'.ucfirst($opts['method']);
$package->{$method}(
$this->parseLinks(
$package->getName(),
$package->getPrettyVersion(),
$opts['description'],
$config[$type]
)
);
}
}
$package = $this->configureObject($package, $config);
return $package;
}
public function loadPackages(array $versions, $class)
{
static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
$packages = array();
$linkCache = array();
foreach ($versions as $version) {
if (isset($version['versions'])) {
$baseVersion = $version;
foreach ($uniqKeys as $key) {
unset($baseVersion[$key.'s']);
}
foreach ($version['versions'] as $index => $dummy) {
$unpackedVersion = $baseVersion;
foreach ($uniqKeys as $key) {
$unpackedVersion[$key] = $version[$key.'s'][$index];
}
$package = $this->createObject($unpackedVersion, $class);
$this->configureCachedLinks($linkCache, $package, $unpackedVersion);
$package = $this->configureObject($package, $unpackedVersion);
$packages[] = $package;
}
} else {
$package = $this->createObject($version, $class);
$this->configureCachedLinks($linkCache, $package, $version);
$package = $this->configureObject($package, $version);
$packages[] = $package;
}
}
return $packages;
}
private function createObject(array $config, $class)
{
if (!isset($config['name'])) {
throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
@ -52,7 +116,12 @@ class ArrayLoader implements LoaderInterface
} else {
$version = $this->versionParser->normalize($config['version']);
}
$package = new $class($config['name'], $version, $config['version']);
return new $class($config['name'], $version, $config['version']);
}
private function configureObject($package, array $config)
{
$package->setType(isset($config['type']) ? strtolower($config['type']) : 'library');
if (isset($config['target-dir'])) {
@ -109,20 +178,6 @@ class ArrayLoader implements LoaderInterface
}
}
foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
if (isset($config[$type])) {
$method = 'set'.ucfirst($opts['method']);
$package->{$method}(
$this->parseLinks(
$package->getName(),
$package->getPrettyVersion(),
$opts['description'],
$config[$type]
)
);
}
}
if (isset($config['suggest']) && is_array($config['suggest'])) {
foreach ($config['suggest'] as $target => $reason) {
if ('self.version' === trim($reason)) {
@ -202,21 +257,50 @@ class ArrayLoader implements LoaderInterface
}
}
if ($aliasNormalized = $this->getBranchAlias($config)) {
if ($package instanceof RootPackageInterface) {
$package = new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
} else {
$package = new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
}
}
if ($this->loadOptions && isset($config['transport-options'])) {
$package->setTransportOptions($config['transport-options']);
}
if ($aliasNormalized = $this->getBranchAlias($config)) {
if ($package instanceof RootPackageInterface) {
return new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
}
return new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
}
return $package;
}
private function configureCachedLinks(&$linkCache, $package, array $config)
{
$name = $package->getName();
$prettyVersion = $package->getPrettyVersion();
foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
if (isset($config[$type])) {
$method = 'set'.ucfirst($opts['method']);
$links = array();
foreach ($config[$type] as $prettyTarget => $constraint) {
$target = strtolower($prettyTarget);
if ($constraint === 'self.version') {
$links[$target] = $this->createLink($name, $prettyVersion, $opts['description'], $target, $constraint);
} else {
if (!isset($linkCache[$name][$type][$target][$constraint])) {
$linkCache[$name][$type][$target][$constraint] = array($target, $this->createLink($name, $prettyVersion, $opts['description'], $target, $constraint));
}
list($target, $link) = $linkCache[$name][$type][$target][$constraint];
$links[$target] = $link;
}
}
$package->{$method}($links);
}
}
}
/**
* @param string $source source package name
* @param string $sourceVersion source package version (pretty version ideally)
@ -228,21 +312,26 @@ class ArrayLoader implements LoaderInterface
{
$res = array();
foreach ($links as $target => $constraint) {
if (!is_string($constraint)) {
throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($constraint) . ' (' . var_export($constraint, true) . ')');
}
if ('self.version' === $constraint) {
$parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
} else {
$parsedConstraint = $this->versionParser->parseConstraints($constraint);
}
$res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint);
$res[strtolower($target)] = $this->createLink($source, $sourceVersion, $description, $target, $constraint);
}
return $res;
}
private function createLink($source, $sourceVersion, $description, $target, $prettyConstraint)
{
if (!is_string($prettyConstraint)) {
throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')');
}
if ('self.version' === $prettyConstraint) {
$parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
} else {
$parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint);
}
return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint);
}
/**
* Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
*

View File

@ -61,7 +61,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
private $rootData;
private $hasPartialPackages;
private $partialPackagesByName;
private $versionParser;
/**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @private
*/
public $versionParser;
public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null)
{
@ -414,6 +418,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->providers[$name] = array();
foreach ($packages['packages'] as $versions) {
$versionsToLoad = array();
foreach ($versions as $version) {
if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) {
continue;
@ -440,40 +445,44 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
continue;
}
// load acceptable packages in the providers
$package = $this->createPackage($version, 'Composer\Package\CompletePackage');
$package->setRepository($this);
$versionsToLoad[] = $version;
}
}
if ($package instanceof AliasPackage) {
$aliased = $package->getAliasOf();
$aliased->setRepository($this);
// load acceptable packages in the providers
$loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage');
foreach ($loadedPackages as $package) {
$package->setRepository($this);
$this->providers[$name][$version['uid']] = $aliased;
$this->providers[$name][$version['uid'].'-alias'] = $package;
if ($package instanceof AliasPackage) {
$aliased = $package->getAliasOf();
$aliased->setRepository($this);
// override provider with its alias so it can be expanded in the if block above
$this->providersByUid[$version['uid']] = $package;
} else {
$this->providers[$name][$version['uid']] = $package;
$this->providersByUid[$version['uid']] = $package;
}
$this->providers[$name][$version['uid']] = $aliased;
$this->providers[$name][$version['uid'].'-alias'] = $package;
// handle root package aliases
unset($rootAliasData);
// override provider with its alias so it can be expanded in the if block above
$this->providersByUid[$version['uid']] = $package;
} else {
$this->providers[$name][$version['uid']] = $package;
$this->providersByUid[$version['uid']] = $package;
}
if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) {
$rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()];
} elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) {
$rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()];
}
// handle root package aliases
unset($rootAliasData);
if (isset($rootAliasData)) {
$alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']);
$alias->setRepository($this);
if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) {
$rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()];
} elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) {
$rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()];
}
$this->providers[$name][$version['uid'].'-root'] = $alias;
$this->providersByUid[$version['uid'].'-root'] = $alias;
}
if (isset($rootAliasData)) {
$alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']);
$alias->setRepository($this);
$this->providers[$name][$version['uid'].'-root'] = $alias;
$this->providersByUid[$version['uid'].'-root'] = $alias;
}
}
}
@ -501,8 +510,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$repoData = $this->loadDataFromServer();
foreach ($repoData as $package) {
$this->addPackage($this->createPackage($package, 'Composer\Package\CompletePackage'));
foreach ($this->createPackages($repoData, 'Composer\Package\CompletePackage') as $package) {
$this->addPackage($package);
}
}
@ -545,6 +554,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->asyncFetchFile($url, $cacheKey, $lastModified)
->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) {
static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
if (true === $response) {
$response = $contents;
}
@ -553,24 +564,37 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
return;
}
$uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
$versionsToLoad = array();
foreach ($response['packages'][$name] as $version) {
if (isset($version['versions'])) {
$baseVersion = $version;
foreach ($uniqKeys as $key) {
unset($baseVersion[$key.'s']);
}
foreach ($version['versions'] as $index => $dummy) {
$unpackedVersion = $baseVersion;
foreach ($uniqKeys as $key) {
$unpackedVersion[$key] = $version[$key.'s'][$index];
if (isset($version['version_normalizeds'])) {
foreach ($version['version_normalizeds'] as $index => $normalizedVersion) {
if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) {
foreach ($uniqKeys as $key) {
unset($version[$key.'s'][$index]);
}
}
$repo->createPackageIfAcceptable($packages, $isPackageAcceptableCallable, $unpackedVersion, $constraint);
}
if (count($version['version_normalizeds'])) {
$versionsToLoad[] = $version;
}
} else {
$repo->createPackageIfAcceptable($packages, $isPackageAcceptableCallable, $version, $constraint);
if (!isset($version['version_normalized'])) {
$version['version_normalized'] = $repo->versionParser->normalize($version['version']);
}
if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) {
$versionsToLoad[] = $version;
}
}
}
$loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage');
foreach ($loadedPackages as $package) {
$package->setRepository($this);
$packages[spl_object_hash($package)] = $package;
if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
}
}
}, function ($e) {
@ -592,27 +616,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
*
* @private
*/
public function createPackageIfAcceptable(&$packages, $isPackageAcceptableCallable, $version, $constraint)
public function isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $versionNormalized)
{
if (!call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) {
return;
if (!call_user_func($isPackageAcceptableCallable, strtolower($name), VersionParser::parseStability($versionNormalized))) {
return false;
}
if (isset($version['version_normalized']) && $constraint && !$constraint->matches(new Constraint('==', $version['version_normalized']))) {
return;
if ($constraint && !$constraint->matches(new Constraint('==', $versionNormalized))) {
return false;
}
// load acceptable packages in the providers
$package = $this->createPackage($version, 'Composer\Package\CompletePackage');
$package->setRepository($this);
// if there was no version_normalized, then we need to check now for the constraint
if (!$constraint || isset($version['version_normalized']) || $constraint->matches(new Constraint('==', $package->getVersion()))) {
$packages[spl_object_hash($package)] = $package;
if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
}
}
return true;
}
protected function loadRootServerFile()
@ -775,23 +789,37 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
return $packages;
}
protected function createPackage(array $data, $class = 'Composer\Package\CompletePackage')
/**
* TODO v3 should make this private once we can drop PHP 5.3 support
*
* @private
*/
public function createPackages(array $packages, $class = 'Composer\Package\CompletePackage')
{
if (!$packages) {
return;
}
try {
if (!isset($data['notification-url'])) {
$data['notification-url'] = $this->notifyUrl;
foreach ($packages as &$data) {
if (!isset($data['notification-url'])) {
$data['notification-url'] = $this->notifyUrl;
}
}
$package = $this->loader->load($data, $class);
if (isset($this->sourceMirrors[$package->getSourceType()])) {
$package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]);
}
$package->setDistMirrors($this->distMirrors);
$this->configurePackageTransportOptions($package);
$packages = $this->loader->loadPackages($packages, $class);
return $package;
foreach ($packages as $package) {
if (isset($this->sourceMirrors[$package->getSourceType()])) {
$package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]);
}
$package->setDistMirrors($this->distMirrors);
$this->configurePackageTransportOptions($package);
}
return $packages;
} catch (\Exception $e) {
throw new \RuntimeException('Could not load package '.(isset($data['name']) ? $data['name'] : json_encode($data)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
throw new \RuntimeException('Could not load packages '.(isset($packages[0]['name']) ? $packages[0]['name'] : json_encode($packages)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
}
}

View File

@ -32,7 +32,7 @@ class ComposerRepositoryTest extends TestCase
);
$repository = $this->getMockBuilder('Composer\Repository\ComposerRepository')
->setMethods(array('loadRootServerFile', 'createPackage'))
->setMethods(array('loadRootServerFile', 'createPackages'))
->setConstructorArgs(array(
$repoConfig,
new NullIO,
@ -47,16 +47,17 @@ class ComposerRepositoryTest extends TestCase
->method('loadRootServerFile')
->will($this->returnValue($repoPackages));
$stubs = array();
foreach ($expected as $at => $arg) {
$stubPackage = $this->getPackage('stub/stub', '1.0.0');
$repository
->expects($this->at($at + 2))
->method('createPackage')
->with($this->identicalTo($arg), $this->equalTo('Composer\Package\CompletePackage'))
->will($this->returnValue($stubPackage));
$stubs[] = $this->getPackage('stub/stub', '1.0.0');
}
$repository
->expects($this->at(2))
->method('createPackages')
->with($this->identicalTo($expected), $this->equalTo('Composer\Package\CompletePackage'))
->will($this->returnValue($stubs));
// Triggers initialization
$packages = $repository->getPackages();