From ee2834a16933470386fe95243537715ddada60ac Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Sat, 9 Jun 2012 16:48:04 +1100 Subject: [PATCH 01/10] Add Mock for RemoteFileSystem --- .../Test/Mock/RemoteFilesystemMock.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/Composer/Test/Mock/RemoteFilesystemMock.php diff --git a/tests/Composer/Test/Mock/RemoteFilesystemMock.php b/tests/Composer/Test/Mock/RemoteFilesystemMock.php new file mode 100644 index 000000000..118232359 --- /dev/null +++ b/tests/Composer/Test/Mock/RemoteFilesystemMock.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Mock; + +use Composer\Util\RemoteFilesystem; + +/** + * Remote filesystem mock + */ +class RemoteFilesystemMock extends RemoteFilesystem +{ + /** + * @param array $contentMap associative array of locations and content + */ + public function __construct(array $contentMap) + { + $this->contentMap = $contentMap; + } + + public function getContents($originUrl, $fileUrl, $progress = true) + { + if(!empty($this->contentMap[$fileUrl])) + return $this->contentMap[$fileUrl]; + + throw new \Composer\Downloader\TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404); + } + +} From e173f11b372883449564145b43e45fbabe6e10a6 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Sat, 9 Jun 2012 16:48:52 +1100 Subject: [PATCH 02/10] Add PEAR channel reader & Update PearRepository to use it. --- .../Repository/Pear/BaseChannelReader.php | 81 ++++ src/Composer/Repository/Pear/ChannelInfo.php | 67 +++ .../Repository/Pear/ChannelReader.php | 200 ++++++++ .../Repository/Pear/ChannelRest10Reader.php | 164 +++++++ .../Repository/Pear/ChannelRest11Reader.php | 136 ++++++ .../Repository/Pear/DependencyConstraint.php | 60 +++ .../Repository/Pear/DependencyInfo.php | 50 ++ .../Pear/PackageDependencyParser.php | 312 +++++++++++++ src/Composer/Repository/Pear/PackageInfo.php | 94 ++++ src/Composer/Repository/Pear/ReleaseInfo.php | 50 ++ src/Composer/Repository/PearRepository.php | 439 +++++------------- .../Test/Mock/RemoteFilesystemMock.php | 6 +- .../Repository/Pear/ChannelReaderTest.php | 144 ++++++ .../Pear/ChannelRest10ReaderTest.php | 41 ++ .../Pear/ChannelRest11ReaderTest.php | 37 ++ .../Fixtures/DependencyParserTestData.json | 167 +++++++ .../Rest1.0/http_client_allreleases.xml | 9 + .../Rest1.0/http_client_deps.1.2.1.txt | 1 + .../Fixtures/Rest1.0/http_client_info.xml | 14 + .../Rest1.0/http_request_allreleases.xml | 9 + .../Rest1.0/http_request_deps.1.4.0.txt | 1 + .../Fixtures/Rest1.0/http_request_info.xml | 12 + .../Pear/Fixtures/Rest1.0/packages.xml | 6 + .../Pear/Fixtures/Rest1.1/categories.xml | 5 + .../Pear/Fixtures/Rest1.1/packagesinfo.xml | 97 ++++ .../Repository/Pear/Fixtures/channel.1.0.xml | 12 + .../Repository/Pear/Fixtures/channel.1.1.xml | 12 + .../Pear/PackageDependencyParserTest.php | 58 +++ .../Test/Repository/PearRepositoryTest.php | 28 +- 29 files changed, 1970 insertions(+), 342 deletions(-) create mode 100644 src/Composer/Repository/Pear/BaseChannelReader.php create mode 100644 src/Composer/Repository/Pear/ChannelInfo.php create mode 100644 src/Composer/Repository/Pear/ChannelReader.php create mode 100644 src/Composer/Repository/Pear/ChannelRest10Reader.php create mode 100644 src/Composer/Repository/Pear/ChannelRest11Reader.php create mode 100644 src/Composer/Repository/Pear/DependencyConstraint.php create mode 100644 src/Composer/Repository/Pear/DependencyInfo.php create mode 100644 src/Composer/Repository/Pear/PackageDependencyParser.php create mode 100644 src/Composer/Repository/Pear/PackageInfo.php create mode 100644 src/Composer/Repository/Pear/ReleaseInfo.php create mode 100644 tests/Composer/Test/Repository/Pear/ChannelReaderTest.php create mode 100644 tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php create mode 100644 tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml create mode 100644 tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml create mode 100644 tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php new file mode 100644 index 000000000..fa6944ed5 --- /dev/null +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -0,0 +1,81 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Util\RemoteFilesystem; + +/** + * Base PEAR Channel reader. + * + * Provides xml namespaces and red + * + * @author Alexey Prilipko + */ +abstract class BaseChannelReader +{ + /** + * PEAR REST Interface namespaces + */ + const channelNS = 'http://pear.php.net/channel-1.0'; + const allCategoriesNS = 'http://pear.php.net/dtd/rest.allcategories'; + const categoryPackagesInfoNS = 'http://pear.php.net/dtd/rest.categorypackageinfo'; + const allPackagesNS = 'http://pear.php.net/dtd/rest.allpackages'; + const allReleasesNS = 'http://pear.php.net/dtd/rest.allreleases'; + const packageInfoNS = 'http://pear.php.net/dtd/rest.package'; + + /** @var RemoteFilesystem */ + private $rfs; + + protected function __construct($rfs) + { + $this->rfs = $rfs; + } + + /** + * Read content from remote filesystem. + * + * @param $origin string server + * @param $path string relative path to content + * @return \SimpleXMLElement + */ + protected function requestContent($origin, $path) + { + $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); + $content = $this->rfs->getContents($origin, $url, false); + if (!$content) { + throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); + } + + return $content; + } + + /** + * Read xml content from remote filesystem + * + * @param $origin string server + * @param $path string relative path to content + * @return \SimpleXMLElement + */ + protected function requestXml($origin, $path) + { + // http://components.ez.no/p/packages.xml is malformed. to read it we must ignore parsing errors. + $xml = simplexml_load_string($this->requestContent($origin, $path), "SimpleXMLElement", LIBXML_NOERROR); + + if (false == $xml) { + $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); + throw new \UnexpectedValueException('The PEAR channel at '.$origin.' is broken.'); + } + + return $xml; + } +} diff --git a/src/Composer/Repository/Pear/ChannelInfo.php b/src/Composer/Repository/Pear/ChannelInfo.php new file mode 100644 index 000000000..69e33b887 --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelInfo.php @@ -0,0 +1,67 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR channel info + * + * @author Alexey Prilipko + */ +class ChannelInfo +{ + private $name; + private $alias; + private $packages; + + /** + * @param string $name + * @param string $alias + * @param PackageInfo[] $packages + */ + public function __construct($name, $alias, array $packages) + { + $this->name = $name; + $this->alias = $alias; + $this->packages = $packages; + } + + /** + * Name of the channel + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Alias of the channel + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * List of channel packages + * + * @return PackageInfo[] + */ + public function getPackages() + { + return $this->packages; + } +} diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php new file mode 100644 index 000000000..54bdc39fa --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -0,0 +1,200 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Util\RemoteFilesystem; +use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Package\Link; + +/** + * PEAR Channel package reader. + * + * Reads channel packages info from and builds MemoryPackage's + * + * @author Alexey Prilipko + */ +class ChannelReader extends BaseChannelReader +{ + /** @var array of ('xpath test' => 'rest implementation') */ + private $readerMap; + + public function __construct(RemoteFilesystem $rfs) + { + parent::__construct($rfs); + + $rest10reader = new ChannelRest10Reader($rfs); + $rest11reader = new ChannelRest11Reader($rfs); + + $this->readerMap = array( + 'REST1.3' => $rest11reader, + 'REST1.2' => $rest11reader, + 'REST1.1' => $rest11reader, + 'REST1.0' => $rest10reader, + ); + } + + /** + * Reads channel supported REST interfaces and selects one of them + * + * @param $channelXml \SimpleXMLElement + * @param $supportedVersions string[] supported PEAR REST protocols + * @return array|bool hash with selected version and baseUrl + */ + private function selectRestVersion($channelXml, $supportedVersions) + { + $channelXml->registerXPathNamespace('ns', self::channelNS); + + foreach ($supportedVersions as $version) { + $xpathTest = "ns:servers/ns:primary/ns:rest/ns:baseurl[@type='{$version}']"; + $testResult = $channelXml->xpath($xpathTest); + if (count($testResult) > 0) { + return array('version' => $version, 'baseUrl' => (string) $testResult[0]); + } + } + + return false; + } + + /** + * Reads PEAR channel through REST interface and builds list of packages + * + * @param $url string PEAR Channel url + * @return ChannelInfo + */ + public function read($url) + { + $xml = $this->requestXml($url, "/channel.xml"); + + $channelName = (string) $xml->name; + $channelSummary = (string) $xml->summary; + $channelAlias = (string) $xml->suggestedalias; + + $supportedVersions = array_keys($this->readerMap); + $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions); + if (false === $selectedRestVersion) { + throw new \UnexpectedValueException(sprintf('PEAR repository $s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions))); + } + + $reader = $this->readerMap[$selectedRestVersion['version']]; + $packageDefinitions = $reader->read($selectedRestVersion['baseUrl']); + + return new ChannelInfo($channelName, $channelAlias, $packageDefinitions); + } + + /** + * Builds MemoryPackages from PEAR package definition data. + * + * @param $channelName string channel name + * @param $channelAlias string channel alias + * @param $packageDefinitions PackageInfo[] package definition + * @return array + */ + private function buildComposerPackages($channelName, $channelAlias, $packageDefinitions) + { + $versionParser = new \Composer\Package\Version\VersionParser(); + $result = array(); + foreach ($packageDefinitions as $packageDefinition) { + foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { + $normalizedVersion = $this->parseVersion($version); + if (false === $normalizedVersion) { + continue; // skip packages with unparsable versions + } + + $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); + + // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text() + $distUrl = "http://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz"; + + $requires = array(); + $suggests = array(); + $conflicts = array(); + $replaces = array(); + + // alias package only when its channel matches repository channel, + // cause we've know only repository channel alias + if ($channelName == $packageDefinition->getChannelName()) { + $composerPackageAlias = $this->buildComposerPackageName($channelAlias, $packageDefinition->getPackageName()); + $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $aliasLink = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); + $replaces[] = $aliasLink; + } + + $dependencyInfo = $releaseInfo->getDependencyInfo(); + foreach ($dependencyInfo->getRequires() as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint()); + $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint()); + switch ($dependencyConstraint->getType()) { + case 'required': + $requires[] = $link; + break; + case 'conflicts': + $conflicts[] = $link; + break; + case 'replaces': + $replaces[] = $link; + break; + } + } + + foreach ($dependencyInfo->getOptionals() as $groupName => $dependencyConstraints) { + foreach ($dependencyConstraints as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $suggests[$groupName.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint(); + } + } + + $package = new \Composer\Package\MemoryPackage($composerPackageName, $normalizedVersion, $version); + $package->setType('library'); + $package->setDescription($packageDefinition->getDescription()); + $package->setDistType('pear'); + $package->setDistUrl($distUrl); + $package->setAutoload(array('classmap' => array(''))); + $package->setIncludePaths(array('/')); + $package->setRequires($requires); + $package->setConflicts($conflicts); + $package->setSuggests($suggests); + $package->setReplaces($replaces); + $result[] = $package; + } + } + + return $result; + } + + private function buildComposerPackageName($pearChannelName, $pearPackageName) + { + if ($pearChannelName == 'php') { + return "php"; + } + if ($pearChannelName == 'ext') { + return "ext-{$pearPackageName}"; + } + + return "pear-{$pearChannelName}/{$pearPackageName}"; + } + + protected function parseVersion($version) + { + if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { + $version = $matches[1] + .(!empty($matches[2]) ? $matches[2] : '.0') + .(!empty($matches[3]) ? $matches[3] : '.0') + .(!empty($matches[4]) ? $matches[4] : '.0'); + + return $version; + } else { + return false; + } + } +} diff --git a/src/Composer/Repository/Pear/ChannelRest10Reader.php b/src/Composer/Repository/Pear/ChannelRest10Reader.php new file mode 100644 index 000000000..fb5b91957 --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelRest10Reader.php @@ -0,0 +1,164 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Downloader\TransportException; + +/** + * Read PEAR packages using REST 1.0 interface + * + * At version 1.0 package descriptions read from: + * {baseUrl}/p/packages.xml + * {baseUrl}/p/{package}/info.xml + * {baseUrl}/p/{package}/allreleases.xml + * {baseUrl}/p/{package}/deps.{version}.txt + * + * @author Alexey Prilipko + */ +class ChannelRest10Reader extends BaseChannelReader +{ + private $dependencyReader; + + public function __construct($rfs) + { + parent::__construct($rfs); + + $this->dependencyReader = new PackageDependencyParser(); + } + + /** + * Reads package descriptions using PEAR Rest 1.0 interface + * + * @param $baseUrl string base Url interface + * + * @return PackageInfo[] + */ + public function read($baseUrl) + { + return $this->readPackages($baseUrl); + } + + /** + * Read list of packages from + * {baseUrl}/p/packages.xml + * + * @param $baseUrl string + * @return PackageInfo[] + */ + private function readPackages($baseUrl) + { + $result = array(); + + $xmlPath = '/p/packages.xml'; + $xml = $this->requestXml($baseUrl, $xmlPath); + $xml->registerXPathNamespace('ns', self::allPackagesNS); + foreach ($xml->xpath('ns:p') as $node) { + $packageName = (string) $node; + $packageInfo = $this->readPackage($baseUrl, $packageName); + $result[] = $packageInfo; + } + + return $result; + } + + /** + * Read package info from + * {baseUrl}/p/{package}/info.xml + * + * @param $baseUrl string + * @param $packageName string + * @return PackageInfo + */ + private function readPackage($baseUrl, $packageName) + { + $xmlPath = '/p/' . strtolower($packageName) . '/info.xml'; + $xml = $this->requestXml($baseUrl, $xmlPath); + $xml->registerXPathNamespace('ns', self::packageInfoNS); + + $channelName = (string) $xml->c; + $packageName = (string) $xml->n; + $license = (string) $xml->l; + $shortDescription = (string) $xml->s; + $description = (string) $xml->d; + + return new PackageInfo( + $channelName, + $packageName, + $license, + $shortDescription, + $description, + $this->readPackageReleases($baseUrl, $packageName) + ); + } + + /** + * Read package releases from + * {baseUrl}/p/{package}/allreleases.xml + * + * @param $baseUrl string + * @param $packageName string + * @return ReleaseInfo[] hash array with keys as version numbers + */ + private function readPackageReleases($baseUrl, $packageName) + { + $result = array(); + + try { + $xmlPath = '/r/' . strtolower($packageName) . '/allreleases.xml'; + $xml = $this->requestXml($baseUrl, $xmlPath); + $xml->registerXPathNamespace('ns', self::allReleasesNS); + foreach ($xml->xpath('ns:r') as $node) { + $releaseVersion = (string) $node->v; + $releaseStability = (string) $node->s; + + try { + $result[$releaseVersion] = new ReleaseInfo( + $releaseStability, + $this->readPackageReleaseDependencies($baseUrl, $packageName, $releaseVersion) + ); + } catch (TransportException $exception) { + if ($exception->getCode() != 404) { + throw $exception; + } + } + } + } catch (TransportException $exception) { + if ($exception->getCode() != 404) { + throw $exception; + } + } + + return $result; + } + + /** + * Read package dependencies from + * {baseUrl}/p/{package}/deps.{version}.txt + * + * @param $baseUrl string + * @param $packageName string + * @param $version string + * @return DependencyInfo[] + */ + private function readPackageReleaseDependencies($baseUrl, $packageName, $version) + { + $dependencyReader = new PackageDependencyParser(); + + $depthPath = '/r/' . strtolower($packageName) . '/deps.' . $version . '.txt'; + $content = $this->requestContent($baseUrl, $depthPath); + $dependencyArray = unserialize($content); + $result = $dependencyReader->buildDependencyInfo($dependencyArray); + + return $result; + } +} diff --git a/src/Composer/Repository/Pear/ChannelRest11Reader.php b/src/Composer/Repository/Pear/ChannelRest11Reader.php new file mode 100644 index 000000000..90f988b59 --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelRest11Reader.php @@ -0,0 +1,136 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * Read PEAR packages using REST 1.1 interface + * + * At version 1.1 package descriptions read from: + * {baseUrl}/c/categories.xml + * {baseUrl}/c/{category}/packagesinfo.xml + * + * @author Alexey Prilipko + */ +class ChannelRest11Reader extends BaseChannelReader +{ + private $dependencyReader; + + public function __construct($rfs) + { + parent::__construct($rfs); + + $this->dependencyReader = new PackageDependencyParser(); + } + + /** + * Reads package descriptions using PEAR Rest 1.1 interface + * + * @param $baseUrl string base Url interface + * + * @return PackageInfo[] + */ + public function read($baseUrl) + { + return $this->readChannelPackages($baseUrl); + } + + /** + * Read list of channel categories from + * {baseUrl}/c/categories.xml + * + * @param $baseUrl string + * @return PackageInfo[] + */ + private function readChannelPackages($baseUrl) + { + $result = array(); + + $xml = $this->requestXml($baseUrl, "/c/categories.xml"); + $xml->registerXPathNamespace('ns', self::allCategoriesNS); + foreach ($xml->xpath('ns:c') as $node) { + $categoryName = (string) $node; + $categoryPackages = $this->readCategoryPackages($baseUrl, $categoryName); + $result = array_merge($result, $categoryPackages); + } + + return $result; + } + + /** + * Read packages from + * {baseUrl}/c/{category}/packagesinfo.xml + * + * @param $baseUrl string + * @param $categoryName string + * @return PackageInfo[] + */ + private function readCategoryPackages($baseUrl, $categoryName) + { + $result = array(); + + $categoryPath = '/c/'.urlencode($categoryName).'/packagesinfo.xml'; + $xml = $this->requestXml($baseUrl, $categoryPath); + $xml->registerXPathNamespace('ns', self::categoryPackagesInfoNS); + foreach ($xml->xpath('ns:pi') as $node) { + $packageInfo = $this->parsePackage($node); + $result[] = $packageInfo; + } + + return $result; + } + + /** + * Parses package node. + * + * @param $packageInfo \SimpleXMLElement xml element describing package + * @return PackageInfo + */ + private function parsePackage($packageInfo) + { + $packageInfo->registerXPathNamespace('ns', self::categoryPackagesInfoNS); + $channelName = (string) $packageInfo->p->c; + $packageName = (string) $packageInfo->p->n; + $license = (string) $packageInfo->p->l; + $shortDescription = (string) $packageInfo->p->s; + $description = (string) $packageInfo->p->d; + + $dependencies = array(); + foreach ($packageInfo->xpath('ns:deps') as $node) { + $dependencyVersion = (string) $node->v; + $dependencyArray = unserialize((string) $node->d); + + $dependencyInfo = $this->dependencyReader->buildDependencyInfo($dependencyArray); + + $dependencies[$dependencyVersion] = $dependencyInfo; + } + + $releases = array(); + foreach ($packageInfo->xpath('ns:a/ns:r') as $node) { + $releaseVersion = (string) $node->v; + $releaseStability = (string) $node->s; + $releases[$releaseVersion] = new ReleaseInfo( + $releaseStability, + isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array()) + ); + } + + return new PackageInfo( + $channelName, + $packageName, + $license, + $shortDescription, + $description, + $releases + ); + } +} diff --git a/src/Composer/Repository/Pear/DependencyConstraint.php b/src/Composer/Repository/Pear/DependencyConstraint.php new file mode 100644 index 000000000..13a790026 --- /dev/null +++ b/src/Composer/Repository/Pear/DependencyConstraint.php @@ -0,0 +1,60 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR package release dependency info + * + * @author Alexey Prilipko + */ +class DependencyConstraint +{ + private $type; + private $constraint; + private $channelName; + private $packageName; + + /** + * @param string $type + * @param string $constraint + * @param string $channelName + * @param string $packageName + */ + public function __construct($type, $constraint, $channelName, $packageName) + { + $this->type = $type; + $this->constraint = $constraint; + $this->channelName = $channelName; + $this->packageName = $packageName; + } + + public function getChannelName() + { + return $this->channelName; + } + + public function getConstraint() + { + return $this->constraint; + } + + public function getPackageName() + { + return $this->packageName; + } + + public function getType() + { + return $this->type; + } +} diff --git a/src/Composer/Repository/Pear/DependencyInfo.php b/src/Composer/Repository/Pear/DependencyInfo.php new file mode 100644 index 000000000..c6b266e37 --- /dev/null +++ b/src/Composer/Repository/Pear/DependencyInfo.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR package release dependency info + * + * @author Alexey Prilipko + */ +class DependencyInfo +{ + private $requires; + private $optionals; + + /** + * @param DependencyConstraint[] $requires list of requires/conflicts/replaces + * @param array $optionals [groupName => DependencyConstraint[]] list of optional groups + */ + public function __construct($requires, $optionals) + { + $this->requires = $requires; + $this->optionals = $optionals; + } + + /** + * @return DependencyConstraint[] list of requires/conflicts/replaces + */ + public function getRequires() + { + return $this->requires; + } + + /** + * @return array [groupName => DependencyConstraint[]] list of optional groups + */ + public function getOptionals() + { + return $this->optionals; + } +} diff --git a/src/Composer/Repository/Pear/PackageDependencyParser.php b/src/Composer/Repository/Pear/PackageDependencyParser.php new file mode 100644 index 000000000..6902facac --- /dev/null +++ b/src/Composer/Repository/Pear/PackageDependencyParser.php @@ -0,0 +1,312 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * Read PEAR packages using REST 1.0 interface + * + * @author Alexey Prilipko + */ +class PackageDependencyParser +{ + /** + * Builds dependency information from package.xml 1.0 format + * + * http://pear.php.net/manual/en/guide.developers.package2.dependencies.php + * + * package.xml 1.0 format consists of array of + * { type="php|os|sapi|ext|pkg" rel="has|not|eq|ge|gt|le|lt" optional="yes" + * channel="channelName" name="extName|packageName" } + * + * @param $depArray array Dependency data in package.xml 1.0 format + * @return DependencyConstraint[] + */ + private function buildDependency10Info($depArray) + { + static $dep10toOperatorMap = array('has'=>'==', 'eq' => '==', 'ge' => '>=', 'gt' => '>', 'le' => '<=', 'lt' => '<', 'not' => '!='); + + $result = array(); + + foreach ($depArray as $depItem) { + if (empty($depItem['rel']) || !array_key_exists($depItem['rel'], $dep10toOperatorMap)) { + // 'unknown rel type:' . $depItem['rel']; + continue; + } + + $depType = !empty($depItem['optional']) && 'yes' == $depItem['optional'] + ? 'optional' + : 'required'; + $depType = 'not' == $depItem['rel'] + ? 'conflicts' + : $depType; + + $depVersion = !empty($depItem['version']) ? $this->parseVersion($depItem['version']) : '*'; + + // has & not are special operators that does not requires version + $depVersionConstraint = ('has' == $depItem['rel'] || 'not' == $depItem['rel']) && '*' == $depVersion + ? '*' + : $dep10toOperatorMap[$depItem['rel']] . $depVersion; + + switch ($depItem['type']) { + case 'php': + $depChannelName = 'php'; + $depPackageName = ''; + break; + case 'pkg': + $depChannelName = !empty($depItem['channel']) ? $depItem['channel'] : 'pear.php.net'; + $depPackageName = $depItem['name']; + break; + case 'ext': + $depChannelName = 'ext'; + $depPackageName = $depItem['name']; + break; + case 'os': + case 'sapi': + $depChannelName = ''; + $depPackageName = ''; + break; + default: + $depChannelName = ''; + $depPackageName = ''; + break; + } + + if ('' != $depChannelName) { + $result[] = new DependencyConstraint( + $depType, + $depVersionConstraint, + $depChannelName, + $depPackageName + ); + } + } + + return $result; + } + + /** + * Builds dependency information from package.xml 2.0 format + * + * @param $depArray array Dependency data in package.xml 1.0 format + * @return DependencyInfo + */ + private function buildDependency20Info($depArray) + { + $result = array(); + $optionals = array(); + $defaultOptionals = array(); + foreach ($depArray as $depType => $depTypeGroup) { + if (!is_array($depTypeGroup)) { + continue; + } + if ('required' == $depType || 'optional' == $depType) { + foreach ($depTypeGroup as $depItemType => $depItem) { + switch ($depItemType) { + case 'php': + $result[] = new DependencyConstraint( + $depType, + $this->parse20VersionConstraint($depItem), + 'php', + '' + ); + break; + case 'package': + $deps = $this->buildDepPackageConstraints($depItem, $depType); + $result = array_merge($result, $deps); + break; + case 'extension': + $deps = $this->buildDepExtensionConstraints($depItem, $depType); + $result = array_merge($result, $deps); + break; + case 'subpackage': + $deps = $this->buildDepPackageConstraints($depItem, 'replaces'); + $defaultOptionals += $deps; + break; + case 'os': + case 'pearinstaller': + break; + default: + break; + } + } + } elseif ('group' == $depType) { + if ($this->isHash($depTypeGroup)) { + $depTypeGroup = array($depTypeGroup); + } + + foreach ($depTypeGroup as $depItem) { + $groupName = $depItem['attribs']['name']; + if (!isset($optionals[$groupName])) { + $optionals[$groupName] = array(); + } + + if (isset($depItem['subpackage'])) { + $optionals[$groupName] += $this->buildDepPackageConstraints($depItem['subpackage'], 'replaces'); + } else { + $result += $this->buildDepPackageConstraints($depItem['package'], 'optional'); + } + } + } + } + + if (count($defaultOptionals) > 0) { + $optionals['*'] = $defaultOptionals; + } + + return new DependencyInfo($result, $optionals); + } + + /** + * Builds dependency constraint of 'extension' type + * + * @param $depItem array dependency constraint or array of dependency constraints + * @param $depType string target type of building constraint. + * @return DependencyConstraint[] + */ + private function buildDepExtensionConstraints($depItem, $depType) + { + if ($this->isHash($depItem)) { + $depItem = array($depItem); + } + + $result = array(); + foreach ($depItem as $subDepItem) { + $depChannelName = 'ext'; + $depPackageName = $subDepItem['name']; + $depVersionConstraint = $this->parse20VersionConstraint($subDepItem); + + $result[] = new DependencyConstraint( + $depType, + $depVersionConstraint, + $depChannelName, + $depPackageName + ); + } + + return $result; + } + + /** + * Builds dependency constraint of 'package' type + * + * @param $depItem array dependency constraint or array of dependency constraints + * @param $depType string target type of building constraint. + * @return DependencyConstraint[] + */ + private function buildDepPackageConstraints($depItem, $depType) + { + if ($this->isHash($depItem)) { + $depItem = array($depItem); + } + + $result = array(); + foreach ($depItem as $subDepItem) { + $depChannelName = $subDepItem['channel']; + $depPackageName = $subDepItem['name']; + $depVersionConstraint = $this->parse20VersionConstraint($subDepItem); + if (isset($subDepItem['conflicts'])) { + $depType = 'conflicts'; + } + + $result[] = new DependencyConstraint( + $depType, + $depVersionConstraint, + $depChannelName, + $depPackageName + ); + } + + return $result; + } + + /** + * Builds dependency information. It detects used package.xml format. + * + * @param $depArray array + * @return DependencyInfo + */ + public function buildDependencyInfo($depArray) + { + if (!is_array($depArray)) { + return new DependencyInfo(array(), array()); + } elseif (!$this->isHash($depArray)) { + return new DependencyInfo($this->buildDependency10Info($depArray), array()); + } else { + return $this->buildDependency20Info($depArray); + } + } + + /** + * Parses version constraint + * + * @param array $data array containing serveral 'min', 'max', 'has', 'exclude' and other keys. + * @return string + */ + private function parse20VersionConstraint(array $data) + { + static $dep20toOperatorMap = array('has'=>'==', 'min' => '>=', 'max' => '<=', 'exclude' => '!='); + + $versions = array(); + $values = array_intersect_key($data, $dep20toOperatorMap); + if (0 == count($values)) { + return '*'; + } elseif (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) { + $versions[] = '>' . $this->parseVersion($values['min']); + } elseif (isset($values['max']) && isset($values['exclude']) && $data['max'] == $data['exclude']) { + $versions[] = '<' . $this->parseVersion($values['max']); + } else { + foreach ($values as $op => $version) { + if ('exclude' == $op && is_array($version)) { + foreach ($version as $versionPart) { + $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($versionPart); + } + } else { + $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($version); + } + } + } + + return implode(',', $versions); + } + + /** + * Softened version parser + * + * @param $version + * @return bool|string + */ + private function parseVersion($version) + { + if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { + $version = $matches[1] + .(!empty($matches[2]) ? $matches[2] : '.0') + .(!empty($matches[3]) ? $matches[3] : '.0') + .(!empty($matches[4]) ? $matches[4] : '.0'); + + return $version; + } else { + return false; + } + } + + /** + * Test if array is associative or hash type + * + * @param array $array + * @return bool + */ + private function isHash(array $array) + { + return !array_key_exists(1, $array) && !array_key_exists(0, $array); + } +} diff --git a/src/Composer/Repository/Pear/PackageInfo.php b/src/Composer/Repository/Pear/PackageInfo.php new file mode 100644 index 000000000..5fd5956d3 --- /dev/null +++ b/src/Composer/Repository/Pear/PackageInfo.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR Package info + * + * @author Alexey Prilipko + */ +class PackageInfo +{ + private $channelName; + private $packageName; + private $license; + private $shortDescription; + private $description; + private $releases; + + /** + * @param string $channelName + * @param string $packageName + * @param string $license + * @param string $shortDescription + * @param string $description + * @param ReleaseInfo[] $releases associative array maps release version to release info + */ + public function __construct($channelName, $packageName, $license, $shortDescription, $description, $releases) + { + $this->channelName = $channelName; + $this->packageName = $packageName; + $this->license = $license; + $this->shortDescription = $shortDescription; + $this->description = $description; + $this->releases = $releases; + } + + /** + * @return string the package channel name + */ + public function getChannelName() + { + return $this->channelName; + } + + /** + * @return string the package name + */ + public function getPackageName() + { + return $this->packageName; + } + + /** + * @return string the package description + */ + public function getDescription() + { + return $this->description; + } + + /** + * @return string the package short escription + */ + public function getShortDescription() + { + return $this->shortDescription; + } + + /** + * @return string the package licence + */ + public function getLicense() + { + return $this->license; + } + + /** + * @return ReleaseInfo[] + */ + public function getReleases() + { + return $this->releases; + } +} diff --git a/src/Composer/Repository/Pear/ReleaseInfo.php b/src/Composer/Repository/Pear/ReleaseInfo.php new file mode 100644 index 000000000..4ad984d80 --- /dev/null +++ b/src/Composer/Repository/Pear/ReleaseInfo.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR package release info + * + * @author Alexey Prilipko + */ +class ReleaseInfo +{ + private $stability; + private $dependencyInfo; + + /** + * @param string $stability + * @param DependencyInfo $dependencies + */ + public function __construct($stability, $dependencyInfo) + { + $this->stability = $stability; + $this->dependencyInfo = $dependencyInfo; + } + + /** + * @return DependencyInfo release dependencies + */ + public function getDependencyInfo() + { + return $this->dependencyInfo; + } + + /** + * @return string release stability + */ + public function getStability() + { + return $this->stability; + } +} diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index fb4b64736..22045b863 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -13,26 +13,33 @@ namespace Composer\Repository; use Composer\IO\IOInterface; -use Composer\Package\Loader\ArrayLoader; +use Composer\Package\MemoryPackage; +use Composer\Repository\Pear\ChannelInfo; +use Composer\Package\Link; +use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Util\RemoteFilesystem; -use Composer\Json\JsonFile; use Composer\Config; -use Composer\Downloader\TransportException; /** + * Builds list of package from PEAR channel. + * + * Packages read from channel are named as 'pear-{channelName}/{packageName}' + * and has aliased as 'pear-{channelAlias}/{packageName}' + * * @author Benjamin Eberlei * @author Jordi Boggiano */ class PearRepository extends ArrayRepository { - private static $channelNames = array(); - private $url; - private $baseUrl; - private $channel; private $io; private $rfs; + /** @var string vendor makes additional alias for each channel as {prefix}/{packagename}. It allows smoother + * package transition to composer-like repositories. + */ + private $vendorAlias; + public function __construct(array $repoConfig, IOInterface $io, Config $config, RemoteFilesystem $rfs = null) { if (!preg_match('{^https?://}', $repoConfig['url'])) { @@ -44,9 +51,9 @@ class PearRepository extends ArrayRepository } $this->url = rtrim($repoConfig['url'], '/'); - $this->channel = !empty($repoConfig['channel']) ? $repoConfig['channel'] : null; $this->io = $io; $this->rfs = $rfs ?: new RemoteFilesystem($this->io); + $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null; } protected function initialize() @@ -54,341 +61,127 @@ class PearRepository extends ArrayRepository parent::initialize(); $this->io->write('Initializing PEAR repository '.$this->url); - $this->initializeChannel(); - $this->io->write('Packages names will be prefixed with: pear-'.$this->channel.'/'); - // try to load as a composer repo + $reader = new \Composer\Repository\Pear\ChannelReader($this->rfs); try { - $json = new JsonFile($this->url.'/packages.json', new RemoteFilesystem($this->io)); - $packages = $json->read(); - - if ($this->io->isVerbose()) { - $this->io->write('Repository is Composer-compatible, loading via packages.json instead of PEAR protocol'); - } - - $loader = new ArrayLoader(); - foreach ($packages as $data) { - foreach ($data['versions'] as $rev) { - if (strpos($rev['name'], 'pear-'.$this->channel) !== 0) { - $rev['name'] = 'pear-'.$this->channel.'/'.$rev['name']; - } - $this->addPackage($loader->load($rev)); - if ($this->io->isVerbose()) { - $this->io->write('Loaded '.$rev['name'].' '.$rev['version']); - } - } - } - - return; + $channelInfo = $reader->read($this->url); } catch (\Exception $e) { + $this->io->write('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); + return; } - - $this->fetchFromServer(); - } - - protected function initializeChannel() - { - $channelXML = $this->requestXml($this->url . "/channel.xml"); - if (!$this->channel) { - $this->channel = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue - ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue; - } - if (!$this->baseUrl) { - $this->baseUrl = $channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue - ? trim($channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue, '/') - : $this->url . '/rest'; - } - - self::$channelNames[$channelXML->getElementsByTagName("name")->item(0)->nodeValue] = $this->channel; - } - - protected function fetchFromServer() - { - $categoryXML = $this->requestXml($this->baseUrl . "/c/categories.xml"); - $categories = $categoryXML->getElementsByTagName("c"); - - foreach ($categories as $category) { - $link = $this->baseUrl . '/c/' . str_replace(' ', '+', $category->nodeValue); - try { - $packagesLink = $link . "/packagesinfo.xml"; - $this->fetchPear2Packages($packagesLink); - } catch (TransportException $e) { - if (false === strpos($e->getMessage(), '404')) { - throw $e; - } - $categoryLink = $link . "/packages.xml"; - $this->fetchPearPackages($categoryLink); - } - - } - } - - /** - * @param string $categoryLink - * @throws TransportException - * @throws \InvalidArgumentException - */ - private function fetchPearPackages($categoryLink) - { - $packagesXML = $this->requestXml($categoryLink); - $packages = $packagesXML->getElementsByTagName('p'); - $loader = new ArrayLoader(); + $packages = $this->buildComposerPackages($channelInfo); foreach ($packages as $package) { - $packageName = $package->nodeValue; - $fullName = 'pear-'.$this->channel.'/'.$packageName; - - $releaseLink = $this->baseUrl . "/r/" . $packageName; - $allReleasesLink = $releaseLink . "/allreleases2.xml"; - - try { - $releasesXML = $this->requestXml($allReleasesLink); - } catch (TransportException $e) { - if (strpos($e->getMessage(), '404')) { - continue; - } - throw $e; - } - - $releases = $releasesXML->getElementsByTagName('r'); - - foreach ($releases as $release) { - /* @var $release \DOMElement */ - $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue; - - $packageData = array( - 'name' => $fullName, - 'type' => 'library', - 'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"), - 'version' => $pearVersion, - 'autoload' => array( - 'classmap' => array(''), - ), - 'include-path' => array('/'), - ); - - try { - $deps = $this->rfs->getContents($this->url, $releaseLink . "/deps.".$pearVersion.".txt", false); - } catch (TransportException $e) { - if (strpos($e->getMessage(), '404')) { - continue; - } - throw $e; - } - - $packageData += $this->parseDependencies($deps); - - try { - $this->addPackage($loader->load($packageData)); - if ($this->io->isVerbose()) { - $this->io->write('Loaded '.$packageData['name'].' '.$packageData['version']); - } - } catch (\UnexpectedValueException $e) { - if ($this->io->isVerbose()) { - $this->io->write('Could not load '.$packageData['name'].' '.$packageData['version'].': '.$e->getMessage()); - } - continue; - } - } + $this->addPackage($package); } } /** - * @param array $data - * @return string + * Builds MemoryPackages from PEAR package definition data. + * + * @param ChannelInfo $channelInfo + * @return MemoryPackage */ - private function parseVersion(array $data) + private function buildComposerPackages(ChannelInfo $channelInfo) { - if (!isset($data['min']) && !isset($data['max'])) { - return '*'; - } - $versions = array(); - if (isset($data['min'])) { - $versions[] = '>=' . $data['min']; - } - if (isset($data['max'])) { - $versions[] = '<=' . $data['max']; + $versionParser = new \Composer\Package\Version\VersionParser(); + $result = array(); + foreach ($channelInfo->getPackages() as $packageDefinition) { + foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { + $normalizedVersion = $this->parseVersion($version); + if (false === $normalizedVersion) { + continue; // skip packages with unparsable versions + } + + $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); + + // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text() + // but this location is 'de-facto' standard + $distUrl = "http://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz"; + + $requires = array(); + $suggests = array(); + $conflicts = array(); + $replaces = array(); + + // alias package only when its channel matches repository channel, + // cause we've know only repository channel alias + if ($channelInfo->getName() == $packageDefinition->getChannelName()) { + $composerPackageAlias = $this->buildComposerPackageName($channelInfo->getAlias(), $packageDefinition->getPackageName()); + $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); + } + + // alias package with user-specified prefix. it makes private pear channels looks like composer's. + if (!empty($this->vendorAlias)) { + $composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}"; + $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); + } + + foreach ($releaseInfo->getDependencyInfo()->getRequires() as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint()); + $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint()); + switch ($dependencyConstraint->getType()) { + case 'required': + $requires[] = $link; + break; + case 'conflicts': + $conflicts[] = $link; + break; + case 'replaces': + $replaces[] = $link; + break; + } + } + + foreach ($releaseInfo->getDependencyInfo()->getOptionals() as $group => $dependencyConstraints) { + foreach ($dependencyConstraints as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $suggests[$group.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint(); + } + } + + $package = new MemoryPackage($composerPackageName, $normalizedVersion, $version); + $package->setType('library'); + $package->setDescription($packageDefinition->getDescription()); + $package->setDistType('pear'); + $package->setDistUrl($distUrl); + $package->setAutoload(array('classmap' => array(''))); + $package->setIncludePaths(array('/')); + $package->setRequires($requires); + $package->setConflicts($conflicts); + $package->setSuggests($suggests); + $package->setReplaces($replaces); + $result[] = $package; + } } - return implode(',', $versions); + return $result; } - /** - * @todo Improve dependencies resolution of pear packages. - * @param array $depsOptions - * @return array - */ - private function parseDependenciesOptions(array $depsOptions) + private function buildComposerPackageName($pearChannelName, $pearPackageName) { - $data = array(); - foreach ($depsOptions as $name => $options) { - // make sure single deps are wrapped in an array - if (isset($options['name'])) { - $options = array($options); - } - if ('php' == $name) { - $data[$name] = $this->parseVersion($options); - } elseif ('package' == $name) { - foreach ($options as $key => $value) { - if (isset($value['providesextension'])) { - // skip PECL dependencies - continue; - } - if (isset($value['uri'])) { - // skip uri-based dependencies - continue; - } - - if (is_array($value)) { - $dataKey = $value['name']; - if (false === strpos($dataKey, '/')) { - $dataKey = $this->getChannelShorthand($value['channel']).'/'.$dataKey; - } - $data['pear-'.$dataKey] = $this->parseVersion($value); - } - } - } elseif ('extension' == $name) { - foreach ($options as $key => $value) { - $dataKey = 'ext-' . $value['name']; - $data[$dataKey] = $this->parseVersion($value); - } - } - } - - return $data; - } - - /** - * @param string $deps - * @return array - * @throws \InvalidArgumentException - */ - private function parseDependencies($deps) - { - if (preg_match('((O:([0-9])+:"([^"]+)"))', $deps, $matches)) { - if (strlen($matches[3]) == $matches[2]) { - throw new \InvalidArgumentException("Invalid dependency data, it contains serialized objects."); - } - } - $deps = (array) @unserialize($deps); - unset($deps['required']['pearinstaller']); - - $depsData = array(); - if (!empty($deps['required'])) { - $depsData['require'] = $this->parseDependenciesOptions($deps['required']); - } - - if (!empty($deps['optional'])) { - $depsData['suggest'] = $this->parseDependenciesOptions($deps['optional']); - } - - return $depsData; - } - - /** - * @param string $packagesLink - * @return void - * @throws \InvalidArgumentException - */ - private function fetchPear2Packages($packagesLink) - { - $loader = new ArrayLoader(); - $packagesXml = $this->requestXml($packagesLink); - - $informations = $packagesXml->getElementsByTagName('pi'); - foreach ($informations as $information) { - $package = $information->getElementsByTagName('p')->item(0); - - $packageName = $package->getElementsByTagName('n')->item(0)->nodeValue; - $fullName = 'pear-'.$this->channel.'/'.$packageName; - $packageData = array( - 'name' => $fullName, - 'type' => 'library', - 'autoload' => array( - 'classmap' => array(''), - ), - 'include-path' => array('/'), - ); - $packageKeys = array('l' => 'license', 'd' => 'description'); - foreach ($packageKeys as $pear => $composer) { - if ($package->getElementsByTagName($pear)->length > 0 - && ($pear = $package->getElementsByTagName($pear)->item(0)->nodeValue)) { - $packageData[$composer] = $pear; - } - } - - $depsData = array(); - foreach ($information->getElementsByTagName('deps') as $depElement) { - $depsVersion = $depElement->getElementsByTagName('v')->item(0)->nodeValue; - $depsData[$depsVersion] = $this->parseDependencies( - $depElement->getElementsByTagName('d')->item(0)->nodeValue - ); - } - - $releases = $information->getElementsByTagName('a')->item(0); - if (!$releases) { - continue; - } - - $releases = $releases->getElementsByTagName('r'); - $packageUrl = $this->url . '/get/' . $packageName; - foreach ($releases as $release) { - $version = $release->getElementsByTagName('v')->item(0)->nodeValue; - $releaseData = array( - 'dist' => array( - 'type' => 'pear', - 'url' => $packageUrl . '-' . $version . '.tgz' - ), - 'version' => $version - ); - if (isset($depsData[$version])) { - $releaseData += $depsData[$version]; - } - - $package = $packageData + $releaseData; - try { - $this->addPackage($loader->load($package)); - if ($this->io->isVerbose()) { - $this->io->write('Loaded '.$package['name'].' '.$package['version']); - } - } catch (\UnexpectedValueException $e) { - if ($this->io->isVerbose()) { - $this->io->write('Could not load '.$package['name'].' '.$package['version'].': '.$e->getMessage()); - } - continue; - } - } + if ($pearChannelName == 'php') { + return "php"; + } elseif ($pearChannelName == 'ext') { + return "ext-{$pearPackageName}"; + } else { + return "pear-{$pearChannelName}/{$pearPackageName}"; } } - /** - * @param string $url - * @return \DOMDocument - */ - private function requestXml($url) + protected function parseVersion($version) { - $content = $this->rfs->getContents($this->url, $url, false); - if (!$content) { - throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); + if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { + $version = $matches[1] + .(!empty($matches[2]) ? $matches[2] : '.0') + .(!empty($matches[3]) ? $matches[3] : '.0') + .(!empty($matches[4]) ? $matches[4] : '.0'); + + return $version; + } else { + return false; } - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->loadXML($content); - - return $dom; - } - - private function getChannelShorthand($url) - { - if (!isset(self::$channelNames[$url])) { - try { - $channelXML = $this->requestXml('http://'.$url."/channel.xml"); - $shorthand = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue - ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue; - self::$channelNames[$url] = $shorthand; - } catch (\Exception $e) { - self::$channelNames[$url] = substr($url, 0, strpos($url, '.')); - } - } - - return self::$channelNames[$url]; } } diff --git a/tests/Composer/Test/Mock/RemoteFilesystemMock.php b/tests/Composer/Test/Mock/RemoteFilesystemMock.php index 118232359..444c557e2 100644 --- a/tests/Composer/Test/Mock/RemoteFilesystemMock.php +++ b/tests/Composer/Test/Mock/RemoteFilesystemMock.php @@ -13,6 +13,7 @@ namespace Composer\Test\Mock; use Composer\Util\RemoteFilesystem; +use Composer\Downloader\TransportException; /** * Remote filesystem mock @@ -29,10 +30,11 @@ class RemoteFilesystemMock extends RemoteFilesystem public function getContents($originUrl, $fileUrl, $progress = true) { - if(!empty($this->contentMap[$fileUrl])) + if (!empty($this->contentMap[$fileUrl])) { return $this->contentMap[$fileUrl]; + } - throw new \Composer\Downloader\TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404); + throw new TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404); } } diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php new file mode 100644 index 000000000..a5f77e02d --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -0,0 +1,144 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; +use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Package\Link; +use Composer\Package\MemoryPackage; +use Composer\Test\Mock\RemoteFilesystemMock; + +class ChannelReaderTest extends TestCase +{ + public function testShouldBuildPackagesFromPearSchema() + { + $rfs = new RemoteFilesystemMock(array( + 'http://pear.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), + 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), + 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), + )); + + $reader = new \Composer\Repository\Pear\ChannelReader($rfs); + + $channelInfo = $reader->read('http://pear.net/'); + $packages = $channelInfo->getPackages(); + + $this->assertCount(3, $packages); + $this->assertEquals('HTTP_Client', $packages[0]->getPackageName()); + $this->assertEquals('HTTP_Request', $packages[1]->getPackageName()); + $this->assertEquals('MDB2', $packages[2]->getPackageName()); + + $mdb2releases = $packages[2]->getReleases(); + $this->assertEquals(9, count($mdb2releases['2.4.0']->getDependencyInfo()->getOptionals())); + } + + public function testShouldSelectCorrectReader() + { + $rfs = new RemoteFilesystemMock(array( + 'http://pear.1.0.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.0.xml'), + 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), + 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), + 'http://test.loc/rest10/p/http_request/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_info.xml'), + 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), + 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), + 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), + )); + + $reader = new \Composer\Repository\Pear\ChannelReader($rfs); + + $reader->read('http://pear.1.0.net/'); + $reader->read('http://pear.1.1.net/'); + } + + public function testShouldCreatePackages() + { + $reader = $this->getMockBuilder('\Composer\Repository\Pear\ChannelReader') + ->disableOriginalConstructor() + ->getMock(); + + $ref = new \ReflectionMethod($reader, 'buildComposerPackages'); + $ref->setAccessible(true); + + $packageInfo = new PackageInfo( + 'test.loc', + 'sample', + 'license', + 'shortDescription', + 'description', + array( + '1.0.0.1' => new ReleaseInfo( + 'stable', + new DependencyInfo( + array( + new DependencyConstraint( + 'required', + '> 5.2.0.0', + 'php', + '' + ), + new DependencyConstraint( + 'conflicts', + '== 2.5.6.0', + 'pear.php.net', + 'broken' + ), + ), + array( + '*' => array( + new DependencyConstraint( + 'optional', + '*', + 'ext', + 'xml' + ), + ) + ) + ) + ) + ) + ); + + $packages = $ref->invoke($reader, 'test.loc', 'test', array($packageInfo)); + + $expectedPackage = new MemoryPackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1'); + $expectedPackage->setType('library'); + $expectedPackage->setDistType('pear'); + $expectedPackage->setDescription('description'); + $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz"); + $expectedPackage->setAutoload(array('classmap' => array(''))); + $expectedPackage->setIncludePaths(array('/')); + $expectedPackage->setRequires(array( + new Link('pear-test.loc/sample', 'php', $this->createConstraint('>', '5.2.0.0'), 'required', '> 5.2.0.0'), + )); + $expectedPackage->setConflicts(array( + new Link('pear-test.loc/sample', 'pear-pear.php.net/broken', $this->createConstraint('==', '2.5.6.0'), 'conflicts', '== 2.5.6.0'), + )); + $expectedPackage->setSuggests(array( + '*-ext-xml' => '*', + )); + $expectedPackage->setReplaces(array( + new Link('pear-test.loc/sample', 'pear-test/sample', new VersionConstraint('==', '1.0.0.1'), 'replaces', '== 1.0.0.1'), + )); + + $this->assertCount(1, $packages); + $this->assertEquals($expectedPackage, $packages[0], 0, 1); + } + + private function createConstraint($operator, $version) + { + $constraint = new VersionConstraint($operator, $version); + $constraint->setPrettyString($operator.' '.$version); + + return $constraint; + } +} diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php new file mode 100644 index 000000000..ac4f377be --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php @@ -0,0 +1,41 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; +use Composer\Test\Mock\RemoteFilesystemMock; + +class ChannelRest10ReaderTest extends TestCase +{ + public function testShouldBuildPackagesFromPearSchema() + { + $rfs = new RemoteFilesystemMock(array( + 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), + 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), + 'http://test.loc/rest10/r/http_client/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_allreleases.xml'), + 'http://test.loc/rest10/r/http_client/deps.1.2.1.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_deps.1.2.1.txt'), + 'http://test.loc/rest10/p/http_request/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_info.xml'), + 'http://test.loc/rest10/r/http_request/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_allreleases.xml'), + 'http://test.loc/rest10/r/http_request/deps.1.4.0.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_deps.1.4.0.txt'), + )); + + $reader = new \Composer\Repository\Pear\ChannelRest10Reader($rfs); + + /** @var $packages \Composer\Package\PackageInterface[] */ + $packages = $reader->read('http://test.loc/rest10'); + + $this->assertCount(2, $packages); + $this->assertEquals('HTTP_Client', $packages[0]->getPackageName()); + $this->assertEquals('HTTP_Request', $packages[1]->getPackageName()); + } +} diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php new file mode 100644 index 000000000..58105a5eb --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php @@ -0,0 +1,37 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; +use Composer\Test\Mock\RemoteFilesystemMock; + +class ChannelRest11ReaderTest extends TestCase +{ + public function testShouldBuildPackagesFromPearSchema() + { + $rfs = new RemoteFilesystemMock(array( + 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), + 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), + 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), + )); + + $reader = new \Composer\Repository\Pear\ChannelRest11Reader($rfs); + + /** @var $packages \Composer\Package\PackageInterface[] */ + $packages = $reader->read('http://test.loc/rest11'); + + $this->assertCount(3, $packages); + $this->assertEquals('HTTP_Client', $packages[0]->getPackageName()); + $this->assertEquals('HTTP_Request', $packages[1]->getPackageName()); + } +} diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json b/tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json new file mode 100644 index 000000000..e87ba40de --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json @@ -0,0 +1,167 @@ +[ + { + "expected": [ + { + "type" : "required", + "constraint" : "*", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "has", "name": "Foo" } + ], + "2.0": { + "required": { + "package": { + "name": "Foo", + "channel": "pear.php.net" + } + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : ">1.0.0.0", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "gt", "version": "1.0.0", "name": "Foo" } + ], + "2.0": { + "required": { + "package": { + "name": "Foo", + "channel": "pear.php.net", + "min": "1.0.0", + "exclude": "1.0.0" + } + } + } + }, + { + "expected": [ + { + "type" : "conflicts", + "constraint" : "*", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "not", "name": "Foo" } + ], + "2.0": { + "required": { + "package": { + "name": "Foo", + "channel": "pear.php.net", + "conflicts": true + } + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : ">=1.0.0.0", + "channel" : "pear.php.net", + "name" : "Foo" + }, + { + "type" : "required", + "constraint" : "<2.0.0.0", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "ge", "version": "1.0.0", "name": "Foo" }, + { "type": "pkg", "rel": "lt", "version": "2.0.0", "name": "Foo" } + ], + "2.0": { + "required": { + "package": [ + { + "name": "Foo", + "channel": "pear.php.net", + "min": "1.0.0" + }, + { + "name": "Foo", + "channel": "pear.php.net", + "max": "2.0.0", + "exclude": "2.0.0" + } + ] + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : ">=5.3.0.0", + "channel" : "php", + "name" : "" + } + ], + "1.0": [ + { "type": "php", "rel": "ge", "version": "5.3"} + ], + "2.0": { + "required": { + "php": { + "min": "5.3" + } + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : "*", + "channel" : "ext", + "name" : "xmllib" + } + ], + "1.0": [ + { "type": "ext", "rel": "has", "name": "xmllib"} + ], + "2.0": { + "required": { + "extension": [ + { + "name": "xmllib" + } + ] + } + } + }, + { + "expected": [ + { + "type" : "optional", + "constraint" : "*", + "channel" : "ext", + "name" : "xmllib" + } + ], + "1.0": false, + "2.0": { + "optional": { + "extension": [ + { + "name": "xmllib" + } + ] + } + } + } +] \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml new file mode 100644 index 000000000..1ce9d2f85 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml @@ -0,0 +1,9 @@ + + +

HTTP_Client

+ pear.net + + 1.2.1 + stable + +
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt new file mode 100644 index 000000000..db0effb7d --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt @@ -0,0 +1 @@ +a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.3.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.3";}s:7:"package";a:3:{s:4:"name";s:12:"HTTP_Request";s:7:"channel";s:8:"pear.net";s:3:"min";s:5:"1.4.0";}}} \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml new file mode 100644 index 000000000..2197591b0 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml @@ -0,0 +1,14 @@ + +

+ HTTP_Client + pear.net + Default + BSD + + Easy way to perform multiple HTTP requests and process their results + + + The HTTP_Client class wraps around HTTP_Request and provides a higher level interface for performing multiple HTTP requests. Features: * Manages cookies and referrers between requests * Handles HTTP redirection * Has methods to set default headers and request parameters * Implements the Subject-Observer design pattern: the base class sends events to listeners that do the response processing. + + +

diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml new file mode 100644 index 000000000..b6516b743 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml @@ -0,0 +1,9 @@ + + +

HTTP_Request

+ pear.net + + 1.4.0 + stable + +
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt new file mode 100644 index 000000000..da7412d09 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt @@ -0,0 +1 @@ +a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.0.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:7:"1.4.0b1";}s:7:"package";a:2:{i:0;a:3:{s:4:"name";s:7:"Net_URL";s:7:"channel";s:12:"pear.dev.loc";s:3:"min";s:6:"1.0.12";}i:1;a:3:{s:4:"name";s:10:"Net_Socket";s:7:"channel";s:8:"pear.net";s:3:"min";s:5:"1.0.2";}}}} \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml new file mode 100644 index 000000000..8af662792 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml @@ -0,0 +1,12 @@ + +

+ HTTP_Request + pear.net + Default + BSD + Provides an easy way to perform HTTP requests + + Supports GET/POST/HEAD/TRACE/PUT/DELETE, Basic authentication, Proxy, Proxy Authentication, SSL, file uploads etc. + + +

diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml new file mode 100644 index 000000000..c38497ecd --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml @@ -0,0 +1,6 @@ + + + pear.net +

HTTP_Client

+

HTTP_Request

+
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml new file mode 100644 index 000000000..934c12655 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml @@ -0,0 +1,5 @@ + + + pear.net + Default + diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml new file mode 100644 index 000000000..889ba9ace --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml @@ -0,0 +1,97 @@ + + + +

+ HTTP_Client + pear.net + Default + BSD + + Easy way to perform multiple HTTP requests and process their results + + + The HTTP_Client class wraps around HTTP_Request and provides a higher level interface for performing multiple HTTP requests. Features: * Manages cookies and referrers between requests * Handles HTTP redirection * Has methods to set default headers and request parameters * Implements the Subject-Observer design pattern: the base class sends events to listeners that do the response processing. + + +

+ + + 1.2.1 + stable + + + + 1.2.1 + a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.3.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.3";}s:7:"package";a:3:{s:4:"name";s:12:"HTTP_Request";s:7:"channel";s:8:"pear.net";s:3:"min";s:5:"1.4.0";}}} + +
+ +

+ HTTP_Request + pear.net + Default + BSD + Provides an easy way to perform HTTP requests + + Supports GET/POST/HEAD/TRACE/PUT/DELETE, Basic authentication, Proxy, Proxy Authentication, SSL, file uploads etc. + + +

+ + + 1.4.0 + stable + + + + 1.4.0 + a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.0.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:7:"1.4.0b1";}s:7:"package";a:2:{i:0;a:3:{s:4:"name";s:7:"Net_URL";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:6:"1.0.12";}i:1;a:3:{s:4:"name";s:10:"Net_Socket";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.0.2";}}}} + +
+ +

MDB2 + pear.net + Database + BSD License + database abstraction layer + PEAR MDB2 is a merge of the PEAR DB and Metabase php database abstraction layers. + + It provides a common API for all supported RDBMS. The main difference to most + other DB abstraction packages is that MDB2 goes much further to ensure + portability. MDB2 provides most of its many features optionally that + can be used to construct portable SQL statements: + * Object-Oriented API + * A DSN (data source name) or array format for specifying database servers + * Datatype abstraction and on demand datatype conversion + * Various optional fetch modes to fix portability issues + * Portable error codes + * Sequential and non sequential row fetching as well as bulk fetching + * Ability to make buffered and unbuffered queries + * Ordered array and associative array for the fetched rows + * Prepare/execute (bind) named and unnamed placeholder emulation + * Sequence/autoincrement emulation + * Replace emulation + * Limited sub select emulation + * Row limit emulation + * Transactions/savepoint support + * Large Object support + * Index/Unique Key/Primary Key support + * Pattern matching abstraction + * Module framework to load advanced functionality on demand + * Ability to read the information schema + * RDBMS management methods (creating, dropping, altering) + * Reverse engineering schemas from an existing database + * SQL function call abstraction + * Full integration into the PEAR Framework + * PHPDoc API documentation + +

+ + 2.4.0stable + + + 2.4.0 + a:2:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.3.2";}s:13:"pearinstaller";a:1:{s:3:"min";s:7:"1.4.0b1";}s:7:"package";a:3:{s:4:"name";s:4:"PEAR";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.3.6";}}s:5:"group";a:9:{i:0;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:29:"Frontbase SQL driver for MDB2";s:4:"name";s:5:"fbsql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_fbsql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"0.3.0";}}i:1;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:34:"Interbase/Firebird driver for MDB2";s:4:"name";s:5:"ibase";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_ibase";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:2;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:21:"MySQL driver for MDB2";s:4:"name";s:5:"mysql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_mysql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:3;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:22:"MySQLi driver for MDB2";s:4:"name";s:6:"mysqli";}s:10:"subpackage";a:3:{s:4:"name";s:18:"MDB2_Driver_mysqli";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:4;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:29:"MS SQL Server driver for MDB2";s:4:"name";s:5:"mssql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_mssql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.2.0";}}i:5;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:22:"Oracle driver for MDB2";s:4:"name";s:4:"oci8";}s:10:"subpackage";a:3:{s:4:"name";s:16:"MDB2_Driver_oci8";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:6;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:26:"PostgreSQL driver for MDB2";s:4:"name";s:5:"pgsql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_pgsql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:7;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:24:"Querysim driver for MDB2";s:4:"name";s:8:"querysim";}s:10:"subpackage";a:3:{s:4:"name";s:20:"MDB2_Driver_querysim";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"0.6.0";}}i:8;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:23:"SQLite2 driver for MDB2";s:4:"name";s:6:"sqlite";}s:10:"subpackage";a:3:{s:4:"name";s:18:"MDB2_Driver_sqlite";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}}} + +
+
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml new file mode 100644 index 000000000..dc3c9e8bb --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml @@ -0,0 +1,12 @@ + + pear.net + Test PEAR channel + test_alias + + + + http://test.loc/rest10/ + + + + diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml new file mode 100644 index 000000000..bf1e55d68 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml @@ -0,0 +1,12 @@ + + pear.net + Test PEAR channel + test_alias + + + + http://test.loc/rest11/ + + + + diff --git a/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php new file mode 100644 index 000000000..959fe2a9e --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php @@ -0,0 +1,58 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; + +class PackageDependencyParserTest extends TestCase +{ + /** + * @dataProvider dataProvider10 + * @param $expected + * @param $data + */ + public function testShouldParseDependencies($expected, $data10, $data20) + { + $expectedDependencies = array(); + foreach ($expected as $expectedItem) { + $expectedDependencies[] = new DependencyConstraint( + $expectedItem['type'], + $expectedItem['constraint'], + $expectedItem['channel'], + $expectedItem['name'] + ); + } + + $parser = new PackageDependencyParser(); + + if (false !== $data10) { + $result = $parser->buildDependencyInfo($data10); + $this->assertEquals($expectedDependencies, $result->getRequires() + $result->getOptionals(), "Failed for package.xml 1.0 format"); + } + + if (false !== $data20) { + $result = $parser->buildDependencyInfo($data20); + $this->assertEquals($expectedDependencies, $result->getRequires() + $result->getOptionals(), "Failed for package.xml 2.0 format"); + } + } + + public function dataProvider10() + { + $data = json_decode(file_get_contents(__DIR__.'/Fixtures/DependencyParserTestData.json'), true); + if (0 !== json_last_error()) { + throw new \PHPUnit_Framework_Exception('Invalid json file.'); + } + + return $data; + } +} diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index 7021c8615..c14682553 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -29,11 +29,11 @@ class PearRepositoryTest extends TestCase */ private $remoteFilesystem; - public function testComposerNonCompatibleRepositoryShouldSetIncludePath() + public function testComposerShouldSetIncludePath() { $url = 'pear.phpmd.org'; $expectedPackages = array( - array('name' => 'pear-phpmd/PHP_PMD', 'version' => '1.3.3'), + array('name' => 'pear-pear.phpmd.org/PHP_PMD', 'version' => '1.3.3'), ); $repoConfig = array( @@ -78,53 +78,47 @@ class PearRepositoryTest extends TestCase public function repositoryDataProvider() { return array( - array( + array( 'pear.phpunit.de', array( - array('name' => 'pear-phpunit/PHPUnit_MockObject', 'version' => '1.1.1'), - array('name' => 'pear-phpunit/PHPUnit', 'version' => '3.6.10'), + array('name' => 'pear-pear.phpunit.de/PHPUnit_MockObject', 'version' => '1.1.1'), + array('name' => 'pear-pear.phpunit.de/PHPUnit', 'version' => '3.6.10'), ) ), array( 'pear.php.net', array( - array('name' => 'pear-pear/PEAR', 'version' => '1.9.4'), + array('name' => 'pear-pear.php.net/PEAR', 'version' => '1.9.4'), ) ), array( 'pear.pdepend.org', array( - array('name' => 'pear-pdepend/PHP_Depend', 'version' => '1.0.5'), + array('name' => 'pear-pear.pdepend.org/PHP_Depend', 'version' => '1.0.5'), ) ), array( 'pear.phpmd.org', array( - array('name' => 'pear-phpmd/PHP_PMD', 'version' => '1.3.3'), + array('name' => 'pear-pear.phpmd.org/PHP_PMD', 'version' => '1.3.3'), ) ), array( 'pear.doctrine-project.org', array( - array('name' => 'pear-doctrine/DoctrineORM', 'version' => '2.2.2'), + array('name' => 'pear-pear.doctrine-project.org/DoctrineORM', 'version' => '2.2.2'), ) ), array( 'pear.symfony-project.com', array( - array('name' => 'pear-symfony/YAML', 'version' => '1.0.6'), + array('name' => 'pear-pear.symfony-project.com/YAML', 'version' => '1.0.6'), ) ), array( 'pear.pirum-project.org', array( - array('name' => 'pear-pirum/Pirum', 'version' => '1.1.4'), - ) - ), - array( - 'packages.zendframework.com', - array( - array('name' => 'pear-zf2/Zend_Code', 'version' => '2.0.0.0-beta3'), + array('name' => 'pear-pear.pirum-project.org/Pirum', 'version' => '1.1.4'), ) ), ); From f2853c842bd7d6184189ef82a1b7e3b6c5a225cc Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Mon, 25 Jun 2012 02:41:54 +1100 Subject: [PATCH 03/10] Review fixes --- .../Repository/Pear/BaseChannelReader.php | 18 +-- .../Repository/Pear/ChannelReader.php | 133 ++---------------- .../Repository/Pear/ChannelRest10Reader.php | 6 +- .../Repository/Pear/ChannelRest11Reader.php | 6 +- .../Pear/PackageDependencyParser.php | 43 +++--- src/Composer/Repository/PearRepository.php | 38 +++-- .../Repository/Pear/ChannelReaderTest.php | 69 +++++---- 7 files changed, 112 insertions(+), 201 deletions(-) diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php index fa6944ed5..3533a0e26 100644 --- a/src/Composer/Repository/Pear/BaseChannelReader.php +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -26,17 +26,17 @@ abstract class BaseChannelReader /** * PEAR REST Interface namespaces */ - const channelNS = 'http://pear.php.net/channel-1.0'; - const allCategoriesNS = 'http://pear.php.net/dtd/rest.allcategories'; - const categoryPackagesInfoNS = 'http://pear.php.net/dtd/rest.categorypackageinfo'; - const allPackagesNS = 'http://pear.php.net/dtd/rest.allpackages'; - const allReleasesNS = 'http://pear.php.net/dtd/rest.allreleases'; - const packageInfoNS = 'http://pear.php.net/dtd/rest.package'; + const CHANNEL_NS = 'http://pear.php.net/channel-1.0'; + const ALL_CATEGORIES_NS = 'http://pear.php.net/dtd/rest.allcategories'; + const CATEGORY_PACKAGES_INFO_NS = 'http://pear.php.net/dtd/rest.categorypackageinfo'; + const ALL_PACKAGES_NS = 'http://pear.php.net/dtd/rest.allpackages'; + const ALL_RELEASES_NS = 'http://pear.php.net/dtd/rest.allreleases'; + const PACKAGE_INFO_NS = 'http://pear.php.net/dtd/rest.package'; /** @var RemoteFilesystem */ private $rfs; - protected function __construct($rfs) + protected function __construct(RemoteFilesystem $rfs) { $this->rfs = $rfs; } @@ -53,7 +53,7 @@ abstract class BaseChannelReader $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); $content = $this->rfs->getContents($origin, $url, false); if (!$content) { - throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); + throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.'); } return $content; @@ -73,7 +73,7 @@ abstract class BaseChannelReader if (false == $xml) { $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); - throw new \UnexpectedValueException('The PEAR channel at '.$origin.' is broken.'); + throw new \UnexpectedValueException('The PEAR channel at ' . $origin . ' is broken.'); } return $xml; diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php index 54bdc39fa..407b03f79 100644 --- a/src/Composer/Repository/Pear/ChannelReader.php +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -43,28 +43,6 @@ class ChannelReader extends BaseChannelReader ); } - /** - * Reads channel supported REST interfaces and selects one of them - * - * @param $channelXml \SimpleXMLElement - * @param $supportedVersions string[] supported PEAR REST protocols - * @return array|bool hash with selected version and baseUrl - */ - private function selectRestVersion($channelXml, $supportedVersions) - { - $channelXml->registerXPathNamespace('ns', self::channelNS); - - foreach ($supportedVersions as $version) { - $xpathTest = "ns:servers/ns:primary/ns:rest/ns:baseurl[@type='{$version}']"; - $testResult = $channelXml->xpath($xpathTest); - if (count($testResult) > 0) { - return array('version' => $version, 'baseUrl' => (string) $testResult[0]); - } - } - - return false; - } - /** * Reads PEAR channel through REST interface and builds list of packages * @@ -81,7 +59,7 @@ class ChannelReader extends BaseChannelReader $supportedVersions = array_keys($this->readerMap); $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions); - if (false === $selectedRestVersion) { + if (!$selectedRestVersion) { throw new \UnexpectedValueException(sprintf('PEAR repository $s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions))); } @@ -92,109 +70,24 @@ class ChannelReader extends BaseChannelReader } /** - * Builds MemoryPackages from PEAR package definition data. + * Reads channel supported REST interfaces and selects one of them * - * @param $channelName string channel name - * @param $channelAlias string channel alias - * @param $packageDefinitions PackageInfo[] package definition - * @return array + * @param $channelXml \SimpleXMLElement + * @param $supportedVersions string[] supported PEAR REST protocols + * @return array|null hash with selected version and baseUrl */ - private function buildComposerPackages($channelName, $channelAlias, $packageDefinitions) + private function selectRestVersion($channelXml, $supportedVersions) { - $versionParser = new \Composer\Package\Version\VersionParser(); - $result = array(); - foreach ($packageDefinitions as $packageDefinition) { - foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { - $normalizedVersion = $this->parseVersion($version); - if (false === $normalizedVersion) { - continue; // skip packages with unparsable versions - } + $channelXml->registerXPathNamespace('ns', self::CHANNEL_NS); - $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); - - // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text() - $distUrl = "http://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz"; - - $requires = array(); - $suggests = array(); - $conflicts = array(); - $replaces = array(); - - // alias package only when its channel matches repository channel, - // cause we've know only repository channel alias - if ($channelName == $packageDefinition->getChannelName()) { - $composerPackageAlias = $this->buildComposerPackageName($channelAlias, $packageDefinition->getPackageName()); - $aliasConstraint = new VersionConstraint('==', $normalizedVersion); - $aliasLink = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); - $replaces[] = $aliasLink; - } - - $dependencyInfo = $releaseInfo->getDependencyInfo(); - foreach ($dependencyInfo->getRequires() as $dependencyConstraint) { - $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); - $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint()); - $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint()); - switch ($dependencyConstraint->getType()) { - case 'required': - $requires[] = $link; - break; - case 'conflicts': - $conflicts[] = $link; - break; - case 'replaces': - $replaces[] = $link; - break; - } - } - - foreach ($dependencyInfo->getOptionals() as $groupName => $dependencyConstraints) { - foreach ($dependencyConstraints as $dependencyConstraint) { - $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); - $suggests[$groupName.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint(); - } - } - - $package = new \Composer\Package\MemoryPackage($composerPackageName, $normalizedVersion, $version); - $package->setType('library'); - $package->setDescription($packageDefinition->getDescription()); - $package->setDistType('pear'); - $package->setDistUrl($distUrl); - $package->setAutoload(array('classmap' => array(''))); - $package->setIncludePaths(array('/')); - $package->setRequires($requires); - $package->setConflicts($conflicts); - $package->setSuggests($suggests); - $package->setReplaces($replaces); - $result[] = $package; + foreach ($supportedVersions as $version) { + $xpathTest = "ns:servers/ns:primary/ns:rest/ns:baseurl[@type='{$version}']"; + $testResult = $channelXml->xpath($xpathTest); + if (count($testResult) > 0) { + return array('version' => $version, 'baseUrl' => (string) $testResult[0]); } } - return $result; - } - - private function buildComposerPackageName($pearChannelName, $pearPackageName) - { - if ($pearChannelName == 'php') { - return "php"; - } - if ($pearChannelName == 'ext') { - return "ext-{$pearPackageName}"; - } - - return "pear-{$pearChannelName}/{$pearPackageName}"; - } - - protected function parseVersion($version) - { - if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { - $version = $matches[1] - .(!empty($matches[2]) ? $matches[2] : '.0') - .(!empty($matches[3]) ? $matches[3] : '.0') - .(!empty($matches[4]) ? $matches[4] : '.0'); - - return $version; - } else { - return false; - } + return null; } } diff --git a/src/Composer/Repository/Pear/ChannelRest10Reader.php b/src/Composer/Repository/Pear/ChannelRest10Reader.php index fb5b91957..cd3985da5 100644 --- a/src/Composer/Repository/Pear/ChannelRest10Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest10Reader.php @@ -61,7 +61,7 @@ class ChannelRest10Reader extends BaseChannelReader $xmlPath = '/p/packages.xml'; $xml = $this->requestXml($baseUrl, $xmlPath); - $xml->registerXPathNamespace('ns', self::allPackagesNS); + $xml->registerXPathNamespace('ns', self::ALL_PACKAGES_NS); foreach ($xml->xpath('ns:p') as $node) { $packageName = (string) $node; $packageInfo = $this->readPackage($baseUrl, $packageName); @@ -83,7 +83,7 @@ class ChannelRest10Reader extends BaseChannelReader { $xmlPath = '/p/' . strtolower($packageName) . '/info.xml'; $xml = $this->requestXml($baseUrl, $xmlPath); - $xml->registerXPathNamespace('ns', self::packageInfoNS); + $xml->registerXPathNamespace('ns', self::PACKAGE_INFO_NS); $channelName = (string) $xml->c; $packageName = (string) $xml->n; @@ -116,7 +116,7 @@ class ChannelRest10Reader extends BaseChannelReader try { $xmlPath = '/r/' . strtolower($packageName) . '/allreleases.xml'; $xml = $this->requestXml($baseUrl, $xmlPath); - $xml->registerXPathNamespace('ns', self::allReleasesNS); + $xml->registerXPathNamespace('ns', self::ALL_RELEASES_NS); foreach ($xml->xpath('ns:r') as $node) { $releaseVersion = (string) $node->v; $releaseStability = (string) $node->s; diff --git a/src/Composer/Repository/Pear/ChannelRest11Reader.php b/src/Composer/Repository/Pear/ChannelRest11Reader.php index 90f988b59..42d52c928 100644 --- a/src/Composer/Repository/Pear/ChannelRest11Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest11Reader.php @@ -56,7 +56,7 @@ class ChannelRest11Reader extends BaseChannelReader $result = array(); $xml = $this->requestXml($baseUrl, "/c/categories.xml"); - $xml->registerXPathNamespace('ns', self::allCategoriesNS); + $xml->registerXPathNamespace('ns', self::ALL_CATEGORIES_NS); foreach ($xml->xpath('ns:c') as $node) { $categoryName = (string) $node; $categoryPackages = $this->readCategoryPackages($baseUrl, $categoryName); @@ -80,7 +80,7 @@ class ChannelRest11Reader extends BaseChannelReader $categoryPath = '/c/'.urlencode($categoryName).'/packagesinfo.xml'; $xml = $this->requestXml($baseUrl, $categoryPath); - $xml->registerXPathNamespace('ns', self::categoryPackagesInfoNS); + $xml->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS); foreach ($xml->xpath('ns:pi') as $node) { $packageInfo = $this->parsePackage($node); $result[] = $packageInfo; @@ -97,7 +97,7 @@ class ChannelRest11Reader extends BaseChannelReader */ private function parsePackage($packageInfo) { - $packageInfo->registerXPathNamespace('ns', self::categoryPackagesInfoNS); + $packageInfo->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS); $channelName = (string) $packageInfo->p->c; $packageName = (string) $packageInfo->p->n; $license = (string) $packageInfo->p->l; diff --git a/src/Composer/Repository/Pear/PackageDependencyParser.php b/src/Composer/Repository/Pear/PackageDependencyParser.php index 6902facac..75ee27acc 100644 --- a/src/Composer/Repository/Pear/PackageDependencyParser.php +++ b/src/Composer/Repository/Pear/PackageDependencyParser.php @@ -19,6 +19,23 @@ namespace Composer\Repository\Pear; */ class PackageDependencyParser { + /** + * Builds dependency information. It detects used package.xml format. + * + * @param $depArray array + * @return DependencyInfo + */ + public function buildDependencyInfo($depArray) + { + if (!is_array($depArray)) { + return new DependencyInfo(array(), array()); + } + if (!$this->isHash($depArray)) { + return new DependencyInfo($this->buildDependency10Info($depArray), array()); + } + return $this->buildDependency20Info($depArray); + } + /** * Builds dependency information from package.xml 1.0 format * @@ -229,23 +246,6 @@ class PackageDependencyParser return $result; } - /** - * Builds dependency information. It detects used package.xml format. - * - * @param $depArray array - * @return DependencyInfo - */ - public function buildDependencyInfo($depArray) - { - if (!is_array($depArray)) { - return new DependencyInfo(array(), array()); - } elseif (!$this->isHash($depArray)) { - return new DependencyInfo($this->buildDependency10Info($depArray), array()); - } else { - return $this->buildDependency20Info($depArray); - } - } - /** * Parses version constraint * @@ -260,7 +260,8 @@ class PackageDependencyParser $values = array_intersect_key($data, $dep20toOperatorMap); if (0 == count($values)) { return '*'; - } elseif (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) { + } + if (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) { $versions[] = '>' . $this->parseVersion($values['min']); } elseif (isset($values['max']) && isset($values['exclude']) && $data['max'] == $data['exclude']) { $versions[] = '<' . $this->parseVersion($values['max']); @@ -283,7 +284,7 @@ class PackageDependencyParser * Softened version parser * * @param $version - * @return bool|string + * @return null|string */ private function parseVersion($version) { @@ -294,9 +295,9 @@ class PackageDependencyParser .(!empty($matches[4]) ? $matches[4] : '.0'); return $version; - } else { - return false; } + + return null; } /** diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 22045b863..7c7d3ffae 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -13,6 +13,8 @@ namespace Composer\Repository; use Composer\IO\IOInterface; +use Composer\Package\Version\VersionParser; +use Composer\Repository\Pear\ChannelReader; use Composer\Package\MemoryPackage; use Composer\Repository\Pear\ChannelInfo; use Composer\Package\Link; @@ -34,6 +36,7 @@ class PearRepository extends ArrayRepository private $url; private $io; private $rfs; + private $versionParser; /** @var string vendor makes additional alias for each channel as {prefix}/{packagename}. It allows smoother * package transition to composer-like repositories. @@ -54,6 +57,7 @@ class PearRepository extends ArrayRepository $this->io = $io; $this->rfs = $rfs ?: new RemoteFilesystem($this->io); $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null; + $this->versionParser = new VersionParser(); } protected function initialize() @@ -62,14 +66,14 @@ class PearRepository extends ArrayRepository $this->io->write('Initializing PEAR repository '.$this->url); - $reader = new \Composer\Repository\Pear\ChannelReader($this->rfs); + $reader = new ChannelReader($this->rfs); try { $channelInfo = $reader->read($this->url); } catch (\Exception $e) { $this->io->write('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); return; } - $packages = $this->buildComposerPackages($channelInfo); + $packages = $this->buildComposerPackages($channelInfo, $this->versionParser); foreach ($packages as $package) { $this->addPackage($package); } @@ -81,14 +85,13 @@ class PearRepository extends ArrayRepository * @param ChannelInfo $channelInfo * @return MemoryPackage */ - private function buildComposerPackages(ChannelInfo $channelInfo) + private function buildComposerPackages(ChannelInfo $channelInfo, VersionParser $versionParser) { - $versionParser = new \Composer\Package\Version\VersionParser(); $result = array(); foreach ($channelInfo->getPackages() as $packageDefinition) { foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { $normalizedVersion = $this->parseVersion($version); - if (false === $normalizedVersion) { + if (!$normalizedVersion) { continue; // skip packages with unparsable versions } @@ -160,18 +163,25 @@ class PearRepository extends ArrayRepository return $result; } - private function buildComposerPackageName($pearChannelName, $pearPackageName) + private function buildComposerPackageName($channelName, $packageName) { - if ($pearChannelName == 'php') { + if ('php' === $channelName) { return "php"; - } elseif ($pearChannelName == 'ext') { - return "ext-{$pearPackageName}"; - } else { - return "pear-{$pearChannelName}/{$pearPackageName}"; } + if ('ext' === $channelName) { + return "ext-{$packageName}"; + } + + return "pear-{$channelName}/{$packageName}"; } - protected function parseVersion($version) + /** + * Softened version parser. + * + * @param string $version + * @return null|string + */ + private function parseVersion($version) { if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { $version = $matches[1] @@ -180,8 +190,8 @@ class PearRepository extends ArrayRepository .(!empty($matches[4]) ? $matches[4] : '.0'); return $version; - } else { - return false; } + + return null; } } diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index a5f77e02d..a1ce4f7c6 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Pear; use Composer\Test\TestCase; +use Composer\Package\Version\VersionParser; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\Link; use Composer\Package\MemoryPackage; @@ -62,45 +63,51 @@ class ChannelReaderTest extends TestCase public function testShouldCreatePackages() { - $reader = $this->getMockBuilder('\Composer\Repository\Pear\ChannelReader') + $reader = $this->getMockBuilder('\Composer\Repository\PearRepository') ->disableOriginalConstructor() ->getMock(); $ref = new \ReflectionMethod($reader, 'buildComposerPackages'); $ref->setAccessible(true); - $packageInfo = new PackageInfo( + $channelInfo = new ChannelInfo( 'test.loc', - 'sample', - 'license', - 'shortDescription', - 'description', + 'test', array( - '1.0.0.1' => new ReleaseInfo( - 'stable', - new DependencyInfo( - array( - new DependencyConstraint( - 'required', - '> 5.2.0.0', - 'php', - '' - ), - new DependencyConstraint( - 'conflicts', - '== 2.5.6.0', - 'pear.php.net', - 'broken' - ), - ), - array( - '*' => array( - new DependencyConstraint( - 'optional', - '*', - 'ext', - 'xml' + new PackageInfo( + 'test.loc', + 'sample', + 'license', + 'shortDescription', + 'description', + array( + '1.0.0.1' => new ReleaseInfo( + 'stable', + new DependencyInfo( + array( + new DependencyConstraint( + 'required', + '> 5.2.0.0', + 'php', + '' + ), + new DependencyConstraint( + 'conflicts', + '== 2.5.6.0', + 'pear.php.net', + 'broken' + ), ), + array( + '*' => array( + new DependencyConstraint( + 'optional', + '*', + 'ext', + 'xml' + ), + ) + ) ) ) ) @@ -108,7 +115,7 @@ class ChannelReaderTest extends TestCase ) ); - $packages = $ref->invoke($reader, 'test.loc', 'test', array($packageInfo)); + $packages = $ref->invoke($reader, $channelInfo, new VersionParser()); $expectedPackage = new MemoryPackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1'); $expectedPackage->setType('library'); From 3ff941ed3049785acfb3145e9e8a54cc88242e23 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Mon, 25 Jun 2012 10:03:05 +1100 Subject: [PATCH 04/10] Add docs for 'vendor-alias' --- doc/05-repositories.md | 44 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index eef485c05..c7e1f34d5 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -202,7 +202,7 @@ should you need to specify one for whatever reason, you can use `git`, `svn` or It is possible to install packages from any PEAR channel by using the `pear` repository. Composer will prefix all package names with `pear-{channelName}/` to -avoid conflicts. +avoid conflicts. All packages are also aliased with prefix `pear-{channelAlias}/` Example using `pear2.php.net`: @@ -214,6 +214,7 @@ Example using `pear2.php.net`: } ], "require": { + "pear-pear2.php.net/PEAR2_Text_Markdown": "*", "pear-pear2/PEAR2_HTTP_Request": "*" } } @@ -224,6 +225,47 @@ In this case the short name of the channel is `pear2`, so the > **Note:** The `pear` repository requires doing quite a few requests per > package, so this may considerably slow down the installation process. +#### Custom channel alias +It is possible to alias all pear channel packages with custom name. + +Example: + You own private pear repository and going to use composer abilities to bring + dependencies from vcs or transit to composer repository scheme. + List of packages: + * BasePackage, requires nothing + * IntermediatePackage, depends on BasePackage + * TopLevelPackage1 and TopLevelPackage2 both dependth on IntermediatePackage. + + For composer it looks like: + * "pear-pear.foobar.repo/IntermediatePackage" depends on "pear-pear.foobar.repo/BasePackage", + * "pear-pear.foobar.repo/TopLevelPackage1" depends on "pear-pear.foobar.repo/IntermediatePackage", + * "pear-pear.foobar.repo/TopLevelPackage2" depends on "pear-pear.foobar.repo/IntermediatePackage" + When you update one of your packages to composer naming scheme or made it + available through vcs your older dependencies would not see new version cause it would be named + like "foobar/IntermediatePackage". + + Specifying 'vendor-alias' for pear repository you will get all its packages aliased with composer-like names. + Following example would take BasePackage, TopLevelPackage1 and TopLevelPackage2 packages from pear repository + and IntermediatePackage from github repository: + { + "repositories": [ + { + "type": "git", + "https://github.com/foobar/intermediate.git" + }, + { + "type": "pear", + "url": "http://pear.foobar.repo", + "vendor-alias": "foobar" + } + ], + "require": { + "foobar/TopLevelPackage1": "*", + "foobar/TopLevelPackage2": "*" + } + } + + ### Package If you want to use a project that does not support composer through any of the From 77715e254ada8cf457cb57bfeabf4d400f75bede Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Tue, 26 Jun 2012 17:17:58 +1100 Subject: [PATCH 05/10] Fix exception message template --- src/Composer/Repository/Pear/ChannelReader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php index 407b03f79..e9e316f0b 100644 --- a/src/Composer/Repository/Pear/ChannelReader.php +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -60,7 +60,7 @@ class ChannelReader extends BaseChannelReader $supportedVersions = array_keys($this->readerMap); $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions); if (!$selectedRestVersion) { - throw new \UnexpectedValueException(sprintf('PEAR repository $s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions))); + throw new \UnexpectedValueException(sprintf('PEAR repository %s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions))); } $reader = $this->readerMap[$selectedRestVersion['version']]; From 0117108efb6fe3318fa4ff94b6194fae80e2f628 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Wed, 27 Jun 2012 16:33:29 +1100 Subject: [PATCH 06/10] Fix Locker to store exact package version. --- src/Composer/Package/Locker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 8b37e6c0d..9104ce780 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -211,7 +211,7 @@ class Locker } $name = $package->getPrettyName(); - $version = $package->getPrettyVersion(); + $version = $package->getVersion(); if (!$name || !$version) { throw new \LogicException(sprintf( From ac3cebc633c64d52b02a60f8f1fe48904ae293e6 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Wed, 27 Jun 2012 16:27:01 +1100 Subject: [PATCH 07/10] Update PEAR Package Extractor to use 'task:replace', 'phprelease' commands and install role='script' files Add PearInstaller Change PEAR packages type from 'library' to 'pear-library' and dist type from 'pear' to 'file' Remove PearDownloader Refactor Channel Installer --- src/Composer/Downloader/PearDownloader.php | 52 -------- .../Downloader/PearPackageExtractor.php | 117 +++++++++++++----- src/Composer/Factory.php | 2 +- src/Composer/Installer/LibraryInstaller.php | 39 ++++-- src/Composer/Installer/PearInstaller.php | 95 ++++++++++++++ .../Repository/Pear/BaseChannelReader.php | 2 +- .../Repository/Pear/ChannelReader.php | 2 - .../Pear/PackageDependencyParser.php | 1 + src/Composer/Repository/PearRepository.php | 7 +- .../Fixtures/Package_v2.1/package.xml | 12 +- .../Test/Downloader/PearDownloaderTest.php | 38 ------ .../Downloader/PearPackageExtractorTest.php | 72 +++++++++-- .../Test/Installer/LibraryInstallerTest.php | 6 +- tests/Composer/Test/Package/LockerTest.php | 4 +- .../Repository/Pear/ChannelReaderTest.php | 4 +- tests/bootstrap.php | 2 + 16 files changed, 305 insertions(+), 150 deletions(-) delete mode 100644 src/Composer/Downloader/PearDownloader.php create mode 100644 src/Composer/Installer/PearInstaller.php delete mode 100644 tests/Composer/Test/Downloader/PearDownloaderTest.php diff --git a/src/Composer/Downloader/PearDownloader.php b/src/Composer/Downloader/PearDownloader.php deleted file mode 100644 index 6f250abd3..000000000 --- a/src/Composer/Downloader/PearDownloader.php +++ /dev/null @@ -1,52 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Downloader; - -use Composer\Package\PackageInterface; - -/** - * Downloader for pear packages - * - * @author Jordi Boggiano - * @author Kirill chEbba Chebunin - */ -class PearDownloader extends FileDownloader -{ - /** - * {@inheritDoc} - */ - public function download(PackageInterface $package, $path) - { - parent::download($package, $path); - - $fileName = $this->getFileName($package, $path); - if ($this->io->isVerbose()) { - $this->io->write(' Installing PEAR package'); - } - try { - $pearExtractor = new PearPackageExtractor($fileName); - $pearExtractor->extractTo($path); - - if ($this->io->isVerbose()) { - $this->io->write(' Cleaning up'); - } - unlink($fileName); - } catch (\Exception $e) { - // clean up - $this->filesystem->removeDirectory($path); - throw $e; - } - - $this->io->write(''); - } -} diff --git a/src/Composer/Downloader/PearPackageExtractor.php b/src/Composer/Downloader/PearPackageExtractor.php index 3a83a91dd..eeb88806c 100644 --- a/src/Composer/Downloader/PearPackageExtractor.php +++ b/src/Composer/Downloader/PearPackageExtractor.php @@ -35,22 +35,22 @@ class PearPackageExtractor throw new \UnexpectedValueException('PEAR package file is not found at '.$file); } + $this->filesystem = new Filesystem(); $this->file = $file; } /** * Installs PEAR source files according to package.xml definitions and removes extracted files * - * @param $file string path to downloaded PEAR archive file - * @param $target string target install location. all source installation would be performed relative to target path. - * @param $role string type of files to install. default role for PEAR source files are 'php'. - * + * @param string $target target install location. all source installation would be performed relative to target path. + * @param array $roles types of files to install. default role for PEAR source files are 'php'. + * @param array $vars used for replacement tasks * @throws \RuntimeException + * @throws \UnexpectedValueException + * */ - public function extractTo($target, $role = 'php') + public function extractTo($target, array $roles = array('php' => '/', 'script' => '/bin'), $vars = array()) { - $this->filesystem = new Filesystem(); - $extractionPath = $target.'/tarball'; try { @@ -61,8 +61,8 @@ class PearPackageExtractor throw new \RuntimeException('Invalid PEAR package. It must contain package.xml file.'); } - $fileCopyActions = $this->buildCopyActions($extractionPath, $role); - $this->copyFiles($fileCopyActions, $extractionPath, $target); + $fileCopyActions = $this->buildCopyActions($extractionPath, $roles, $vars); + $this->copyFiles($fileCopyActions, $extractionPath, $target, $roles, $vars); $this->filesystem->removeDirectory($extractionPath); } catch (\Exception $exception) { throw new \UnexpectedValueException(sprintf('Failed to extract PEAR package %s to %s. Reason: %s', $this->file, $target, $exception->getMessage()), 0, $exception); @@ -72,20 +72,24 @@ class PearPackageExtractor /** * Perform copy actions on files * - * @param $files array array('from', 'to') with relative paths + * @param array $files array of copy actions ('from', 'to') with relative paths * @param $source string path to source dir. * @param $target string path to destination dir + * @param array $roles array [role => roleRoot] relative root for files having that role + * @param array $vars list of values can be used for replacement tasks */ - private function copyFiles($files, $source, $target) + private function copyFiles($files, $source, $target, $roles, $vars) { foreach ($files as $file) { $from = $this->combine($source, $file['from']); - $to = $this->combine($target, $file['to']); - $this->copyFile($from, $to); + $to = $this->combine($target, $roles[$file['role']]); + $to = $this->combine($to, $file['to']); + $tasks = $file['tasks']; + $this->copyFile($from, $to, $tasks, $vars); } } - private function copyFile($from, $to) + private function copyFile($from, $to, $tasks, $vars) { if (!is_file($from)) { throw new \RuntimeException('Invalid PEAR package. package.xml defines file that is not located inside tarball.'); @@ -93,7 +97,24 @@ class PearPackageExtractor $this->filesystem->ensureDirectoryExists(dirname($to)); - if (!copy($from, $to)) { + if (0 == count($tasks)) { + $copied = copy($from, $to); + } else { + $content = file_get_contents($from); + $replacements = array(); + foreach ($tasks as $task) { + $pattern = $task['from']; + $varName = $task['to']; + if (isset($vars[$varName])) { + $replacements[$pattern] = $vars[$varName]; + } + } + $content = strtr($content, $replacements); + + $copied = file_put_contents($to, $content); + } + + if (false === $copied) { throw new \RuntimeException(sprintf('Failed to copy %s to %s', $from, $to)); } } @@ -107,7 +128,7 @@ class PearPackageExtractor * path, and target is destination of file (also relative to $source path) * @throws \RuntimeException */ - private function buildCopyActions($source, $role) + private function buildCopyActions($source, array $roles, $vars) { /** @var $package \SimpleXmlElement */ $package = simplexml_load_file($this->combine($source, 'package.xml')); @@ -120,13 +141,18 @@ class PearPackageExtractor $packageName = (string) $package->name; $packageVersion = (string) $package->release->version; $sourceDir = $packageName . '-' . $packageVersion; - $result = $this->buildSourceList10($children, $role, $sourceDir); + $result = $this->buildSourceList10($children, $roles, $sourceDir); } elseif ('2.0' == $packageSchemaVersion || '2.1' == $packageSchemaVersion) { $children = $package->contents->children(); $packageName = (string) $package->name; $packageVersion = (string) $package->version->release; $sourceDir = $packageName . '-' . $packageVersion; - $result = $this->buildSourceList20($children, $role, $sourceDir); + $result = $this->buildSourceList20($children, $roles, $sourceDir); + + $namespaces = $package->getNamespaces(); + $package->registerXPathNamespace('ns', $namespaces['']); + $releaseNodes = $package->xpath('ns:phprelease'); + $this->applyRelease($result, $releaseNodes, $vars); } else { throw new \RuntimeException('Unsupported schema version of package definition file.'); } @@ -134,7 +160,34 @@ class PearPackageExtractor return $result; } - private function buildSourceList10($children, $targetRole, $source = '', $target = '', $role = null) + private function applyRelease(&$actions, $releaseNodes, $vars) + { + foreach ($releaseNodes as $releaseNode) { + $requiredOs = (string) ($releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name) ?: ''; + if ($requiredOs && $vars['os'] != $requiredOs) + continue; + + if ($releaseNode->filelist) { + foreach ($releaseNode->filelist->children() as $action) { + if ('install' == $action->getName()) { + $name = (string) $action['name']; + $as = (string) $action['as']; + if (isset($actions[$name])) { + $actions[$name]['to'] = $as; + } + } elseif ('ignore' == $action->getName()) { + $name = (string) $action['name']; + unset($actions[$name]); + } else { + // unknown action + } + } + } + break; + } + } + + private function buildSourceList10($children, $targetRoles, $source = '', $target = '', $role = null) { $result = array(); @@ -145,14 +198,15 @@ class PearPackageExtractor $dirSource = $this->combine($source, (string) $child['name']); $dirTarget = $child['baseinstalldir'] ? : $target; $dirRole = $child['role'] ? : $role; - $dirFiles = $this->buildSourceList10($child->children(), $targetRole, $dirSource, $dirTarget, $dirRole); + $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole); $result = array_merge($result, $dirFiles); } elseif ($child->getName() == 'file') { - if (($child['role'] ? : $role) == $targetRole) { + $fileRole = (string) $child['role'] ? : $role; + if (isset($targetRoles[$fileRole])) { $fileName = (string) ($child['name'] ? : $child[0]); // $child[0] means text content $fileSource = $this->combine($source, $fileName); $fileTarget = $this->combine((string) $child['baseinstalldir'] ? : $target, $fileName); - $result[] = array('from' => $fileSource, 'to' => $fileTarget); + $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => array()); } } } @@ -160,24 +214,31 @@ class PearPackageExtractor return $result; } - private function buildSourceList20($children, $targetRole, $source = '', $target = '', $role = null) + private function buildSourceList20($children, $targetRoles, $source = '', $target = '', $role = null) { $result = array(); // enumerating files foreach ($children as $child) { /** @var $child \SimpleXMLElement */ - if ($child->getName() == 'dir') { + if ('dir' == $child->getName()) { $dirSource = $this->combine($source, $child['name']); $dirTarget = $child['baseinstalldir'] ? : $target; $dirRole = $child['role'] ? : $role; - $dirFiles = $this->buildSourceList20($child->children(), $targetRole, $dirSource, $dirTarget, $dirRole); + $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole); $result = array_merge($result, $dirFiles); - } elseif ($child->getName() == 'file') { - if (($child['role'] ? : $role) == $targetRole) { + } elseif ('file' == $child->getName()) { + $fileRole = (string) $child['role'] ? : $role; + if (isset($targetRoles[$fileRole])) { $fileSource = $this->combine($source, (string) $child['name']); $fileTarget = $this->combine((string) ($child['baseinstalldir'] ? : $target), (string) $child['name']); - $result[] = array('from' => $fileSource, 'to' => $fileTarget); + $fileTasks = array(); + foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) { + if ('replace' == $taskNode->getName()) { + $fileTasks[] = array('from' => (string) $taskNode->attributes()->from, 'to' => (string) $taskNode->attributes()->to); + } + } + $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => $fileTasks); } } } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 18567c14b..75b47f790 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -225,7 +225,6 @@ class Factory $dm->setDownloader('git', new Downloader\GitDownloader($io)); $dm->setDownloader('svn', new Downloader\SvnDownloader($io)); $dm->setDownloader('hg', new Downloader\HgDownloader($io)); - $dm->setDownloader('pear', new Downloader\PearDownloader($io)); $dm->setDownloader('zip', new Downloader\ZipDownloader($io)); $dm->setDownloader('tar', new Downloader\TarDownloader($io)); $dm->setDownloader('phar', new Downloader\PharDownloader($io)); @@ -251,6 +250,7 @@ class Factory protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io) { $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null)); + $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library')); $im->addInstaller(new Installer\InstallerInstaller($io, $composer)); $im->addInstaller(new Installer\MetapackageInstaller($io)); } diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 6652edec8..2c3facad7 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -82,7 +82,7 @@ class LibraryInstaller implements InstallerInterface $this->removeBinaries($package); } - $this->downloadManager->download($package, $downloadPath); + $this->installCode($package); $this->installBinaries($package); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); @@ -102,7 +102,7 @@ class LibraryInstaller implements InstallerInterface $downloadPath = $this->getInstallPath($initial); $this->removeBinaries($initial); - $this->downloadManager->update($initial, $target, $downloadPath); + $this->updateCode($initial, $target, $downloadPath); $this->installBinaries($target); $repo->removePackage($initial); if (!$repo->hasPackage($target)) { @@ -123,7 +123,7 @@ class LibraryInstaller implements InstallerInterface $downloadPath = $this->getInstallPath($package); - $this->downloadManager->remove($package, $downloadPath); + $this->removeCode($package); $this->removeBinaries($package); $repo->removePackage($package); @@ -146,12 +146,36 @@ class LibraryInstaller implements InstallerInterface return ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName() . ($targetDir ? '/'.$targetDir : ''); } + protected function installCode(PackageInterface $package) + { + $downloadPath = $this->getInstallPath($package); + $this->downloadManager->download($package, $downloadPath); + } + + protected function updateCode(PackageInterface $initial, PackageInterface $target) + { + $downloadPath = $this->getInstallPath($initial); + $this->downloadManager->update($initial, $target, $downloadPath); + } + + protected function removeCode(PackageInterface $package) + { + $downloadPath = $this->getInstallPath($package); + $this->downloadManager->remove($package, $downloadPath); + } + + protected function getBinaries(PackageInterface $package) + { + return $package->getBinaries(); + } + protected function installBinaries(PackageInterface $package) { - if (!$package->getBinaries()) { + $binaries = $this->getBinaries($package); + if (!$binaries) { return; } - foreach ($package->getBinaries() as $bin) { + foreach ($binaries as $bin) { $this->initializeBinDir(); $link = $this->binDir.'/'.basename($bin); if (file_exists($link)) { @@ -193,10 +217,11 @@ class LibraryInstaller implements InstallerInterface protected function removeBinaries(PackageInterface $package) { - if (!$package->getBinaries()) { + $binaries = $this->getBinaries($package); + if (!$binaries) { return; } - foreach ($package->getBinaries() as $bin) { + foreach ($binaries as $bin) { $link = $this->binDir.'/'.basename($bin); if (!file_exists($link)) { continue; diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php new file mode 100644 index 000000000..75f8d0008 --- /dev/null +++ b/src/Composer/Installer/PearInstaller.php @@ -0,0 +1,95 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\Composer; +use Composer\Downloader\PearPackageExtractor; +use Composer\Downloader\DownloadManager; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Util\Filesystem; + +/** + * Package installation manager. + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + */ +class PearInstaller extends LibraryInstaller +{ + private $filesystem; + + /** + * Initializes library installer. + * + * @param string $vendorDir relative path for packages home + * @param string $binDir relative path for binaries + * @param DownloadManager $dm download manager + * @param IOInterface $io io instance + * @param string $type package type that this installer handles + */ + public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library') + { + $this->filesystem = new Filesystem(); + parent::__construct($io, $composer, $type); + } + + /** + * {@inheritDoc} + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + $this->uninstall($repo, $initial); + $this->install($repo, $target); + } + + protected function installCode(PackageInterface $package) + { + parent::installCode($package); + + $isWindows = defined('PHP_WINDOWS_VERSION_BUILD') ? true : false; + + $vars = array( + 'os' => $isWindows ? 'windows' : 'linux', + 'php_bin' => ($isWindows ? getenv('PHPRC') .'php.exe' : `which php`), + 'pear_php' => $this->getInstallPath($package), + 'bin_dir' => $this->getInstallPath($package) . '/bin', + 'php_dir' => $this->getInstallPath($package), + 'data_dir' => '@DATA_DIR@', + 'version' => $package->getPrettyVersion(), + ); + + $packageArchive = $this->getInstallPath($package).'/'.pathinfo($package->getDistUrl(), PATHINFO_BASENAME); + $pearExtractor = new PearPackageExtractor($packageArchive); + $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin'), $vars); + + if ($this->io->isVerbose()) { + $this->io->write(' Cleaning up'); + } + unlink($packageArchive); + } + + protected function getBinaries(PackageInterface $package) + { + $binariesPath = $this->getInstallPath($package) . '/bin/'; + $binaries = array(); + if (file_exists($binariesPath)) { + foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME) as $fileName => $value) { + $binaries[] = 'bin/'.$fileName; + } + } + + return $binaries; + } +} diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php index 3533a0e26..2f586ad87 100644 --- a/src/Composer/Repository/Pear/BaseChannelReader.php +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -73,7 +73,7 @@ abstract class BaseChannelReader if (false == $xml) { $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); - throw new \UnexpectedValueException('The PEAR channel at ' . $origin . ' is broken.'); + throw new \UnexpectedValueException(sprintf('The PEAR channel at ' . $origin . ' is broken. (Invalid XML at file `%s`)', $path)); } return $xml; diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php index e9e316f0b..7beb37d3c 100644 --- a/src/Composer/Repository/Pear/ChannelReader.php +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -13,8 +13,6 @@ namespace Composer\Repository\Pear; use Composer\Util\RemoteFilesystem; -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Package\Link; /** * PEAR Channel package reader. diff --git a/src/Composer/Repository/Pear/PackageDependencyParser.php b/src/Composer/Repository/Pear/PackageDependencyParser.php index 75ee27acc..aa198ceb4 100644 --- a/src/Composer/Repository/Pear/PackageDependencyParser.php +++ b/src/Composer/Repository/Pear/PackageDependencyParser.php @@ -33,6 +33,7 @@ class PackageDependencyParser if (!$this->isHash($depArray)) { return new DependencyInfo($this->buildDependency10Info($depArray), array()); } + return $this->buildDependency20Info($depArray); } diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 7c7d3ffae..180ee5584 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -71,6 +71,7 @@ class PearRepository extends ArrayRepository $channelInfo = $reader->read($this->url); } catch (\Exception $e) { $this->io->write('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); + return; } $packages = $this->buildComposerPackages($channelInfo, $this->versionParser); @@ -146,9 +147,9 @@ class PearRepository extends ArrayRepository } $package = new MemoryPackage($composerPackageName, $normalizedVersion, $version); - $package->setType('library'); + $package->setType('pear-library'); $package->setDescription($packageDefinition->getDescription()); - $package->setDistType('pear'); + $package->setDistType('file'); $package->setDistUrl($distUrl); $package->setAutoload(array('classmap' => array(''))); $package->setIncludePaths(array('/')); @@ -178,7 +179,7 @@ class PearRepository extends ArrayRepository /** * Softened version parser. * - * @param string $version + * @param string $version * @return null|string */ private function parseVersion($version) diff --git a/tests/Composer/Test/Downloader/Fixtures/Package_v2.1/package.xml b/tests/Composer/Test/Downloader/Fixtures/Package_v2.1/package.xml index e49f38bfe..80a8eb686 100644 --- a/tests/Composer/Test/Downloader/Fixtures/Package_v2.1/package.xml +++ b/tests/Composer/Test/Downloader/Fixtures/Package_v2.1/package.xml @@ -19,7 +19,17 @@ + + + + + - + + + + + + diff --git a/tests/Composer/Test/Downloader/PearDownloaderTest.php b/tests/Composer/Test/Downloader/PearDownloaderTest.php deleted file mode 100644 index d3cc8d0fd..000000000 --- a/tests/Composer/Test/Downloader/PearDownloaderTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Downloader; - -use Composer\Downloader\PearDownloader; - -class PearDownloaderTest extends \PHPUnit_Framework_TestCase -{ - public function testErrorMessages() - { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); - $packageMock->expects($this->any()) - ->method('getDistUrl') - ->will($this->returnValue('file://'.__FILE__)) - ; - - $io = $this->getMock('Composer\IO\IOInterface'); - $downloader = new PearDownloader($io); - - try { - $downloader->download($packageMock, sys_get_temp_dir().'/composer-pear-test'); - $this->fail('Download of invalid pear packages should throw an exception'); - } catch (\UnexpectedValueException $e) { - $this->assertContains('Failed to extract PEAR package', $e->getMessage()); - } - } - -} diff --git a/tests/Composer/Test/Downloader/PearPackageExtractorTest.php b/tests/Composer/Test/Downloader/PearPackageExtractorTest.php index 42010a39c..fa393833d 100644 --- a/tests/Composer/Test/Downloader/PearPackageExtractorTest.php +++ b/tests/Composer/Test/Downloader/PearPackageExtractorTest.php @@ -22,20 +22,26 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase $method = new \ReflectionMethod($extractor, 'buildCopyActions'); $method->setAccessible(true); - $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v1.0', 'php'); + $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v1.0', array('php' => '/'), array()); $expectedFileActions = array( - 0 => Array( + 'Gtk.php' => Array( 'from' => 'PEAR_Frontend_Gtk-0.4.0/Gtk.php', 'to' => 'PEAR/Frontend/Gtk.php', + 'role' => 'php', + 'tasks' => array(), ), - 1 => Array( + 'Gtk/Config.php' => Array( 'from' => 'PEAR_Frontend_Gtk-0.4.0/Gtk/Config.php', 'to' => 'PEAR/Frontend/Gtk/Config.php', + 'role' => 'php', + 'tasks' => array(), ), - 2 => Array( + 'Gtk/xpm/black_close_icon.xpm' => Array( 'from' => 'PEAR_Frontend_Gtk-0.4.0/Gtk/xpm/black_close_icon.xpm', 'to' => 'PEAR/Frontend/Gtk/xpm/black_close_icon.xpm', + 'role' => 'php', + 'tasks' => array(), ) ); $this->assertSame($expectedFileActions, $fileActions); @@ -47,12 +53,14 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase $method = new \ReflectionMethod($extractor, 'buildCopyActions'); $method->setAccessible(true); - $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.0', 'php'); + $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.0', array('php' => '/'), array()); $expectedFileActions = array( - 0 => Array( + 'URL.php' => Array( 'from' => 'Net_URL-1.0.15/URL.php', 'to' => 'Net/URL.php', + 'role' => 'php', + 'tasks' => array(), ) ); $this->assertSame($expectedFileActions, $fileActions); @@ -64,18 +72,62 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase $method = new \ReflectionMethod($extractor, 'buildCopyActions'); $method->setAccessible(true); - $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.1', 'php'); + $fileActions = $method->invoke($extractor, __DIR__ . '/Fixtures/Package_v2.1', array('php' => '/', 'script' => '/bin'), array()); $expectedFileActions = array( - 0 => Array( + 'php/Zend/Authentication/Storage/StorageInterface.php' => Array( 'from' => 'Zend_Authentication-2.0.0beta4/php/Zend/Authentication/Storage/StorageInterface.php', 'to' => '/php/Zend/Authentication/Storage/StorageInterface.php', + 'role' => 'php', + 'tasks' => array(), ), - 1 => Array( + 'php/Zend/Authentication/Result.php' => Array( 'from' => 'Zend_Authentication-2.0.0beta4/php/Zend/Authentication/Result.php', 'to' => '/php/Zend/Authentication/Result.php', - ) + 'role' => 'php', + 'tasks' => array(), + ), + 'php/Test.php' => array ( + 'from' => 'Zend_Authentication-2.0.0beta4/php/Test.php', + 'to' => '/php/Test.php', + 'role' => 'script', + 'tasks' => array ( + array ( + 'from' => '@version@', + 'to' => 'version', + ) + ) + ), + 'renamedFile.php' => Array( + 'from' => 'Zend_Authentication-2.0.0beta4/renamedFile.php', + 'to' => 'correctFile.php', + 'role' => 'php', + 'tasks' => array(), + ), ); $this->assertSame($expectedFileActions, $fileActions); } + + public function testShouldPerformReplacements() + { + $from = tempnam(sys_get_temp_dir(), 'pear-extract'); + $to = $from.'-to'; + + $original = 'replaced: @placeholder@; not replaced: @another@; replaced again: @placeholder@'; + $expected = 'replaced: value; not replaced: @another@; replaced again: value'; + + file_put_contents($from, $original); + + $extractor = new PearPackageExtractor($from); + $method = new \ReflectionMethod($extractor, 'copyFile'); + $method->setAccessible(true); + + $method->invoke($extractor, $from, $to, array(array('from' => '@placeholder@', 'to' => 'variable')), array('variable' => 'value')); + $result = file_get_contents($to); + + unlink($to); + unlink($from); + + $this->assertEquals($expected, $result); + } } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 4badc2f37..496092dc6 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -106,7 +106,7 @@ class LibraryInstallerTest extends TestCase $package = $this->createPackageMock(); $package - ->expects($this->once()) + ->expects($this->any()) ->method('getPrettyName') ->will($this->returnValue('some/package')); @@ -136,7 +136,7 @@ class LibraryInstallerTest extends TestCase $target = $this->createPackageMock(); $initial - ->expects($this->once()) + ->expects($this->any()) ->method('getPrettyName') ->will($this->returnValue('package1')); @@ -175,7 +175,7 @@ class LibraryInstallerTest extends TestCase $package = $this->createPackageMock(); $package - ->expects($this->once()) + ->expects($this->any()) ->method('getPrettyName') ->will($this->returnValue('pkg')); diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index 2f93d77e2..8136651b2 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -138,7 +138,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('pkg1')); $package1 ->expects($this->once()) - ->method('getPrettyVersion') + ->method('getVersion') ->will($this->returnValue('1.0.0-beta')); $package2 @@ -147,7 +147,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('pkg2')); $package2 ->expects($this->once()) - ->method('getPrettyVersion') + ->method('getVersion') ->will($this->returnValue('0.1.10')); $json diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index a1ce4f7c6..8a93a60aa 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -118,8 +118,8 @@ class ChannelReaderTest extends TestCase $packages = $ref->invoke($reader, $channelInfo, new VersionParser()); $expectedPackage = new MemoryPackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1'); - $expectedPackage->setType('library'); - $expectedPackage->setDistType('pear'); + $expectedPackage->setType('pear-library'); + $expectedPackage->setDistType('file'); $expectedPackage->setDescription('description'); $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz"); $expectedPackage->setAutoload(array('classmap' => array(''))); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a4f2e7cec..1974415d3 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -10,5 +10,7 @@ * file that was distributed with this source code. */ +error_reporting(E_ALL); + $loader = require __DIR__.'/../src/bootstrap.php'; $loader->add('Composer\Test', __DIR__); From 59773dd9f616a8e6cab679966ac4c4de07bc1992 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Sat, 30 Jun 2012 14:09:11 +1100 Subject: [PATCH 08/10] Use trim filter on 'php' location --- src/Composer/Downloader/PearPackageExtractor.php | 5 +++-- src/Composer/Installer/PearInstaller.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/PearPackageExtractor.php b/src/Composer/Downloader/PearPackageExtractor.php index eeb88806c..e33cd07a8 100644 --- a/src/Composer/Downloader/PearPackageExtractor.php +++ b/src/Composer/Downloader/PearPackageExtractor.php @@ -163,9 +163,10 @@ class PearPackageExtractor private function applyRelease(&$actions, $releaseNodes, $vars) { foreach ($releaseNodes as $releaseNode) { - $requiredOs = (string) ($releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name) ?: ''; - if ($requiredOs && $vars['os'] != $requiredOs) + $requiredOs = $releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name ? (string) $releaseNode->installconditions->os->name : ''; + if ($requiredOs && $vars['os'] != $requiredOs) { continue; + } if ($releaseNode->filelist) { foreach ($releaseNode->filelist->children() as $action) { diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php index 75f8d0008..47ed4f70b 100644 --- a/src/Composer/Installer/PearInstaller.php +++ b/src/Composer/Installer/PearInstaller.php @@ -62,7 +62,7 @@ class PearInstaller extends LibraryInstaller $vars = array( 'os' => $isWindows ? 'windows' : 'linux', - 'php_bin' => ($isWindows ? getenv('PHPRC') .'php.exe' : `which php`), + 'php_bin' => ($isWindows ? getenv('PHPRC') .'php.exe' : trim(`which php`)), 'pear_php' => $this->getInstallPath($package), 'bin_dir' => $this->getInstallPath($package) . '/bin', 'php_dir' => $this->getInstallPath($package), From 9ba3deb91bbf47e2496c48973ba980548ba68133 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Mon, 2 Jul 2012 10:10:23 +1100 Subject: [PATCH 09/10] Fix text spacing and remove excess getInstallPath call in LibraryInstaller::update --- doc/05-repositories.md | 4 +--- src/Composer/Installer/LibraryInstaller.php | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index c7e1f34d5..2cd0af19b 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -235,7 +235,6 @@ Example: * BasePackage, requires nothing * IntermediatePackage, depends on BasePackage * TopLevelPackage1 and TopLevelPackage2 both dependth on IntermediatePackage. - For composer it looks like: * "pear-pear.foobar.repo/IntermediatePackage" depends on "pear-pear.foobar.repo/BasePackage", * "pear-pear.foobar.repo/TopLevelPackage1" depends on "pear-pear.foobar.repo/IntermediatePackage", @@ -243,10 +242,10 @@ Example: When you update one of your packages to composer naming scheme or made it available through vcs your older dependencies would not see new version cause it would be named like "foobar/IntermediatePackage". - Specifying 'vendor-alias' for pear repository you will get all its packages aliased with composer-like names. Following example would take BasePackage, TopLevelPackage1 and TopLevelPackage2 packages from pear repository and IntermediatePackage from github repository: + { "repositories": [ { @@ -265,7 +264,6 @@ Example: } } - ### Package If you want to use a project that does not support composer through any of the diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 2c3facad7..4518a7cff 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -99,10 +99,9 @@ class LibraryInstaller implements InstallerInterface } $this->initializeVendorDir(); - $downloadPath = $this->getInstallPath($initial); $this->removeBinaries($initial); - $this->updateCode($initial, $target, $downloadPath); + $this->updateCode($initial, $target); $this->installBinaries($target); $repo->removePackage($initial); if (!$repo->hasPackage($target)) { From 22aef0124e2a8f6d5fa7395410faf3f498ffc044 Mon Sep 17 00:00:00 2001 From: Alexey Prilipko Date: Mon, 2 Jul 2012 23:11:37 +1100 Subject: [PATCH 10/10] Revert Locker change, ignore PEAR packages whose version cannot be parsed. --- doc/05-repositories.md | 16 +++++------- src/Composer/Package/Locker.php | 2 +- src/Composer/Repository/PearRepository.php | 30 +++++----------------- tests/Composer/Test/Package/LockerTest.php | 4 +-- 4 files changed, 16 insertions(+), 36 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 2cd0af19b..e79a7767f 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -229,22 +229,18 @@ In this case the short name of the channel is `pear2`, so the It is possible to alias all pear channel packages with custom name. Example: - You own private pear repository and going to use composer abilities to bring - dependencies from vcs or transit to composer repository scheme. - List of packages: +You own private pear repository and going to use composer abilities to bring dependencies from vcs or transit to composer repository scheme. +Your repository list of packages: * BasePackage, requires nothing * IntermediatePackage, depends on BasePackage * TopLevelPackage1 and TopLevelPackage2 both dependth on IntermediatePackage. - For composer it looks like: + +For composer it looks like: * "pear-pear.foobar.repo/IntermediatePackage" depends on "pear-pear.foobar.repo/BasePackage", * "pear-pear.foobar.repo/TopLevelPackage1" depends on "pear-pear.foobar.repo/IntermediatePackage", * "pear-pear.foobar.repo/TopLevelPackage2" depends on "pear-pear.foobar.repo/IntermediatePackage" - When you update one of your packages to composer naming scheme or made it - available through vcs your older dependencies would not see new version cause it would be named - like "foobar/IntermediatePackage". - Specifying 'vendor-alias' for pear repository you will get all its packages aliased with composer-like names. - Following example would take BasePackage, TopLevelPackage1 and TopLevelPackage2 packages from pear repository - and IntermediatePackage from github repository: + +When you update one of your packages to composer naming scheme or made it available through vcs, your older dependencies would not see new version, cause it would be named like "foobar/IntermediatePackage". Specifying 'vendor-alias' for pear repository, you will get all its packages aliased with composer-like names. Following example would take BasePackage, TopLevelPackage1 and TopLevelPackage2 packages from pear repository and IntermediatePackage from github repository: { "repositories": [ diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 9104ce780..8b37e6c0d 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -211,7 +211,7 @@ class Locker } $name = $package->getPrettyName(); - $version = $package->getVersion(); + $version = $package->getPrettyVersion(); if (!$name || !$version) { throw new \LogicException(sprintf( diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 180ee5584..d5f070549 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -91,9 +91,13 @@ class PearRepository extends ArrayRepository $result = array(); foreach ($channelInfo->getPackages() as $packageDefinition) { foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { - $normalizedVersion = $this->parseVersion($version); - if (!$normalizedVersion) { - continue; // skip packages with unparsable versions + try { + $normalizedVersion = $versionParser->normalize($version); + } catch (\UnexpectedValueException $e) { + if ($this->io->isVerbose()) { + $this->io->write('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage()); + } + continue; } $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); @@ -175,24 +179,4 @@ class PearRepository extends ArrayRepository return "pear-{$channelName}/{$packageName}"; } - - /** - * Softened version parser. - * - * @param string $version - * @return null|string - */ - private function parseVersion($version) - { - if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) { - $version = $matches[1] - .(!empty($matches[2]) ? $matches[2] : '.0') - .(!empty($matches[3]) ? $matches[3] : '.0') - .(!empty($matches[4]) ? $matches[4] : '.0'); - - return $version; - } - - return null; - } } diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index 8136651b2..2f93d77e2 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -138,7 +138,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('pkg1')); $package1 ->expects($this->once()) - ->method('getVersion') + ->method('getPrettyVersion') ->will($this->returnValue('1.0.0-beta')); $package2 @@ -147,7 +147,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('pkg2')); $package2 ->expects($this->once()) - ->method('getVersion') + ->method('getPrettyVersion') ->will($this->returnValue('0.1.10')); $json