Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

Conflicts:
	composer.lock
  • Loading branch information...
commit 781d6813b39db1d107ecc1b3bdc42e45234b53b7 2 parents a27471c + 5e0b39e
@till authored
Showing with 1,045 additions and 299 deletions.
  1. +18 −0 CHANGELOG.md
  2. +4 −5 composer.json
  3. +7 −5 composer.lock
  4. +27 −0 doc/02-libraries.md
  5. +31 −0 doc/03-cli.md
  6. +4 −4 doc/04-schema.md
  7. +2 −1  doc/05-repositories.md
  8. +105 −53 src/Composer/Autoload/AutoloadGenerator.php
  9. +2 −0  src/Composer/Autoload/ClassLoader.php
  10. +7 −2 src/Composer/Autoload/ClassMapGenerator.php
  11. +21 −7 src/Composer/Command/CreateProjectCommand.php
  12. +1 −1  src/Composer/Command/DependsCommand.php
  13. +55 −0 src/Composer/Command/DumpAutoloadCommand.php
  14. +34 −7 src/Composer/Command/InitCommand.php
  15. +6 −0 src/Composer/Command/InstallCommand.php
  16. +92 −0 src/Composer/Command/StatusCommand.php
  17. +6 −0 src/Composer/Command/UpdateCommand.php
  18. +2 −0  src/Composer/Console/Application.php
  19. +2 −2 src/Composer/DependencyResolver/DefaultPolicy.php
  20. +1 −1  src/Composer/DependencyResolver/Solver.php
  21. +2 −2 src/Composer/Downloader/DownloadManager.php
  22. +3 −2 src/Composer/Downloader/FileDownloader.php
  23. +27 −15 src/Composer/Downloader/GitDownloader.php
  24. +16 −3 src/Composer/Downloader/HgDownloader.php
  25. +5 −1 src/Composer/Downloader/PearPackageExtractor.php
  26. +17 −4 src/Composer/Downloader/SvnDownloader.php
  27. +66 −5 src/Composer/Downloader/VcsDownloader.php
  28. +4 −1 src/Composer/Downloader/ZipDownloader.php
  29. +15 −2 src/Composer/Installer.php
  30. +18 −0 src/Composer/Installer/InstallationManager.php
  31. +2 −2 src/Composer/Installer/LibraryInstaller.php
  32. +46 −8 src/Composer/Installer/PearInstaller.php
  33. +1 −1  src/Composer/Json/JsonFile.php
  34. +1 −1  src/Composer/Package/Loader/JsonLoader.php
  35. +1 −1  src/Composer/Package/Loader/LoaderInterface.php
  36. +1 −0  src/Composer/Repository/ComposerRepository.php
  37. +4 −0 src/Composer/Repository/Vcs/GitDriver.php
  38. +4 −0 src/Composer/Repository/VcsRepository.php
  39. +1 −1  src/Composer/Util/Filesystem.php
  40. +10 −2 src/Composer/Util/RemoteFilesystem.php
  41. +101 −45 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  42. +1 −0  tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php
  43. +1 −30 tests/Composer/Test/Autoload/Fixtures/autoload_functions.php
  44. +1 −1  tests/Composer/Test/Autoload/Fixtures/autoload_main.php
  45. +1 −1  tests/Composer/Test/Autoload/Fixtures/autoload_main2.php
  46. +1 −1  tests/Composer/Test/Autoload/Fixtures/autoload_main3.php
  47. +0 −12 tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php
  48. +33 −0 tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
  49. +49 −0 tests/Composer/Test/Autoload/Fixtures/autoload_real_target_dir.php
  50. +1 −47 tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
  51. +1 −1  tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php
  52. +8 −8 tests/Composer/Test/Autoload/Fixtures/php5.4/traits.php
  53. +12 −0 tests/Composer/Test/Fixtures/functional/create-project-command.test
  54. +4 −0 tests/Composer/Test/Fixtures/functional/create-project-shows-full-hash-for-dev-packages.test
  55. +3 −1 tests/Composer/Test/Fixtures/installer/SAMPLE
  56. +1 −1  tests/Composer/Test/Fixtures/installer/update-all.test
  57. +146 −0 tests/Composer/Test/FunctionalTest.php
  58. +9 −9 tests/Composer/Test/Installer/LibraryInstallerTest.php
  59. +1 −1  tests/Composer/Test/InstallerTest.php
  60. +0 −1  tests/Composer/Test/Json/JsonFileTest.php
  61. +0 −1  tests/Composer/Test/Mock/FactoryMock.php
View
18 CHANGELOG.md
@@ -1,3 +1,21 @@
+* 1.0.0-alpha5 (2012-08-18)
+
+ * Added `dump-autoload` command to only regenerate the autoloader
+ * Added --optimize to `dump-autoload` to generate a more performant classmap-based autoloader for production
+ * Added `status` command to show if any source-installed dependency has local changes, use --verbose to see changed files
+ * Added --verbose flag to `install` and `update` that shows the new commits when updating source-installed dependencies
+ * Added --no-update flag to `require` to only modify the composer.json file but skip the update
+ * Added --no-custom-installers and --no-scripts to `install`, `update` and `create-project` to prevent all automatic code execution
+ * Added support for installing archives that contain only a single file
+ * Fixed APC related issues in the autoload script on high load websites
+ * Fixed installation of branches containing capital letters
+ * Fixed installation of custom dev versions/branches
+ * Improved the coverage of the `validate` command
+ * Improved PEAR scripts/binaries support
+ * Improved and fixed the output of various commands
+ * Improved error reporting on network failures and some other edge cases
+ * Various minor bug fixes and docs improvements
+
* 1.0.0-alpha4 (2012-07-04)
* Break: The default `minimum-stability` is now `stable`, [read more](https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion)
View
9 composer.json
@@ -25,9 +25,9 @@
"php": ">=5.3.2",
"justinrainbow/json-schema": "1.1.*",
"seld/jsonlint": "1.*",
- "symfony/console": "2.1.*",
- "symfony/finder": "2.1.*",
- "symfony/process": "2.1.*@dev"
+ "symfony/console": "2.1.*@RC",
+ "symfony/finder": "2.1.*@RC",
+ "symfony/process": "2.1.*@RC"
},
"suggest": {
"ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic",
@@ -41,6 +41,5 @@
"branch-alias": {
"dev-master": "1.0-dev"
}
- },
- "minimum-stability": "beta"
+ }
}
View
12 composer.lock
@@ -1,5 +1,5 @@
{
- "hash": "34f13094ecccd74e3a821515658b66bb",
+ "hash": "a1b94e69712d09a07bb817423533c483",
"packages": [
{
"package": "justinrainbow/json-schema",
@@ -30,8 +30,8 @@
{
"package": "symfony/finder",
"version": "dev-master",
- "source-reference": "v2.1.0-RC1",
- "commit-date": "1343512949"
+ "source-reference": "1af11ab3a686f9c45f2a8d9d721d5717cdd5f1d6",
+ "commit-date": "1345026762"
},
{
"package": "symfony/process",
@@ -50,8 +50,10 @@
"aliases": [
],
- "minimum-stability": "beta",
+ "minimum-stability": "stable",
"stability-flags": {
- "symfony/process": 20
+ "symfony/console": 5,
+ "symfony/finder": 5,
+ "symfony/process": 5
}
}
View
27 doc/02-libraries.md
@@ -94,6 +94,33 @@ on it. It only has an effect on the main project.
If you do not want to commit the lock file and you are using git, add it to
the `.gitignore`.
+## Light-weight distribution packages
+
+Including the tests and other useless information like .travis.yml in
+distributed packages is not a good idea.
+
+The `.gitattributes` file is a git specific file like `.gitignore` also living
+at the root directory of your library. It overrides local and global
+configuration (`.git/config` and `~/.gitconfig` respectively) when present and
+tracked by git.
+
+Use `.gitattributes` to prevent unwanted files from bloating the zip
+distribution packages.
+
+ // .gitattributes
+ Tests/ export-ignore
+ phpunit.xml.dist export-ignore
+ Resources/doc/ export-ignore
+ .travis.yml export-ignore
+
+Test it by inspecting the zip file generated manually:
+
+ git archive branchName --format zip -o file.zip
+
+> **Note:** files would be still tracked by git just not included in the
+> distribution. This will only work for GitHub packages installed from
+> dist (i.e. tagged releases) for now.
+
## Publishing to a VCS
Once you have a vcs repository (version control system, e.g. git) containing a
View
31 doc/03-cli.md
@@ -211,6 +211,25 @@ By default the command checks for the packages on packagist.org.
from version control.
* **--dev:** Install packages listed in `require-dev`.
+## dump-autoload
+
+If you need to update the autoloader because of new classes in a classmap
+package for example, you can use "dump-autoload" to do that without having to
+go through an install or update.
+
+Additionally, it can dump an optimized autoloader that converts PSR-0 packages
+into classmap ones for performance reasons. In large applications with many
+classes, the autoloader can take up a substantial portion of every request's
+time. Using classmaps for everything is less convenient in development, but
+using this option you can still use PSR-0 for convenience and classmaps for
+performance.
+
+### Options
+
+* **--optimize:** Convert PSR-0 autoloading to classmap to get a faster
+ autoloader. This is recommended especially for production, but can take
+ a bit of time to run so it is currently not done by default.
+
## help
To get more information about a certain command, just use `help`.
@@ -269,6 +288,18 @@ By default it points to `/home/<user>/.composer` on *nix,
`/Users/<user>/.composer` on OSX and
`C:\Users\<user>\AppData\Roaming\Composer` on Windows.
+#### COMPOSER_HOME/config.json
+
+You may put a `config.json` file into the location which `COMPOSER_HOME` points
+to. Composer will merge this configuration with your project's `composer.json`
+when you run the `install` and `update` commands.
+
+This file allows you to set [configuration](04-schema.md#config) and
+[repositories](05-repositories.md) for the user's projects.
+
+In case global configuration matches _local_ configuration, the _local_
+configuration in the project's `composer.json` always wins.
+
### COMPOSER_PROCESS_TIMEOUT
This env var controls the time composer waits for commands (such as git
View
8 doc/04-schema.md
@@ -140,8 +140,8 @@ The recommended notation for the most common licenses is (alphabetical):
GPL-2.0+
GPL-3.0
GPL-3.0+
- LGPL-2.0
- LGPL-2.0+
+ LGPL-2.1
+ LGPL-2.1+
LGPL-3.0
LGPL-3.0+
MIT
@@ -163,7 +163,7 @@ An Example for disjunctive licenses:
{
"license": [
- "LGPL-2.0",
+ "LGPL-2.1",
"GPL-3.0+"
]
}
@@ -171,7 +171,7 @@ An Example for disjunctive licenses:
Alternatively they can be separated with "or" and enclosed in parenthesis;
{
- "license": "(LGPL-2.0 or GPL-3.0+)"
+ "license": "(LGPL-2.1 or GPL-3.0+)"
}
Similarly when multiple licenses need to be applied ("conjunctive license"),
View
3  doc/05-repositories.md
@@ -161,7 +161,8 @@ project to use the patched version. If the library is on GitHub (this is the
case most of the time), you can simply fork it there and push your changes to
your fork. After that you update the project's `composer.json`. All you have
to do is add your fork as a repository and update the version constraint to
-point to your custom branch.
+point to your custom branch. For version constraint naming conventions see
+[Libraries](02-libraries.md) for more information.
Example assuming you patched monolog to fix a bug in the `bugfix` branch:
View
158 src/Composer/Autoload/AutoloadGenerator.php
@@ -25,7 +25,7 @@
*/
class AutoloadGenerator
{
- public function dump(Config $config, RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $classSuffix = '')
+ public function dump(Config $config, RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '')
{
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
@@ -43,7 +43,7 @@ public function dump(Config $config, RepositoryInterface $localRepo, PackageInte
$namespacesFile = <<<EOF
<?php
-// autoload_namespace.php generated by Composer
+// autoload_namespaces.php generated by Composer
\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;
@@ -90,45 +90,70 @@ public function dump(Config $config, RepositoryInterface $localRepo, PackageInte
$prefixes = implode(', ', array_map(function ($prefix) {
return var_export($prefix, true);
}, array_keys($mainAutoload['psr-0'])));
- $baseDirFromVendorDirCode = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
+ $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, getcwd(), true);
$targetDirLoader = <<<EOF
- public static function autoload(\$class)
- {
- \$dir = $baseDirFromVendorDirCode . '/';
- \$prefixes = array($prefixes);
- foreach (\$prefixes as \$prefix) {
- if (0 !== strpos(\$class, \$prefix)) {
- continue;
- }
- \$path = \$dir . implode('/', array_slice(explode('\\\\', \$class), $levels)).'.php';
- if (!\$path = stream_resolve_include_path(\$path)) {
- return false;
- }
- require \$path;
-
- return true;
+ public static function autoload(\$class)
+ {
+ \$dir = $baseDirFromTargetDirCode . '/';
+ \$prefixes = array($prefixes);
+ foreach (\$prefixes as \$prefix) {
+ if (0 !== strpos(\$class, \$prefix)) {
+ continue;
+ }
+ \$path = \$dir . implode('/', array_slice(explode('\\\\', \$class), $levels)).'.php';
+ if (!\$path = stream_resolve_include_path(\$path)) {
+ return false;
}
+ require \$path;
+
+ return true;
}
+ }
EOF;
}
// flatten array
+ $classMap = array();
$autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
+
+ if ($scanPsr0Packages) {
+ foreach ($autoloads['psr-0'] as $namespace => $paths) {
+ foreach ($paths as $dir) {
+ $dir = $this->getPath($filesystem, $relVendorPath, $vendorPath, $dir);
+ $whitelist = sprintf(
+ '{%s/%s.+(?<!(?<!/)Test\.php)$}',
+ preg_quote(rtrim($dir, '/')),
+ strpos($namespace, '_') === false ? preg_quote(strtr($namespace, '\\', '/')) : ''
+ );
+ foreach (ClassMapGenerator::createMap($dir, $whitelist) as $class => $path) {
+ if ('' === $namespace || 0 === strpos($class, $namespace)) {
+ $path = '/'.$filesystem->findShortestPath(getcwd(), $path, true);
+ if (!isset($classMap[$class])) {
+ $classMap[$class] = '$baseDir . '.var_export($path, true).",\n";
+ }
+ }
+ }
+ }
+ }
+ }
foreach ($autoloads['classmap'] as $dir) {
foreach (ClassMapGenerator::createMap($dir) as $class => $path) {
$path = '/'.$filesystem->findShortestPath(getcwd(), $path, true);
- $classmapFile .= ' '.var_export($class, true).' => $baseDir . '.var_export($path, true).",\n";
+ $classMap[$class] = '$baseDir . '.var_export($path, true).",\n";
}
}
+ foreach ($classMap as $class => $code) {
+ $classmapFile .= ' '.var_export($class, true).' => '.$code;
+ }
$classmapFile .= ");\n";
$filesCode = "";
$autoloads['files'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['files']));
foreach ($autoloads['files'] as $functionFile) {
- $filesCode .= ' require '.$this->getPathCode($filesystem, $relVendorPath, $vendorPath, $functionFile).";\n";
+ $filesCode .= ' require '.$this->getPathCode($filesystem, $relVendorPath, $vendorPath, $functionFile).";\n";
}
file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
@@ -136,7 +161,8 @@ public static function autoload(\$class)
if ($includePathFile = $this->getIncludePathsFile($packageMap, $filesystem, $relVendorPath, $vendorPath, $vendorPathCode, $appBaseDirCode)) {
file_put_contents($targetDir.'/include_paths.php', $includePathFile);
}
- file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, true, true, (bool) $includePathFile, $targetDirLoader, $filesCode, $classSuffix));
+ file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
+ file_put_contents($targetDir.'/autoload_real'.$suffix.'.php', $this->getAutoloadRealFile(true, true, (bool) $includePathFile, $targetDirLoader, $filesCode, $vendorPathCode, $appBaseDirCode, $suffix));
copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
}
@@ -278,42 +304,71 @@ protected function getPathCode(Filesystem $filesystem, $relVendorPath, $vendorPa
return $baseDir.var_export($path, true);
}
- protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $filesCode, $classSuffix)
+ protected function getPath(Filesystem $filesystem, $relVendorPath, $vendorPath, $path)
+ {
+ $path = strtr($path, '\\', '/');
+ if (!$filesystem->isAbsolutePath($path)) {
+ if (strpos($path, $relVendorPath) === 0) {
+ // path starts with vendor dir
+ return $vendorPath . substr($path, strlen($relVendorPath));
+ }
+
+ return strtr(getcwd(), '\\', '/').'/'.$path;
+ }
+
+ return $path;
+ }
+
+ protected function getAutoloadFile($vendorPathToTargetDirCode, $suffix)
+ {
+ return <<<AUTOLOAD
+<?php
+
+// autoload.php generated by Composer
+
+require_once $vendorPathToTargetDirCode . '/autoload_real$suffix.php';
+
+return ComposerAutoloaderInit$suffix::getLoader();
+
+AUTOLOAD;
+ }
+
+ protected function getAutoloadRealFile($usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $filesCode, $vendorPathCode, $appBaseDirCode, $suffix)
{
// TODO the class ComposerAutoloaderInit should be revert to a closure
// when APC has been fixed:
// - https://github.com/composer/composer/issues/959
// - https://bugs.php.net/bug.php?id=52144
// - https://bugs.php.net/bug.php?id=61576
+ // - https://bugs.php.net/bug.php?id=59298
if ($filesCode) {
- $filesCode = "\n".$filesCode;
+ $filesCode = "\n\n".rtrim($filesCode);
}
$file = <<<HEADER
<?php
-// autoload.php generated by Composer
-if (!class_exists('Composer\\\\Autoload\\\\ClassLoader', false)) {
- require $vendorPathToTargetDirCode . '/ClassLoader.php';
-}
+// autoload_real$suffix.php generated by Composer
+
+require __DIR__ . '/ClassLoader.php';
-if (!class_exists('ComposerAutoloaderInit$classSuffix', false)) {
- class ComposerAutoloaderInit$classSuffix
+class ComposerAutoloaderInit$suffix
+{
+ public static function getLoader()
{
- public static function getLoader()
- {
- \$loader = new \\Composer\\Autoload\\ClassLoader();
- \$composerDir = $vendorPathToTargetDirCode;
+ \$loader = new \\Composer\\Autoload\\ClassLoader();
+ \$vendorDir = $vendorPathCode;
+ \$baseDir = $appBaseDirCode;
HEADER;
if ($useIncludePath) {
$file .= <<<'INCLUDE_PATH'
- $includePaths = require $composerDir . '/include_paths.php';
- array_push($includePaths, get_include_path());
- set_include_path(join(PATH_SEPARATOR, $includePaths));
+ $includePaths = require __DIR__ . '/include_paths.php';
+ array_push($includePaths, get_include_path());
+ set_include_path(join(PATH_SEPARATOR, $includePaths));
INCLUDE_PATH;
@@ -321,10 +376,10 @@ public static function getLoader()
if ($usePSR0) {
$file .= <<<'PSR0'
- $map = require $composerDir . '/autoload_namespaces.php';
- foreach ($map as $namespace => $path) {
- $loader->add($namespace, $path);
- }
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->add($namespace, $path);
+ }
PSR0;
@@ -332,10 +387,10 @@ public static function getLoader()
if ($useClassMap) {
$file .= <<<'CLASSMAP'
- $classMap = require $composerDir . '/autoload_classmap.php';
- if ($classMap) {
- $loader->addClassMap($classMap);
- }
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
CLASSMAP;
@@ -343,7 +398,7 @@ public static function getLoader()
if ($targetDirLoader) {
$file .= <<<REGISTER_AUTOLOAD
- spl_autoload_register(array('ComposerAutoloaderInit$classSuffix', 'autoload'));
+ spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'autoload'));
REGISTER_AUTOLOAD;
@@ -351,23 +406,20 @@ public static function getLoader()
}
$file .= <<<METHOD_FOOTER
- \$loader->register();
-$filesCode
- return \$loader;
- }
+ \$loader->register();{$filesCode}
+
+ return \$loader;
+ }
METHOD_FOOTER;
$file .= $targetDirLoader;
return $file . <<<FOOTER
- }
}
-return ComposerAutoloaderInit$classSuffix::getLoader();
-
FOOTER;
}
-}
+}
View
2  src/Composer/Autoload/ClassLoader.php
@@ -201,5 +201,7 @@ public function findFile($class)
if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
return $file;
}
+
+ $this->classMap[$class] = false;
}
}
View
9 src/Composer/Autoload/ClassMapGenerator.php
@@ -40,11 +40,12 @@ public static function dump($dirs, $file)
/**
* Iterate over all files in the given directory searching for classes
*
- * @param Iterator|string $dir The directory to search in or an iterator
+ * @param Iterator|string $dir The directory to search in or an iterator
+ * @param string $whitelist Regex that matches against the file path
*
* @return array A class map array
*/
- public static function createMap($dir)
+ public static function createMap($dir, $whitelist = null)
{
if (is_string($dir)) {
if (is_file($dir)) {
@@ -67,6 +68,10 @@ public static function createMap($dir)
continue;
}
+ if ($whitelist && !preg_match($whitelist, strtr($path, '\\', '/'))) {
+ continue;
+ }
+
$classes = self::findClasses($path);
foreach ($classes as $class) {
View
28 src/Composer/Command/CreateProjectCommand.php
@@ -47,7 +47,9 @@ protected function configure()
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.'),
- new InputOption('dev', null, InputOption::VALUE_NONE, 'Whether to install dependencies for development.')
+ new InputOption('dev', null, InputOption::VALUE_NONE, 'Whether to install dependencies for development.'),
+ new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Whether to disable custom installers.'),
+ new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.')
))
->setHelp(<<<EOT
The <info>create-project</info> command creates a new project from a given
@@ -62,7 +64,7 @@ protected function configure()
advisable to install all dependencies required for development by appending the
<info>'--dev'</info> flag.
-To install a package from another repository repository than the default one you
+To install a package from another repository than the default one you
can pass the <info>'--repository-url=http://myrepository.org'</info> flag.
EOT
@@ -79,11 +81,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
$input->getArgument('version'),
$input->getOption('prefer-source'),
$input->getOption('dev'),
- $input->getOption('repository-url')
+ $input->getOption('repository-url'),
+ $input->getOption('no-custom-installers'),
+ $input->getOption('no-scripts')
);
}
- public function installProject(IOInterface $io, $packageName, $directory = null, $version = null, $preferSource = false, $installDevPackages = false, $repositoryUrl = null)
+ public function installProject(IOInterface $io, $packageName, $directory = null, $version = null, $preferSource = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false)
{
$dm = $this->createDownloadManager($io);
if ($preferSource) {
@@ -120,6 +124,11 @@ public function installProject(IOInterface $io, $packageName, $directory = null,
}
$io->write('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>', true);
+
+ if ($disableCustomInstallers) {
+ $io->write('<info>Custom installers have been disabled.</info>');
+ }
+
if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) {
$package->setSourceReference(substr($package->getPrettyVersion(), 4));
}
@@ -138,10 +147,15 @@ public function installProject(IOInterface $io, $packageName, $directory = null,
$composer = Factory::create($io);
$installer = Installer::create($io, $composer);
- $installer
- ->setPreferSource($preferSource)
+ $installer->setPreferSource($preferSource)
->setDevMode($installDevPackages)
- ->run();
+ ->setRunScripts( ! $noScripts);
+
+ if ($disableCustomInstallers) {
+ $installer->disableCustomInstallers();
+ }
+
+ $installer->run();
}
protected function createDownloadManager(IOInterface $io)
View
2  src/Composer/Command/DependsCommand.php
@@ -36,7 +36,7 @@ protected function configure()
->setDescription('Shows which packages depend on the given package')
->setDefinition(array(
new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
- new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes))
+ new InputOption('link-type', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes))
))
->setHelp(<<<EOT
Displays detailed information about where a package is referenced.
View
55 src/Composer/Command/DumpAutoloadCommand.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Command;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Composer\Repository\CompositeRepository;
+use Symfony\Component\Console\Output\OutputInterface;
+use Composer\Autoload\AutoloadGenerator;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class DumpAutoloadCommand extends Command
+{
+ protected function configure()
+ {
+ $this
+ ->setName('dump-autoload')
+ ->setAliases(array('dumpautoload'))
+ ->setDescription('dumps the autoloader')
+ ->setDefinition(array(
+ new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 packages to be loaded with classmaps too, good for production.'),
+ ))
+ ->setHelp(<<<EOT
+<info>php composer.phar dump-autoload</info>
+EOT
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $output->writeln('<info>Generating autoload files</info>');
+
+ $composer = $this->getComposer();
+ $installationManager = $composer->getInstallationManager();
+ $localRepos = new CompositeRepository($composer->getRepositoryManager()->getLocalRepositories());
+ $package = $composer->getPackage();
+ $config = $composer->getConfig();
+
+ $generator = new AutoloadGenerator();
+ $generator->dump($config, $localRepos, $package, $installationManager, 'composer', $input->getOption('optimize'));
+ }
+}
View
41 src/Composer/Command/InitCommand.php
@@ -14,6 +14,7 @@
use Composer\Json\JsonFile;
use Composer\Factory;
+use Composer\Package\BasePackage;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Symfony\Component\Console\Input\InputInterface;
@@ -54,13 +55,14 @@ protected function configure()
->setName('init')
->setDescription('Creates a basic composer.json file in current directory.')
->setDefinition(array(
- new InputOption('name', null, InputOption::VALUE_NONE, 'Name of the package'),
- new InputOption('description', null, InputOption::VALUE_NONE, 'Description of package'),
- new InputOption('author', null, InputOption::VALUE_NONE, 'Author name of package'),
+ new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'),
+ new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'),
+ new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'),
// new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'),
- new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'),
+ new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'),
new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
+ new InputOption('minimum-stability', null, InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'),
))
->setHelp(<<<EOT
The <info>init</info> command creates a basic composer.json file
@@ -77,7 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelperSet()->get('dialog');
- $whitelist = array('name', 'description', 'author', 'require');
+ $whitelist = array('name', 'description', 'author', 'homepage', 'require', 'require-dev', 'minimum-stability');
$options = array_filter(array_intersect_key($input->getOptions(), array_flip($whitelist)));
@@ -90,6 +92,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->formatRequirements($options['require']) :
new \stdClass;
+ if (isset($options['require-dev'])) {
+ $options['require-dev'] = $this->formatRequirements($options['require-dev']) ;
+ }
+
$file = new JsonFile('composer.json');
$json = $file->encode($options);
@@ -147,7 +153,7 @@ protected function interact(InputInterface $input, OutputInterface $output)
$cwd = realpath(".");
- if (false === $name = $input->getOption('name')) {
+ if (!$name = $input->getOption('name')) {
$name = basename($cwd);
if (isset($git['github.user'])) {
$name = $git['github.user'] . '/' . $name;
@@ -187,7 +193,7 @@ function ($value) use ($name) {
);
$input->setOption('description', $description);
- if (false === $author = $input->getOption('author')) {
+ if (null === $author = $input->getOption('author')) {
if (isset($git['user.name']) && isset($git['user.email'])) {
$author = sprintf('%s <%s>', $git['user.name'], $git['user.email']);
}
@@ -209,6 +215,27 @@ function ($value) use ($self, $author) {
);
$input->setOption('author', $author);
+ $minimumStability = $input->getOption('minimum-stability') ?: '';
+ $minimumStability = $dialog->askAndValidate(
+ $output,
+ $dialog->getQuestion('Minimum Stability', $minimumStability),
+ function ($value) use ($self, $minimumStability) {
+ if (null === $value) {
+ return $minimumStability;
+ }
+
+ if (!isset(BasePackage::$stabilities[$value])) {
+ throw new \InvalidArgumentException(
+ 'Invalid minimum stability "'.$value.'". Must be empty or one of: '.
+ implode(', ', array_keys(BasePackage::$stabilities))
+ );
+ }
+
+ return $value;
+ }
+ );
+ $input->setOption('minimum-stability', $minimumStability);
+
$output->writeln(array(
'',
'Define your dependencies.',
View
6 src/Composer/Command/InstallCommand.php
@@ -33,7 +33,9 @@ protected function configure()
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
+ new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Disables all custom installers.'),
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
+ new InputOption('verbose', 'v', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
))
->setHelp(<<<EOT
The <info>install</info> command reads the composer.json file from the
@@ -61,6 +63,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
->setRunScripts(!$input->getOption('no-scripts'))
;
+ if ($input->getOption('no-custom-installers')) {
+ $install->disableCustomInstallers();
+ }
+
return $install->run() ? 0 : 1;
}
}
View
92 src/Composer/Command/StatusCommand.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Command;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Composer\Downloader\VcsDownloader;
+
+/**
+ * @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
+ * @author Rui Marinho <rui.marinho@seegno.com>
+ */
+class StatusCommand extends Command
+{
+ protected function configure()
+ {
+ $this
+ ->setName('status')
+ ->setDescription('Show a list of locally modified packages')
+ ->setDefinition(array(
+ new InputOption('verbose', 'v', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'),
+ ))
+ ->setHelp(<<<EOT
+The status command displays a list of dependencies that have
+been modified locally.
+
+EOT
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ // init repos
+ $composer = $this->getComposer();
+ $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
+
+ $dm = $composer->getDownloadManager();
+ $im = $composer->getInstallationManager();
+
+ $errors = array();
+
+ // list packages
+ foreach ($installedRepo->getPackages() as $package) {
+ $downloader = $dm->getDownloaderForInstalledPackage($package);
+
+ if ($downloader instanceof VcsDownloader) {
+ $targetDir = $im->getInstallPath($package);
+
+ if ($changes = $downloader->getLocalChanges($targetDir)) {
+ $errors[$targetDir] = $changes;
+ }
+ }
+ }
+
+ // output errors/warnings
+ if (!$errors) {
+ $output->writeln('<info>No local changes</info>');
+ } else {
+ $output->writeln('<error>You have changes in the following dependencies:</error>');
+ }
+
+ foreach ($errors as $path => $changes) {
+ if ($input->getOption('verbose')) {
+ $indentedChanges = implode("\n", array_map(function ($line) {
+ return ' ' . $line;
+ }, explode("\n", $changes)));
+ $output->writeln('<info>'.$path.'</info>:');
+ $output->writeln($indentedChanges);
+ } else {
+ $output->writeln($path);
+ }
+ }
+
+ if ($errors && !$input->getOption('verbose')) {
+ $output->writeln('Use --verbose (-v) to see modified files');
+ }
+
+ return $errors ? 1 : 0;
+ }
+}
View
6 src/Composer/Command/UpdateCommand.php
@@ -33,7 +33,9 @@ protected function configure()
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
+ new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Disables all custom installers.'),
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
+ new InputOption('verbose', 'v', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
))
->setHelp(<<<EOT
The <info>update</info> command reads the composer.json file from the
@@ -67,6 +69,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
->setUpdateWhitelist($input->getArgument('packages'))
;
+ if ($input->getOption('no-custom-installers')) {
+ $install->disableCustomInstallers();
+ }
+
return $install->run() ? 0 : 1;
}
}
View
2  src/Composer/Console/Application.php 100644 → 100755
@@ -130,6 +130,8 @@ protected function getDefaultCommands()
$commands[] = new Command\ValidateCommand();
$commands[] = new Command\ShowCommand();
$commands[] = new Command\RequireCommand();
+ $commands[] = new Command\DumpAutoloadCommand();
+ $commands[] = new Command\StatusCommand();
if ('phar:' === substr(__FILE__, 0, 5)) {
$commands[] = new Command\SelfUpdateCommand();
View
4 src/Composer/DependencyResolver/DefaultPolicy.php
@@ -146,8 +146,8 @@ public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap
* Replace constraints are ignored. This method should only be used for
* prioritisation, not for actual constraint verification.
*
- * @param PackageInterface $source
- * @param PackageInterface $target
+ * @param PackageInterface $source
+ * @param PackageInterface $target
* @return bool
*/
protected function replaces(PackageInterface $source, PackageInterface $target)
View
2  src/Composer/DependencyResolver/Solver.php
@@ -524,7 +524,7 @@ private function disableProblem($why)
if (!$job) {
$why->disable();
-
+
return;
}
View
4 src/Composer/Downloader/DownloadManager.php
@@ -30,8 +30,8 @@ class DownloadManager
/**
* Initializes download manager.
*
- * @param bool $preferSource prefer downloading from source
- * @param Filesystem|null $filesystem custom Filesystem object
+ * @param bool $preferSource prefer downloading from source
+ * @param Filesystem|null $filesystem custom Filesystem object
*/
public function __construct($preferSource = false, Filesystem $filesystem = null)
{
View
5 src/Composer/Downloader/FileDownloader.php
@@ -14,6 +14,7 @@
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
+use Composer\Package\Version\VersionParser;
use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
@@ -64,7 +65,7 @@ public function download(PackageInterface $package, $path)
$fileName = $this->getFileName($package, $path);
- $this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
+ $this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
$processUrl = $this->processUrl($url);
@@ -101,7 +102,7 @@ public function update(PackageInterface $initial, PackageInterface $target, $pat
*/
public function remove(PackageInterface $package, $path)
{
- $this->io->write(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
+ $this->io->write(" - Removing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
if (!$this->filesystem->removeDirectory($path)) {
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
}
View
42 src/Composer/Downloader/GitDownloader.php
@@ -63,6 +63,19 @@ public function doUpdate(PackageInterface $initial, PackageInterface $target, $p
$this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate());
}
+ /**
+ * {@inheritDoc}
+ */
+ public function getLocalChanges($path)
+ {
+ $command = sprintf('cd %s && git status --porcelain --untracked-files=no', escapeshellarg($path));
+ if (0 !== $this->process->execute($command, $output)) {
+ throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+ }
+
+ return trim($output) ?: null;
+ }
+
protected function updateToCommit($path, $reference, $branch, $date)
{
$template = 'git checkout %s && git reset --hard %1$s';
@@ -125,21 +138,6 @@ protected function updateToCommit($path, $reference, $branch, $date)
}
/**
- * {@inheritDoc}
- */
- protected function enforceCleanDirectory($path)
- {
- $command = sprintf('cd %s && git status --porcelain --untracked-files=no', escapeshellarg($path));
- if (0 !== $this->process->execute($command, $output)) {
- throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
- }
-
- if (trim($output)) {
- 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
@@ -235,4 +233,18 @@ protected function setPushUrl(PackageInterface $package, $path)
$this->process->execute($cmd, $ignoredOutput, $path);
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getCommitLogs($fromReference, $toReference, $path)
+ {
+ $command = sprintf('cd %s && git log %s..%s --pretty=format:"%%h - %%an: %%s"', escapeshellarg($path), $fromReference, $toReference);
+
+ if (0 !== $this->process->execute($command, $output)) {
+ throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+ }
+
+ return $output;
+ }
}
View
19 src/Composer/Downloader/HgDownloader.php
@@ -52,11 +52,24 @@ public function doUpdate(PackageInterface $initial, PackageInterface $target, $p
/**
* {@inheritDoc}
*/
- protected function enforceCleanDirectory($path)
+ public function getLocalChanges($path)
{
$this->process->execute(sprintf('cd %s && hg st', escapeshellarg($path)), $output);
- if (trim($output)) {
- throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes');
+
+ return trim($output) ?: null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getCommitLogs($fromReference, $toReference, $path)
+ {
+ $command = sprintf('cd %s && hg log -r %s:%s --style compact', escapeshellarg($path), $fromReference, $toReference);
+
+ if (0 !== $this->process->execute($command, $output)) {
+ throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
}
+
+ return $output;
}
}
View
6 src/Composer/Downloader/PearPackageExtractor.php
@@ -107,7 +107,11 @@ private function copyFile($from, $to, $tasks, $vars)
$pattern = $task['from'];
$varName = $task['to'];
if (isset($vars[$varName])) {
- $replacements[$pattern] = $vars[$varName];
+ if ($varName === 'php_bin' && false === strpos($to, '.bat')) {
+ $replacements[$pattern] = preg_replace('{\.bat$}', '', $vars[$varName]);
+ } else {
+ $replacements[$pattern] = $vars[$varName];
+ }
}
}
$content = strtr($content, $replacements);
View
21 src/Composer/Downloader/SvnDownloader.php
@@ -48,12 +48,11 @@ public function doUpdate(PackageInterface $initial, PackageInterface $target, $p
/**
* {@inheritDoc}
*/
- protected function enforceCleanDirectory($path)
+ public function getLocalChanges($path)
{
$this->process->execute('svn status --ignore-externals', $output, $path);
- if (preg_match('{^ *[^X ] +}m', $output)) {
- throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes:'."\n\n".rtrim($output));
- }
+
+ return preg_match('{^ *[^X ] +}m', $output) ? $output : null;
}
/**
@@ -79,4 +78,18 @@ protected function execute($baseUrl, $command, $url, $cwd = null, $path = null)
);
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getCommitLogs($fromReference, $toReference, $path)
+ {
+ $command = sprintf('cd %s && svn log -r%s:%s --incremental', escapeshellarg($path), $fromReference, $toReference);
+
+ if (0 !== $this->process->execute($command, $output)) {
+ throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+ }
+
+ return $output;
+ }
}
View
71 src/Composer/Downloader/VcsDownloader.php
@@ -13,6 +13,7 @@
namespace Composer\Downloader;
use Composer\Package\PackageInterface;
+use Composer\Package\Version\VersionParser;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
@@ -50,7 +51,7 @@ public function download(PackageInterface $package, $path)
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
}
- $this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
+ $this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
$this->filesystem->removeDirectory($path);
$this->doDownload($package, $path);
$this->io->write('');
@@ -65,9 +66,46 @@ public function update(PackageInterface $initial, PackageInterface $target, $pat
throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
}
- $this->io->write(" - Updating <info>" . $target->getName() . "</info> (<comment>" . $target->getPrettyVersion() . "</comment>)");
+ $name = $target->getName();
+ if ($initial->getPrettyVersion() == $target->getPrettyVersion()) {
+ if ($target->getSourceType() === 'svn') {
+ $from = $initial->getSourceReference();
+ $to = $target->getSourceReference();
+ } else {
+ $from = substr($initial->getSourceReference(), 0, 6);
+ $to = substr($target->getSourceReference(), 0, 6);
+ }
+ $name .= ' '.$initial->getPrettyVersion();
+ } else {
+ $from = VersionParser::formatVersion($initial);
+ $to = VersionParser::formatVersion($target);
+ }
+
+ $this->io->write(" - Updating <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
+
$this->enforceCleanDirectory($path);
$this->doUpdate($initial, $target, $path);
+
+ //print the commit logs if in verbose mode
+ if ($this->io->isVerbose()) {
+ $message = 'Pulling in changes:';
+ $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path);
+
+ if (!trim($logs)) {
+ $message = 'Rolling back changes:';
+ $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path);
+ }
+
+ if (trim($logs)) {
+ $logs = implode("\n", array_map(function ($line) {
+ return ' ' . $line;
+ }, explode("\n", $logs)));
+
+ $this->io->write(' '.$message);
+ $this->io->write($logs);
+ }
+ }
+
$this->io->write('');
}
@@ -84,6 +122,18 @@ public function remove(PackageInterface $package, $path)
}
/**
+ * Guarantee that no changes have been made to the local copy
+ *
+ * @throws \RuntimeException if the directory is not clean
+ */
+ protected function enforceCleanDirectory($path)
+ {
+ if (null !== $this->getLocalChanges($path)) {
+ throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.');
+ }
+ }
+
+ /**
* Downloads specific package into specific folder.
*
* @param PackageInterface $package package instance
@@ -101,9 +151,20 @@ public function remove(PackageInterface $package, $path)
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path);
/**
- * Checks that no changes have been made to the local copy
+ * Checks for changes to the local copy
*
- * @throws \RuntimeException if the directory is not clean
+ * @param string $path package directory
+ * @return string|null changes or null
+ */
+ abstract public function getLocalChanges($path);
+
+ /**
+ * Fetches the commit logs between two commits
+ *
+ * @param string $fromReference the source reference
+ * @param string $toReference the target reference
+ * @param string $path the package path
+ * @return string
*/
- abstract protected function enforceCleanDirectory($path);
+ abstract protected function getCommitLogs($fromReference, $toReference, $path);
}
View
5 src/Composer/Downloader/ZipDownloader.php
@@ -54,7 +54,10 @@ protected function extract($file, $path)
throw new \UnexpectedValueException($this->getErrorMessage($retval, $file));
}
- $zipArchive->extractTo($path);
+ if (true !== $zipArchive->extractTo($path)) {
+ throw new \RuntimeException("There was an error extracting the ZIP file. Corrupt file?");
+ }
+
$zipArchive->close();
}
View
17 src/Composer/Installer.php
@@ -465,8 +465,9 @@ protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = fa
}
}
- if ($this->verbose) {
- $this->io->write((string) $operation);
+ // output alias operations in verbose mode, or all ops in dry run
+ if ($this->dryRun || ($this->verbose && false !== strpos($operation->getJobType(), 'Alias'))) {
+ $this->io->write(' - ' . $operation);
}
$this->installationManager->execute($localRepo, $operation);
@@ -722,4 +723,16 @@ public function setUpdateWhitelist(array $packages)
return $this;
}
+
+ /**
+ * Disables custom installers.
+ *
+ * Call this if you want to ensure that third-party code never gets
+ * executed. The default is to automatically install, and execute
+ * custom third-party installers.
+ */
+ public function disableCustomInstallers()
+ {
+ $this->installationManager->disableCustomInstallers();
+ }
}
View
18 src/Composer/Installer/InstallationManager.php
@@ -47,6 +47,24 @@ public function addInstaller(InstallerInterface $installer)
}
/**
+ * Disables custom installers.
+ *
+ * We prevent any custom installers from being instantiated by simply
+ * deactivating the installer for them. This ensure that no third-party
+ * code is ever executed.
+ */
+ public function disableCustomInstallers()
+ {
+ foreach ($this->installers as $i => $installer) {
+ if (!$installer instanceof InstallerInstaller) {
+ continue;
+ }
+
+ unset($this->installers[$i]);
+ }
+ }
+
+ /**
* Returns installer for a specific package type.
*
* @param string $type package type
View
4 src/Composer/Installer/LibraryInstaller.php
@@ -241,7 +241,7 @@ protected function initializeBinDir()
$this->binDir = realpath($this->binDir);
}
- private function generateWindowsProxyCode($bin, $link)
+ protected function generateWindowsProxyCode($bin, $link)
{
$binPath = $this->filesystem->findShortestPath($link, $bin);
if ('.bat' === substr($bin, -4)) {
@@ -266,7 +266,7 @@ private function generateWindowsProxyCode($bin, $link)
$caller." \"%BIN_TARGET%\" %*\r\n";
}
- private function generateUnixyProxyCode($bin, $link)
+ protected function generateUnixyProxyCode($bin, $link)
{
$binPath = $this->filesystem->findShortestPath($link, $bin);
View
54 src/Composer/Installer/PearInstaller.php
@@ -52,7 +52,7 @@ protected function installCode(PackageInterface $package)
parent::installCode($package);
parent::initializeBinDir();
- $isWindows = defined('PHP_WINDOWS_VERSION_BUILD') ? true : false;
+ $isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
$php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php');
$installPath = $this->getInstallPath($package);
@@ -100,15 +100,53 @@ protected function initializeBinDir()
chmod($this->binDir.'/composer-php.bat', 0777);
}
+ protected function generateWindowsProxyCode($bin, $link)
+ {
+ $binPath = $this->filesystem->findShortestPath($link, $bin);
+ if ('.bat' === substr($bin, -4)) {
+ $caller = 'call';
+ } else {
+ $handle = fopen($bin, 'r');
+ $line = fgets($handle);
+ fclose($handle);
+ if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
+ $caller = trim($match[1]);
+ } else {
+ $caller = 'php';
+ }
+
+ if ($caller === 'php') {
+ return "@echo off\r\n".
+ "pushd .\r\n".
+ "cd %~dp0\r\n".
+ "set PHP_PROXY=%CD%\\composer-php.bat\r\n".
+ "cd ".escapeshellarg(dirname($binPath))."\r\n".
+ "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
+ "popd\r\n".
+ "%PHP_PROXY% \"%BIN_TARGET%\" %*\r\n";
+ }
+ }
+
+ return "@echo off\r\n".
+ "pushd .\r\n".
+ "cd %~dp0\r\n".
+ "cd ".escapeshellarg(dirname($binPath))."\r\n".
+ "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
+ "popd\r\n".
+ $caller." \"%BIN_TARGET%\" %*\r\n";
+ }
+
private function generateWindowsPhpProxyCode()
{
+ $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true);
+
return
"@echo off\r\n" .
"setlocal enabledelayedexpansion\r\n" .
"set BIN_DIR=%~dp0\r\n" .
- "set VENDOR_DIR=%BIN_DIR%..\\\r\n" .
- " set DIRS=.\r\n" .
- "FOR /D %%V IN (%VENDOR_DIR%*) DO (\r\n" .
+ "set VENDOR_DIR=%BIN_DIR%\\".$binToVendor."\r\n" .
+ "set DIRS=.\r\n" .
+ "FOR /D %%V IN (%VENDOR_DIR%\\*) DO (\r\n" .
" FOR /D %%P IN (%%V\\*) DO (\r\n" .
" set DIRS=!DIRS!;%%~fP\r\n" .
" )\r\n" .
@@ -118,12 +156,13 @@ private function generateWindowsPhpProxyCode()
private function generateUnixyPhpProxyCode()
{
+ $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true);
+
return
"#!/usr/bin/env sh\n".
"SRC_DIR=`pwd`\n".
"BIN_DIR=`dirname $(readlink -f $0)`\n".
- "VENDOR_DIR=`dirname \$BIN_DIR`\n".
- "cd \$BIN_DIR\n".
+ "VENDOR_DIR=\$BIN_DIR/".escapeshellarg($binToVendor)."\n".
"DIRS=\"\"\n".
"for vendor in \$VENDOR_DIR/*; do\n".
" if [ -d \"\$vendor\" ]; then\n".
@@ -134,7 +173,6 @@ private function generateUnixyPhpProxyCode()
" done\n".
" fi\n".
"done\n".
- "cd \$SRC_DIR\n".
- "`which php` -d include_path=\".\$DIRS\" $@\n";
+ "php -d include_path=\".\$DIRS\" $@\n";
}
}
View
2  src/Composer/Json/JsonFile.php
@@ -85,7 +85,7 @@ public function read()
$json = file_get_contents($this->path);
}
} catch (TransportException $e) {
- throw new \RuntimeException('Could not read '.$this->path.', either you or the remote host is probably offline'."\n\n".$e->getMessage());
+ throw new \RuntimeException($e->getMessage());
} catch (\Exception $e) {
throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage());
}
View
2  src/Composer/Package/Loader/JsonLoader.php
@@ -27,7 +27,7 @@ public function __construct(LoaderInterface $loader)
}
/**
- * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from
+ * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from
* @return \Composer\Package\PackageInterface
*/
public function load($json)
View
2  src/Composer/Package/Loader/LoaderInterface.php
@@ -22,7 +22,7 @@
/**
* Converts a package from an array to a real instance
*
- * @param array $package Package config
+ * @param array $package Package config
* @return \Composer\Package\PackageInterface
*/
public function load(array $package);
View
1  src/Composer/Repository/ComposerRepository.php
@@ -101,6 +101,7 @@ protected function initialize()
$this->cache->write('packages.json', json_encode($data));
} catch (\Exception $e) {
if ($contents = $this->cache->read('packages.json')) {
+ $this->io->write('<warning>'.$e->getMessage().'</warning>');
$this->io->write('<warning>'.$this->url.' could not be loaded, package information was loaded from the local cache and may be out of date</warning>');
$data = json_decode($contents, true);
} else {
View
4 src/Composer/Repository/Vcs/GitDriver.php
@@ -191,6 +191,10 @@ public static function supports(IOInterface $io, $url, $deep = false)
// local filesystem
if (static::isLocalUrl($url)) {
+ if (!is_dir($url)) {
+ throw new \RuntimeException('Directory does not exist: '.$url);
+ }
+
$process = new ProcessExecutor();
$url = str_replace('file://', '', $url);
// check whether there is a git repo in that path
View
4 src/Composer/Repository/VcsRepository.php
@@ -226,6 +226,10 @@ protected function initialize()
}
$this->io->overwrite('', false);
+
+ if (!$this->getPackages()) {
+ throw new \RuntimeException('No composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.');
+ }
}
private function preProcess(VcsDriverInterface $driver, array $data, $identifier)
View
2  src/Composer/Util/Filesystem.php
@@ -76,7 +76,7 @@ public function rename($source, $target)
return;
}
- throw new \RuntimeException(sprintf('Could not rename "%s" to "%s".', $source, $target));
+ throw new \RuntimeException(sprintf('Could not rename "%s" to "%s".', $source, $target));
}
}
View
12 src/Composer/Util/RemoteFilesystem.php
@@ -101,7 +101,15 @@ protected function get($originUrl, $fileUrl, $fileName = null, $progress = true)
$this->io->write(" Downloading: <comment>connection...</comment>", false);
}
- $result = @file_get_contents($fileUrl, false, $ctx);
+ $errorMessage = null;
+ set_error_handler(function ($code, $msg) use (&$errorMessage) {
+ $errorMessage = preg_replace('{^file_get_contents\(.+?\): }', '', $msg);
+ if (!ini_get('allow_url_fopen')) {
+ $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')';
+ }
+ });
+ $result = file_get_contents($fileUrl, false, $ctx);
+ restore_error_handler();
// 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])) {
@@ -148,7 +156,7 @@ protected function get($originUrl, $fileUrl, $fileName = null, $progress = true)
}
if (false === $this->result) {
- throw new TransportException('The "'.$fileUrl.'" file could not be downloaded');
+ throw new TransportException('The "'.$fileUrl.'" file could not be downloaded: '.$errorMessage);
}
}
View
146 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -33,7 +33,8 @@ protected function setUp()
$this->fs = new Filesystem;
$that = $this;
- $this->workingDir = realpath(sys_get_temp_dir());
+ $this->workingDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest';
+ $this->fs->ensureDirectoryExists($this->workingDir);
$this->vendorDir = $this->workingDir.DIRECTORY_SEPARATOR.'composer-test-autoload';
$this->ensureDirectoryExistsAndClear($this->vendorDir);
@@ -63,18 +64,14 @@ protected function setUp()
protected function tearDown()
{
- if ($this->vendorDir === $this->workingDir) {
- if (is_dir($this->workingDir.'/composer')) {
- $this->fs->removeDirectory($this->workingDir.'/composer');
- }
- } elseif (is_dir($this->vendorDir)) {
- $this->fs->removeDirectory($this->vendorDir);
+ chdir($this->dir);
+
+ if (is_dir($this->workingDir)) {
+ $this->fs->removeDirectory($this->workingDir);
}
- if (is_dir($this->workingDir.'/composersrc')) {
- $this->fs->removeDirectory($this->workingDir.'/composersrc');
+ if (is_dir($this->vendorDir)) {
+ $this->fs->removeDirectory($this->vendorDir);
}
-
- chdir($this->dir);
}
public function testMainPackageAutoloading()
@@ -89,13 +86,13 @@ public function testMainPackageAutoloading()
->method('getPackages')
->will($this->returnValue(array()));
- if (!is_dir($this->vendorDir.'/composer')) {
- mkdir($this->vendorDir.'/composer');
- }
+ $this->fs->ensureDirectoryExists($this->workingDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/src');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/lib');
$this->createClassFile($this->workingDir);
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', '_1');
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_1');
$this->assertAutoloadFiles('main', $this->vendorDir.'/composer');
$this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap');
}
@@ -114,13 +111,13 @@ public function testVendorDirSameAsWorkingDir()
->method('getPackages')
->will($this->returnValue(array()));
- if (!is_dir($this->vendorDir.'/composer')) {
- mkdir($this->vendorDir.'/composer', 0777, true);
- }
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/src/Main');
+ file_put_contents($this->vendorDir.'/src/Main/Foo.php', '<?php namespace Main; class Foo {}');
$this->createClassFile($this->vendorDir);
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', '_2');
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_2');
$this->assertAutoloadFiles('main3', $this->vendorDir.'/composer');
$this->assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap');
}
@@ -138,9 +135,12 @@ public function testMainPackageAutoloadingAlternativeVendorDir()
->will($this->returnValue(array()));
$this->vendorDir .= '/subdir';
- mkdir($this->vendorDir.'/composer', 0777, true);
+
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/src');
+
$this->createClassFile($this->workingDir);
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', '_3');
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_3');
$this->assertAutoloadFiles('main2', $this->vendorDir.'/composer');
$this->assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap');
}
@@ -157,8 +157,11 @@ public function testMainPackageAutoloadingWithTargetDir()
->method('getPackages')
->will($this->returnValue(array()));
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', 'TargetDir');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a');
+
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'TargetDir');
$this->assertFileEquals(__DIR__.'/Fixtures/autoload_target_dir.php', $this->vendorDir.'/autoload.php');
+ $this->assertFileEquals(__DIR__.'/Fixtures/autoload_real_target_dir.php', $this->vendorDir.'/composer/autoload_realTargetDir.php');
}
public function testVendorsAutoloading()
@@ -176,8 +179,12 @@ public function testVendorsAutoloading()
->method('getPackages')
->will($this->returnValue($packages));
- mkdir($this->vendorDir.'/composer', 0777, true);
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', '_5');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src');
+
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5');
$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.");
}
@@ -196,15 +203,15 @@ public function testVendorsClassMapAutoloading()
->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);
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/lib');
file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
file_put_contents($this->vendorDir.'/b/b/src/b.php', '<?php class ClassMapBar {}');
file_put_contents($this->vendorDir.'/b/b/lib/c.php', '<?php class ClassMapBaz {}');
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', '_6');
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_6');
$this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
$this->assertEquals(
array(
@@ -233,15 +240,15 @@ public function testClassMapAutoloadingEmptyDirAndExactFile()
->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', 0777, true);
- mkdir($this->vendorDir.'/c/c/foo', 0777, true);
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo');
file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
file_put_contents($this->vendorDir.'/b/b/test.php', '<?php class ClassMapBar {}');
file_put_contents($this->vendorDir.'/c/c/foo/test.php', '<?php class ClassMapBaz {}');
- $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', '_7');
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7');
$this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
$this->assertEquals(
array(
@@ -257,6 +264,7 @@ public function testClassMapAutoloadingEmptyDirAndExactFile()
public function testFilesAutoloadGeneration()
{
$package = new MemoryPackage('a', '1.0', '1.0');
+ $package->setAutoload(array('files' => array('root.php')));
$packages = array();
$packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
@@ -268,23 +276,29 @@ public function testFilesAutoloadGeneration()
->method('getPackages')
->will($this->returnValue($packages));
- mkdir($this->vendorDir.'/a/a', 0777, true);
- mkdir($this->vendorDir.'/b/b', 0777, true);
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a');
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b');