Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

Conflicts:
	composer.lock
  • Loading branch information...
commit 951d962021a3079f5e1058e66f926642826e1217 2 parents 926a36d + ebc0f88
@till authored
Showing with 1,293 additions and 263 deletions.
  1. +14 −3 CHANGELOG.md
  2. +3 −2 composer.json
  3. +10 −7 composer.lock
  4. +3 −3 doc/00-intro.md
  5. +3 −3 doc/01-basic-usage.md
  6. +16 −10 doc/04-schema.md
  7. +4 −2 doc/05-repositories.md
  8. +43 −0 doc/faqs/how-do-i-install-a-package-in-a-custom-directory.md
  9. +68 −37 src/Composer/Autoload/AutoloadGenerator.php
  10. +7 −0 src/Composer/Command/RequireCommand.php
  11. +2 −1  src/Composer/Command/SelfUpdateCommand.php
  12. +27 −0 src/Composer/Command/ValidateCommand.php
  13. +10 −10 src/Composer/DependencyResolver/DefaultPolicy.php
  14. +32 −6 src/Composer/DependencyResolver/Pool.php
  15. +2 −2 src/Composer/DependencyResolver/Problem.php
  16. +11 −9 src/Composer/DependencyResolver/Solver.php
  17. +4 −0 src/Composer/DependencyResolver/SolverProblemsException.php
  18. +14 −9 src/Composer/Downloader/ArchiveDownloader.php
  19. +2 −1  src/Composer/Downloader/DownloadManager.php
  20. +11 −2 src/Composer/Downloader/GitDownloader.php
  21. +13 −6 src/Composer/Downloader/PearPackageExtractor.php
  22. +1 −1  src/Composer/Installer.php
  23. +0 −1  src/Composer/Installer/InstallationManager.php
  24. +16 −13 src/Composer/Installer/PearInstaller.php
  25. +5 −2 src/Composer/Json/JsonFile.php
  26. +11 −5 src/Composer/Package/BasePackage.php
  27. +11 −2 src/Composer/Package/LinkConstraint/VersionConstraint.php
  28. +4 −4 src/Composer/Package/Loader/ArrayLoader.php
  29. +13 −2 src/Composer/Package/Loader/JsonLoader.php
  30. +29 −0 src/Composer/Package/Loader/LoaderInterface.php
  31. +10 −4 src/Composer/Package/Loader/RootPackageLoader.php
  32. +280 −0 src/Composer/Package/Loader/ValidatingArrayLoader.php
  33. +1 −2  src/Composer/Package/Locker.php
  34. +2 −5 src/Composer/Package/MemoryPackage.php
  35. +7 −0 src/Composer/Package/PackageInterface.php
  36. +13 −5 src/Composer/Package/Version/VersionParser.php
  37. +9 −1 src/Composer/Repository/ComposerRepository.php
  38. +10 −7 src/Composer/Repository/Pear/ChannelRest11Reader.php
  39. +3 −1 src/Composer/Repository/PearRepository.php
  40. +1 −1  src/Composer/Repository/Vcs/GitHubDriver.php
  41. +1 −0  src/Composer/Repository/Vcs/SvnDriver.php
  42. +12 −3 src/Composer/Repository/VcsRepository.php
  43. +29 −2 src/Composer/Util/Filesystem.php
  44. +3 −3 src/Composer/Util/RemoteFilesystem.php
  45. +2 −2 src/Composer/Util/StreamContextFactory.php
  46. +14 −14 tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  47. +24 −16 tests/Composer/Test/Autoload/Fixtures/autoload_functions.php
  48. +39 −28 tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
  49. +1 −1  tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php
  50. +6 −3 tests/Composer/Test/DependencyResolver/SolverTest.php
  51. +20 −2 tests/Composer/Test/Downloader/GitDownloaderTest.php
  52. +2 −1  tests/Composer/Test/Fixtures/installer/aliased-priority-conflicting.test
  53. +2 −1  tests/Composer/Test/Fixtures/installer/aliased-priority.test
  54. +34 −0 tests/Composer/Test/Fixtures/installer/provide-priorities.test
  55. +30 −0 tests/Composer/Test/Fixtures/installer/replace-priorities.test
  56. +2 −1  tests/Composer/Test/Installer/InstallerInstallerTest.php
  57. +2 −2 tests/Composer/Test/InstallerTest.php
  58. +45 −13 tests/Composer/Test/Package/Dumper/ArrayDumperTest.php
  59. +11 −0 tests/Composer/Test/Package/LinkConstraint/VersionConstraintTest.php
  60. +222 −0 tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php
  61. +7 −2 tests/Composer/Test/Package/Version/VersionParserTest.php
  62. +60 −0 tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php
View
17 CHANGELOG.md
@@ -1,18 +1,29 @@
-* 1.0.0-alpha4
+* 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)
+ * Break: Custom installers now receive the IO instance and a Composer instance in their constructor
* Schema: Added references for dev versions, requiring `dev-master#abcdef` for example will force the abcdef commit
* Schema: Added `support` key with some more metadata (email, issues, forum, wiki, irc, source)
+ * Schema: Added `!=` operator for version constraints in `require`/`require-dev`
+ * Added a recommendation for package names to be `lower-cased/with-dashes`, it will be enforced for new packages on Pacakgist
* Added `require` command to add a package to your requirements and install it
* Added a whitelist to `update`. Calling `composer update foo/bar foo/baz` allows you to update only those packages
+ * Added support for overriding repositories in the system config (define repositories in ~/.composer/config.json)
+ * Added `lib-*` packages to the platform repository, e.g. `lib-pcre` contains the pcre version
* Added caching of GitHub metadata (faster startup time with custom GitHub VCS repos)
+ * Added caching of SVN metadata (faster startup time with custom SVN VCS repos)
* Added support for file:// URLs to GitDriver
+ * Added --self flag to the `show` command to display the infos of the root package
* Added --dev flag to `create-project` command
* Added --no-scripts to `install` and `update` commands to avoid triggering the scripts
* Added `COMPOSER_ROOT_VERSION` env var to specify the version of the root package (fixes some edge cases)
* Added support for multiple custom installers in one package
- * Added files autoloading method which requires files on every request, e.g. to load functions that are not autoloadable
+ * Added files autoloading method which requires files on every request, e.g. to load functional code
+ * Added automatic recovery for lock files that contain references to rewritten (force pushed) commits
+ * Improved PEAR repositories support and package.xml extraction
* Improved and fixed the output of various commands
- * Cleaned up / refactored the dependency solver code
+ * Fixed the order of installation of requirements (they are always installed before the packages requiring them)
+ * Cleaned up / refactored the dependency solver code as well as the output for unsolvable requirements
* Various bug fixes and docs improvements
* 1.0.0-alpha3 (2012-05-13)
View
5 composer.json
@@ -27,10 +27,11 @@
"seld/jsonlint": "1.*",
"symfony/console": "2.1.*",
"symfony/finder": "2.1.*",
- "symfony/process": "2.1.*"
+ "symfony/process": "2.1.*@dev"
},
"suggest": {
- "ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic"
+ "ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic",
+ "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages"
},
"autoload": {
"psr-0": { "Composer": "src/" }
View
17 composer.lock
@@ -1,5 +1,5 @@
{
- "hash": "3ee1db513743783e9b34310017552a3e",
+ "hash": "34f13094ecccd74e3a821515658b66bb",
"packages": [
{
"package": "justinrainbow/json-schema",
@@ -18,7 +18,8 @@
{
"package": "symfony/console",
"version": "dev-master",
- "source-reference": "808bf42b4b74c12e6c6a6a9750688035f1c49f9c"
+ "source-reference": "3b66f1056f5ac37146d3b755964e8a43a205f2f5",
+ "commit-date": "1344349441"
},
{
"package": "symfony/finder",
@@ -29,7 +30,8 @@
{
"package": "symfony/finder",
"version": "dev-master",
- "source-reference": "v2.1.0-BETA1"
+ "source-reference": "v2.1.0-RC1",
+ "commit-date": "1343512949"
},
{
"package": "symfony/process",
@@ -40,7 +42,8 @@
{
"package": "symfony/process",
"version": "dev-master",
- "source-reference": "v2.1.0-BETA1"
+ "source-reference": "v2.1.0-RC1",
+ "commit-date": "1343512949"
}
],
"packages-dev": null,
@@ -48,7 +51,7 @@
],
"minimum-stability": "beta",
- "stability-flags": [
-
- ]
+ "stability-flags": {
+ "symfony/process": 20
+ }
}
View
6 doc/00-intro.md
@@ -51,7 +51,7 @@ any version beginning with `1.0`.
To actually get Composer, we need to do two things. The first one is installing
Composer (again, this mean downloading it into your project):
- $ curl -s http://getcomposer.org/installer | php
+ $ curl -s https://getcomposer.org/installer | php
This will just check a few PHP settings and then download `composer.phar` to
your working directory. This file is the Composer binary. It is a PHAR (PHP
@@ -61,7 +61,7 @@ line, amongst other things.
You can install Composer to a specific directory by using the `--install-dir`
option and providing a target directory (it can be an absolute or relative path):
- $ curl -s http://getcomposer.org/installer | php -- --install-dir=bin
+ $ curl -s https://getcomposer.org/installer | php -- --install-dir=bin
#### Globally
@@ -71,7 +71,7 @@ executable and invoke it without `php`.
You can run these commands to easily access `composer` from anywhere on your system:
- $ curl -s http://getcomposer.org/installer | php
+ $ curl -s https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
Then, just run `composer` in order to run composer
View
6 doc/01-basic-usage.md
@@ -165,8 +165,8 @@ Composer will register a
autoloader for the `Acme` namespace.
You define a mapping from namespaces to directories. The `src` directory would
-be in your project root. An example filename would be `src/Acme/Foo.php`
-containing an `Acme\Foo` class.
+be in your project root, on the same level as `vendor` directory is. An example
+filename would be `src/Acme/Foo.php` containing an `Acme\Foo` class.
After adding the `autoload` field, you have to re-run `install` to re-generate
the `vendor/autoload.php` file.
@@ -183,7 +183,7 @@ classes to be autoloaded even if they do not conform to PSR-0. See the
[autoload reference](04-schema.md#autoload) for more details.
> **Note:** Composer provides its own autoloader. If you don't want to use
-that one, you can just include `vendor/autoload_namespaces.php`,
+that one, you can just include `vendor/composer/autoload_namespaces.php`,
which returns an associative array mapping namespaces to directories.
← [Intro](00-intro.md) | [Libraries](02-libraries.md) →
View
26 doc/04-schema.md
@@ -23,6 +23,11 @@ The config of dependencies is ignored. This makes the `config` field
If you clone one of those dependencies to work on it, then that package is the
root package. The `composer.json` is identical, but the context is different.
+> **Note:** A package can be the root package or not, depending on the context.
+> For example, if your project depends on the `monolog` library, your project
+> is the root package. However, if you clone `monolog` from GitHub in order to
+> fix a bug in it, then `monolog` is the root package.
+
## Properties
### name
@@ -78,7 +83,7 @@ that needs some special logic, you can define a custom type. This could be a
all be specific to certain projects, and they will need to provide an
installer capable of installing packages of that type.
-Out of the box, composer supports two types:
+Out of the box, composer supports three types:
- **library:** This is the default. It will simply copy the files to `vendor`.
- **metapackage:** An empty package that contains requirements and will trigger
@@ -163,14 +168,14 @@ An Example for disjunctive licenses:
]
}
-Alternatively they can be separated with "or" and enclosed in brackets;
+Alternatively they can be separated with "or" and enclosed in parenthesis;
{
"license": "(LGPL-2.0 or GPL-3.0+)"
}
Similarly when multiple licenses need to be applied ("conjunctive license"),
-they should be separated with "and" and enclosed in brackets.
+they should be separated with "and" and enclosed in parenthesis.
### authors
@@ -253,7 +258,7 @@ Example:
{
"require": {
- "monolog/monolog": "1.0.*@beta"
+ "monolog/monolog": "1.0.*@beta",
"acme/foo": "@dev"
}
}
@@ -270,7 +275,7 @@ Example:
{
"require": {
- "monolog/monolog": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7"
+ "monolog/monolog": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7",
"acme/foo": "1.0.x-dev#abc123"
}
}
@@ -411,7 +416,7 @@ to search for classes.
Example:
{
- "autoload: {
+ "autoload": {
"classmap": ["src/", "lib/", "Something.php"]
}
}
@@ -472,16 +477,17 @@ Optional.
### minimum-stability <span>(root-only)</span>
This defines the default behavior for filtering packages by stability. This
-defaults to `dev` but in the future will be switched to `stable`. As such if
-you rely on a default of `dev` you should specify it in your file to avoid
-surprises.
+defaults to `stable`, so if you rely on a `dev` package, you should specify
+it in your file to avoid surprises.
-All versions of each package is checked for stability, and those that are less
+All versions of each package are checked for stability, and those that are less
stable than the `minimum-stability` setting will be ignored when resolving
your project dependencies. Specific changes to the stability requirements of
a given package can be done in `require` or `require-dev` (see
[package links](#package-links)).
+Available options are `dev`, `alpha`, `beta`, `RC`, and `stable`.
+
### repositories <span>(root-only)</span>
Custom package repositories to use.
View
6 doc/05-repositories.md
@@ -100,7 +100,9 @@ It may include any of the other fields specified in the [schema](04-schema.md).
#### notify
The `notify` field allows you to specify an URL template for a URL that will
-be called every time a user installs a package.
+be called every time a user installs a package. The URL can be either an
+absolute path (that will use the same domain as the repository) or a fully
+qualified URL.
An example value:
@@ -246,7 +248,7 @@ When you update one of your packages to composer naming scheme or made it availa
"repositories": [
{
"type": "git",
- "https://github.com/foobar/intermediate.git"
+ "url": "https://github.com/foobar/intermediate.git"
},
{
"type": "pear",
View
43 doc/faqs/how-do-i-install-a-package-in-a-custom-directory.md
@@ -0,0 +1,43 @@
+# How do I install a package in a custom directory?
+
+Composer can be configured to install packages to a folder other than the
+default `vendor` folder. A simple way is to use the
+[composer/installers](https://github.com/composer/installers) package and if
+you are using a framework, chances are a custom directory has been already
+configured for you.
+
+If you are a **package author** and want your package installed to a custom
+directory, simply require `composer/installers` and set the appropriate `type`.
+This is common if your package is intended for a specific framework such as
+CakePHP, Drupal or WordPress. Here is an example composer.json file for a
+WordPress theme:
+
+ {
+ "name": "you/themename",
+ "type": "wordpress-theme",
+ "require": {
+ "composer/installers": "*"
+ }
+ }
+
+Now when your theme is installed with Composer it will be placed into
+`wp-content/themes/themename/` folder. Check the
+[current supported types](https://github.com/composer/installers#current-supported-types)
+for your package.
+
+As a **package consumer** you can set or override the install path for each
+package with the `installer-paths` extra. A useful example would be for a
+Drupal multisite setup where the package should be installed into your sites
+subdirectory. Here we are overriding the install path for a module that uses
+composer/installers:
+
+ {
+ "extra": {
+ "installer-paths": {
+ "sites/example.com/modules/{$name}": ["vendor/package"]
+ }
+ }
+ }
+
+Now the package would be installed to your folder location, rather than the default
+composer/installers determined location.
View
105 src/Composer/Autoload/AutoloadGenerator.php
@@ -25,7 +25,7 @@
*/
class AutoloadGenerator
{
- public function dump(Config $config, RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir)
+ public function dump(Config $config, RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $classSuffix = '')
{
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
@@ -93,23 +93,24 @@ public function dump(Config $config, RepositoryInterface $localRepo, PackageInte
$baseDirFromVendorDirCode = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
$targetDirLoader = <<<EOF
- spl_autoload_register(function(\$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 = $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;
+ }
+ }
EOF;
}
@@ -127,7 +128,7 @@ public function dump(Config $config, RepositoryInterface $localRepo, PackageInte
$filesCode = "";
$autoloads['files'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['files']));
foreach ($autoloads['files'] as $functionFile) {
- $filesCode .= ' require __DIR__ . '. var_export('/'.$filesystem->findShortestPath($vendorPath, $functionFile), true).";\n";
+ $filesCode .= ' require '.$this->getPathCode($filesystem, $relVendorPath, $vendorPath, $functionFile).";\n";
}
file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
@@ -135,7 +136,7 @@ public function dump(Config $config, RepositoryInterface $localRepo, PackageInte
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));
+ file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, true, true, (bool) $includePathFile, $targetDirLoader, $filesCode, $classSuffix));
copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
}
@@ -181,6 +182,7 @@ public function parseAutoloads(array $packageMap)
if (!is_array($mapping)) {
continue;
}
+
foreach ($mapping as $namespace => $paths) {
foreach ((array) $paths as $path) {
$autoloads[$type][$namespace][] = empty($installPath) ? $path : $installPath.'/'.$path;
@@ -276,8 +278,14 @@ protected function getPathCode(Filesystem $filesystem, $relVendorPath, $vendorPa
return $baseDir.var_export($path, true);
}
- protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $filesCode)
+ protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $filesCode, $classSuffix)
{
+ // 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
+
if ($filesCode) {
$filesCode = "\n".$filesCode;
}
@@ -290,18 +298,22 @@ protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useCla
require $vendorPathToTargetDirCode . '/ClassLoader.php';
}
-return call_user_func(function() {
- \$loader = new \\Composer\\Autoload\\ClassLoader();
- \$composerDir = $vendorPathToTargetDirCode;
+if (!class_exists('ComposerAutoloaderInit$classSuffix', false)) {
+ class ComposerAutoloaderInit$classSuffix
+ {
+ public static function getLoader()
+ {
+ \$loader = new \\Composer\\Autoload\\ClassLoader();
+ \$composerDir = $vendorPathToTargetDirCode;
HEADER;
if ($useIncludePath) {
$file .= <<<'INCLUDE_PATH'
- $includePaths = require $composerDir . '/include_paths.php';
- array_unshift($includePaths, get_include_path());
- set_include_path(join(PATH_SEPARATOR, $includePaths));
+ $includePaths = require $composerDir . '/include_paths.php';
+ array_push($includePaths, get_include_path());
+ set_include_path(join(PATH_SEPARATOR, $includePaths));
INCLUDE_PATH;
@@ -309,10 +321,10 @@ protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useCla
if ($usePSR0) {
$file .= <<<'PSR0'
- $map = require $composerDir . '/autoload_namespaces.php';
- foreach ($map as $namespace => $path) {
- $loader->add($namespace, $path);
- }
+ $map = require $composerDir . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->add($namespace, $path);
+ }
PSR0;
@@ -320,23 +332,42 @@ protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useCla
if ($useClassMap) {
$file .= <<<'CLASSMAP'
- $classMap = require $composerDir . '/autoload_classmap.php';
- if ($classMap) {
- $loader->addClassMap($classMap);
- }
+ $classMap = require $composerDir . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
CLASSMAP;
}
+ if ($targetDirLoader) {
+ $file .= <<<REGISTER_AUTOLOAD
+ spl_autoload_register(array('ComposerAutoloaderInit$classSuffix', 'autoload'));
+
+
+REGISTER_AUTOLOAD;
+
+ }
+
+ $file .= <<<METHOD_FOOTER
+ \$loader->register();
+$filesCode
+ return \$loader;
+ }
+
+METHOD_FOOTER;
+
$file .= $targetDirLoader;
return $file . <<<FOOTER
- \$loader->register();
-$filesCode
- return \$loader;
-});
+ }
+}
+
+return ComposerAutoloaderInit$classSuffix::getLoader();
FOOTER;
+
}
}
+
View
7 src/Composer/Command/RequireCommand.php
@@ -36,10 +36,13 @@ protected function configure()
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package 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('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
+ new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
))
->setHelp(<<<EOT
The require command adds required packages to your composer.json and installs them
+If you do not want to install the new dependencies immediately you can call it with --no-update
+
EOT
)
;
@@ -83,6 +86,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->writeln('<info>'.$file.' has been updated</info>');
+ if ($input->getOption('no-update')) {
+ return 0;
+ }
+
// Update packages
$composer = $this->getComposer();
$io = $this->getIO();
View
3  src/Composer/Command/SelfUpdateCommand.php
@@ -26,6 +26,7 @@ protected function configure()
{
$this
->setName('self-update')
+ ->setAliases(array('selfupdate'))
->setDescription('Updates composer.phar to the latest version.')
->setHelp(<<<EOT
The <info>self-update</info> command checks getcomposer.org for newer
@@ -64,7 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
throw $e;
}
unlink($tempFilename);
- $output->writeln('<error>The download is corrupt ('.$e->getMessage().').</error>');
+ $output->writeln('<error>The download is corrupted ('.$e->getMessage().').</error>');
$output->writeln('<error>Please re-run the self-update command to try again.</error>');
}
} else {
View
27 src/Composer/Command/ValidateCommand.php
@@ -17,6 +17,8 @@
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Json\JsonFile;
use Composer\Json\JsonValidationException;
+use Composer\Package\Loader\ValidatingArrayLoader;
+use Composer\Package\Loader\ArrayLoader;
use Composer\Util\RemoteFilesystem;
use Composer\Util\SpdxLicenseIdentifier;
@@ -109,6 +111,31 @@ protected function execute(InputInterface $input, OutputInterface $output)
$warnings[] = 'No license specified, it is recommended to do so';
}
+ if (!empty($manifest['name']) && preg_match('{[A-Z]}', $manifest['name'])) {
+ $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']);
+ $suggestName = strtolower($suggestName);
+
+ $warnings[] = sprintf(
+ 'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.',
+ $manifest['name'],
+ $suggestName
+ );
+ }
+
+ // TODO validate package repositories' packages using the same technique as below
+ try {
+ $loader = new ValidatingArrayLoader(new ArrayLoader(), false);
+ if (!isset($manifest['version'])) {
+ $manifest['version'] = '1.0.0';
+ }
+ if (!isset($manifest['name'])) {
+ $manifest['name'] = 'dummy/dummy';
+ }
+ $loader->load($manifest);
+ } catch (\Exception $e) {
+ $errors = array_merge($errors, explode("\n", $e->getMessage()));
+ }
+
// output errors/warnings
if (!$errors && !$publishErrors && !$warnings) {
$output->writeln('<info>' . $file . ' is valid</info>');
View
20 src/Composer/DependencyResolver/DefaultPolicy.php
@@ -49,7 +49,7 @@ public function getPriority(Pool $pool, PackageInterface $package)
public function selectPreferedPackages(Pool $pool, array $installedMap, array $literals)
{
- $packages = $this->groupLiteralsByNamePreferInstalled($pool,$installedMap, $literals);
+ $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);
foreach ($packages as &$literals) {
$policy = $this;
@@ -141,15 +141,15 @@ public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap
}
/**
- * Checks if source replaces a package with the same name as target.
- *
- * Replace constraints are ignored. This method should only be used for
- * prioritisation, not for actual constraint verification.
- *
- * @param PackageInterface $source
- * @param PackageInterface $target
- * @return bool
- */
+ * Checks if source replaces a package with the same name as target.
+ *
+ * Replace constraints are ignored. This method should only be used for
+ * prioritisation, not for actual constraint verification.
+ *
+ * @param PackageInterface $source
+ * @param PackageInterface $target
+ * @return bool
+ */
protected function replaces(PackageInterface $source, PackageInterface $target)
{
foreach ($source->getReplaces() as $link) {
View
38 src/Composer/DependencyResolver/Pool.php
@@ -33,8 +33,7 @@ class Pool
protected $acceptableStabilities;
protected $stabilityFlags;
- // TODO BC change to stable end of june?
- public function __construct($minimumStability = 'dev', array $stabilityFlags = array())
+ public function __construct($minimumStability = 'stable', array $stabilityFlags = array())
{
$stabilities = BasePackage::$stabilities;
$this->acceptableStabilities = array();
@@ -141,15 +140,42 @@ public function whatProvides($name, LinkConstraintInterface $constraint = null)
return $candidates;
}
- $result = array();
+ $matches = $provideMatches = array();
+ $nameMatch = false;
foreach ($candidates as $candidate) {
- if ($candidate->matches($name, $constraint)) {
- $result[] = $candidate;
+ switch ($candidate->matches($name, $constraint)) {
+ case BasePackage::MATCH_NONE:
+ break;
+
+ case BasePackage::MATCH_NAME:
+ $nameMatch = true;
+ break;
+
+ case BasePackage::MATCH:
+ $nameMatch = true;
+ $matches[] = $candidate;
+ break;
+
+ case BasePackage::MATCH_PROVIDE:
+ $provideMatches[] = $candidate;
+ break;
+
+ case BasePackage::MATCH_REPLACE:
+ $matches[] = $candidate;
+ break;
+
+ default:
+ throw new \UnexpectedValueException('Unexpected match type');
}
}
- return $result;
+ // if a package with the required name exists, we ignore providers
+ if ($nameMatch) {
+ return $matches;
+ }
+
+ return array_merge($matches, $provideMatches);
}
public function literalToPackage($literal)
View
4 src/Composer/DependencyResolver/Problem.php
@@ -78,10 +78,10 @@ public function getPrettyString(array $installedMap = array())
$ext = substr($job['packageName'], 4);
$error = extension_loaded($ext) ? 'has the wrong version ('.phpversion($ext).') installed' : 'is missing from your system';
- return 'The requested PHP extension '.$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'.';
+ return "\n - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'.';
}
- return 'The requested package '.$job['packageName'].$this->constraintToText($job['constraint']).' could not be found.';
+ return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' could not be found.';
}
}
View
20 src/Composer/DependencyResolver/Solver.php
@@ -524,12 +524,14 @@ private function disableProblem($why)
if (!$job) {
$why->disable();
- } else {
- // disable all rules of this job
- foreach ($this->rules as $rule) {
- if ($job === $rule->getJob()) {
- $rule->disable();
- }
+
+ return;
+ }
+
+ // disable all rules of this job
+ foreach ($this->rules as $rule) {
+ if ($job === $rule->getJob()) {
+ $rule->disable();
}
}
}
@@ -600,12 +602,12 @@ private function runSat($disableRules = true)
if (1 === $level) {
$conflictRule = $this->propagate($level);
- if ($conflictRule !== null) {
+ if (null !== $conflictRule) {
if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
continue;
- } else {
- return;
}
+
+ return;
}
}
View
4 src/Composer/DependencyResolver/SolverProblemsException.php
@@ -35,6 +35,10 @@ protected function createMessage()
$text .= " Problem ".($i+1).$problem->getPrettyString($this->installedMap)."\n";
}
+ if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
+ $text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion for more details.\n";
+ }
+
return $text;
}
View
23 src/Composer/Downloader/ArchiveDownloader.php
@@ -47,18 +47,23 @@ public function download(PackageInterface $package, $path)
if (1 === count($contentDir)) {
$contentDir = $contentDir[0];
- // Rename the content directory to avoid error when moving up
- // a child folder with the same name
- $temporaryName = md5(time().rand());
- rename($contentDir, $temporaryName);
- $contentDir = $temporaryName;
+ if (is_file($contentDir)) {
+ $this->filesystem->rename($contentDir, $path . '/' . basename($contentDir));
+ } else {
+ // Rename the content directory to avoid error when moving up
+ // a child folder with the same name
+ $temporaryName = md5(time().rand());
+ $this->filesystem->rename($contentDir, $temporaryName);
+ $contentDir = $temporaryName;
- foreach (array_merge(glob($contentDir . '/.*'), glob($contentDir . '/*')) as $file) {
- if (trim(basename($file), '.')) {
- rename($file, $path . '/' . basename($file));
+ foreach (array_merge(glob($contentDir . '/.*'), glob($contentDir . '/*')) as $file) {
+ if (trim(basename($file), '.')) {
+ $this->filesystem->rename($file, $path . '/' . basename($file));
+ }
}
+
+ rmdir($contentDir);
}
- rmdir($contentDir);
}
} catch (\Exception $e) {
// clean up
View
3  src/Composer/Downloader/DownloadManager.php
@@ -30,7 +30,8 @@ class DownloadManager
/**
* Initializes download manager.
*
- * @param bool $preferSource prefer downloading from source
+ * @param bool $preferSource prefer downloading from source
+ * @param Filesystem|null $filesystem custom Filesystem object
*/
public function __construct($preferSource = false, Filesystem $filesystem = null)
{
View
13 src/Composer/Downloader/GitDownloader.php
@@ -67,7 +67,16 @@ protected function updateToCommit($path, $reference, $branch, $date)
{
$template = 'git checkout %s && git reset --hard %1$s';
- $command = sprintf($template, escapeshellarg($reference));
+ // check whether non-commitish are branches or tags, and fetch branches with the remote name
+ $gitRef = $reference;
+ if (!preg_match('{^[a-f0-9]{40}$}', $reference)
+ && 0 === $this->process->execute('git branch -r', $output, $path)
+ && preg_match('{^\s+composer/'.preg_quote($reference).'$}m', $output)
+ ) {
+ $gitRef = 'composer/'.$reference;
+ }
+
+ $command = sprintf($template, escapeshellarg($gitRef));
if (0 === $this->process->execute($command, $output, $path)) {
return;
}
@@ -104,7 +113,7 @@ protected function updateToCommit($path, $reference, $branch, $date)
}
// checkout the new recovered ref
- $command = sprintf($template, escapeshellarg($newReference));
+ $command = sprintf($template, escapeshellarg($reference));
if (0 === $this->process->execute($command, $output, $path)) {
$this->io->write(' '.$reference.' is gone (history was rewritten?), recovered by checking out '.$newReference);
View
19 src/Composer/Downloader/PearPackageExtractor.php
@@ -25,6 +25,7 @@
*/
class PearPackageExtractor
{
+ private static $rolesWithoutPackageNamePrefix = array('php', 'script', 'www');
/** @var Filesystem */
private $filesystem;
private $file;
@@ -141,13 +142,13 @@ private function buildCopyActions($source, array $roles, $vars)
$packageName = (string) $package->name;
$packageVersion = (string) $package->release->version;
$sourceDir = $packageName . '-' . $packageVersion;
- $result = $this->buildSourceList10($children, $roles, $sourceDir);
+ $result = $this->buildSourceList10($children, $roles, $sourceDir, '', null, $packageName);
} elseif ('2.0' == $packageSchemaVersion || '2.1' == $packageSchemaVersion) {
$children = $package->contents->children();
$packageName = (string) $package->name;
$packageVersion = (string) $package->version->release;
$sourceDir = $packageName . '-' . $packageVersion;
- $result = $this->buildSourceList20($children, $roles, $sourceDir);
+ $result = $this->buildSourceList20($children, $roles, $sourceDir, '', null, $packageName);
$namespaces = $package->getNamespaces();
$package->registerXPathNamespace('ns', $namespaces['']);
@@ -188,7 +189,7 @@ private function applyRelease(&$actions, $releaseNodes, $vars)
}
}
- private function buildSourceList10($children, $targetRoles, $source = '', $target = '', $role = null)
+ private function buildSourceList10($children, $targetRoles, $source = '', $target = '', $role = null, $packageName)
{
$result = array();
@@ -199,7 +200,7 @@ private function buildSourceList10($children, $targetRoles, $source = '', $targe
$dirSource = $this->combine($source, (string) $child['name']);
$dirTarget = $child['baseinstalldir'] ? : $target;
$dirRole = $child['role'] ? : $role;
- $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole);
+ $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
$result = array_merge($result, $dirFiles);
} elseif ($child->getName() == 'file') {
$fileRole = (string) $child['role'] ? : $role;
@@ -207,6 +208,9 @@ private function buildSourceList10($children, $targetRoles, $source = '', $targe
$fileName = (string) ($child['name'] ? : $child[0]); // $child[0] means text content
$fileSource = $this->combine($source, $fileName);
$fileTarget = $this->combine((string) $child['baseinstalldir'] ? : $target, $fileName);
+ if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
+ $fileTarget = $packageName . '/' . $fileTarget;
+ }
$result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => array());
}
}
@@ -215,7 +219,7 @@ private function buildSourceList10($children, $targetRoles, $source = '', $targe
return $result;
}
- private function buildSourceList20($children, $targetRoles, $source = '', $target = '', $role = null)
+ private function buildSourceList20($children, $targetRoles, $source = '', $target = '', $role = null, $packageName)
{
$result = array();
@@ -226,7 +230,7 @@ private function buildSourceList20($children, $targetRoles, $source = '', $targe
$dirSource = $this->combine($source, $child['name']);
$dirTarget = $child['baseinstalldir'] ? : $target;
$dirRole = $child['role'] ? : $role;
- $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole);
+ $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
$result = array_merge($result, $dirFiles);
} elseif ('file' == $child->getName()) {
$fileRole = (string) $child['role'] ? : $role;
@@ -239,6 +243,9 @@ private function buildSourceList20($children, $targetRoles, $source = '', $targe
$fileTasks[] = array('from' => (string) $taskNode->attributes()->from, 'to' => (string) $taskNode->attributes()->to);
}
}
+ if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
+ $fileTarget = $packageName . '/' . $fileTarget;
+ }
$result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => $fileTasks);
}
}
View
2  src/Composer/Installer.php
@@ -346,7 +346,7 @@ protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = fa
try {
$operations = $solver->solve($request);
} catch (SolverProblemsException $e) {
- $this->io->write('<error>Your requirements could not be solved to an installable set of packages.</error>');
+ $this->io->write('<error>Your requirements could not be resolved to an installable set of packages.</error>');
$this->io->write($e->getMessage());
return false;
View
1  src/Composer/Installer/InstallationManager.php
@@ -23,7 +23,6 @@
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
-use Composer\Util\Filesystem;
/**
* Package operation manager.
View
29 src/Composer/Installer/PearInstaller.php
@@ -15,10 +15,8 @@
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Downloader\PearPackageExtractor;
-use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
-use Composer\Util\Filesystem;
/**
* Package installation manager.
@@ -31,9 +29,9 @@ class PearInstaller extends LibraryInstaller
/**
* Initializes library installer.
*
- * @param IOInterface $io io instance
- * @param Composer $composer
- * @param string $type package type that this installer handles
+ * @param IOInterface $io io instance
+ * @param Composer $composer
+ * @param string $type package type that this installer handles
*/
public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library')
{
@@ -52,22 +50,25 @@ public function update(InstalledRepositoryInterface $repo, PackageInterface $ini
protected function installCode(PackageInterface $package)
{
parent::installCode($package);
+ parent::initializeBinDir();
$isWindows = defined('PHP_WINDOWS_VERSION_BUILD') ? true : false;
+ $php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php');
+ $installPath = $this->getInstallPath($package);
$vars = array(
'os' => $isWindows ? 'windows' : 'linux',
- 'php_bin' => ($isWindows ? getenv('PHPRC') .'php.exe' : trim(`which php`)),
- 'pear_php' => $this->getInstallPath($package),
- 'bin_dir' => $this->getInstallPath($package) . '/bin',
- 'php_dir' => $this->getInstallPath($package),
- 'data_dir' => '@DATA_DIR@',
+ 'php_bin' => $php_bin,
+ 'pear_php' => $installPath,
+ 'php_dir' => $installPath,
+ 'bin_dir' => $installPath . '/bin',
+ 'data_dir' => $installPath . '/data',
'version' => $package->getPrettyVersion(),
);
$packageArchive = $this->getInstallPath($package).'/'.pathinfo($package->getDistUrl(), PATHINFO_BASENAME);
$pearExtractor = new PearPackageExtractor($packageArchive);
- $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin'), $vars);
+ $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars);
if ($this->io->isVerbose()) {
$this->io->write(' Cleaning up');
@@ -80,8 +81,10 @@ protected function getBinaries(PackageInterface $package)
$binariesPath = $this->getInstallPath($package) . '/bin/';
$binaries = array();
if (file_exists($binariesPath)) {
- foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME) as $fileName => $value) {
- $binaries[] = 'bin/'.$fileName;
+ foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO) as $fileName => $value) {
+ if (!$value->isDir()) {
+ $binaries[] = 'bin/'.$fileName;
+ }
}
}
View
7 src/Composer/Json/JsonFile.php
@@ -40,8 +40,8 @@ class JsonFile
/**
* Initializes json file reader/parser.
*
- * @param string $lockFile path to a lockfile
- * @param RemoteFilesystem $rfs required for loading http/https json files
+ * @param string $path path to a lockfile
+ * @param RemoteFilesystem $rfs required for loading http/https json files
*/
public function __construct($path, RemoteFilesystem $rfs = null)
{
@@ -53,6 +53,9 @@ public function __construct($path, RemoteFilesystem $rfs = null)
$this->rfs = $rfs;
}
+ /**
+ * @return string
+ */
public function getPath()
{
return $this->path;
View
16 src/Composer/Package/BasePackage.php
@@ -38,6 +38,12 @@
const STABILITY_ALPHA = 15;
const STABILITY_DEV = 20;
+ const MATCH_NAME = -1;
+ const MATCH_NONE = 0;
+ const MATCH = 1;
+ const MATCH_PROVIDE = 2;
+ const MATCH_REPLACE = 3;
+
public static $stabilities = array(
'stable' => self::STABILITY_STABLE,
'RC' => self::STABILITY_RC,
@@ -122,27 +128,27 @@ public function getId()
*
* @param string $name Name of the package to be matched
* @param LinkConstraintInterface $constraint The constraint to verify
- * @return bool Whether this package matches the name and constraint
+ * @return int One of the MATCH* constants of this class or 0 if there is no match
*/
public function matches($name, LinkConstraintInterface $constraint)
{
if ($this->name === $name) {
- return $constraint->matches(new VersionConstraint('==', $this->getVersion()));
+ return $constraint->matches(new VersionConstraint('==', $this->getVersion())) ? self::MATCH : self::MATCH_NAME;
}
foreach ($this->getProvides() as $link) {
if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) {
- return true;
+ return self::MATCH_PROVIDE;
}
}
foreach ($this->getReplaces() as $link) {
if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) {
- return true;
+ return self::MATCH_REPLACE;
}
}
- return false;
+ return self::MATCH_NONE;
}
public function getRepository()
View
13 src/Composer/Package/LinkConstraint/VersionConstraint.php
@@ -44,6 +44,15 @@ public function __construct($operator, $version)
$this->version = $version;
}
+ public function versionCompare($a, $b, $operator)
+ {
+ if ('dev-' === substr($a, 0, 4) && 'dev-' === substr($b, 0, 4)) {
+ return $operator == '==' && $a === $b;
+ }
+
+ return version_compare($a, $b, $operator);
+ }
+
/**
*
* @param VersionConstraint $provider
@@ -62,7 +71,7 @@ public function matchSpecific(VersionConstraint $provider)
// these kinds of comparisons always have a solution
if ($isNonEqualOp || $isProviderNonEqualOp) {
return !$isEqualOp && !$isProviderEqualOp
- || version_compare($provider->version, $this->version, '!=');
+ || $this->versionCompare($provider->version, $this->version, '!=');
}
// an example for the condition is <= 2.0 & < 1.0
@@ -71,7 +80,7 @@ public function matchSpecific(VersionConstraint $provider)
return true;
}
- if (version_compare($provider->version, $this->version, $this->operator)) {
+ if ($this->versionCompare($provider->version, $this->version, $this->operator)) {
// special case, e.g. require >= 1.0 and provide < 1.0
// 1.0 >= 1.0 but 1.0 is outside of the provided interval
if ($provider->version == $this->version && $provider->operator == $providerNoEqualOp && $this->operator != $noEqualOp) {
View
8 src/Composer/Package/Loader/ArrayLoader.php
@@ -19,7 +19,7 @@
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
-class ArrayLoader
+class ArrayLoader implements LoaderInterface
{
protected $versionParser;
@@ -31,7 +31,7 @@ public function __construct(VersionParser $parser = null)
$this->versionParser = $parser;
}
- public function load($config)
+ public function load(array $config)
{
if (!isset($config['name'])) {
throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
@@ -82,8 +82,8 @@ public function load($config)
$package->setHomepage($config['homepage']);
}
- if (!empty($config['keywords'])) {
- $package->setKeywords(is_array($config['keywords']) ? $config['keywords'] : array($config['keywords']));
+ if (!empty($config['keywords']) && is_array($config['keywords'])) {
+ $package->setKeywords($config['keywords']);
}
if (!empty($config['license'])) {
View
15 src/Composer/Package/Loader/JsonLoader.php
@@ -17,8 +17,19 @@
/**
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
*/
-class JsonLoader extends ArrayLoader
+class JsonLoader
{
+ private $loader;
+
+ public function __construct(LoaderInterface $loader)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from
+ * @return \Composer\Package\PackageInterface
+ */
public function load($json)
{
if ($json instanceof JsonFile) {
@@ -29,6 +40,6 @@ public function load($json)
$config = JsonFile::parseJson($json);
}
- return parent::load($config);
+ return $this->loader->load($config);
}
}
View
29 src/Composer/Package/Loader/LoaderInterface.php
@@ -0,0 +1,29 @@
+<?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\Package\Loader;
+
+/**
+ * Defines a loader that takes an array to create package instances
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface LoaderInterface
+{
+ /**
+ * Converts a package from an array to a real instance
+ *
+ * @param array $package Package config
+ * @return \Composer\Package\PackageInterface
+ */
+ public function load(array $package);
+}
View
14 src/Composer/Package/Loader/RootPackageLoader.php
@@ -40,7 +40,7 @@ public function __construct(RepositoryManager $manager, Config $config, VersionP
parent::__construct($parser);
}
- public function load($config)
+ public function load(array $config)
{
if (!isset($config['name'])) {
$config['name'] = '__root__';
@@ -69,9 +69,15 @@ public function load($config)
$references = array();
foreach (array('require', 'require-dev') as $linkType) {
if (isset($config[$linkType])) {
- $aliases = $this->extractAliases($config[$linkType], $aliases);
- $stabilityFlags = $this->extractStabilityFlags($config[$linkType], $stabilityFlags);
- $references = $this->extractReferences($config[$linkType], $references);
+ $linkInfo = BasePackage::$supportedLinkTypes[$linkType];
+ $method = 'get'.ucfirst($linkInfo['method']);
+ $links = array();
+ foreach ($package->$method() as $link) {
+ $links[$link->getTarget()] = $link->getConstraint()->getPrettyString();
+ }
+ $aliases = $this->extractAliases($links, $aliases);
+ $stabilityFlags = $this->extractStabilityFlags($links, $stabilityFlags);
+ $references = $this->extractReferences($links, $references);
}
}
View
280 src/Composer/Package/Loader/ValidatingArrayLoader.php
@@ -0,0 +1,280 @@
+<?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\Package\Loader;
+
+use Composer\Package;
+use Composer\Package\Version\VersionParser;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class ValidatingArrayLoader implements LoaderInterface
+{
+ private $loader;
+ private $versionParser;
+ private $ignoreErrors;
+ private $errors = array();
+ private $config;
+
+ public function __construct(LoaderInterface $loader, $ignoreErrors = true, VersionParser $parser = null)
+ {
+ $this->loader = $loader;
+ $this->ignoreErrors = $ignoreErrors;
+ if (!$parser) {
+ $parser = new VersionParser();
+ }
+ $this->versionParser = $parser;
+ }
+
+ public function load(array $config)
+ {
+ $this->config = $config;
+
+ $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true);
+
+ if (!empty($config['version'])) {
+ try {
+ $this->versionParser->normalize($config['version']);
+ } catch (\Exception $e) {
+ unset($this->config['version']);
+ $this->errors[] = 'version : invalid value ('.$config['version'].'): '.$e->getMessage();
+ }
+ }
+
+ $this->validateRegex('type', '[a-z0-9-]+');
+ $this->validateString('target-dir');
+ $this->validateArray('extra');
+ $this->validateFlatArray('bin');
+ $this->validateArray('scripts'); // TODO validate event names & listener syntax
+ $this->validateString('description');
+ $this->validateUrl('homepage');
+ $this->validateFlatArray('keywords', '[A-Za-z0-9 -]+');
+
+ if (isset($config['license'])) {
+ if (is_string($config['license'])) {
+ $this->validateRegex('license', '[A-Za-z0-9+. ()-]+');
+ } else {
+ $this->validateFlatArray('license', '[A-Za-z0-9+. ()-]+');
+ }
+ }
+
+ $this->validateString('time');
+ if (!empty($this->config['time'])) {
+ try {
+ $date = new \DateTime($config['time']);
+ } catch (\Exception $e) {
+ $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage();
+ unset($this->config['time']);
+ }
+ }
+
+ $this->validateArray('authors');
+ if (!empty($this->config['authors'])) {
+ foreach ($this->config['authors'] as $key => $author) {
+ if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) {
+ $this->errors[] = 'authors.'.$key.'.homepage : invalid value, must be a valid http/https URL';
+ unset($this->config['authors'][$key]['homepage']);
+ }
+ if (isset($author['email']) && !filter_var($author['email'], FILTER_VALIDATE_EMAIL)) {
+ $this->errors[] = 'authors.'.$key.'.email : invalid value, must be a valid email address';
+ unset($this->config['authors'][$key]['email']);
+ }
+ if (isset($author['name']) && !is_string($author['name'])) {
+ $this->errors[] = 'authors.'.$key.'.name : invalid value, must be a string';
+ unset($this->config['authors'][$key]['name']);
+ }
+ if (isset($author['role']) && !is_string($author['role'])) {
+ $this->errors[] = 'authors.'.$key.'.role : invalid value, must be a string';
+ unset($this->config['authors'][$key]['role']);
+ }
+ }
+ if (empty($this->config['authors'])) {
+ unset($this->config['authors']);
+ }
+ }
+
+ $this->validateArray('support');
+ if (!empty($this->config['support'])) {
+ if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) {
+ $this->errors[] = 'support.email : invalid value, must be a valid email address';
+ unset($this->config['support']['email']);
+ }
+
+ if (isset($this->config['support']['irc'])
+ && (!filter_var($this->config['support']['irc'], FILTER_VALIDATE_URL) || !preg_match('{^irc://}iu', $this->config['support']['irc']))
+ ) {
+ $this->errors[] = 'support.irc : invalid value, must be ';
+ unset($this->config['support']['irc']);
+ }
+
+ foreach (array('issues', 'forum', 'wiki', 'source') as $key) {
+ if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) {
+ $this->errors[] = 'support.'.$key.' : invalid value, must be a valid http/https URL';
+ unset($this->config['support'][$key]);
+ }
+ }
+ if (empty($this->config['support'])) {
+ unset($this->config['support']);
+ }
+ }
+
+ // TODO validate require/require-dev/replace/provide
+ // TODO validate suggest
+ // TODO validate autoload
+ // TODO validate minimum-stability
+
+ // TODO validate dist
+ // TODO validate source
+
+ // TODO validate repositories
+
+ $this->validateFlatArray('include-path');
+
+ // branch alias validation
+ if (isset($this->config['extra']['branch-alias'])) {
+ if (!is_array($this->config['extra']['branch-alias'])) {
+ $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases';
+ } else {
+ foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
+ // ensure it is an alias to a -dev package
+ if ('-dev' !== substr($targetBranch, -4)) {
+ $this->errors[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev';
+ unset($this->config['extra']['branch-alias'][$sourceBranch]);
+
+ continue;
+ }
+
+ // normalize without -dev and ensure it's a numeric branch that is parseable
+ $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
+ if ('-dev' !== substr($validatedTargetBranch, -4)) {
+ $this->errors[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
+ unset($this->config['extra']['branch-alias'][$sourceBranch]);
+ }
+ }
+ }
+ }
+
+ if ($this->errors && !$this->ignoreErrors) {
+ throw new \Exception(implode("\n", $this->errors));
+ }
+
+ $package = $this->loader->load($this->config);
+ $this->errors = array();
+ $this->config = null;
+
+ return $package;
+ }
+
+ private function validateRegex($property, $regex, $mandatory = false)
+ {
+ if (!$this->validateString($property, $mandatory)) {
+ return false;
+ }
+
+ if (!preg_match('{^'.$regex.'$}u', $this->config[$property])) {
+ $this->errors[] = $property.' : invalid value, must match '.$regex;
+ unset($this->config[$property]);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private function validateString($property, $mandatory = false)
+ {
+ if (isset($this->config[$property]) && !is_string($this->config[$property])) {
+ $this->errors[] = $property.' : should be a string, '.gettype($this->config[$property]).' given';
+ unset($this->config[$property]);
+
+ return false;
+ }
+
+ if (!isset($this->config[$property]) || trim($this->config[$property]) === '') {
+ if ($mandatory) {
+ $this->errors[] = $property.' : must be present';
+ }
+ unset($this->config[$property]);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private function validateArray($property, $mandatory = false)
+ {
+ if (isset($this->config[$property]) && !is_array($this->config[$property])) {
+ $this->errors[] = $property.' : should be an array, '.gettype($this->config[$property]).' given';
+ unset($this->config[$property]);
+
+ return false;
+ }
+
+ if (!isset($this->config[$property]) || !count($this->config[$property])) {
+ if ($mandatory) {
+ $this->errors[] = $property.' : must be present and contain at least one element';
+ }
+ unset($this->config[$property]);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private function validateFlatArray($property, $regex = null, $mandatory = false)
+ {
+ if (!$this->validateArray($property, $mandatory)) {
+ return false;
+ }
+
+ $pass = true;
+ foreach ($this->config[$property] as $key => $value) {
+ if (!is_string($value) && !is_numeric($value)) {
+ $this->errors[] = $property.'.'.$key.' : must be a string or int, '.gettype($value).' given';
+ unset($this->config[$property][$key]);
+ $pass = false;
+
+ continue;
+ }
+
+ if ($regex && !preg_match('{^'.$regex.'$}u', $value)) {
+ $this->errors[] = $property.'.'.$key.' : invalid value, must match '.$regex;
+ unset($this->config[$property][$key]);
+ $pass = false;
+ }
+ }
+
+ return $pass;
+ }
+
+ private function validateUrl($property, $mandatory = false)
+ {
+ if (!$this->validateString($property, $mandatory)) {
+ return false;
+ }
+
+ if (!$this->filterUrl($this->config[$property])) {
+ $this->errors[] = $property.' : invalid value, must be a valid http/https URL';
+ unset($this->config[$property]);
+
+ return false;
+ }
+ }
+
+ private function filterUrl($value)
+ {
+ return filter_var($value, FILTER_VALIDATE_URL) && preg_match('{^https?://}iu', $value);
+ }
+}
View
3  src/Composer/Package/Locker.php
@@ -132,8 +132,7 @@ public function getMinimumStability()
{
$lockData = $this->getLockData();
- // TODO BC change dev to stable end of june?
- return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'dev';
+ return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'stable';
}
public function getStabilityFlags()
View
7 src/Composer/Package/MemoryPackage.php
@@ -48,8 +48,7 @@ class MemoryPackage extends BasePackage
protected $prettyAlias;
protected $dev;
- // TODO BC change dev to stable end of june?
- protected $minimumStability = 'dev';
+ protected $minimumStability = 'stable';
protected $stabilityFlags = array();
protected $references = array();
@@ -704,9 +703,7 @@ public function setSupport(array $support)
}
/**
- * Returns the support information
- *
- * @return array
+ * {@inheritDoc}
*/
public function getSupport()
{
View
7 src/Composer/Package/PackageInterface.php
@@ -363,4 +363,11 @@ public function __toString();
* @return string
*/
public function getPrettyString();
+
+ /**
+ * Returns the support information
+ *
+ * @return array
+ */
+ public function getSupport();
}
View
18 src/Composer/Package/Version/VersionParser.php
@@ -24,7 +24,7 @@
*/
class VersionParser
{
- private static $modifierRegex = '[.-]?(?:(beta|RC|alpha|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
+ private static $modifierRegex = '[._-]?(?:(beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
/**
* Returns the stability of a version
@@ -45,8 +45,16 @@ public static function parseStability($version)
return 'dev';
}
- if (!empty($match[1]) && ($match[1] === 'beta' || $match[1] === 'alpha' || $match[1] === 'RC')) {
- return $match[1];
+ if (!empty($match[1])) {
+ if ('beta' === $match[1] || 'b' === $match[1]) {
+ return 'beta';
+ }
+ if ('alpha' === $match[1] || 'a' === $match[1]) {
+ return 'alpha';
+ }
+ if ('RC' === $match[1]) {
+ return 'RC';
+ }
}
return 'stable';
@@ -89,7 +97,7 @@ public function normalize($version)
}
if ('dev-' === strtolower(substr($version, 0, 4))) {
- return strtolower($version);
+ return 'dev-'.substr($version, 4);
}
// match classical versioning
@@ -127,7 +135,7 @@ public function normalize($version)
} catch (\Exception $e) {}
}
- throw new \UnexpectedValueException('Invalid version string '.$version);
+ throw new \UnexpectedValueException('Invalid version string "'.$version.'"');
}
/**
View
10 src/Composer/Repository/ComposerRepository.php
@@ -82,12 +82,20 @@ protected function initialize()
{
parent::initialize();
+ if (!extension_loaded('openssl') && 'https' === substr($this->url, 0, 5)) {
+ throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
+ }
+
try {
$json = new JsonFile($this->url.'/packages.json', new RemoteFilesystem($this->io));
$data = $json->read();
if (!empty($data['notify'])) {
- $this->notifyUrl = preg_replace('{(https?://[^/]+).*}i', '$1' . $data['notify'], $this->url);
+ if ('/' === $data['notify'][0]) {
+ $this->notifyUrl = preg_replace('{(https?://[^/]+).*}i', '$1' . $data['notify'], $this->url);
+ } else {
+ $this->notifyUrl = $data['notify'];
+ }
}
$this->cache->write('packages.json', json_encode($data));
View
17 src/Composer/Repository/Pear/ChannelRest11Reader.php
@@ -115,13 +115,16 @@ private function parsePackage($packageInfo)
}
$releases = array();
- foreach ($packageInfo->xpath('ns:a/ns:r') as $node) {
- $releaseVersion = (string) $node->v;
- $releaseStability = (string) $node->s;
- $releases[$releaseVersion] = new ReleaseInfo(
- $releaseStability,
- isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array())
- );
+ $releasesInfo = $packageInfo->xpath('ns:a/ns:r');
+ if ($releasesInfo) {
+ foreach ($releasesInfo as $node) {
+ $releaseVersion = (string) $node->v;
+ $releaseStability = (string) $node->s;
+ $releases[$releaseVersion] = new ReleaseInfo(
+ $releaseStability,
+ isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array())
+ );
+ }
}
return new PackageInfo(
View
4 src/Composer/Repository/PearRepository.php
@@ -120,7 +120,9 @@ private function buildComposerPackages(ChannelInfo $channelInfo, VersionParser $
}
// alias package with user-specified prefix. it makes private pear channels looks like composer's.
- if (!empty($this->vendorAlias)) {
+ if (!empty($this->vendorAlias)
+ && ($this->vendorAlias != 'pear-'.$channelInfo->getAlias() || $channelInfo->getName() != $packageDefinition->getChannelName())
+ ) {
$composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}";
$aliasConstraint = new VersionConstraint('==', $normalizedVersion);
$replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint);
View
2  src/Composer/Repository/Vcs/GitHubDriver.php
@@ -141,7 +141,7 @@ public function getComposerInformation($identifier)
$composer = JsonFile::parseJson($composer, $resource);
if (!isset($composer['time'])) {
- $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier;
+ $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
$commit = JsonFile::parseJson($this->getContents($resource), $resource);
$composer['time'] = $commit['commit']['committer']['date'];
}
View
1  src/Composer/Repository/Vcs/SvnDriver.php
@@ -103,6 +103,7 @@ public function getComposerInformation($identifier)
$path = $match[1];
$rev = $match[2];
} else {
+ $path = '';
$rev = '';
}
View
15 src/Composer/Repository/VcsRepository.php
@@ -16,6 +16,7 @@
use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Loader\ArrayLoader;
+use Composer\Package\Loader\LoaderInterface;
use Composer\IO\IOInterface;
use Composer\Config;
@@ -31,6 +32,7 @@ class VcsRepository extends ArrayRepository
protected $config;
protected $versionParser;
protected $type;
+ protected $loader;
public function __construct(array $repoConfig, IOInterface $io, Config $config, array $drivers = null)
{
@@ -50,6 +52,11 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config,
$this->config = $config;
}
+ public function setLoader(LoaderInterface $loader)
+ {
+ $this->loader = $loader;
+ }
+
public function getDriver()
{
if (isset($this->drivers[$this->type])) {
@@ -91,7 +98,9 @@ protected function initialize()
}