diff --git a/CHANGELOG.md b/CHANGELOG.md index 5328073a2..a2a726272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ * 1.0.0-alpha2 + * Added `create-project` command to install a project from scratch with composer + * Added automated `classmap` autoloading support for non-PSR-0 compliant projects * Git clones from GitHub automatically select between git/https/http protocols * Enhanced `validate` command to give more feedback * Added "file" downloader type to download plain files + * Added support for authentication with svn repositories * Dependency on filter_var is now optional * Various robustness & error handling improvements diff --git a/doc/04-schema.md b/doc/04-schema.md index 5ce67e8b5..e3308630e 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -183,9 +183,10 @@ 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 -`psr-0` key you define a mapping from namespaces to paths, relative to the +Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) +autoloading and ClassMap generation are supported. + +Under the `psr-0` key you define a mapping from namespaces to paths, relative to the package root. Example: @@ -198,6 +199,18 @@ Example: Optional, but it is highly recommended that you follow PSR-0 and use this. +You can use the classmap generation support to define autoloading for all libraries +that do not follow "PSR-0". To configure this you specify all directories +to search for classes. + +Example: + + { + "autoload: { + "classmap": ["src/", "lib/"] + } + } + ## target-dir Defines the installation target. @@ -389,4 +402,4 @@ See (Vendor Bins)[articles/vendor-bins.md] for more details. Optional. -← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) → \ No newline at end of file +← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) → diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 7485a3550..cd8b94a89 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -138,7 +138,9 @@ VCS repository provides `dist`s for them that fetch the packages as zips. * **GitHub:** [github.com](https://github.com) (Git) * **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial) -The VCS driver to be used is detected automatically based on the URL. +The VCS driver to be used is detected automatically based on the URL. However, +should you need to specify one for whatever reason, you can use `git`, `svn` or +`hg` as the repository type instead of `vcs`. ### PEAR diff --git a/doc/articles/create-projects.md b/doc/articles/create-projects.md new file mode 100644 index 000000000..9dda91117 --- /dev/null +++ b/doc/articles/create-projects.md @@ -0,0 +1,23 @@ +# Create Projects + +You can use Composer to create new projects from an existing package. +There are several applications for this: + +1. You can deploy application packages. +2. You can check out any package and start developing on patches for example. +3. Projects with multiple developers can use this feature to bootstrap the initial application for development. + +To create a new project using composer you can use the "create-project" command. +Pass it a package name, and the directory to create the project in. You can also +provide a version as third argument, otherwise the latest version is used. + +The directory is not allowed to exist, it will be created during installation. + + php composer.phar create-project doctrine/orm path 2.2.0 + +By default the command checks for the packages on packagist.org. To change this behavior +you can use the --repository-url parameter and either point it to an HTTP url +for your own packagist repository or to a packages.json file. + +If you want to get a development version of the code directly checked out +from version control you have to add the --prefer-source parameter. diff --git a/res/composer-schema.json b/res/composer-schema.json index ed25a5b07..d35591a1c 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -127,6 +127,10 @@ "type": "object", "description": "This is a hash of namespaces (keys) and the directories they can be found into (values) by the autoloader.", "additionalProperties": true + }, + "classmap": { + "type": "array", + "description": "This is an array of directories that contain classes to be included in the class-map generation process." } } }, diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index e33c8ff99..036b40b95 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -44,6 +44,11 @@ return call_user_func(function() { $loader->add($namespace, $path); } + $classMap = require __DIR__.'/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + $loader->register(); return $loader; @@ -56,12 +61,16 @@ EOF; $relVendorPath = $filesystem->findShortestPath(getcwd(), $vendorPath); $vendorDirCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); + $appBaseDir = $filesystem->findShortestPathCode($vendorPath, getcwd(), true); + $appBaseDir = str_replace('__DIR__', '$vendorDir', $appBaseDir); + $namespacesFile = <<buildPackageMap($installationManager, $mainPackage, $localRepo->getPackages()); $autoloads = $this->parseAutoloads($packageMap); - $appBaseDir = $filesystem->findShortestPathCode($vendorPath, getcwd(), true); - $appBaseDir = str_replace('__DIR__', '$vendorDir', $appBaseDir); - - if (isset($autoloads['psr-0'])) { - foreach ($autoloads['psr-0'] as $namespace => $paths) { - $exportedPaths = array(); - foreach ($paths as $path) { - $path = strtr($path, '\\', '/'); - $baseDir = ''; - if (!$filesystem->isAbsolutePath($path)) { - // vendor dir == working dir - if (preg_match('{^(\./?)?$}', $relVendorPath)) { - $path = '/'.$path; - $baseDir = '$vendorDir . '; - } elseif (strpos($path, $relVendorPath) === 0) { - // path starts with vendor dir - $path = substr($path, strlen($relVendorPath)); - $baseDir = '$vendorDir . '; - } else { - $path = '/'.$path; - $baseDir = $appBaseDir . ' . '; - } - } elseif (strpos($path, $vendorPath) === 0) { - $path = substr($path, strlen($vendorPath)); + foreach ($autoloads['psr-0'] as $namespace => $paths) { + $exportedPaths = array(); + foreach ($paths as $path) { + $path = strtr($path, '\\', '/'); + $baseDir = ''; + if (!$filesystem->isAbsolutePath($path)) { + // vendor dir == working dir + if (preg_match('{^(\./?)?$}', $relVendorPath)) { + $path = '/'.$path; $baseDir = '$vendorDir . '; + } elseif (strpos($path, $relVendorPath) === 0) { + // path starts with vendor dir + $path = substr($path, strlen($relVendorPath)); + $baseDir = '$vendorDir . '; + } else { + $path = '/'.$path; + $baseDir = '$baseDir . '; } - $exportedPaths[] = $baseDir.var_export($path, true); - } - $exportedPrefix = var_export($namespace, true); - $namespacesFile .= " $exportedPrefix => "; - if (count($exportedPaths) > 1) { - $namespacesFile .= "array(".implode(', ',$exportedPaths)."),\n"; - } else { - $namespacesFile .= $exportedPaths[0].",\n"; + } elseif (strpos($path, $vendorPath) === 0) { + $path = substr($path, strlen($vendorPath)); + $baseDir = '$vendorDir . '; } + $exportedPaths[] = $baseDir.var_export($path, true); + } + $exportedPrefix = var_export($namespace, true); + $namespacesFile .= " $exportedPrefix => "; + if (count($exportedPaths) > 1) { + $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n"; + } else { + $namespacesFile .= $exportedPaths[0].",\n"; } } - $namespacesFile .= ");\n"; + $classmapFile = << $path) { + $path = '/'.$filesystem->findShortestPath(getcwd(), $path); + $classmapFile .= ' '.var_export($class, true).' => $baseDir . '.var_export($path, true).",\n"; + } + } + $classmapFile .= ");\n"; + file_put_contents($targetDir.'/autoload.php', $autoloadFile); file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile); + file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile); copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); } @@ -141,7 +167,7 @@ EOF; */ public function parseAutoloads(array $packageMap) { - $autoloads = array(); + $autoloads = array('classmap' => array(), 'psr-0' => array()); foreach ($packageMap as $item) { list($package, $installPath) = $item; diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index d4f21bff4..94fc76ac1 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -45,6 +45,7 @@ class ClassLoader private $prefixes = array(); private $fallbackDirs = array(); private $useIncludePath = false; + private $classMap = array(); public function getPrefixes() { @@ -56,6 +57,23 @@ class ClassLoader return $this->fallbackDirs; } + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + /** * Registers a set of classes * @@ -142,6 +160,10 @@ class ClassLoader */ public function findFile($class) { + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ('\\' == $class[0]) { $class = substr($class, 1); } diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php new file mode 100644 index 000000000..99b2a29ad --- /dev/null +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @license MIT + */ + +namespace Composer\Autoload; + +/** + * ClassMapGenerator + * + * @author Gyula Sallai + */ +class ClassMapGenerator +{ + /** + * Generate a class map file + * + * @param Traversable $dirs Directories or a single path to search in + * @param string $file The name of the class map file + */ + static public function dump($dirs, $file) + { + $maps = array(); + + foreach ($dirs as $dir) { + $maps = array_merge($maps, static::createMap($dir)); + } + + file_put_contents($file, sprintf('isFile()) { + continue; + } + + $path = $file->getRealPath(); + + if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') { + continue; + } + + $classes = self::findClasses($path); + + foreach ($classes as $class) { + $map[$class] = $path; + } + + } + + return $map; + } + + /** + * Extract the classes in the given file + * + * @param string $path The file to check + * + * @return array The found classes + */ + static private function findClasses($path) + { + $contents = file_get_contents($path); + $tokens = token_get_all($contents); + $T_TRAIT = version_compare(PHP_VERSION, '5.4', '<') ? -1 : T_TRAIT; + + $classes = array(); + + $namespace = ''; + for ($i = 0, $max = count($tokens); $i < $max; $i++) { + $token = $tokens[$i]; + + if (is_string($token)) { + continue; + } + + $class = ''; + + switch ($token[0]) { + case T_NAMESPACE: + $namespace = ''; + // If there is a namespace, extract it + while (($t = $tokens[++$i]) && is_array($t)) { + if (in_array($t[0], array(T_STRING, T_NS_SEPARATOR))) { + $namespace .= $t[1]; + } + } + $namespace .= '\\'; + break; + case T_CLASS: + case T_INTERFACE: + case $T_TRAIT: + // Find the classname + while (($t = $tokens[++$i]) && is_array($t)) { + if (T_STRING === $t[0]) { + $class .= $t[1]; + } elseif ($class !== '' && T_WHITESPACE == $t[0]) { + break; + } + } + + $classes[] = ltrim($namespace . $class, '\\'); + break; + default: + break; + } + } + + return $classes; + } +} diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php new file mode 100644 index 000000000..a859ac12a --- /dev/null +++ b/src/Composer/Command/CreateProjectCommand.php @@ -0,0 +1,134 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Installer; +use Composer\Installer\ProjectInstaller; +use Composer\IO\IOInterface; +use Composer\Repository\ComposerRepository; +use Composer\Repository\FilesystemRepository; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Install a package as new project into new directory. + * + * @author Benjamin Eberlei + */ +class CreateProjectCommand extends Command +{ + protected function configure() + { + $this + ->setName('create-project') + ->setDescription('Create new project from a package into given directory.') + ->setDefinition(array( + new InputArgument('package', InputArgument::REQUIRED, 'Package name to be installed'), + new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), + new InputArgument('version', InputArgument::OPTIONAL, 'Version, will defaults to latest'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'Pick a different repository url to look for the package.'), + )) + ->setHelp(<<create-project command creates a new project from a given +package into a new directory. You can use this command to bootstrap new +projects or setup a clean version-controlled installation +for developers of your project. + +php composer.phar create-project vendor/project target-directory [version] + +To setup a developer workable version you should create the project using the source +controlled code by appending the '--prefer-source' flag. + +To install a package from another repository repository than the default one you +can pass the '--repository-url=http://myrepository.org' flag. + +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = $this->getApplication()->getIO(); + + return $this->installProject( + $io, + $input->getArgument('package'), + $input->getArgument('directory'), + $input->getArgument('version'), + (Boolean)$input->getOption('prefer-source'), + $input->getOption('repository-url') + ); + } + + public function installProject(IOInterface $io, $packageName, $directory = null, $version = null, $preferSource = false, $repositoryUrl = null) + { + $dm = $this->createDownloadManager($io); + if ($preferSource) { + $dm->setPreferSource(true); + } + + if (null === $repositoryUrl) { + $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org')); + } elseif (".json" === substr($repositoryUrl, -5)) { + $sourceRepo = new FilesystemRepository($repositoryUrl); + } elseif (0 === strpos($repositoryUrl, 'http')) { + $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl)); + } else { + throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url."); + } + + $candidates = $sourceRepo->findPackages($packageName, $version); + if (!$candidates) { + throw new \InvalidArgumentException("Could not find package $packageName" . ($version ? " with version $version." : '')); + } + + if (null === $directory) { + $parts = explode("/", $packageName, 2); + $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); + } + + // select highest version if we have many + $package = $candidates[0]; + foreach ($candidates as $candidate) { + if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { + $package = $candidate; + } + } + + $io->write('Installing ' . $package->getName() . ' as new project.', true); + $projectInstaller = new ProjectInstaller($directory, $dm); + $projectInstaller->install($package); + + $io->write('Created project into directory ' . $directory . '', true); + chdir($directory); + + $composer = Factory::create($io); + $installer = Installer::create($io, $composer); + + $installer + ->setPreferSource($preferSource) + ->run(); + } + + protected function createDownloadManager(IOInterface $io) + { + $factory = new Factory(); + return $factory->createDownloadManager($io); + } +} + diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 6b6120bb8..7bf90709e 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -12,30 +12,10 @@ namespace Composer\Command; -use Composer\Script\ScriptEvents; -use Composer\Script\EventDispatcher; -use Composer\Autoload\AutoloadGenerator; -use Composer\Composer; -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; +use Composer\Installer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Composer\DependencyResolver\Operation\InstallOperation; -use Composer\DependencyResolver\Operation\UpdateOperation; -use Composer\DependencyResolver\Solver; -use Composer\IO\IOInterface; /** * @author Jordi Boggiano @@ -71,222 +51,16 @@ EOT { $composer = $this->getComposer(); $io = $this->getApplication()->getIO(); - $eventDispatcher = new EventDispatcher($composer, $io); + $install = Installer::create($io, $composer); - return $this->install( - $io, - $composer, - $eventDispatcher, - (Boolean)$input->getOption('prefer-source'), - (Boolean)$input->getOption('dry-run'), - (Boolean)$input->getOption('verbose'), - (Boolean)$input->getOption('no-install-recommends'), - (Boolean)$input->getOption('install-suggests') - ); - } + $install + ->setDryRun($input->getOption('dry-run')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($input->getOption('prefer-source')) + ->setInstallRecommends(!$input->getOption('no-install-recommends')) + ->setInstallSuggests($input->getOption('install-suggests')) + ; - public function install(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher, $preferSource = false, $dryRun = false, $verbose = false, $noInstallRecommends = false, $installSuggests = false, $update = false, RepositoryInterface $additionalInstalledRepository = null) - { - if ($dryRun) { - $verbose = true; - } - - if ($preferSource) { - $composer->getDownloadManager()->setPreferSource(true); - } - - $repoManager = $composer->getRepositoryManager(); - - // create local repo, this contains all packages that are installed in the local project - $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_normalized'], $alias['alias'])); - } - foreach ($repoManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) { - $repoManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); - $repoManager->getLocalRepository()->removePackage($package); - } - } - - // creating repository pool - $pool = new Pool; - $pool->addRepository($installedRepo); - foreach ($repoManager->getRepositories() as $repository) { - $pool->addRepository($repository); - } - - // dispatch pre event - if (!$dryRun) { - $eventName = $update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; - $eventDispatcher->dispatchCommandEvent($eventName); - } - - // creating requirements request - $installFromLock = false; - $request = new Request($pool); - if ($update) { - $io->write('Updating dependencies'); - - $request->updateAll(); - - $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); - - foreach ($links as $link) { - $request->install($link->getTarget(), $link->getConstraint()); - } - } elseif ($composer->getLocker()->isLocked()) { - $installFromLock = true; - $io->write('Installing from lock file'); - - if (!$composer->getLocker()->isFresh()) { - $io->write('Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies'); - } - - foreach ($composer->getLocker()->getLockedPackages() as $package) { - $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 { - $io->write('Installing dependencies'); - - $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); - - foreach ($links as $link) { - $request->install($link->getTarget(), $link->getConstraint()); - } - } - - // prepare solver - $installationManager = $composer->getInstallationManager(); - $policy = new DependencyResolver\DefaultPolicy(); - $solver = new DependencyResolver\Solver($policy, $pool, $installedRepo); - - // solve dependencies - $operations = $solver->solve($request); - - // 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; - } - - // skip packages that will be updated/uninstalled - foreach ($operations as $operation) { - if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage()) - || ('uninstall' === $operation->getJobType() && $package === $operation->getPackage()) - ) { - continue 2; - } - } - - // force update - $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); - } - if (!$dryRun) { - $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation); - - // if installing from lock, restore dev packages' references to their locked state - if ($installFromLock) { - $package = null; - if ('update' === $operation->getJobType()) { - $package = $operation->getTargetPackage(); - } elseif ('install' === $operation->getJobType()) { - $package = $operation->getPackage(); - } - if ($package && $package->isDev()) { - $lockData = $composer->getLocker()->getLockData(); - foreach ($lockData['packages'] as $lockedPackage) { - if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) { - $package->setSourceReference($lockedPackage['source-reference']); - break; - } - } - } - } - $installationManager->execute($operation); - - $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation); - } - } - - if (!$dryRun) { - if ($update || !$composer->getLocker()->isLocked()) { - $composer->getLocker()->setLockData($localRepo->getPackages(), $aliases); - $io->write('Writing lock file'); - } - - $localRepo->write(); - - $io->write('Generating autoload files'); - $generator = new AutoloadGenerator; - $generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer'); - - // dispatch post event - $eventName = $update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; - $eventDispatcher->dispatchCommandEvent($eventName); - } - } - - private function collectLinks(PackageInterface $package, $noInstallRecommends, $installSuggests) - { - $links = $package->getRequires(); - - if (!$noInstallRecommends) { - $links = array_merge($links, $package->getRecommends()); - } - - if ($installSuggests) { - $links = array_merge($links, $package->getSuggests()); - } - - return $links; + return $install->run(); } } diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index d01ebc1b4..0a4779ea5 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -18,6 +18,8 @@ use Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\ComposerRepository; +use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; /** * @author Robert Schönthal @@ -55,27 +57,52 @@ EOT $repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org')))); } - $tokens = array_map('strtolower', $input->getArgument('tokens')); + $tokens = $input->getArgument('tokens'); + $packages = array(); + foreach ($repos->getPackages() as $package) { + if ($package instanceof AliasPackage || isset($packages[$package->getName()])) { + continue; + } + foreach ($tokens as $token) { - if (false === ($pos = strpos($package->getName(), $token))) { + if (!$this->matchPackage($package, $token)) { continue; } - if ($platformRepo->hasPackage($package)) { - $type = 'platform: '; - } elseif ($installedRepo->hasPackage($package)) { - $type = 'installed: '; + if (false !== ($pos = stripos($package->getName(), $token))) { + $name = substr($package->getPrettyName(), 0, $pos) + . '' . substr($package->getPrettyName(), $pos, strlen($token)) . '' + . substr($package->getPrettyName(), $pos + strlen($token)); } else { - $type = 'available: '; + $name = $package->getPrettyName(); } - $name = substr($package->getPrettyName(), 0, $pos) - . '' . substr($package->getPrettyName(), $pos, strlen($token)) . '' - . substr($package->getPrettyName(), $pos + strlen($token)); - $output->writeln($type . ': ' . $name . ' ' . $package->getPrettyVersion() . ''); + $packages[$package->getName()] = array( + 'name' => $name, + 'description' => strtok($package->getDescription(), "\r\n") + ); continue 2; } } + + foreach ($packages as $details) { + $output->writeln($details['name'] .' : '. $details['description']); + } + } + + /** + * tries to find a token within the name/keywords/description + * + * @param PackageInterface $package + * @param string $token + * @return boolean + */ + private function matchPackage(PackageInterface $package, $token) + { + return (false !== stripos($package->getName(), $token)) + || (false !== stripos(join(',', $package->getKeywords() ?: array()), $token)) + || (false !== stripos($package->getDescription(), $token)) + ; } } \ No newline at end of file diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 299e96fe4..042ff420b 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -83,15 +83,32 @@ EOT } // list packages + $packages = array(); foreach ($repos->getPackages() as $package) { if ($platformRepo->hasPackage($package)) { - $type = 'platform: '; + $type = 'platform:'; } elseif ($installedRepo->hasPackage($package)) { - $type = 'installed: '; + $type = 'installed:'; } else { - $type = 'available: '; + $type = 'available:'; + } + if (isset($packages[$type][$package->getName()]) + && version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '>=') + ) { + continue; + } + $packages[$type][$package->getName()] = $package; + } + + foreach (array('platform:', 'available:', 'installed:') as $type) { + if (isset($packages[$type])) { + $output->writeln($type); + ksort($packages[$type]); + foreach ($packages[$type] as $package) { + $output->writeln(' '.$package->getPrettyName() .' : '. strtok($package->getDescription(), "\r\n")); + } + $output->writeln(''); } - $output->writeln($type . ' ' . $package->getPrettyName() . ' ' . $package->getPrettyVersion() . ' (' . $package->getVersion() . ')'); } } @@ -133,20 +150,26 @@ EOT protected function printMeta(InputInterface $input, OutputInterface $output, PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $repos) { $output->writeln('name : ' . $package->getPrettyName()); + $output->writeln('descrip. : ' . $package->getDescription()); + $output->writeln('keywords : ' . join(', ', $package->getKeywords() ?: array())); $this->printVersions($input, $output, $package, $installedRepo, $repos); $output->writeln('type : ' . $package->getType()); - $output->writeln('names : ' . join(', ', $package->getNames())); + $output->writeln('license : ' . implode(', ', $package->getLicense())); $output->writeln('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $output->writeln('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); - $output->writeln('license : ' . join(', ', $package->getLicense())); + $output->writeln('names : ' . implode(', ', $package->getNames())); if ($package->getAutoload()) { $output->writeln("\nautoload"); foreach ($package->getAutoload() as $type => $autoloads) { $output->writeln('' . $type . ''); - foreach ($autoloads as $name => $path) { - $output->writeln($name . ' : ' . ($path ?: '.')); + if ($type === 'psr-0') { + foreach ($autoloads as $name => $path) { + $output->writeln(($name ?: '*') . ' => ' . ($path ?: '.')); + } + } elseif ($type === 'classmap') { + $output->writeln(implode(', ', $autoloads)); } } } @@ -165,10 +188,12 @@ EOT $versions = array(); foreach ($repos->findPackages($package->getName()) as $version) { - $versions[$version->getPrettyVersion()] = true; + $versions[$version->getPrettyVersion()] = $version->getVersion(); } - $versions = join(', ', array_keys($versions)); + uasort($versions, 'version_compare'); + + $versions = implode(', ', array_keys(array_reverse($versions))); // highlight installed version if ($installedRepo->hasPackage($package)) { @@ -193,4 +218,4 @@ EOT } } } -} \ No newline at end of file +} diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 7e1e8e0c1..7fe888139 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -12,14 +12,7 @@ namespace Composer\Command; -use Composer\Autoload\AutoloadGenerator; -use Composer\DependencyResolver; -use Composer\DependencyResolver\Pool; -use Composer\DependencyResolver\Request; -use Composer\DependencyResolver\Operation; -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Repository\PlatformRepository; -use Composer\Script\EventDispatcher; +use Composer\Installer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -54,21 +47,19 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $installCommand = $this->getApplication()->find('install'); $composer = $this->getComposer(); $io = $this->getApplication()->getIO(); - $eventDispatcher = new EventDispatcher($composer, $io); + $install = Installer::create($io, $composer); - return $installCommand->install( - $io, - $composer, - $eventDispatcher, - (Boolean)$input->getOption('prefer-source'), - (Boolean)$input->getOption('dry-run'), - (Boolean)$input->getOption('verbose'), - (Boolean)$input->getOption('no-install-recommends'), - (Boolean)$input->getOption('install-suggests'), - true - ); + $install + ->setDryRun($input->getOption('dry-run')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($input->getOption('prefer-source')) + ->setInstallRecommends(!$input->getOption('no-install-recommends')) + ->setInstallSuggests($input->getOption('install-suggests')) + ->setUpdate(true) + ; + + return $install->run(); } } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 563df4018..ca4875a97 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -82,6 +82,7 @@ class Compiler $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/ClassLoader.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload.php')); $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_namespaces.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_classmap.php')); $this->addComposerBin($phar); // Stubs diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 1881fb575..e836d65c8 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -107,6 +107,7 @@ class Application extends BaseApplication $this->add(new Command\DependsCommand()); $this->add(new Command\InitCommand()); $this->add(new Command\InstallCommand()); + $this->add(new Command\CreateProjectCommand()); $this->add(new Command\UpdateCommand()); $this->add(new Command\SearchCommand()); $this->add(new Command\ValidateCommand()); @@ -128,4 +129,4 @@ class Application extends BaseApplication return $helperSet; } -} \ No newline at end of file +} diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 698ebfd46..d3e668c93 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -84,8 +84,6 @@ class FileDownloader implements DownloaderInterface if ($checksum && hash_file('sha1', $fileName) !== $checksum) { throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')'); } - - $this->io->write(''); } /** diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 0aa043c31..cceb4cf8b 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -29,24 +29,11 @@ class GitDownloader extends VcsDownloader $command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s'; $this->io->write(" Cloning ".$package->getSourceReference()); - // github, autoswitch protocols - if (preg_match('{^(?:https?|git)(://github.com/.*)}', $package->getSourceUrl(), $match)) { - $protocols = array('git', 'https', 'http'); - foreach ($protocols as $protocol) { - $url = escapeshellarg($protocol . $match[1]); - if (0 === $this->process->execute(sprintf($command, $url, escapeshellarg($path), $ref), $ignoredOutput)) { - return; - } - $this->filesystem->removeDirectory($path); - } - throw new \RuntimeException('Failed to checkout ' . $url .' via git, https and http protocols, aborting.' . "\n\n" . $this->process->getErrorOutput()); - } else { - $url = escapeshellarg($package->getSourceUrl()); - $command = sprintf($command, $url, escapeshellarg($path), $ref); - if (0 !== $this->process->execute($command, $ignoredOutput)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } - } + $commandCallable = function($url) use ($ref, $path, $command) { + return sprintf($command, $url, escapeshellarg($path), $ref); + }; + + $this->runCommand($commandCallable, $package->getSourceUrl(), $path); } /** @@ -57,10 +44,13 @@ class GitDownloader extends VcsDownloader $ref = escapeshellarg($target->getSourceReference()); $path = escapeshellarg($path); $this->io->write(" Checking out ".$target->getSourceReference()); - $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 . "\n\n" . $this->process->getErrorOutput()); - } + $command = 'cd %s && git remote set-url origin %s && git fetch && git checkout %3$s && git reset --hard %3$s'; + + $commandCallable = function($url) use ($ref, $path, $command) { + return sprintf($command, $path, $url, $ref); + }; + + $this->runCommand($commandCallable, $target->getSourceUrl()); } /** @@ -77,4 +67,36 @@ class GitDownloader extends VcsDownloader throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes'); } } + + /** + * Runs a command doing attempts for each protocol supported by github. + * + * @param callable $commandCallable A callable building the command for the given url + * @param string $url + * @param string $path The directory to remove for each attempt (null if not needed) + * @throws \RuntimeException + */ + protected function runCommand($commandCallable, $url, $path = null) + { + // github, autoswitch protocols + if (preg_match('{^(?:https?|git)(://github.com/.*)}', $url, $match)) { + $protocols = array('git', 'https', 'http'); + foreach ($protocols as $protocol) { + $url = escapeshellarg($protocol . $match[1]); + if (0 === $this->process->execute(call_user_func($commandCallable, $url), $ignoredOutput)) { + return; + } + if (null !== $path) { + $this->filesystem->removeDirectory($path); + } + } + throw new \RuntimeException('Failed to checkout ' . $url .' via git, https and http protocols, aborting.' . "\n\n" . $this->process->getErrorOutput()); + } + + $url = escapeshellarg($url); + $command = call_user_func($commandCallable, $url); + if (0 !== $this->process->execute($command, $ignoredOutput)) { + throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); + } + } } diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 629883dc8..f537de5ae 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -37,10 +37,11 @@ class HgDownloader extends VcsDownloader */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path) { + $url = escapeshellarg($target->getSourceUrl()); $ref = escapeshellarg($target->getSourceReference()); $path = escapeshellarg($path); $this->io->write(" Updating to ".$target->getSourceReference()); - $this->process->execute(sprintf('cd %s && hg pull && hg up %s', $path, $ref), $ignoredOutput); + $this->process->execute(sprintf('cd %s && hg pull %s && hg up %s', $path, $url, $ref), $ignoredOutput); } /** diff --git a/src/Composer/Downloader/TransportException.php b/src/Composer/Downloader/TransportException.php new file mode 100644 index 000000000..61bd67d11 --- /dev/null +++ b/src/Composer/Downloader/TransportException.php @@ -0,0 +1,20 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +/** + * @author Jordi Boggiano + */ +class TransportException extends \Exception +{ +} diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index faab0add1..3f4812927 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -123,8 +123,11 @@ class Factory $rm = new RepositoryManager($io); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); - $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); + $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); + $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); return $rm; } @@ -139,7 +142,7 @@ class Factory $rm->addRepository(new Repository\ComposerRepository(array('url' => 'http://packagist.org'))); } - protected function createDownloadManager(IOInterface $io) + public function createDownloadManager(IOInterface $io) { $dm = new Downloader\DownloadManager(); $dm->setDownloader('git', new Downloader\GitDownloader($io)); diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index e1d33b5d2..30c80355c 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -31,6 +31,7 @@ class ConsoleIO implements IOInterface protected $authorizations = array(); protected $lastUsername; protected $lastPassword; + protected $lastMessage; /** * Constructor. @@ -60,31 +61,40 @@ class ConsoleIO implements IOInterface public function write($messages, $newline = true) { $this->output->write($messages, $newline); + $this->lastMessage = join($newline ? "\n" : '', (array) $messages); } /** * {@inheritDoc} */ - public function overwrite($messages, $newline = true, $size = 80) + public function overwrite($messages, $newline = true, $size = null) { - for ($place = $size; $place > 0; $place--) { - $this->write("\x08", false); - } + // messages can be an array, let's convert it to string anyway + $messages = join($newline ? "\n" : '', (array) $messages); + // since overwrite is supposed to overwrite last message... + if (!isset($size)) { + // removing possible formatting of lastMessage with strip_tags + $size = strlen(strip_tags($this->lastMessage)); + } + // ...let's fill its length with backspaces + $this->write(str_repeat("\x08", $size), false); + + // write the new message $this->write($messages, false); - for ($place = ($size - strlen($messages)); $place > 0; $place--) { - $this->write(' ', false); - } - - // clean up the end line - for ($place = ($size - strlen($messages)); $place > 0; $place--) { - $this->write("\x08", false); + $fill = $size - strlen(strip_tags($messages)); + if ($fill > 0) { + // whitespace whatever has left + $this->write(str_repeat(' ', $fill), false); + // move the cursor back + $this->write(str_repeat("\x08", $fill), false); } if ($newline) { $this->write(''); } + $this->lastMessage = $messages; } /** diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php new file mode 100644 index 000000000..dad3dc08e --- /dev/null +++ b/src/Composer/Installer.php @@ -0,0 +1,426 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\AutoloadGenerator; +use Composer\DependencyResolver\DefaultPolicy; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\Request; +use Composer\DependencyResolver\Solver; +use Composer\Downloader\DownloadManager; +use Composer\Installer\InstallationManager; +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\Link; +use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Package\Locker; +use Composer\Package\PackageInterface; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositoryManager; +use Composer\Script\EventDispatcher; +use Composer\Script\ScriptEvents; + +/** + * @author Jordi Boggiano + * @author Beau Simensen + * @author Konstantin Kudryashov + */ +class Installer +{ + /** + * @var IOInterface + */ + protected $io; + + /** + * @var PackageInterface + */ + protected $package; + + /** + * @var DownloadManager + */ + protected $downloadManager; + + /** + * @var RepositoryManager + */ + protected $repositoryManager; + + /** + * @var Locker + */ + protected $locker; + + /** + * @var InstallationManager + */ + protected $installationManager; + + /** + * @var EventDispatcher + */ + protected $eventDispatcher; + + protected $preferSource = false; + protected $dryRun = false; + protected $verbose = false; + protected $installRecommends = true; + protected $installSuggests = false; + protected $update = false; + + /** + * @var RepositoryInterface + */ + protected $additionalInstalledRepository; + + /** + * Constructor + * + * @param IOInterface $io + * @param PackageInterface $package + * @param DownloadManager $downloadManager + * @param RepositoryManager $repositoryManager + * @param Locker $locker + * @param InstallationManager $installationManager + * @param EventDispatcher $eventDispatcher + */ + public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher) + { + $this->io = $io; + $this->package = $package; + $this->downloadManager = $downloadManager; + $this->repositoryManager = $repositoryManager; + $this->locker = $locker; + $this->installationManager = $installationManager; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * Run installation (or update) + */ + public function run() + { + if ($this->dryRun) { + $this->verbose = true; + } + + if ($this->preferSource) { + $this->downloadManager->setPreferSource(true); + } + + // create local repo, this contains all packages that are installed in the local project + $localRepo = $this->repositoryManager->getLocalRepository(); + // create installed repo, this contains all local packages + platform packages (php & extensions) + $installedRepo = new CompositeRepository(array($localRepo, new PlatformRepository())); + if ($this->additionalInstalledRepository) { + $installedRepo->addRepository($this->additionalInstalledRepository); + } + + // prepare aliased packages + if (!$this->update && $this->locker->isLocked()) { + $aliases = $this->locker->getAliases(); + } else { + $aliases = $this->package->getAliases(); + } + foreach ($aliases as $alias) { + foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) { + $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); + } + foreach ($this->repositoryManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) { + $this->repositoryManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); + $this->repositoryManager->getLocalRepository()->removePackage($package); + } + } + + // creating repository pool + $pool = new Pool; + $pool->addRepository($installedRepo); + foreach ($this->repositoryManager->getRepositories() as $repository) { + $pool->addRepository($repository); + } + + // dispatch pre event + if (!$this->dryRun) { + $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; + $this->eventDispatcher->dispatchCommandEvent($eventName); + } + + // creating requirements request + $installFromLock = false; + $request = new Request($pool); + if ($this->update) { + $this->io->write('Updating dependencies'); + + $request->updateAll(); + + $links = $this->collectLinks(); + + foreach ($links as $link) { + $request->install($link->getTarget(), $link->getConstraint()); + } + } elseif ($this->locker->isLocked()) { + $installFromLock = true; + $this->io->write('Installing from lock file'); + + if (!$this->locker->isFresh()) { + $this->io->write('Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies'); + } + + foreach ($this->locker->getLockedPackages() as $package) { + $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 { + $this->io->write('Installing dependencies'); + + $links = $this->collectLinks(); + + foreach ($links as $link) { + $request->install($link->getTarget(), $link->getConstraint()); + } + } + + // prepare solver + $policy = new DefaultPolicy(); + $solver = new Solver($policy, $pool, $installedRepo); + + // solve dependencies + $operations = $solver->solve($request); + + // force dev packages to be updated to latest reference on update + if ($this->update) { + foreach ($localRepo->getPackages() as $package) { + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + // skip non-dev packages + if (!$package->isDev()) { + continue; + } + + // skip packages that will be updated/uninstalled + foreach ($operations as $operation) { + if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage()) + || ('uninstall' === $operation->getJobType() && $package === $operation->getPackage()) + ) { + continue 2; + } + } + + // force update + $newPackage = $this->repositoryManager->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 ($this->repositoryManager->getLocalRepository()->getPackages() as $package) { + if ($package instanceof AliasPackage) { + $this->repositoryManager->getLocalRepository()->addPackage(clone $package->getAliasOf()); + $this->repositoryManager->getLocalRepository()->removePackage($package); + } + } + + // execute operations + if (!$operations) { + $this->io->write('Nothing to install/update'); + } + + foreach ($operations as $operation) { + if ($this->verbose) { + $this->io->write((string) $operation); + } + if (!$this->dryRun) { + $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation); + + // if installing from lock, restore dev packages' references to their locked state + if ($installFromLock) { + $package = null; + if ('update' === $operation->getJobType()) { + $package = $operation->getTargetPackage(); + } elseif ('install' === $operation->getJobType()) { + $package = $operation->getPackage(); + } + if ($package && $package->isDev()) { + $lockData = $this->locker->getLockData(); + foreach ($lockData['packages'] as $lockedPackage) { + if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) { + $package->setSourceReference($lockedPackage['source-reference']); + break; + } + } + } + } + $this->installationManager->execute($operation); + + $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation); + + $localRepo->write(); + } + } + + if (!$this->dryRun) { + if ($this->update || !$this->locker->isLocked()) { + $this->locker->setLockData($localRepo->getPackages(), $aliases); + $this->io->write('Writing lock file'); + } + + $localRepo->write(); + + $this->io->write('Generating autoload files'); + $generator = new AutoloadGenerator; + $generator->dump($localRepo, $this->package, $this->installationManager, $this->installationManager->getVendorPath().'/.composer'); + + // dispatch post event + $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; + $this->eventDispatcher->dispatchCommandEvent($eventName); + } + } + + private function collectLinks() + { + $links = $this->package->getRequires(); + + if ($this->installRecommends) { + $links = array_merge($links, $this->package->getRecommends()); + } + + if ($this->installSuggests) { + $links = array_merge($links, $this->package->getSuggests()); + } + + return $links; + } + + /** + * Create Installer + * + * @param IOInterface $io + * @param Composer $composer + * @param EventDispatcher $eventDispatcher + * @return Installer + */ + static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null) + { + $eventDispatcher = $eventDispatcher ?: new EventDispatcher($composer, $io); + + return new static( + $io, + $composer->getPackage(), + $composer->getDownloadManager(), + $composer->getRepositoryManager(), + $composer->getLocker(), + $composer->getInstallationManager(), + $eventDispatcher + ); + } + + public function setAdditionalInstalledRepository(RepositoryInterface $additionalInstalledRepository) + { + $this->additionalInstalledRepository = $additionalInstalledRepository; + + return $this; + } + + /** + * wether to run in drymode or not + * + * @param boolean $dryRun + * @return Installer + */ + public function setDryRun($dryRun=true) + { + $this->dryRun = (boolean) $dryRun; + + return $this; + } + + /** + * install recommend packages + * + * @param boolean $noInstallRecommends + * @return Installer + */ + public function setInstallRecommends($installRecommends=true) + { + $this->installRecommends = (boolean) $installRecommends; + + return $this; + } + + /** + * also install suggested packages + * + * @param boolean $installSuggests + * @return Installer + */ + public function setInstallSuggests($installSuggests=true) + { + $this->installSuggests = (boolean) $installSuggests; + + return $this; + } + + /** + * prefer source installation + * + * @param boolean $preferSource + * @return Installer + */ + public function setPreferSource($preferSource=true) + { + $this->preferSource = (boolean) $preferSource; + + return $this; + } + + /** + * update packages + * + * @param boolean $update + * @return Installer + */ + public function setUpdate($update=true) + { + $this->update = (boolean) $update; + + return $this; + } + + /** + * run in verbose mode + * + * @param boolean $verbose + * @return Installer + */ + public function setVerbose($verbose=true) + { + $this->verbose = (boolean) $verbose; + + return $this; + } +} diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php new file mode 100644 index 000000000..da9236a97 --- /dev/null +++ b/src/Composer/Installer/ProjectInstaller.php @@ -0,0 +1,111 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Package\PackageInterface; +use Composer\Downloader\DownloadManager; + +/** + * Project Installer is used to install a single package into a directory as + * root project. + * + * @author Benjamin Eberlei + */ +class ProjectInstaller implements InstallerInterface +{ + private $installPath; + private $downloadManager; + + public function __construct($installPath, DownloadManager $dm) + { + $this->installPath = $installPath; + $this->downloadManager = $dm; + } + + /** + * Decides if the installer supports the given type + * + * @param string $packageType + * @return Boolean + */ + public function supports($packageType) + { + return true; + } + + /** + * Checks that provided package is installed. + * + * @param PackageInterface $package package instance + * + * @return Boolean + */ + public function isInstalled(PackageInterface $package) + { + return false; + } + + /** + * Installs specific package. + * + * @param PackageInterface $package package instance + */ + public function install(PackageInterface $package) + { + $installPath = $this->installPath; + if (file_exists($installPath)) { + throw new \InvalidArgumentException("Project directory $installPath already exists."); + } + if (!file_exists(dirname($installPath))) { + throw new \InvalidArgumentException("Project root " . dirname($installPath) . " does not exist."); + } + mkdir($installPath, 0777); + $this->downloadManager->download($package, $installPath); + } + + /** + * Updates specific package. + * + * @param PackageInterface $initial already installed package version + * @param PackageInterface $target updated version + * + * @throws InvalidArgumentException if $from package is not installed + */ + public function update(PackageInterface $initial, PackageInterface $target) + { + throw new \InvalidArgumentException("not supported"); + } + + /** + * Uninstalls specific package. + * + * @param PackageInterface $package package instance + */ + public function uninstall(PackageInterface $package) + { + throw new \InvalidArgumentException("not supported"); + } + + /** + * Returns the installation path of a package + * + * @param PackageInterface $package + * @return string path + */ + public function getInstallPath(PackageInterface $package) + { + return $this->installPath; + } +} + diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 1b20af274..458265e3e 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -18,16 +18,6 @@ use JsonSchema\Validator; use Seld\JsonLint\JsonParser; use Composer\Util\StreamContextFactory; -if (!defined('JSON_UNESCAPED_SLASHES')) { - define('JSON_UNESCAPED_SLASHES', 64); -} -if (!defined('JSON_PRETTY_PRINT')) { - define('JSON_PRETTY_PRINT', 128); -} -if (!defined('JSON_UNESCAPED_UNICODE')) { - define('JSON_UNESCAPED_UNICODE', 256); -} - /** * Reads/writes json files. * @@ -39,6 +29,10 @@ class JsonFile const LAX_SCHEMA = 1; const STRICT_SCHEMA = 2; + const JSON_UNESCAPED_SLASHES = 64; + const JSON_PRETTY_PRINT = 128; + const JSON_UNESCAPED_UNICODE = 256; + private $path; /** @@ -108,7 +102,7 @@ class JsonFile ); } } - file_put_contents($this->path, static::encode($hash, $options). ($options & JSON_PRETTY_PRINT ? "\n" : '')); + file_put_contents($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : '')); } /** @@ -170,9 +164,9 @@ class JsonFile $json = json_encode($data); - $prettyPrint = (Boolean) ($options & JSON_PRETTY_PRINT); - $unescapeUnicode = (Boolean) ($options & JSON_UNESCAPED_UNICODE); - $unescapeSlashes = (Boolean) ($options & JSON_UNESCAPED_SLASHES); + $prettyPrint = (Boolean) ($options & self::JSON_PRETTY_PRINT); + $unescapeUnicode = (Boolean) ($options & self::JSON_UNESCAPED_UNICODE); + $unescapeSlashes = (Boolean) ($options & self::JSON_UNESCAPED_SLASHES); if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) { return $json; diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 12d5a6aa8..495155276 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -36,14 +36,15 @@ class PlatformRepository extends ArrayRepository } $php = new MemoryPackage('php', $version, $prettyVersion); + $php->setDescription('The PHP interpreter'); parent::addPackage($php); - foreach (get_loaded_extensions() as $ext) { - if (in_array($ext, array('standard', 'Core'))) { + foreach (get_loaded_extensions() as $name) { + if (in_array($name, array('standard', 'Core'))) { continue; } - $reflExt = new \ReflectionExtension($ext); + $reflExt = new \ReflectionExtension($name); try { $prettyVersion = $reflExt->getVersion(); $version = $versionParser->normalize($prettyVersion); @@ -52,7 +53,8 @@ class PlatformRepository extends ArrayRepository $version = $versionParser->normalize($prettyVersion); } - $ext = new MemoryPackage('ext-'.strtolower($ext), $version, $prettyVersion); + $ext = new MemoryPackage('ext-'.$name, $version, $prettyVersion); + $ext->setDescription('The '.$name.' PHP extension'); parent::addPackage($ext); } } diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 20e060e68..8021a2cb8 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -49,7 +49,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface public function getRootIdentifier() { if (null === $this->rootIdentifier) { - $repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository), true); + $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository)); $this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master'; } @@ -93,13 +93,13 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface if (!isset($this->infoCache[$identifier])) { $composer = $this->getContents($this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json'); if (!$composer) { - throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl()); + return; } $composer = JsonFile::parseJson($composer); if (!isset($composer['time'])) { - $changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true); + $changeset = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier)); $composer['time'] = $changeset['timestamp']; } $this->infoCache[$identifier] = $composer; @@ -114,7 +114,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface public function getTags() { if (null === $this->tags) { - $tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true); + $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags')); $this->tags = array(); foreach ($tagsData as $tag => $data) { $this->tags[$tag] = $data['raw_node']; @@ -130,7 +130,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface public function getBranches() { if (null === $this->branches) { - $branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true); + $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches')); $this->branches = array(); foreach ($branchData as $branch => $data) { $this->branches[$branch] = $data['raw_node']; @@ -140,20 +140,6 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface return $this->branches; } - /** - * {@inheritDoc} - */ - public function hasComposerFile($identifier) - { - try { - $this->getComposerInformation($identifier); - return true; - } catch (\Exception $e) { - } - - return false; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index d6c49daf2..90f5ff926 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -9,7 +9,7 @@ use Composer\IO\IOInterface; /** * @author Jordi Boggiano */ -class GitDriver extends VcsDriver implements VcsDriverInterface +class GitDriver extends VcsDriver { protected $tags; protected $branches; @@ -117,7 +117,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface $this->process->execute(sprintf('cd %s && git show %s:composer.json', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $composer); if (!trim($composer)) { - throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl()); + return; } $composer = JsonFile::parseJson($composer); @@ -173,20 +173,6 @@ class GitDriver extends VcsDriver implements VcsDriverInterface return $this->branches; } - /** - * {@inheritDoc} - */ - public function hasComposerFile($identifier) - { - try { - $this->getComposerInformation($identifier); - return true; - } catch (\Exception $e) { - } - - return false; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 13071a594..01371c1a2 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -8,7 +8,7 @@ use Composer\IO\IOInterface; /** * @author Jordi Boggiano */ -class GitHubDriver extends VcsDriver implements VcsDriverInterface +class GitHubDriver extends VcsDriver { protected $owner; protected $repository; @@ -39,7 +39,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface public function getRootIdentifier() { if (null === $this->rootIdentifier) { - $repoData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository), true); + $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository)); $this->rootIdentifier = $repoData['master_branch'] ?: 'master'; } @@ -83,13 +83,13 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface if (!isset($this->infoCache[$identifier])) { $composer = $this->getContents($this->getScheme() . '://raw.github.com/'.$this->owner.'/'.$this->repository.'/'.$identifier.'/composer.json'); if (!$composer) { - throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl()); + return; } $composer = JsonFile::parseJson($composer); if (!isset($composer['time'])) { - $commit = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier), true); + $commit = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier)); $composer['time'] = $commit['commit']['committer']['date']; } $this->infoCache[$identifier] = $composer; @@ -104,7 +104,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface public function getTags() { if (null === $this->tags) { - $tagsData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'), true); + $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags')); $this->tags = array(); foreach ($tagsData as $tag) { $this->tags[$tag['name']] = $tag['commit']['sha']; @@ -120,7 +120,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface public function getBranches() { if (null === $this->branches) { - $branchData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'), true); + $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches')); $this->branches = array(); foreach ($branchData as $branch) { $this->branches[$branch['name']] = $branch['commit']['sha']; @@ -130,20 +130,6 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface return $this->branches; } - /** - * {@inheritDoc} - */ - public function hasComposerFile($identifier) - { - try { - $this->getComposerInformation($identifier); - return true; - } catch (\Exception $e) { - } - - return false; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 6d2e8b066..54dfd5d30 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -18,7 +18,7 @@ use Composer\IO\IOInterface; /** * @author Per Bernhardt */ -class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface +class HgBitbucketDriver extends VcsDriver { protected $owner; protected $repository; @@ -49,7 +49,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface public function getRootIdentifier() { if (null === $this->rootIdentifier) { - $repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true); + $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags')); $this->rootIdentifier = $repoData['tip']['raw_node']; } @@ -93,13 +93,13 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface if (!isset($this->infoCache[$identifier])) { $composer = $this->getContents($this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json'); if (!$composer) { - throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl()); + return; } $composer = JsonFile::parseJson($composer); if (!isset($composer['time'])) { - $changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true); + $changeset = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier)); $composer['time'] = $changeset['timestamp']; } $this->infoCache[$identifier] = $composer; @@ -114,7 +114,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface public function getTags() { if (null === $this->tags) { - $tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true); + $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags')); $this->tags = array(); foreach ($tagsData as $tag => $data) { $this->tags[$tag] = $data['raw_node']; @@ -130,7 +130,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface public function getBranches() { if (null === $this->branches) { - $branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true); + $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches')); $this->branches = array(); foreach ($branchData as $branch => $data) { $this->branches[$branch] = $data['raw_node']; @@ -140,25 +140,11 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface return $this->branches; } - /** - * {@inheritDoc} - */ - public function hasComposerFile($identifier) - { - try { - $this->getComposerInformation($identifier); - return true; - } catch (\Exception $e) { - } - - return false; - } - /** * {@inheritDoc} */ public static function supports($url, $deep = false) { - return preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url); + return extension_loaded('openssl') && preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url); } } diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 1ca21948d..f7390fb47 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -19,7 +19,7 @@ use Composer\IO\IOInterface; /** * @author Per Bernhardt */ -class HgDriver extends VcsDriver implements VcsDriverInterface +class HgDriver extends VcsDriver { protected $tags; protected $branches; @@ -100,7 +100,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface $this->process->execute(sprintf('cd %s && hg cat -r %s composer.json', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $composer); if (!trim($composer)) { - throw new \UnexpectedValueException('Failed to retrieve composer information for identifier ' . $identifier . ' in ' . $this->getUrl()); + return; } $composer = JsonFile::parseJson($composer); @@ -159,20 +159,6 @@ class HgDriver extends VcsDriver implements VcsDriverInterface return $this->branches; } - /** - * {@inheritDoc} - */ - public function hasComposerFile($identifier) - { - try { - $this->getComposerInformation($identifier); - return true; - } catch (\Exception $e) { - } - - return false; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 330eaeeb5..d76629c62 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -9,7 +9,7 @@ use Composer\IO\IOInterface; /** * @author Jordi Boggiano */ -class SvnDriver extends VcsDriver implements VcsDriverInterface +class SvnDriver extends VcsDriver { protected $baseUrl; protected $tags; @@ -158,7 +158,7 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface { $identifier = '/' . trim($identifier, '/') . '/'; if (!isset($this->infoCache[$identifier])) { - preg_match('{^(.+?)(@\d+)?$}', $identifier, $match); + preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); if (!empty($match[2])) { $identifier = $match[1]; $rev = $match[2]; @@ -167,11 +167,8 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface } $output = $this->execute('svn cat', $this->baseUrl . $identifier . 'composer.json' . $rev); - if (!trim($output)) { - throw new \UnexpectedValueException( - 'Failed to retrieve composer information for identifier ' . $identifier . ' in ' . $this->getUrl() - ); + return; } $composer = JsonFile::parseJson($output); @@ -312,20 +309,6 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface return '--non-interactive '; } - /** - * {@inheritDoc} - */ - public function hasComposerFile($identifier) - { - try { - $this->getComposerInformation($identifier); - return true; - } catch (\Exception $e) { - } - - return false; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 75b631a42..a20e959fd 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -12,6 +12,7 @@ namespace Composer\Repository\Vcs; +use Composer\Downloader\TransportException; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; @@ -21,7 +22,7 @@ use Composer\Util\RemoteFilesystem; * * @author François Pluchino */ -abstract class VcsDriver +abstract class VcsDriver implements VcsDriverInterface { protected $url; protected $io; @@ -41,6 +42,20 @@ abstract class VcsDriver $this->process = $process ?: new ProcessExecutor; } + /** + * {@inheritDoc} + */ + public function hasComposerFile($identifier) + { + try { + return (Boolean) $this->getComposerInformation($identifier); + } catch (TransportException $e) { + } + + return false; + } + + /** * Get the https or http protocol depending on SSL support. * diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index afdc12bcb..65e432b18 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -2,6 +2,7 @@ namespace Composer\Repository; +use Composer\Downloader\TransportException; use Composer\Repository\Vcs\VcsDriverInterface; use Composer\Package\Version\VersionParser; use Composer\Package\PackageInterface; @@ -19,20 +20,22 @@ class VcsRepository extends ArrayRepository protected $debug; protected $io; protected $versionParser; + protected $type; public function __construct(array $config, IOInterface $io, array $drivers = null) { $this->drivers = $drivers ?: array( - 'Composer\Repository\Vcs\GitHubDriver', - 'Composer\Repository\Vcs\GitBitbucketDriver', - 'Composer\Repository\Vcs\GitDriver', - 'Composer\Repository\Vcs\SvnDriver', - 'Composer\Repository\Vcs\HgBitbucketDriver', - 'Composer\Repository\Vcs\HgDriver', + 'github' => 'Composer\Repository\Vcs\GitHubDriver', + 'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', + 'git' => 'Composer\Repository\Vcs\GitDriver', + 'svn' => 'Composer\Repository\Vcs\SvnDriver', + 'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver', + 'hg' => 'Composer\Repository\Vcs\HgDriver', ); $this->url = $config['url']; $this->io = $io; + $this->type = $config['type']; } public function setDebug($debug) @@ -42,6 +45,13 @@ class VcsRepository extends ArrayRepository public function getDriver() { + if (isset($this->drivers[$this->type])) { + $class = $this->drivers[$this->type]; + $driver = new $class($this->url, $this->io); + $driver->initialize(); + return $driver; + } + foreach ($this->drivers as $driver) { if ($driver::supports($this->url)) { $driver = new $driver($this->url, $this->io); @@ -73,9 +83,15 @@ class VcsRepository extends ArrayRepository $this->versionParser = new VersionParser; $loader = new ArrayLoader(); - if ($driver->hasComposerFile($driver->getRootIdentifier())) { - $data = $driver->getComposerInformation($driver->getRootIdentifier()); - $this->packageName = !empty($data['name']) ? $data['name'] : null; + try { + if ($driver->hasComposerFile($driver->getRootIdentifier())) { + $data = $driver->getComposerInformation($driver->getRootIdentifier()); + $this->packageName = !empty($data['name']) ? $data['name'] : null; + } + } catch (\Exception $e) { + if ($debug) { + $this->io->write('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage()); + } } foreach ($driver->getTags() as $tag => $identifier) { @@ -86,46 +102,53 @@ class VcsRepository extends ArrayRepository $this->io->overwrite($msg, false); } - $parsedTag = $this->validateTag($tag); - if ($parsedTag && $driver->hasComposerFile($identifier)) { - try { - $data = $driver->getComposerInformation($identifier); - } catch (\Exception $e) { - if ($debug) { - $this->io->write('Skipped tag '.$tag.', '.$e->getMessage()); - } - continue; - } - - // manually versioned package - if (isset($data['version'])) { - $data['version_normalized'] = $this->versionParser->normalize($data['version']); - } else { - // auto-versionned package, read value from tag - $data['version'] = $tag; - $data['version_normalized'] = $parsedTag; - } - - // make sure tag packages have no -dev flag - $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']); - $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); - - // broken package, version doesn't match tag - if ($data['version_normalized'] !== $parsedTag) { - if ($debug) { - $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); - } - continue; - } - + if (!$parsedTag = $this->validateTag($tag)) { if ($debug) { - $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')'); + $this->io->write('Skipped tag '.$tag.', invalid tag name'); } - - $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier))); - } elseif ($debug) { - $this->io->write('Skipped tag '.$tag.', '.($parsedTag ? 'no composer file was found' : 'invalid name')); + continue; } + + try { + if (!$data = $driver->getComposerInformation($identifier)) { + if ($debug) { + $this->io->write('Skipped tag '.$tag.', no composer file'); + } + continue; + } + } catch (\Exception $e) { + if ($debug) { + $this->io->write('Skipped tag '.$tag.', '.$e->getMessage()); + } + continue; + } + + // manually versioned package + if (isset($data['version'])) { + $data['version_normalized'] = $this->versionParser->normalize($data['version']); + } else { + // auto-versionned package, read value from tag + $data['version'] = $tag; + $data['version_normalized'] = $parsedTag; + } + + // make sure tag packages have no -dev flag + $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']); + $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); + + // broken package, version doesn't match tag + if ($data['version_normalized'] !== $parsedTag) { + if ($debug) { + $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); + } + continue; + } + + if ($debug) { + $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')'); + } + + $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier))); } $this->io->overwrite('', false); @@ -138,36 +161,46 @@ class VcsRepository extends ArrayRepository $this->io->overwrite($msg, false); } - $parsedBranch = $this->validateBranch($branch); - if ($driver->hasComposerFile($identifier)) { - $data = $driver->getComposerInformation($identifier); + if (!$parsedBranch = $this->validateBranch($branch)) { + if ($debug) { + $this->io->write('Skipped branch '.$branch.', invalid name'); + } + continue; + } - if (!$parsedBranch) { + try { + if (!$data = $driver->getComposerInformation($identifier)) { if ($debug) { - $this->io->write('Skipped branch '.$branch.', invalid name and no composer file was found'); + $this->io->write('Skipped branch '.$branch.', no composer file'); } continue; } - - // branches are always auto-versionned, read value from branch name - $data['version'] = $branch; - $data['version_normalized'] = $parsedBranch; - - // make sure branch packages have a dev flag - if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) { - $data['version'] = 'dev-' . $data['version']; - } else { - $data['version'] = $data['version'] . '-dev'; - } - + } catch (TransportException $e) { if ($debug) { - $this->io->write('Importing branch '.$branch.' ('.$data['version_normalized'].')'); + $this->io->write('Skipped branch '.$branch.', no composer file was found'); } - - $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier))); - } elseif ($debug) { - $this->io->write('Skipped branch '.$branch.', no composer file was found'); + continue; + } catch (\Exception $e) { + $this->io->write('Skipped branch '.$branch.', '.$e->getMessage()); + continue; } + + // branches are always auto-versionned, read value from branch name + $data['version'] = $branch; + $data['version_normalized'] = $parsedBranch; + + // make sure branch packages have a dev flag + if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) { + $data['version'] = 'dev-' . $data['version']; + } else { + $data['version'] = $data['version'] . '-dev'; + } + + if ($debug) { + $this->io->write('Importing branch '.$branch.' ('.$data['version_normalized'].')'); + } + + $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier))); } $this->io->overwrite('', false); diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 201f44a7e..dd0163717 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -66,11 +66,12 @@ class Filesystem throw new \InvalidArgumentException('from and to must be absolute paths'); } + $from = lcfirst(rtrim(strtr($from, '\\', '/'), '/')); + $to = lcfirst(rtrim(strtr($to, '\\', '/'), '/')); + if (dirname($from) === dirname($to)) { return './'.basename($to); } - $from = lcfirst(rtrim(strtr($from, '\\', '/'), '/')); - $to = lcfirst(rtrim(strtr($to, '\\', '/'), '/')); $commonPath = $to; while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) { @@ -101,11 +102,12 @@ class Filesystem throw new \InvalidArgumentException('from and to must be absolute paths'); } + $from = lcfirst(strtr($from, '\\', '/')); + $to = lcfirst(strtr($to, '\\', '/')); + if ($from === $to) { return $directories ? '__DIR__' : '__FILE__'; } - $from = lcfirst(strtr($from, '\\', '/')); - $to = lcfirst(strtr($to, '\\', '/')); $commonPath = $to; while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) { diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 7bd0f3bfe..87d725037 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -13,6 +13,7 @@ namespace Composer\Util; use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; /** * @author François Pluchino @@ -81,7 +82,7 @@ class RemoteFilesystem * @param boolean $progress Display the progression * @param boolean $firstCall Whether this is the first attempt at fetching this resource * - * @throws \RuntimeException When the file could not be downloaded + * @throws TransportException When the file could not be downloaded */ protected function get($originUrl, $fileUrl, $fileName = null, $progress = true, $firstCall = true) { @@ -98,7 +99,7 @@ class RemoteFilesystem $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet'))); if ($this->progress) { - $this->io->overwrite(" Downloading: connection...", false); + $this->io->write(" Downloading: connection...", false); } if (null !== $fileName) { @@ -107,6 +108,11 @@ class RemoteFilesystem $result = @file_get_contents($fileUrl, false, $ctx); } + // fix for 5.4.0 https://bugs.php.net/bug.php?id=61336 + if (!empty($http_response_header[0]) && preg_match('{^HTTP/\S+ 404}i', $http_response_header[0])) { + $result = false; + } + // avoid overriding if content was loaded by a sub-call to get() if (null === $this->result) { $this->result = $result; @@ -117,7 +123,7 @@ class RemoteFilesystem } if (false === $this->result) { - throw new \RuntimeException("The '$fileUrl' file could not be downloaded"); + throw new TransportException("The '$fileUrl' file could not be downloaded"); } } @@ -137,7 +143,7 @@ class RemoteFilesystem case STREAM_NOTIFY_AUTH_REQUIRED: case STREAM_NOTIFY_FAILURE: if (404 === $messageCode && !$this->firstCall) { - throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); + throw new TransportException("The '" . $this->fileUrl . "' URL not found", 404); } // for private repository returning 404 error when the authorization is incorrect @@ -149,9 +155,9 @@ class RemoteFilesystem // get authorization informations if (401 === $messageCode || $attemptAuthentication) { if (!$this->io->isInteractive()) { - $mess = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console"; + $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console"; - throw new \RuntimeException($mess); + throw new TransportException($message, 401); } $this->io->overwrite(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 81036e425..9d2d243a9 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -134,6 +134,41 @@ class AutoloadGeneratorTest extends TestCase mkdir($this->vendorDir.'/.composer', 0777, true); $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); $this->assertAutoloadFiles('vendors', $this->vendorDir.'/.composer'); + $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty."); + } + + public function testVendorsClassMapAutoloading() + { + $package = new MemoryPackage('a', '1.0', '1.0'); + + $packages = array(); + $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0'); + $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0'); + $a->setAutoload(array('classmap' => array('src/'))); + $b->setAutoload(array('classmap' => array('src/', 'lib/'))); + + $this->repository->expects($this->once()) + ->method('getPackages') + ->will($this->returnValue($packages)); + + @mkdir($this->vendorDir.'/.composer', 0777, true); + mkdir($this->vendorDir.'/a/a/src', 0777, true); + mkdir($this->vendorDir.'/b/b/src', 0777, true); + mkdir($this->vendorDir.'/b/b/lib', 0777, true); + file_put_contents($this->vendorDir.'/a/a/src/a.php', 'vendorDir.'/b/b/src/b.php', 'vendorDir.'/b/b/lib/c.php', 'generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); + $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated."); + $this->assertEquals( + array( + 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php', + 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/src/b.php', + 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/b/b/lib/c.php', + ), + include ($this->vendorDir.'/.composer/autoload_classmap.php') + ); } public function testOverrideVendorsAutoloading() diff --git a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php new file mode 100644 index 000000000..d40321768 --- /dev/null +++ b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Autoload; + +use Composer\Autoload\ClassMapGenerator; + +class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestCreateMapTests + */ + public function testCreateMap($directory, $expected) + { + $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory)); + } + + public function getTestCreateMapTests() + { + return array( + array(__DIR__.'/Fixtures/Namespaced', array( + 'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php', + 'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php', + 'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php', + ) + ), + array(__DIR__.'/Fixtures/beta/NamespaceCollision', array( + 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php', + 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php', + )), + array(__DIR__.'/Fixtures/Pearlike', array( + 'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php', + 'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php', + 'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php', + )), + array(__DIR__.'/Fixtures/classmap', array( + 'Foo\\Bar\\A' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php', + 'Foo\\Bar\\B' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php', + 'Alpha\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Alpha\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Beta\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Beta\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php', + 'ClassMap\\SomeParent' => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php', + 'ClassMap\\SomeClass' => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php', + )), + ); + } + + public function testCreateMapFinderSupport() + { + if (!class_exists('Symfony\\Component\\Finder\\Finder')) { + $this->markTestSkipped('Finder component is not available'); + } + + $finder = new \Symfony\Component\Finder\Finder(); + $finder->files()->in(__DIR__ . '/Fixtures/beta/NamespaceCollision'); + + $this->assertEqualsNormalized(array( + 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php', + 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php', + ), ClassMapGenerator::createMap($finder)); + } + + protected function assertEqualsNormalized($expected, $actual, $message = null) + { + foreach ($expected as $ns => $path) { + $expected[$ns] = strtr($path, '\\', '/'); + } + foreach ($actual as $ns => $path) { + $actual[$ns] = strtr($path, '\\', '/'); + } + $this->assertEquals($expected, $actual, $message); + } +} diff --git a/tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php b/tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php new file mode 100644 index 000000000..f9c519a66 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php @@ -0,0 +1,8 @@ + dirname($vendorDir) . '/src/', - 'Lala' => dirname($vendorDir) . '/src/', + 'Main' => $baseDir . '/src/', + 'Lala' => $baseDir . '/src/', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_main2.php b/tests/Composer/Test/Autoload/Fixtures/autoload_main2.php index a0fa2c7db..da1c87d88 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_main2.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_main2.php @@ -3,8 +3,9 @@ // autoload_namespace.php generated by Composer $vendorDir = dirname(__DIR__); +$baseDir = dirname(dirname($vendorDir)); return array( - 'Main' => dirname(dirname($vendorDir)) . '/src/', - 'Lala' => dirname(dirname($vendorDir)) . '/src/', + 'Main' => $baseDir . '/src/', + 'Lala' => $baseDir . '/src/', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_main3.php b/tests/Composer/Test/Autoload/Fixtures/autoload_main3.php index 6f12b3d75..2eb74cf44 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_main3.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_main3.php @@ -3,8 +3,9 @@ // autoload_namespace.php generated by Composer $vendorDir = dirname(__DIR__); +$baseDir = $vendorDir; return array( - 'Main' => $vendorDir . '/src/', - 'Lala' => $vendorDir . '/src/', + 'Main' => $baseDir . '/src/', + 'Lala' => $baseDir . '/src/', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php b/tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php index 4b4eacdca..0640aceda 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php @@ -3,6 +3,7 @@ // autoload_namespace.php generated by Composer $vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); return array( 'B\\Sub\\Name' => $vendorDir . '/b/b/src/', diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php b/tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php index b149046df..dd076486d 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php @@ -3,6 +3,7 @@ // autoload_namespace.php generated by Composer $vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); return array( 'B\\Sub\\Name' => $vendorDir . '/b/b/src/', diff --git a/tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php b/tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php new file mode 100644 index 000000000..6a4678832 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php @@ -0,0 +1,8 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Composer; + +class ComposerTest extends TestCase +{ + public function testSetGetPackage() + { + $composer = new Composer(); + $package = $this->getMock('Composer\Package\PackageInterface'); + $composer->setPackage($package); + + $this->assertSame($package, $composer->getPackage()); + } + + public function testSetGetLocker() + { + $composer = new Composer(); + $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock(); + $composer->setLocker($locker); + + $this->assertSame($locker, $composer->getLocker()); + } + + public function testSetGetRepositoryManager() + { + $composer = new Composer(); + $manager = $this->getMockBuilder('Composer\Repository\RepositoryManager')->disableOriginalConstructor()->getMock(); + $composer->setRepositoryManager($manager); + + $this->assertSame($manager, $composer->getRepositoryManager()); + } + + public function testSetGetDownloadManager() + { + $composer = new Composer(); + $manager = $this->getMock('Composer\Downloader\DownloadManager'); + $composer->setDownloadManager($manager); + + $this->assertSame($manager, $composer->getDownloadManager()); + } + + public function testSetGetInstallationManager() + { + $composer = new Composer(); + $manager = $this->getMock('Composer\Installer\InstallationManager'); + $composer->setInstallationManager($manager); + + $this->assertSame($manager, $composer->getInstallationManager()); + } +} \ No newline at end of file diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index fbbf2b269..b7f2c0f53 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -32,4 +32,20 @@ class ArchiveDownloaderTest extends \PHPUnit_Framework_TestCase $this->assertRegExp('#/path/[a-z0-9]+\.js#', $first); $this->assertSame($first, $method->invoke($downloader, $packageMock, '/path')); } + + public function testProcessUrl() + { + $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'))); + $method = new \ReflectionMethod($downloader, 'processUrl'); + $method->setAccessible(true); + + $expected = 'https://github.com/composer/composer/zipball/master'; + $url = $method->invoke($downloader, $expected); + + if (extension_loaded('openssl')) { + $this->assertEquals($expected, $url); + } else { + $this->assertEquals('http://nodeload.github.com/composer/composer/zipball/master', $url); + } + } } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 389f4ddb7..b6eed80a1 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -41,7 +41,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testDownload() { - $expectedGitCommand = $this->getCmd('git clone \'https://example.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -70,19 +70,19 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('https://github.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); - $expectedGitCommand = $this->getCmd('git clone \'git://github.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitCommand = $this->getCmd("git clone 'git://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedGitCommand)) ->will($this->returnValue(1)); - $expectedGitCommand = $this->getCmd('git clone \'https://github.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitCommand = $this->getCmd("git clone 'https://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $processExecutor->expects($this->at(1)) ->method('execute') ->with($this->equalTo($expectedGitCommand)) ->will($this->returnValue(1)); - $expectedGitCommand = $this->getCmd('git clone \'http://github.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitCommand = $this->getCmd("git clone 'http://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $processExecutor->expects($this->at(2)) ->method('execute') ->with($this->equalTo($expectedGitCommand)) @@ -97,7 +97,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase */ public function testDownloadThrowsRuntimeExceptionIfGitCommandFails() { - $expectedGitCommand = $this->getCmd('git clone \'https://example.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -132,8 +132,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testUpdate() { - $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\''); - $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); + $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && 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()) @@ -141,7 +141,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('ref')); $packageMock->expects($this->any()) ->method('getSourceUrl') - ->will($this->returnValue('https://github.com/l3l0/composer')); + ->will($this->returnValue('https://github.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->at(0)) ->method('execute') @@ -161,8 +161,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase */ 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'); + $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && 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()) @@ -170,7 +170,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('ref')); $packageMock->expects($this->any()) ->method('getSourceUrl') - ->will($this->returnValue('https://github.com/l3l0/composer')); + ->will($this->returnValue('https://github.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->at(0)) ->method('execute') @@ -187,7 +187,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testRemove() { - $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); + $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 39e1efb17..8ca89f667 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -75,8 +75,8 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase public function testUpdate() { - $expectedUpdateCommand = $this->getCmd('cd \'composerPath\' && hg pull && hg up \'ref\''); - $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st'); + $expectedUpdateCommand = $this->getCmd("cd 'composerPath' && hg pull 'https://github.com/l3l0/composer' && hg up 'ref'"); + $expectedResetCommand = $this->getCmd("cd 'composerPath' && hg st"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index 78450eb53..73f0faedb 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -53,35 +53,35 @@ class ConsoleIOTest extends TestCase { $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->at(0)) ->method('write') - ->with($this->equalTo("\x08"), $this->equalTo(false)); - $outputMock->expects($this->at(19)) + ->with($this->equalTo('something (strlen = 23)')); + $outputMock->expects($this->at(1)) ->method('write') - ->with($this->equalTo("\x08"), $this->equalTo(false)); - $outputMock->expects($this->at(20)) + ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)); + $outputMock->expects($this->at(2)) ->method('write') - ->with($this->equalTo('some information'), $this->equalTo(false)); - $outputMock->expects($this->at(21)) + ->with($this->equalTo('shorter (12)'), $this->equalTo(false)); + $outputMock->expects($this->at(3)) ->method('write') - ->with($this->equalTo(' '), $this->equalTo(false)); - $outputMock->expects($this->at(24)) + ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)); + $outputMock->expects($this->at(4)) ->method('write') - ->with($this->equalTo(' '), $this->equalTo(false)); - $outputMock->expects($this->at(25)) + ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false)); + $outputMock->expects($this->at(5)) ->method('write') - ->with($this->equalTo("\x08"), $this->equalTo(false)); - $outputMock->expects($this->at(28)) + ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false)); + $outputMock->expects($this->at(6)) ->method('write') - ->with($this->equalTo("\x08"), $this->equalTo(false)); - $outputMock->expects($this->at(29)) - ->method('write') - ->with($this->equalTo('')); + ->with($this->equalTo('something longer than initial (34)')); $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); - $consoleIO->overwrite('some information', true, 20); + $consoleIO->write('something (strlen = 23)'); + $consoleIO->overwrite('shorter (12)', false); + $consoleIO->overwrite('something longer than initial (34)'); } public function testAsk() diff --git a/tests/Composer/Test/IO/NullIOTest.php b/tests/Composer/Test/IO/NullIOTest.php new file mode 100644 index 000000000..854f27862 --- /dev/null +++ b/tests/Composer/Test/IO/NullIOTest.php @@ -0,0 +1,84 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\IO; + +use Composer\IO\NullIO; +use Composer\Test\TestCase; + +class NullIOTest extends TestCase +{ + public function testIsInteractive() + { + $io = new NullIO(); + + $this->assertFalse($io->isInteractive()); + } + + public function testHasAuthorization() + { + $io = new NullIO(); + + $this->assertFalse($io->hasAuthorization('foo')); + } + + public function testGetLastPassword() + { + $io = new NullIO(); + + $this->assertNull($io->getLastPassword()); + } + + public function testGetLastUsername() + { + $io = new NullIO(); + + $this->assertNull($io->getLastUsername()); + } + + public function testAskAndHideAnswer() + { + $io = new NullIO(); + + $this->assertNull($io->askAndHideAnswer('foo')); + } + + public function testGetAuthorizations() + { + $io = new NullIO(); + + $this->assertInternalType('array', $io->getAuthorizations()); + $this->assertEmpty($io->getAuthorizations()); + $this->assertEquals(array('username' => null, 'password' => null), $io->getAuthorization('foo')); + } + + public function testAsk() + { + $io = new NullIO(); + + $this->assertEquals('foo', $io->ask('bar', 'foo')); + } + + public function testAskConfirmation() + { + $io = new NullIO(); + + $this->assertEquals('foo', $io->askConfirmation('bar', 'foo')); + } + + public function testAskAndValidate() + { + $io = new NullIO(); + + $this->assertEquals('foo', $io->askAndValidate('question', 'validator', false, 'foo')); + } +} \ No newline at end of file diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 3090a450f..4e30928cb 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -140,9 +140,10 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase public function testUnicode() { - if (!function_exists('mb_convert_encoding')) { + if (!function_exists('mb_convert_encoding') && version_compare(PHP_VERSION, '5.4', '<')) { $this->markTestSkipped('Test requires the mbstring extension'); } + $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €"); $json = '{ "Žluťoučký \" kůň": "úpěl ďábelské ódy za €" @@ -151,14 +152,23 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->assertJsonFormat($json, $data); } - public function testEscapedSlashes() + public function testOnlyUnicode() { - if (!function_exists('mb_convert_encoding')) { + if (!function_exists('mb_convert_encoding') && version_compare(PHP_VERSION, '5.4', '<')) { $this->markTestSkipped('Test requires the mbstring extension'); } - $data = "\\/fooƌ"; - $this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE); + $data = "\\/ƌ"; + + $this->assertJsonFormat('"\\\\\\/ƌ"', $data, JsonFile::JSON_UNESCAPED_UNICODE); + } + + public function testEscapedSlashes() + { + + $data = "\\/foo"; + + $this->assertJsonFormat('"\\\\\\/foo"', $data, 0); } public function testEscapedUnicode() diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index fe19d9a59..42e88d13c 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -27,23 +27,14 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase * * @return array */ - public static function urlProvider() + public function urlProvider() { $nullIO = new \Composer\IO\NullIO; return array( - array( - 'http://till:test@svn.example.org/', - " --no-auth-cache --username 'till' --password 'test' ", - ), - array( - 'http://svn.apache.org/', - '', - ), - array( - 'svn://johndoe@example.org', - " --no-auth-cache --username 'johndoe' --password '' ", - ), + array('http://till:test@svn.example.org/', $this->getCmd(" --no-auth-cache --username 'till' --password 'test' ")), + array('http://svn.apache.org/', ''), + array('svn://johndoe@example.org', $this->getCmd(" --no-auth-cache --username 'johndoe' --password '' ")), ); } @@ -99,4 +90,13 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase $svn->getSvnCommand('svn ls', $url) ); } + + private function getCmd($cmd) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return strtr($cmd, "'", '"'); + } + + return $cmd; + } } diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php index 255162354..7c008f24f 100644 --- a/tests/Composer/Test/Repository/VcsRepositoryTest.php +++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php @@ -123,7 +123,7 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase 'dev-master' => true, ); - $repo = new VcsRepository(array('url' => self::$gitRepo), new NullIO); + $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO); $packages = $repo->getPackages(); $dumper = new ArrayDumper(); diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index 0db5549d2..81ece7d35 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -41,6 +41,8 @@ class FilesystemTest extends TestCase array('/foo/bar', '/foo/baz', true, "dirname(__DIR__).'/baz'"), array('/foo/bin/run', '/foo/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), array('/foo/bin/run', '/bar/bin/run', true, "'/bar/bin/run'"), + array('/bin/run', '/bin/run', true, "__DIR__"), + array('c:/bin/run', 'c:\\bin/run', true, "__DIR__"), array('c:/bin/run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), array('c:\\bin\\run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), array('c:/bin/run', 'd:/vendor/acme/bin/run', true, "'d:/vendor/acme/bin/run'"), diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index c7e5c076e..bee389941 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -111,7 +111,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0); $this->fail(); } catch (\Exception $e) { - $this->assertInstanceOf('RuntimeException', $e); + $this->assertInstanceOf('Composer\Downloader\TransportException', $e); $this->assertContains('URL not found', $e->getMessage()); } } @@ -137,7 +137,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0); $this->fail(); } catch (\Exception $e) { - $this->assertInstanceOf('RuntimeException', $e); + $this->assertInstanceOf('Composer\Downloader\TransportException', $e); $this->assertContains('URL required authentication', $e->getMessage()); $this->assertAttributeEquals(false, 'firstCall', $fs); }