Browse files

Merge remote-tracking branch 'upstream/master' into feature-dist

  • Loading branch information...
2 parents 7ad3185 + 0a55707 commit 926a36d04e760222b225ba5365f1ecc8f5f1ee77 @till committed Jul 3, 2012
Showing with 2,701 additions and 673 deletions.
  1. +3 −2 doc/04-schema.md
  2. +37 −1 doc/05-repositories.md
  3. +6 −3 src/Composer/Autoload/AutoloadGenerator.php
  4. +1 −1 src/Composer/Command/CreateProjectCommand.php
  5. +10 −3 src/Composer/Command/ShowCommand.php
  6. +0 −1 src/Composer/Command/ValidateCommand.php
  7. +4 −6 src/Composer/Config.php
  8. +4 −0 src/Composer/DependencyResolver/Transaction.php
  9. +0 −52 src/Composer/Downloader/PearDownloader.php
  10. +90 −28 src/Composer/Downloader/PearPackageExtractor.php
  11. +22 −16 src/Composer/Factory.php
  12. +60 −7 src/Composer/Installer.php
  13. +0 −38 src/Composer/Installer/InstallationManager.php
  14. +8 −12 src/Composer/Installer/InstallerInstaller.php
  15. +43 −19 src/Composer/Installer/LibraryInstaller.php
  16. +137 −0 src/Composer/Installer/PearInstaller.php
  17. +12 −8 src/Composer/Json/JsonFile.php
  18. +2 −2 src/Composer/Json/JsonValidationException.php
  19. +1 −1 src/Composer/Package/Loader/JsonLoader.php
  20. +4 −1 src/Composer/Repository/ArrayRepository.php
  21. +81 −0 src/Composer/Repository/Pear/BaseChannelReader.php
  22. +67 −0 src/Composer/Repository/Pear/ChannelInfo.php
  23. +91 −0 src/Composer/Repository/Pear/ChannelReader.php
  24. +164 −0 src/Composer/Repository/Pear/ChannelRest10Reader.php
  25. +136 −0 src/Composer/Repository/Pear/ChannelRest11Reader.php
  26. +60 −0 src/Composer/Repository/Pear/DependencyConstraint.php
  27. +50 −0 src/Composer/Repository/Pear/DependencyInfo.php
  28. +314 −0 src/Composer/Repository/Pear/PackageDependencyParser.php
  29. +94 −0 src/Composer/Repository/Pear/PackageInfo.php
  30. +50 −0 src/Composer/Repository/Pear/ReleaseInfo.php
  31. +95 −307 src/Composer/Repository/PearRepository.php
  32. +11 −6 src/Composer/Repository/Vcs/GitBitbucketDriver.php
  33. +3 −2 src/Composer/Repository/Vcs/GitDriver.php
  34. +10 −6 src/Composer/Repository/Vcs/GitHubDriver.php
  35. +11 −6 src/Composer/Repository/Vcs/HgBitbucketDriver.php
  36. +1 −1 src/Composer/Repository/Vcs/HgDriver.php
  37. +3 −2 src/Composer/Repository/Vcs/SvnDriver.php
  38. +21 −18 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  39. +11 −1 tests/Composer/Test/Downloader/Fixtures/Package_v2.1/package.xml
  40. +0 −38 tests/Composer/Test/Downloader/PearDownloaderTest.php
  41. +62 −10 tests/Composer/Test/Downloader/PearPackageExtractorTest.php
  42. +55 −0 tests/Composer/Test/Fixtures/installer/update-alias-lock.test
  43. +37 −0 tests/Composer/Test/Fixtures/installer/update-alias.test
  44. +46 −0 tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test
  45. +0 −28 tests/Composer/Test/Installer/InstallationManagerTest.php
  46. +34 −12 tests/Composer/Test/Installer/InstallerInstallerTest.php
  47. +27 −12 tests/Composer/Test/Installer/LibraryInstallerTest.php
  48. +24 −3 tests/Composer/Test/InstallerTest.php
  49. +1 −0 tests/Composer/Test/Json/JsonFileTest.php
  50. +6 −1 tests/Composer/Test/Mock/FactoryMock.php
  51. +7 −2 tests/Composer/Test/Mock/InstallationManagerMock.php
  52. +40 −0 tests/Composer/Test/Mock/RemoteFilesystemMock.php
  53. +151 −0 tests/Composer/Test/Repository/Pear/ChannelReaderTest.php
  54. +41 −0 tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php
  55. +37 −0 tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php
  56. +167 −0 tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json
  57. +9 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml
  58. +1 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt
  59. +14 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml
  60. +9 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml
  61. +1 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt
  62. +12 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml
  63. +6 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml
  64. +5 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml
  65. +97 −0 tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml
  66. +12 −0 tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml
  67. +12 −0 tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml
  68. +58 −0 tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php
  69. +11 −17 tests/Composer/Test/Repository/PearRepositoryTest.php
  70. +2 −0 tests/bootstrap.php
View
5 doc/04-schema.md
@@ -280,10 +280,11 @@ Example:
Lists packages required by this package. The package will not be installed
unless those requirements can be met.
-#### require-dev
+#### require-dev <span>(root-only)</span>
Lists packages required for developing this package, or running
-tests, etc. They are installed if install or update is ran with `--dev`.
+tests, etc. The dev requirements of the root package only will be installed
+if `install` or `update` is ran with `--dev`.
Packages listed here and their dependencies can not overrule the resolution
found with the packages listed in require. This is even true if a different
View
38 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,41 @@ 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.
+Your repository 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
View
9 src/Composer/Autoload/AutoloadGenerator.php
@@ -12,6 +12,7 @@
namespace Composer\Autoload;
+use Composer\Config;
use Composer\Installer\InstallationManager;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
@@ -24,12 +25,14 @@
*/
class AutoloadGenerator
{
- public function dump(RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir)
+ public function dump(Config $config, RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir)
{
$filesystem = new Filesystem();
- $filesystem->ensureDirectoryExists($installationManager->getVendorPath());
+ $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
+ $vendorPath = strtr(realpath($config->get('vendor-dir')), '\\', '/');
+ $targetDir = $vendorPath.'/'.$targetDir;
$filesystem->ensureDirectoryExists($targetDir);
- $vendorPath = strtr(realpath($installationManager->getVendorPath()), '\\', '/');
+
$relVendorPath = $filesystem->findShortestPath(getcwd(), $vendorPath, true);
$vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
$vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true);
View
2 src/Composer/Command/CreateProjectCommand.php
@@ -119,11 +119,11 @@ public function installProject(IOInterface $io, $packageName, $directory = null,
}
}
+ $io->write('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>', true);
if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) {
$package->setSourceReference(substr($package->getPrettyVersion(), 4));
}
- $io->write('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>', true);
$projectInstaller = new ProjectInstaller($directory, $dm);
$projectInstaller->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), $package);
if ($package->getRepository() instanceof NotifiableRepositoryInterface) {
View
13 src/Composer/Command/ShowCommand.php
@@ -19,6 +19,7 @@
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
@@ -39,6 +40,7 @@ protected function configure()
new InputArgument('version', InputArgument::OPTIONAL, 'Version to inspect'),
new InputOption('installed', null, InputOption::VALUE_NONE, 'List installed packages only'),
new InputOption('platform', null, InputOption::VALUE_NONE, 'List platform packages only'),
+ new InputOption('self', null, InputOption::VALUE_NONE, 'Show the root package information'),
))
->setHelp(<<<EOT
The show command displays detailed information about a package, or
@@ -53,7 +55,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
{
// init repos
$platformRepo = new PlatformRepository;
- if ($input->getOption('platform')) {
+ if ($input->getOption('self')) {
+ $package = $this->getComposer(false)->getPackage();
+ $repos = $installedRepo = new ArrayRepository(array($package));
+ } elseif ($input->getOption('platform')) {
$repos = $installedRepo = $platformRepo;
} elseif ($input->getOption('installed')) {
$composer = $this->getComposer();
@@ -70,8 +75,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
// show single package or single version
- if ($input->getArgument('package')) {
- $package = $this->getPackage($input, $output, $installedRepo, $repos);
+ if ($input->getArgument('package') || !empty($package)) {
+ if (empty($package)) {
+ $package = $this->getPackage($input, $output, $installedRepo, $repos);
+ }
if (!$package) {
throw new \InvalidArgumentException('Package '.$input->getArgument('package').' not found');
}
View
1 src/Composer/Command/ValidateCommand.php
@@ -91,7 +91,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
}
} catch (\Exception $e) {
- $output->writeln('<error>' . $file . ' contains a JSON Syntax Error:</error>');
$output->writeln('<error>' . $e->getMessage() . '</error>');
return 1;
View
10 src/Composer/Config.php
@@ -64,11 +64,9 @@ public function merge(array $config)
}
// disable a repository with an anonymous {"name": false} repo
- foreach ($this->repositories as $repoName => $repoSpec) {
- if (isset($repository[$repoName]) && false === $repository[$repoName]) {
- unset($this->repositories[$repoName]);
- continue 2;
- }
+ if (1 === count($repository) && false === current($repository)) {
+ unset($this->repositories[key($repository)]);
+ continue;
}
// store repo
@@ -105,7 +103,7 @@ public function get($key)
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
- return $this->process(getenv($env) ?: $this->config[$key]);
+ return rtrim($this->process(getenv($env) ?: $this->config[$key]), '/\\');
case 'home':
return rtrim($this->process($this->config[$key]), '/\\');
View
4 src/Composer/DependencyResolver/Transaction.php
@@ -183,6 +183,10 @@ protected function findUpdates()
$literal = $decision[Decisions::DECISION_LITERAL];
$package = $this->pool->literalToPackage($literal);
+ if ($package instanceof AliasPackage) {
+ continue;
+ }
+
// !wanted & installed
if ($literal <= 0 && isset($this->installedMap[$package->getId()])) {
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
View
52 src/Composer/Downloader/PearDownloader.php
@@ -1,52 +0,0 @@
-<?php
-
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- * Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer\Downloader;
-
-use Composer\Package\PackageInterface;
-
-/**
- * Downloader for pear packages
- *
- * @author Jordi Boggiano <j.boggiano@seld.be>
- * @author Kirill chEbba Chebunin <iam@chebba.org>
- */
-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('');
- }
-}
View
118 src/Composer/Downloader/PearPackageExtractor.php
@@ -35,22 +35,22 @@ public function __construct($file)
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 @@ public function extractTo($target, $role = 'php')
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,28 +72,49 @@ public function extractTo($target, $role = 'php')
/**
* 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.');
}
$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 @@ private function copyFile($from, $to)
* 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,21 +141,54 @@ private function buildCopyActions($source, $role)
$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.');
}
return $result;
}
- private function buildSourceList10($children, $targetRole, $source = '', $target = '', $role = null)
+ private function applyRelease(&$actions, $releaseNodes, $vars)
+ {
+ foreach ($releaseNodes as $releaseNode) {
+ $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) {
+ 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,39 +199,47 @@ private function buildSourceList10($children, $targetRole, $source = '', $target
$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());
}
}
}
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);
}
}
}
View
38 src/Composer/Factory.php
@@ -158,10 +158,7 @@ public function createComposer(IOInterface $io, $localConfig = null)
$dm = $this->createDownloadManager($io);
// initialize installation manager
- $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir, $io);
-
- // purge packages if they have been deleted on the filesystem
- $this->purgePackages($rm, $im);
+ $im = $this->createInstallationManager($config);
// initialize composer
$composer = new Composer();
@@ -171,6 +168,12 @@ public function createComposer(IOInterface $io, $localConfig = null)
$composer->setDownloadManager($dm);
$composer->setInstallationManager($im);
+ // add installers to the manager
+ $this->createDefaultInstallers($im, $composer, $io);
+
+ // purge packages if they have been deleted on the filesystem
+ $this->purgePackages($rm, $im);
+
// init locker if possible
if (isset($composerFile)) {
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
@@ -222,7 +225,6 @@ public function createDownloadManager(IOInterface $io)
$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));
@@ -232,21 +234,25 @@ public function createDownloadManager(IOInterface $io)
}
/**
- * @param Repository\RepositoryManager $rm
- * @param Downloader\DownloadManager $dm
- * @param string $vendorDir
- * @param string $binDir
- * @param IO\IOInterface $io
+ * @param Config $config
* @return Installer\InstallationManager
*/
- protected function createInstallationManager(Repository\RepositoryManager $rm, Downloader\DownloadManager $dm, $vendorDir, $binDir, IOInterface $io)
+ protected function createInstallationManager(Config $config)
{
- $im = new Installer\InstallationManager($vendorDir);
- $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $io, null));
- $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $io, $im, $rm->getLocalRepositories()));
- $im->addInstaller(new Installer\MetapackageInstaller($io));
+ return new Installer\InstallationManager($config->get('vendor-dir'));
+ }
- return $im;
+ /**
+ * @param Installer\InstallationManager $im
+ * @param Composer $composer
+ * @param IO\IOInterface $io
+ */
+ 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));
}
/**
View
67 src/Composer/Installer.php
@@ -21,6 +21,7 @@
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Downloader\DownloadManager;
use Composer\Installer\InstallationManager;
+use Composer\Config;
use Composer\Installer\NoopInstaller;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
@@ -49,6 +50,11 @@ class Installer
protected $io;
/**
+ * @var Config
+ */
+ protected $config;
+
+ /**
* @var PackageInterface
*/
protected $package;
@@ -105,6 +111,7 @@ class Installer
* Constructor
*
* @param IOInterface $io
+ * @param Config $config
* @param PackageInterface $package
* @param DownloadManager $downloadManager
* @param RepositoryManager $repositoryManager
@@ -113,9 +120,10 @@ class Installer
* @param EventDispatcher $eventDispatcher
* @param AutoloadGenerator $autoloadGenerator
*/
- public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
+ public function __construct(IOInterface $io, Config $config, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
{
$this->io = $io;
+ $this->config = $config;
$this->package = $package;
$this->downloadManager = $downloadManager;
$this->repositoryManager = $repositoryManager;
@@ -201,7 +209,7 @@ public function run()
// write autoloader
$this->io->write('<info>Generating autoload files</info>');
$localRepos = new CompositeRepository($this->repositoryManager->getLocalRepositories());
- $this->autoloadGenerator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath() . '/composer');
+ $this->autoloadGenerator->dump($this->config, $localRepos, $this->package, $this->installationManager, 'composer');
if ($this->runScripts) {
// dispatch post event
@@ -229,7 +237,8 @@ protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = fa
$localRepo,
$devMode,
$this->package->getRequires(),
- $this->package->getDevRequires());
+ $this->package->getDevRequires()
+ );
// creating repository pool
$pool = new Pool($minimumStability, $stabilityFlags);
@@ -287,18 +296,48 @@ protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = fa
// fix the version of all installed packages (+ platform) that are not
// in the current local repo to prevent rogue updates (e.g. non-dev
// updating when in dev)
- //
- // if the updateWhitelist is enabled, packages not in it are also fixed
- // to their currently installed version
foreach ($installedRepo->getPackages() as $package) {
- if ($package->getRepository() === $localRepo && (!$this->updateWhitelist || $this->isUpdateable($package))) {
+ if ($package->getRepository() === $localRepo) {
continue;
}
$constraint = new VersionConstraint('=', $package->getVersion());
$request->install($package->getName(), $constraint);
}
+ // if the updateWhitelist is enabled, packages not in it are also fixed
+ // to the version specified in the lock, or their currently installed version
+ if ($this->update && $this->updateWhitelist) {
+ if ($this->locker->isLocked($devMode)) {
+ $currentPackages = $this->locker->getLockedPackages($devMode);
+ } else {
+ $currentPackages = $installedRepo->getPackages();
+ }
+
+ // collect links from composer as well as installed packages
+ $candidates = array();
+ foreach ($links as $link) {
+ $candidates[$link->getTarget()] = true;
+ }
+ foreach ($localRepo->getPackages() as $package) {
+ $candidates[$package->getName()] = true;
+ }
+
+ // fix them to the version in lock (or currently installed) if they are not updateable
+ foreach ($candidates as $candidate => $dummy) {
+ foreach ($currentPackages as $curPackage) {
+ if ($curPackage->getName() === $candidate) {
+ if ($this->isUpdateable($curPackage)) {
+ break;
+ }
+
+ $constraint = new VersionConstraint('=', $curPackage->getVersion());
+ $request->install($curPackage->getName(), $constraint);
+ }
+ }
+ }
+ }
+
// prepare solver
$policy = new DefaultPolicy();
$solver = new Solver($policy, $pool, $installedRepo);
@@ -561,6 +600,7 @@ public static function create(IOInterface $io, Composer $composer, EventDispatch
return new static(
$io,
+ $composer->getConfig(),
$composer->getPackage(),
$composer->getDownloadManager(),
$composer->getRepositoryManager(),
@@ -644,6 +684,19 @@ public function setRunScripts($runScripts = true)
}
/**
+ * set the config instance
+ *
+ * @param Config $config
+ * @return Installer
+ */
+ public function setConfig(Config $config)
+ {
+ $this->config = $config;
+
+ return $this;
+ }
+
+ /**
* run in verbose mode
*
* @param boolean $verbose
View
38 src/Composer/Installer/InstallationManager.php
@@ -35,29 +35,6 @@ class InstallationManager
{
private $installers = array();
private $cache = array();
- private $vendorPath;
-
- /**
- * Creates an instance of InstallationManager
- *
- * @param string $vendorDir Relative path to the vendor directory
- * @throws \InvalidArgumentException
- */
- public function __construct($vendorDir = 'vendor')
- {
- $fs = new Filesystem();
-
- if ($fs->isAbsolutePath($vendorDir)) {
- $basePath = getcwd();
- $relativePath = $fs->findShortestPath($basePath.'/file', $vendorDir);
- if ($fs->isAbsolutePath($relativePath)) {
- throw new \InvalidArgumentException("Vendor dir ($vendorDir) must be accessible from the directory ($basePath).");
- }
- $this->vendorPath = $relativePath;
- } else {
- $this->vendorPath = rtrim($vendorDir, '/');
- }
- }
/**
* Adds installer
@@ -217,21 +194,6 @@ public function getInstallPath(PackageInterface $package)
return $installer->getInstallPath($package);
}
- /**
- * Returns the vendor path
- *
- * @param boolean $absolute Whether or not to return an absolute path
- * @return string path
- */
- public function getVendorPath($absolute = false)
- {
- if (!$absolute) {
- return $this->vendorPath;
- }
-
- return getcwd().DIRECTORY_SEPARATOR.$this->vendorPath;
- }
-
private function notifyInstall(PackageInterface $package)
{
if ($package->getRepository() instanceof NotifiableRepositoryInterface) {
View
20 src/Composer/Installer/InstallerInstaller.php
@@ -12,9 +12,9 @@
namespace Composer\Installer;
+use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Autoload\AutoloadGenerator;
-use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
@@ -29,19 +29,15 @@ class InstallerInstaller extends LibraryInstaller
private static $classCounter = 0;
/**
- * @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 InstallationManager $im installation manager
- * @param array $localRepositories array of InstalledRepositoryInterface
+ * @param IOInterface $io
+ * @param Composer $composer
*/
- public function __construct($vendorDir, $binDir, DownloadManager $dm, IOInterface $io, InstallationManager $im, array $localRepositories)
+ public function __construct(IOInterface $io, Composer $composer, $type = 'library')
{
- parent::__construct($vendorDir, $binDir, $dm, $io, 'composer-installer');
- $this->installationManager = $im;
+ parent::__construct($io, $composer, 'composer-installer');
+ $this->installationManager = $composer->getInstallationManager();
- foreach ($localRepositories as $repo) {
+ foreach ($composer->getRepositoryManager()->getLocalRepositories() as $repo) {
foreach ($repo->getPackages() as $package) {
if ('composer-installer' === $package->getType()) {
$this->registerInstaller($package);
@@ -99,7 +95,7 @@ private function registerInstaller(PackageInterface $package)
self::$classCounter++;
}
- $installer = new $class($this->vendorDir, $this->binDir, $this->downloadManager, $this->io);
+ $installer = new $class($this->io, $this->composer);
$this->installationManager->addInstaller($installer);
}
}
View
62 src/Composer/Installer/LibraryInstaller.php
@@ -12,6 +12,7 @@
namespace Composer\Installer;
+use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
@@ -26,31 +27,30 @@
*/
class LibraryInstaller implements InstallerInterface
{
+ protected $composer;
protected $vendorDir;
protected $binDir;
protected $downloadManager;
protected $io;
- private $type;
- private $filesystem;
+ protected $type;
+ protected $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
+ * @param IOInterface $io
+ * @param Composer $composer
*/
- public function __construct($vendorDir, $binDir, DownloadManager $dm, IOInterface $io, $type = 'library')
+ public function __construct(IOInterface $io, Composer $composer, $type = 'library')
{
- $this->downloadManager = $dm;
+ $this->composer = $composer;
+ $this->downloadManager = $composer->getDownloadManager();
$this->io = $io;
$this->type = $type;
$this->filesystem = new Filesystem();
- $this->vendorDir = rtrim($vendorDir, '/');
- $this->binDir = rtrim($binDir, '/');
+ $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
+ $this->binDir = rtrim($composer->getConfig()->get('bin-dir'), '/');
}
/**
@@ -82,7 +82,7 @@ public function install(InstalledRepositoryInterface $repo, PackageInterface $pa
$this->removeBinaries($package);
}
- $this->downloadManager->download($package, $downloadPath);
+ $this->installCode($package);
$this->installBinaries($package);
if (!$repo->hasPackage($package)) {
$repo->addPackage(clone $package);
@@ -99,10 +99,9 @@ public function update(InstalledRepositoryInterface $repo, PackageInterface $ini
}
$this->initializeVendorDir();
- $downloadPath = $this->getInstallPath($initial);
$this->removeBinaries($initial);
- $this->downloadManager->update($initial, $target, $downloadPath);
+ $this->updateCode($initial, $target);
$this->installBinaries($target);
$repo->removePackage($initial);
if (!$repo->hasPackage($target)) {
@@ -123,7 +122,7 @@ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $
$downloadPath = $this->getInstallPath($package);
- $this->downloadManager->remove($package, $downloadPath);
+ $this->removeCode($package);
$this->removeBinaries($package);
$repo->removePackage($package);
@@ -146,12 +145,36 @@ public function getInstallPath(PackageInterface $package)
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 +216,11 @@ protected function installBinaries(PackageInterface $package)
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;
View
137 src/Composer/Installer/PearInstaller.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\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 <j.boggiano@seld.be>
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class PearInstaller extends LibraryInstaller
+{
+ /**
+ * Initializes library installer.
+ *
+ * @param IOInterface $io io instance
+ * @param Composer $composer
+ * @param string $type package type that this installer handles
+ */
+ public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library')
+ {
+ 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' : trim(`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;
+ }
+
+ protected function initializeBinDir()
+ {
+ parent::initializeBinDir();
+ file_put_contents($this->binDir.'/composer-php', $this->generateUnixyPhpProxyCode());
+ chmod($this->binDir.'/composer-php', 0777);
+ file_put_contents($this->binDir.'/composer-php.bat', $this->generateWindowsPhpProxyCode());
+ chmod($this->binDir.'/composer-php.bat', 0777);
+ }
+
+ private function generateWindowsPhpProxyCode()
+ {
+ return
+ "@echo off\r\n" .
+ "setlocal enabledelayedexpansion\r\n" .
+ "set BIN_DIR=%~dp0\r\n" .
+ "set VENDOR_DIR=%BIN_DIR%..\\\r\n" .
+ " set DIRS=.\r\n" .
+ "FOR /D %%V IN (%VENDOR_DIR%*) DO (\r\n" .
+ " FOR /D %%P IN (%%V\\*) DO (\r\n" .
+ " set DIRS=!DIRS!;%%~fP\r\n" .
+ " )\r\n" .
+ ")\r\n" .
+ "php.exe -d include_path=!DIRS! %*\r\n";
+ }
+
+ private function generateUnixyPhpProxyCode()
+ {
+ return
+ "#!/usr/bin/env sh\n".
+ "SRC_DIR=`pwd`\n".
+ "BIN_DIR=`dirname $(readlink -f $0)`\n".
+ "VENDOR_DIR=`dirname \$BIN_DIR`\n".
+ "cd \$BIN_DIR\n".
+ "DIRS=\"\"\n".
+ "for vendor in \$VENDOR_DIR/*; do\n".
+ " if [ -d \"\$vendor\" ]; then\n".
+ " for package in \$vendor/*; do\n".
+ " if [ -d \"\$package\" ]; then\n".
+ " DIRS=\"\${DIRS}:\${package}\"\n".
+ " fi\n".
+ " done\n".
+ " fi\n".
+ "done\n".
+ "cd \$SRC_DIR\n".
+ "`which php` -d include_path=\".\$DIRS\" $@\n";
+ }
+}
View
20 src/Composer/Json/JsonFile.php 100644 → 100755
@@ -15,6 +15,7 @@
use Composer\Composer;
use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
+use Seld\JsonLint\ParsingException;
use Composer\Util\RemoteFilesystem;
use Composer\Downloader\TransportException;
@@ -86,7 +87,7 @@ public function read()
throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage());
}
- return static::parseJson($json);
+ return static::parseJson($json, $this->path);
}
/**
@@ -126,7 +127,7 @@ public function validateSchema($schema = self::STRICT_SCHEMA)
$data = json_decode($content);
if (null === $data && 'null' !== $content) {
- self::validateSyntax($content);
+ self::validateSyntax($content, $this->path);
}
$schemaFile = __DIR__ . '/../../../res/composer-schema.json';
@@ -148,7 +149,7 @@ public function validateSchema($schema = self::STRICT_SCHEMA)
foreach ((array) $validator->getErrors() as $error) {
$errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message'];
}
- throw new JsonValidationException($errors);
+ throw new JsonValidationException('"'.$this->path.'" does not match the expected JSON schema', $errors);
}
return true;
@@ -265,14 +266,15 @@ public static function encode($data, $options = 448)
* Parses json string and returns hash.
*
* @param string $json json string
+ * @param string $file the json file
*
* @return mixed
*/
- public static function parseJson($json)
+ public static function parseJson($json, $file = null)
{
$data = json_decode($json, true);
if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
- self::validateSyntax($json);
+ self::validateSyntax($json, $file);
}
return $data;
@@ -282,21 +284,23 @@ public static function parseJson($json)
* Validates the syntax of a JSON string
*
* @param string $json
+ * @param string $file
* @return bool true on success
* @throws \UnexpectedValueException
+ * @throws JsonValidationException
*/
- protected static function validateSyntax($json)
+ protected static function validateSyntax($json, $file = null)
{
$parser = new JsonParser();
$result = $parser->lint($json);
if (null === $result) {
if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
- throw new \UnexpectedValueException('JSON file is not UTF-8 encoded');
+ throw new \UnexpectedValueException('"'.$file.'" is not UTF-8, could not parse as JSON');
}
return true;
}
- throw $result;
+ throw new ParsingException('"'.$file.'" does not contain valid JSON'."\n".$result->getMessage(), $result->getDetails());
}
}
View
4 src/Composer/Json/JsonValidationException.php
@@ -21,10 +21,10 @@ class JsonValidationException extends Exception
{
protected $errors;
- public function __construct(array $errors)
+ public function __construct($message, $errors = array())
{
- parent::__construct(implode("\n", $errors));
$this->errors = $errors;
+ parent::__construct($message);
}
public function getErrors()
View
2 src/Composer/Package/Loader/JsonLoader.php
@@ -24,7 +24,7 @@ public function load($json)
if ($json instanceof JsonFile) {
$config = $json->read();
} elseif (file_exists($json)) {
- $config = JsonFile::parseJson(file_get_contents($json));
+ $config = JsonFile::parseJson(file_get_contents($json), $json);
} elseif (is_string($json)) {
$config = JsonFile::parseJson($json);
}
View
5 src/Composer/Repository/ArrayRepository.php
@@ -105,7 +105,10 @@ public function addPackage(PackageInterface $package)
// create alias package on the fly if needed
if ($package->getAlias()) {
- $this->addPackage($this->createAliasPackage($package));
+ $alias = $this->createAliasPackage($package);
+ if (!$this->hasPackage($alias)) {
+ $this->addPackage($alias);
+ }
}
}
View
81 src/Composer/Repository/Pear/BaseChannelReader.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\Pear;
+
+use Composer\Util\RemoteFilesystem;
+
+/**
+ * Base PEAR Channel reader.
+ *
+ * Provides xml namespaces and red
+ *
+ * @author Alexey Prilipko <palex@farpost.com>
+ */
+abstract class BaseChannelReader
+{
+ /**
+ * PEAR REST Interface namespaces
+ */
+ 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(RemoteFilesystem $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(sprintf('The PEAR channel at ' . $origin . ' is broken. (Invalid XML at file `%s`)', $path));
+ }
+
+ return $xml;
+ }
+}
View
67 src/Composer/Repository/Pear/ChannelInfo.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\Pear;
+
+/**
+ * PEAR channel info
+ *
+ * @author Alexey Prilipko <palex@farpost.com>
+ */
+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;
+ }
+}
View
91 src/Composer/Repository/Pear/ChannelReader.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\Pear;
+
+use Composer\Util\RemoteFilesystem;
+
+/**
+ * PEAR Channel package reader.
+ *
+ * Reads channel packages info from and builds MemoryPackage's
+ *
+ * @author Alexey Prilipko <palex@farpost.com>
+ */
+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 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 (!$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);
+ }
+
+ /**
+ * Reads channel supported REST interfaces and selects one of them
+ *
+ * @param $channelXml \SimpleXMLElement
+ * @param $supportedVersions string[] supported PEAR REST protocols
+ * @return array|null hash with selected version and baseUrl
+ */
+ private function selectRestVersion($channelXml, $supportedVersions)
+ {
+ $channelXml->registerXPathNamespace('ns', self::CHANNEL_NS);
+
+ 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 null;
+ }
+}
View
164 src/Composer/Repository/Pear/ChannelRest10Reader.php
@@ -0,0 +1,164 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\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 <palex@farpost.com>
+ */
+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::ALL_PACKAGES_NS);
+ 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::PACKAGE_INFO_NS);
+
+ $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::ALL_RELEASES_NS);
+ 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;
+ }
+}
View
136 src/Composer/Repository/Pear/ChannelRest11Reader.php
@@ -0,0 +1,136 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\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 <palex@farpost.com>
+ */
+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::ALL_CATEGORIES_NS);
+ 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::CATEGORY_PACKAGES_INFO_NS);
+ 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::CATEGORY_PACKAGES_INFO_NS);
+ $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
+ );
+ }
+}
View
60 src/Composer/Repository/Pear/DependencyConstraint.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\Pear;
+
+/**
+ * PEAR package release dependency info