diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 5c17048e9..0ec7afd53 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -136,8 +136,8 @@ packagist. ## Autoloading -For libraries that follow the [PSR-0](https://github.com/php-fig/fig- -standards/blob/master/accepted/PSR-0.md) naming standard, composer generates a +For libraries that follow the [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) +naming standard, composer generates a `vendor/.composer/autoload.php` file for autoloading. You can simply include this file and you will get autoloading for free. diff --git a/doc/04-schema.md b/doc/04-schema.md index 2e1c3d2f6..909041bdd 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -6,9 +6,8 @@ This chapter will explain all of the options available in `composer.json`. We have a [JSON schema](http://json-schema.org) that documents the format and can also be used to validate your `composer.json`. In fact, it is used by the -`validate` command. You can find it at: [`Resources/composer- -schema.json`](https://github.com/composer/composer/blob/master/res -/composer-schema.json). +`validate` command. You can find it at: +[`Resources/composer-schema.json`](https://github.com/composer/composer/blob/master/res/composer-schema.json). ## Package root @@ -188,8 +187,8 @@ Optional. Autoload mapping for a PHP autoloader. -Currently only [PSR-0](https://github.com/php-fig/fig- -standards/blob/master/accepted/PSR-0.md) autoloading is supported. Under the +Currently only [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) +autoloading is supported. Under the `psr-0` key you define a mapping from namespaces to paths, relative to the package root. diff --git a/doc/06-community.md b/doc/06-community.md index 35a92092a..cdf394295 100644 --- a/doc/06-community.md +++ b/doc/06-community.md @@ -22,7 +22,7 @@ The most important guidelines are described as follows: ## IRC / mailing list -The developer mailing list is on [google groups](http://groups.google.com/group -/composer-dev) IRC channels are available for discussion as well, on +The developer mailing list is on [google groups](http://groups.google.com/group/composer-dev/) +IRC channels are available for discussion as well, on irc.freenode.org [#composer](irc://irc.freenode.org/composer) for users and [#composer-dev](irc://irc.freenode.org/composer-dev) for development. diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 5885b033d..426c1fb9e 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -20,9 +20,12 @@ use Composer\DependencyResolver; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Operation; +use Composer\Package\AliasPackage; use Composer\Package\MemoryPackage; +use Composer\Package\Link; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\PackageInterface; +use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; @@ -92,18 +95,36 @@ EOT $composer->getDownloadManager()->setPreferSource(true); } + $repoManager = $composer->getRepositoryManager(); + // create local repo, this contains all packages that are installed in the local project - $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $localRepo = $repoManager->getLocalRepository(); // create installed repo, this contains all local packages + platform packages (php & extensions) $installedRepo = new CompositeRepository(array($localRepo, new PlatformRepository())); if ($additionalInstalledRepository) { $installedRepo->addRepository($additionalInstalledRepository); } + // prepare aliased packages + if (!$update && $composer->getLocker()->isLocked()) { + $aliases = $composer->getLocker()->getAliases(); + } else { + $aliases = $composer->getPackage()->getAliases(); + } + foreach ($aliases as $alias) { + foreach ($repoManager->findPackages($alias['package'], $alias['version']) as $package) { + $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias'])); + } + foreach ($repoManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) { + $repoManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias'])); + $repoManager->getLocalRepository()->removePackage($package); + } + } + // creating repository pool $pool = new Pool; $pool->addRepository($installedRepo); - foreach ($composer->getRepositoryManager()->getRepositories() as $repository) { + foreach ($repoManager->getRepositories() as $repository) { $pool->addRepository($repository); } @@ -118,11 +139,11 @@ EOT $request = new Request($pool); if ($update) { $io->write('Updating dependencies'); - $installedPackages = $installedRepo->getPackages(); - $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); $request->updateAll(); + $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); + foreach ($links as $link) { $request->install($link->getTarget(), $link->getConstraint()); } @@ -135,7 +156,14 @@ EOT } foreach ($composer->getLocker()->getLockedPackages() as $package) { - $constraint = new VersionConstraint('=', $package->getVersion()); + $version = $package->getVersion(); + foreach ($aliases as $alias) { + if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) { + $version = $alias['alias']; + break; + } + } + $constraint = new VersionConstraint('=', $version); $request->install($package->getName(), $constraint); } } else { @@ -156,14 +184,13 @@ EOT // solve dependencies $operations = $solver->solve($request); - // execute operations - if (!$operations) { - $io->write('Nothing to install/update'); - } - // force dev packages to be updated to latest reference on update if ($update) { foreach ($localRepo->getPackages() as $package) { + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + // skip non-dev packages if (!$package->isDev()) { continue; @@ -179,13 +206,26 @@ EOT } // force update - $newPackage = $composer->getRepositoryManager()->findPackage($package->getName(), $package->getVersion()); + $newPackage = $repoManager->findPackage($package->getName(), $package->getVersion()); if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) { $operations[] = new UpdateOperation($package, $newPackage); } } } + // anti-alias local repository to allow updates to work fine + foreach ($repoManager->getLocalRepository()->getPackages() as $package) { + if ($package instanceof AliasPackage) { + $repoManager->getLocalRepository()->addPackage(clone $package->getAliasOf()); + $repoManager->getLocalRepository()->removePackage($package); + } + } + + // execute operations + if (!$operations) { + $io->write('Nothing to install/update'); + } + foreach ($operations as $operation) { if ($verbose) { $io->write((string) $operation); @@ -219,7 +259,7 @@ EOT if (!$dryRun) { if ($update || !$composer->getLocker()->isLocked()) { - $composer->getLocker()->lockPackages($localRepo->getPackages()); + $composer->getLocker()->setLockData($localRepo->getPackages(), $aliases); $io->write('Writing lock file'); } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 4450966b3..e376dad32 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -118,7 +118,7 @@ EOT // we only have a name, so search for the highest version of the given package $highestVersion = null; - foreach ($repos->findPackagesByName($input->getArgument('package')) as $package) { + foreach ($repos->findPackages($input->getArgument('package')) as $package) { if (null === $highestVersion || version_compare($package->getVersion(), $highestVersion->getVersion(), '>=')) { $highestVersion = $package; } @@ -164,7 +164,7 @@ EOT $versions = array(); - foreach ($repos->findPackagesByName($package->getName()) as $version) { + foreach ($repos->findPackages($package->getName()) as $version) { $versions[] = $version->getPrettyVersion(); } diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 63699d883..277188238 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -14,6 +14,7 @@ namespace Composer\DependencyResolver; use Composer\Repository\RepositoryInterface; use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; use Composer\Package\LinkConstraint\VersionConstraint; /** @@ -103,6 +104,16 @@ class DefaultPolicy implements PolicyInterface public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $ignoreReplace = false) { if ($a->getRepository() === $b->getRepository()) { + if ($a->getName() === $b->getName()) { + $aAliased = $a instanceof AliasPackage; + $bAliased = $b instanceof AliasPackage; + if ($aAliased && !$bAliased) { + return -1; // use a + } + if (!$aAliased && $bAliased) { + return 1; // use b + } + } if (!$ignoreReplace) { // return original, not replaced diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index ab85c78c2..fd39faed3 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -1393,7 +1393,7 @@ class Solver // if there are multiple candidates, then branch if (count($literals)) { - $this->branches[] = array($literals, -$level); + $this->branches[] = array($literals, $level); } return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule); @@ -1996,7 +1996,7 @@ class Solver $level = $lastLevel; $this->revert($level); - $why = $this->decisionQueueWhy[count($this->decisionQueueWhy)]; + $why = $this->decisionQueueWhy[count($this->decisionQueueWhy) - 1]; $oLevel = $level; $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 170749674..d5c413dd3 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -29,7 +29,10 @@ class GitDownloader extends VcsDownloader $ref = escapeshellarg($package->getSourceReference()); $path = escapeshellarg($path); $this->io->write(" Cloning ".$package->getSourceReference()); - $this->process->execute(sprintf('git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s', $url, $path, $ref), $ignoredOutput); + $command = sprintf('git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s', $url, $path, $ref); + if (0 !== $this->process->execute($command, $ignoredOutput)) { + throw new \RuntimeException('Failed to execute ' . $command); + } } /** @@ -40,7 +43,10 @@ class GitDownloader extends VcsDownloader $ref = escapeshellarg($target->getSourceReference()); $path = escapeshellarg($path); $this->io->write(" Checking out ".$target->getSourceReference()); - $this->process->execute(sprintf('cd %s && git fetch && git checkout %2$s && git reset --hard %2$s', $path, $ref), $ignoredOutput); + $command = sprintf('cd %s && git fetch && git checkout %2$s && git reset --hard %2$s', $path, $ref); + if (0 !== $this->process->execute($command, $ignoredOutput)) { + throw new \RuntimeException('Failed to execute ' . $command); + } } /** @@ -48,7 +54,11 @@ class GitDownloader extends VcsDownloader */ protected function enforceCleanDirectory($path) { - $this->process->execute(sprintf('cd %s && git status --porcelain', escapeshellarg($path)), $output); + $command = sprintf('cd %s && git status --porcelain', escapeshellarg($path)); + if (0 !== $this->process->execute($command, $output)) { + throw new \RuntimeException('Failed to execute ' . $command); + } + if (trim($output)) { throw new \RuntimeException('Source directory has uncommitted changes'); } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 22bae3667..e27976a4b 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -15,6 +15,7 @@ namespace Composer; use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Repository\RepositoryManager; +use Composer\Util\ProcessExecutor; /** * Creates an configured instance of composer. @@ -67,6 +68,11 @@ class Factory } $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir']; + // setup process timeout + if (false !== getenv('COMPOSER_PROCESS_TIMEOUT')) { + ProcessExecutor::setTimeout((int) getenv('COMPOSER_PROCESS_TIMEOUT')); + } + // initialize repository manager $rm = $this->createRepositoryManager($io); diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 2acb09d5b..6500e6b00 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -13,6 +13,7 @@ namespace Composer\Installer; use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; @@ -123,8 +124,12 @@ class InstallationManager */ public function install(InstallOperation $operation) { - $installer = $this->getInstaller($operation->getPackage()->getType()); - $installer->install($operation->getPackage()); + $package = $operation->getPackage(); + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + $installer = $this->getInstaller($package->getType()); + $installer->install($package); } /** @@ -135,7 +140,13 @@ class InstallationManager public function update(UpdateOperation $operation) { $initial = $operation->getInitialPackage(); + if ($initial instanceof AliasPackage) { + $initial = $initial->getAliasOf(); + } $target = $operation->getTargetPackage(); + if ($target instanceof AliasPackage) { + $target = $target->getAliasOf(); + } $initialType = $initial->getType(); $targetType = $target->getType(); @@ -156,8 +167,12 @@ class InstallationManager */ public function uninstall(UninstallOperation $operation) { - $installer = $this->getInstaller($operation->getPackage()->getType()); - $installer->uninstall($operation->getPackage()); + $package = $operation->getPackage(); + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + $installer = $this->getInstaller($package->getType()); + $installer->uninstall($package); } /** diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php new file mode 100644 index 000000000..02c0a9510 --- /dev/null +++ b/src/Composer/Package/AliasPackage.php @@ -0,0 +1,236 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Package\LinkConstraint\LinkConstraintInterface; +use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\PlatformRepository; + +/** + * @author Jordi Boggiano + */ +class AliasPackage extends BasePackage +{ + protected $version; + protected $dev; + protected $aliasOf; + + protected $requires; + protected $conflicts; + protected $provides; + protected $replaces; + protected $recommends; + protected $suggests; + + /** + * All descendants' constructors should call this parent constructor + * + * @param PackageInterface $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + */ + public function __construct($aliasOf, $version) + { + parent::__construct($aliasOf->getName()); + + $this->version = $version; + $this->aliasOf = $aliasOf; + $this->dev = 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4); + + foreach (self::$supportedLinkTypes as $type => $description) { + $links = $aliasOf->{'get'.ucfirst($description)}(); + $newLinks = array(); + foreach ($links as $link) { + // link is self.version, but must be replacing also the replaced version + if ('self.version' === $link->getPrettyConstraint()) { + $newLinks[] = new Link($link->getSource(), $link->getTarget(), new VersionConstraint('=', $this->version), $description, $this->version); + } + } + $this->$description = array_merge($links, $newLinks); + } + } + + public function getAliasOf() + { + return $this->aliasOf; + } + + /** + * {@inheritDoc} + */ + public function getVersion() + { + return $this->version; + } + + /** + * {@inheritDoc} + */ + public function getPrettyVersion() + { + return $this->version; + } + + /** + * {@inheritDoc} + */ + public function isDev() + { + return $this->dev; + } + + /** + * {@inheritDoc} + */ + function getRequires() + { + return $this->requires; + } + + /** + * {@inheritDoc} + */ + function getConflicts() + { + return $this->conflicts; + } + + /** + * {@inheritDoc} + */ + function getProvides() + { + return $this->provides; + } + + /** + * {@inheritDoc} + */ + function getReplaces() + { + return $this->replaces; + } + + /** + * {@inheritDoc} + */ + function getRecommends() + { + return $this->recommends; + } + + /** + * {@inheritDoc} + */ + function getSuggests() + { + return $this->suggests; + } + + /*************************************** + * Wrappers around the aliased package * + ***************************************/ + + public function getType() + { + return $this->aliasOf->getType(); + } + public function getTargetDir() + { + return $this->aliasOf->getTargetDir(); + } + public function getExtra() + { + return $this->aliasOf->getExtra(); + } + public function setInstallationSource($type) + { + $this->aliasOf->setInstallationSource($type); + } + public function getInstallationSource() + { + return $this->aliasOf->getInstallationSource(); + } + public function getSourceType() + { + return $this->aliasOf->getSourceType(); + } + public function getSourceUrl() + { + return $this->aliasOf->getSourceUrl(); + } + public function getSourceReference() + { + return $this->aliasOf->getSourceReference(); + } + public function getDistType() + { + return $this->aliasOf->getDistType(); + } + public function getDistUrl() + { + return $this->aliasOf->getDistUrl(); + } + public function getDistReference() + { + return $this->aliasOf->getDistReference(); + } + public function getDistSha1Checksum() + { + return $this->aliasOf->getDistSha1Checksum(); + } + public function getScripts() + { + return $this->aliasOf->getScripts(); + } + public function getLicense() + { + return $this->aliasOf->getLicense(); + } + public function getAutoload() + { + return $this->aliasOf->getAutoload(); + } + public function getRepositories() + { + return $this->aliasOf->getRepositories(); + } + public function getReleaseDate() + { + return $this->aliasOf->getReleaseDate(); + } + public function getBinaries() + { + return $this->aliasOf->getBinaries(); + } + public function getKeywords() + { + return $this->aliasOf->getKeywords(); + } + public function getDescription() + { + return $this->aliasOf->getDescription(); + } + public function getHomepage() + { + return $this->aliasOf->getHomepage(); + } + public function getAuthors() + { + return $this->aliasOf->getAuthors(); + } + public function __toString() + { + return $this->aliasOf->__toString(); + } +} diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index d5a0e5230..0c0eef3cb 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -24,8 +24,18 @@ use Composer\Repository\PlatformRepository; */ abstract class BasePackage implements PackageInterface { + public static $supportedLinkTypes = array( + 'require' => 'requires', + 'conflict' => 'conflicts', + 'provide' => 'provides', + 'replace' => 'replaces', + 'recommend' => 'recommends', + 'suggest' => 'suggests', + ); + protected $name; protected $prettyName; + protected $repository; protected $id; diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 7c992392c..98733372b 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -22,15 +22,6 @@ use Composer\Repository\RepositoryManager; */ class ArrayLoader { - protected $supportedLinkTypes = array( - 'require' => 'requires', - 'conflict' => 'conflicts', - 'provide' => 'provides', - 'replace' => 'replaces', - 'recommend' => 'recommends', - 'suggest' => 'suggests', - ); - protected $versionParser; public function __construct(VersionParser $parser = null) @@ -99,7 +90,9 @@ class ArrayLoader if (!empty($config['time'])) { try { - $package->setReleaseDate(new \DateTime($config['time'])); + $date = new \DateTime($config['time']); + $date->setTimezone(new \DateTimeZone('UTC')); + $package->setReleaseDate($date); } catch (\Exception $e) { } } @@ -141,7 +134,7 @@ class ArrayLoader $package->setDistSha1Checksum(isset($config['dist']['shasum']) ? $config['dist']['shasum'] : null); } - foreach ($this->supportedLinkTypes as $type => $description) { + foreach (Package\BasePackage::$supportedLinkTypes as $type => $description) { if (isset($config[$type])) { $method = 'set'.ucfirst($description); $package->{$method}( @@ -162,9 +155,10 @@ class ArrayLoader $links = array(); foreach ($linksSpecs as $packageName => $constraint) { if ('self.version' === $constraint) { - $constraint = $package->getPrettyVersion(); + $parsedConstraint = $this->versionParser->parseConstraints($package->getPrettyVersion()); + } else { + $parsedConstraint = $this->versionParser->parseConstraints($constraint); } - $parsedConstraint = $this->versionParser->parseConstraints($constraint); $links[] = new Package\Link($package->getName(), $packageName, $parsedConstraint, $description, $constraint); } diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 26765c78a..a6865f01e 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -43,6 +43,21 @@ class RootPackageLoader extends ArrayLoader $package = parent::load($config); + if (isset($config['require'])) { + $aliases = array(); + foreach ($config['require'] as $reqName => $reqVersion) { + if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) { + $aliases[] = array( + 'package' => strtolower($reqName), + 'version' => $this->versionParser->normalize($match[1]), + 'alias' => $this->versionParser->normalize($match[2]), + ); + } + } + + $package->setAliases($aliases); + } + if (isset($config['repositories'])) { foreach ($config['repositories'] as $index => $repo) { if (isset($repo['packagist']) && $repo['packagist'] === false) { diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index c55c0e467..205372481 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -91,6 +91,12 @@ class Locker return $packages; } + public function getAliases() + { + $lockList = $this->getLockData(); + return isset($lockList['aliases']) ? $lockList['aliases'] : array(); + } + public function getLockData() { if (!$this->isLocked()) { @@ -101,15 +107,17 @@ class Locker } /** - * Locks provided packages into lockfile. + * Locks provided data into lockfile. * * @param array $packages array of packages + * @param array $aliases array of aliases */ - public function lockPackages(array $packages) + public function setLockData(array $packages, array $aliases) { $lock = array( 'hash' => $this->hash, 'packages' => array(), + 'aliases' => $aliases, ); foreach ($packages as $package) { $name = $package->getPrettyName(); diff --git a/src/Composer/Package/MemoryPackage.php b/src/Composer/Package/MemoryPackage.php index a57f2b6f6..a2a711027 100644 --- a/src/Composer/Package/MemoryPackage.php +++ b/src/Composer/Package/MemoryPackage.php @@ -41,6 +41,7 @@ class MemoryPackage extends BasePackage protected $extra = array(); protected $binaries = array(); protected $scripts = array(); + protected $aliases = array(); protected $dev; protected $requires = array(); @@ -156,6 +157,22 @@ class MemoryPackage extends BasePackage return $this->scripts; } + /** + * @param array $aliases + */ + public function setAliases(array $aliases) + { + $this->aliases = $aliases; + } + + /** + * {@inheritDoc} + */ + public function getAliases() + { + return $this->aliases; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 1f19a835f..cf68cc02d 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -293,6 +293,13 @@ interface PackageInterface */ function getDescription(); + /** + * Returns the package binaries + * + * @return string + */ + function getBinaries(); + /** * Returns the package homepage * diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 716c24731..19cd240a8 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -34,6 +34,11 @@ class VersionParser { $version = trim($version); + // ignore aliases and just assume the alias is required instead of the source + if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $version, $match)) { + $version = $match[2]; + } + // match master-like branches if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { return '9999999-dev'; diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 36ed56b0b..a46bfd20d 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -44,14 +44,21 @@ class ArrayRepository implements RepositoryInterface /** * {@inheritDoc} */ - public function findPackagesByName($name) + public function findPackages($name, $version = null) { // normalize name $name = strtolower($name); + + // normalize version + if (null !== $version) { + $versionParser = new VersionParser(); + $version = $versionParser->normalize($version); + } + $packages = array(); foreach ($this->getPackages() as $package) { - if ($package->getName() === $name) { + if ($package->getName() === $name && (null === $version || $version === $package->getVersion())) { $packages[] = $package; } } diff --git a/src/Composer/Repository/CompositeRepository.php b/src/Composer/Repository/CompositeRepository.php index eccf5f230..e000d97e8 100644 --- a/src/Composer/Repository/CompositeRepository.php +++ b/src/Composer/Repository/CompositeRepository.php @@ -68,12 +68,12 @@ class CompositeRepository implements RepositoryInterface /** * {@inheritdoc} */ - public function findPackagesByName($name) + public function findPackages($name, $version = null) { $packages = array(); foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ - $packages[] = $repository->findPackagesByName($name); + $packages[] = $repository->findPackages($name, $version); } return call_user_func_array('array_merge', $packages); } diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 2c1f7d43c..e4a79695d 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -32,7 +32,7 @@ interface RepositoryInterface extends \Countable function hasPackage(PackageInterface $package); /** - * Searches for a package by it's name and version (if has one). + * Searches for the first match of a package by name and version. * * @param string $name package name * @param string $version package version @@ -42,13 +42,14 @@ interface RepositoryInterface extends \Countable function findPackage($name, $version); /** - * Searches for packages by it's name. + * Searches for all packages matching a name and optionally a version. * * @param string $name package name + * @param string $version package version * * @return array */ - function findPackagesByName($name); + function findPackages($name, $version = null); /** * Returns list of registered packages. diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 1ed0f18d0..4935ef68c 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -50,6 +50,25 @@ class RepositoryManager } } + /** + * Searches for all packages matching a name and optionally a version in managed repositories. + * + * @param string $name package name + * @param string $version package version + * + * @return array + */ + public function findPackages($name, $version) + { + $packages = array(); + + foreach ($this->repositories as $repository) { + $packages = array_merge($packages, $repository->findPackages($name, $version)); + } + + return $packages; + } + /** * Adds repository * diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 1e73104f9..1ca21948d 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -130,6 +130,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface $tags[$match[1]] = $match[2]; } } + unset($tags['tip']); $this->tags = $tags; } diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index e4c7ba811..fef4de96a 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -19,6 +19,8 @@ use Symfony\Component\Process\Process; */ class ProcessExecutor { + static protected $timeout = 60; + /** * runs a process on the commandline * @@ -29,7 +31,7 @@ class ProcessExecutor public function execute($command, &$output = null) { $captureOutput = count(func_get_args()) > 1; - $process = new Process($command); + $process = new Process($command, null, null, null, static::getTimeout()); $process->run(function($type, $buffer) use ($captureOutput) { if ($captureOutput) { return; @@ -49,4 +51,14 @@ class ProcessExecutor { return ((string) $output === '') ? array() : preg_split('{\r?\n}', $output); } -} \ No newline at end of file + + static public function getTimeout() + { + return static::$timeout; + } + + static public function setTimeout($timeout) + { + static::$timeout = $timeout; + } +} diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index ff1c3ac07..885a80d35 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -43,7 +43,31 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->once()) ->method('execute') - ->with($this->equalTo($expectedGitCommand)); + ->with($this->equalTo($expectedGitCommand)) + ->will($this->returnValue(0)); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader->download($packageMock, 'composerPath'); + } + + /** + * @expectedException \RuntimeException + */ + public function testDownloadThrowsRuntimeExceptionIfGitCommandFails() + { + $expectedGitCommand = $this->getCmd('git clone \'https://github.com/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->once()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->once()) + ->method('execute') + ->with($this->equalTo($expectedGitCommand)) + ->will($this->returnValue(1)); $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->download($packageMock, 'composerPath'); @@ -79,10 +103,41 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->at(0)) ->method('execute') - ->with($this->equalTo($expectedGitResetCommand)); + ->with($this->equalTo($expectedGitResetCommand)) + ->will($this->returnValue(0)); + $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($expectedGitUpdateCommand)) + ->will($this->returnValue(0)); + + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader->update($packageMock, $packageMock, 'composerPath'); + } + + /** + * @expectedException \RuntimeException + */ + public function testUpdateThrowsRuntimeExceptionIfGitCommandFails() + { + $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->at(0)) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)) + ->will($this->returnValue(0)); $processExecutor->expects($this->at(1)) ->method('execute') - ->with($this->equalTo($expectedGitUpdateCommand)); + ->with($this->equalTo($expectedGitUpdateCommand)) + ->will($this->returnValue(1)); $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->update($packageMock, $packageMock, 'composerPath'); @@ -96,7 +151,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->any()) ->method('execute') - ->with($this->equalTo($expectedGitResetCommand)); + ->with($this->equalTo($expectedGitResetCommand)) + ->will($this->returnValue(0)); $filesystem = $this->getMock('Composer\Util\Filesystem'); $filesystem->expects($this->any()) ->method('removeDirectory') diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index 8feeabeaf..9c3f3cbe0 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -114,7 +114,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase $locker->getLockedPackages(); } - public function testLockPackages() + public function testSetLockData() { $json = $this->createJsonFileMock(); $repo = $this->createRepositoryManagerMock(); @@ -151,9 +151,10 @@ class LockerTest extends \PHPUnit_Framework_TestCase array('package' => 'pkg1', 'version' => '1.0.0-beta'), array('package' => 'pkg2', 'version' => '0.1.10') ), + 'aliases' => array(), )); - $locker->lockPackages(array($package1, $package2)); + $locker->setLockData(array($package1, $package2), array()); } public function testLockBadPackages() @@ -171,7 +172,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase $this->setExpectedException('LogicException'); - $locker->lockPackages(array($package1)); + $locker->setLockData(array($package1), array()); } public function testIsFresh() diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 64a3e79f4..f45d0a0dc 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -53,6 +53,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase 'parses trunk' => array('dev-trunk', '9999999-dev'), 'parses arbitrary' => array('dev-feature-foo', 'dev-feature-foo'), 'parses arbitrary2' => array('DEV-FOOBAR', 'dev-foobar'), + 'ignores aliases' => array('dev-master as 1.0.0', '1.0.0.0'), ); } @@ -128,6 +129,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase 'accepts master' => array('>=dev-master', new VersionConstraint('>=', '9999999-dev')), 'accepts master/2' => array('dev-master', new VersionConstraint('=', '9999999-dev')), 'accepts arbitrary' => array('dev-feature-a', new VersionConstraint('=', 'dev-feature-a')), + 'ignores aliases' => array('dev-master as 1.0.0', new VersionConstraint('=', '1.0.0.0')), ); } diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php index 5e29f5ed6..ed05819b6 100644 --- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php @@ -51,18 +51,18 @@ class ArrayRepositoryTest extends TestCase $this->assertFalse($repo->hasPackage($this->getPackage('bar', '1'))); } - public function testFindPackagesByName() + public function testFindPackages() { $repo = new ArrayRepository(); $repo->addPackage($this->getPackage('foo', '1')); $repo->addPackage($this->getPackage('bar', '2')); $repo->addPackage($this->getPackage('bar', '3')); - $foo = $repo->findPackagesByName('foo'); + $foo = $repo->findPackages('foo'); $this->assertCount(1, $foo); $this->assertEquals('foo', $foo[0]->getName()); - $bar = $repo->findPackagesByName('bar'); + $bar = $repo->findPackages('bar'); $this->assertCount(2, $bar); $this->assertEquals('bar', $bar[0]->getName()); } diff --git a/tests/Composer/Test/Repository/CompositeRepositoryTest.php b/tests/Composer/Test/Repository/CompositeRepositoryTest.php index 49e017d18..c71196729 100644 --- a/tests/Composer/Test/Repository/CompositeRepositoryTest.php +++ b/tests/Composer/Test/Repository/CompositeRepositoryTest.php @@ -22,15 +22,15 @@ class CompositeRepositoryTest extends TestCase { $arrayRepoOne = new ArrayRepository; $arrayRepoOne->addPackage($this->getPackage('foo', '1')); - + $arrayRepoTwo = new ArrayRepository; $arrayRepoTwo->addPackage($this->getPackage('bar', '1')); - + $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo)); - + $this->assertTrue($repo->hasPackage($this->getPackage('foo', '1')), "Should have package 'foo/1'"); $this->assertTrue($repo->hasPackage($this->getPackage('bar', '1')), "Should have package 'bar/1'"); - + $this->assertFalse($repo->hasPackage($this->getPackage('foo', '2')), "Should not have package 'foo/2'"); $this->assertFalse($repo->hasPackage($this->getPackage('bar', '2')), "Should not have package 'bar/2'"); } @@ -39,12 +39,12 @@ class CompositeRepositoryTest extends TestCase { $arrayRepoOne = new ArrayRepository; $arrayRepoOne->addPackage($this->getPackage('foo', '1')); - + $arrayRepoTwo = new ArrayRepository; $arrayRepoTwo->addPackage($this->getPackage('bar', '1')); - + $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo)); - + $this->assertEquals('foo', $repo->findPackage('foo', '1')->getName(), "Should find package 'foo/1' and get name of 'foo'"); $this->assertEquals('1', $repo->findPackage('foo', '1')->getPrettyVersion(), "Should find package 'foo/1' and get pretty version of '1'"); $this->assertEquals('bar', $repo->findPackage('bar', '1')->getName(), "Should find package 'bar/1' and get name of 'bar'"); @@ -52,7 +52,7 @@ class CompositeRepositoryTest extends TestCase $this->assertNull($repo->findPackage('foo', '2'), "Should not find package 'foo/2'"); } - public function testFindPackagesByName() + public function testFindPackages() { $arrayRepoOne = new ArrayRepository; $arrayRepoOne->addPackage($this->getPackage('foo', '1')); @@ -63,32 +63,32 @@ class CompositeRepositoryTest extends TestCase $arrayRepoTwo->addPackage($this->getPackage('bar', '1')); $arrayRepoTwo->addPackage($this->getPackage('bar', '2')); $arrayRepoTwo->addPackage($this->getPackage('foo', '3')); - + $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo)); - - $bats = $repo->findPackagesByName('bat'); + + $bats = $repo->findPackages('bat'); $this->assertCount(1, $bats, "Should find one instance of 'bats' (defined in just one repository)"); $this->assertEquals('bat', $bats[0]->getName(), "Should find packages named 'bat'"); - $bars = $repo->findPackagesByName('bar'); + $bars = $repo->findPackages('bar'); $this->assertCount(2, $bars, "Should find two instances of 'bar' (both defined in the same repository)"); $this->assertEquals('bar', $bars[0]->getName(), "Should find packages named 'bar'"); - - $foos = $repo->findPackagesByName('foo'); + + $foos = $repo->findPackages('foo'); $this->assertCount(3, $foos, "Should find three instances of 'foo' (two defined in one repository, the third in the other)"); $this->assertEquals('foo', $foos[0]->getName(), "Should find packages named 'foo'"); } - + public function testGetPackages() { $arrayRepoOne = new ArrayRepository; $arrayRepoOne->addPackage($this->getPackage('foo', '1')); - + $arrayRepoTwo = new ArrayRepository; $arrayRepoTwo->addPackage($this->getPackage('bar', '1')); - + $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo)); - + $packages = $repo->getPackages(); $this->assertCount(2, $packages, "Should get two packages"); $this->assertEquals("foo", $packages[0]->getName(), "First package should have name of 'foo'"); @@ -96,17 +96,17 @@ class CompositeRepositoryTest extends TestCase $this->assertEquals("bar", $packages[1]->getName(), "Second package should have name of 'bar'"); $this->assertEquals("1", $packages[1]->getPrettyVersion(), "Second package should have pretty version of '1'"); } - + public function testAddRepository() { $arrayRepoOne = new ArrayRepository; $arrayRepoOne->addPackage($this->getPackage('foo', '1')); - + $arrayRepoTwo = new ArrayRepository; $arrayRepoTwo->addPackage($this->getPackage('bar', '1')); $arrayRepoTwo->addPackage($this->getPackage('bar', '2')); $arrayRepoTwo->addPackage($this->getPackage('bar', '3')); - + $repo = new CompositeRepository(array($arrayRepoOne)); $this->assertCount(1, $repo, "Composite repository should have just one package before addRepository() is called"); $repo->addRepository($arrayRepoTwo); @@ -117,12 +117,12 @@ class CompositeRepositoryTest extends TestCase { $arrayRepoOne = new ArrayRepository; $arrayRepoOne->addPackage($this->getPackage('foo', '1')); - + $arrayRepoTwo = new ArrayRepository; $arrayRepoTwo->addPackage($this->getPackage('bar', '1')); - + $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo)); - + $this->assertEquals(2, count($repo), "Should return '2' for count(\$repo)"); } } diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index c7445308d..0ec924bb4 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -33,6 +33,14 @@ class ProcessExecutorTest extends TestCase $this->assertEquals("foo".PHP_EOL, $output); } + public function testTimeout() + { + ProcessExecutor::setTimeout(1); + $process = new ProcessExecutor; + $this->assertEquals(1, $process->getTimeout()); + ProcessExecutor::setTimeout(60); + } + public function testSplitLines() { $process = new ProcessExecutor;