Merge remote-tracking branch 'upstream/master' into svn-auth-reloaded

main
till 12 years ago
commit c6566825ad

@ -2,13 +2,14 @@
* Added `create-project` command to install a project from scratch with composer * Added `create-project` command to install a project from scratch with composer
* Added automated `classmap` autoloading support for non-PSR-0 compliant projects * Added automated `classmap` autoloading support for non-PSR-0 compliant projects
* Improved clones from GitHub which now automatically select between git/https/http protocols * Added human readable error reporting when deps can not be solved
* Added support for private GitHub repositories (use --no-interaction for CI) * Added support for private GitHub repositories (use --no-interaction for CI)
* Improved `validate` command to give more feedback
* Added "file" downloader type to download plain files * Added "file" downloader type to download plain files
* Added support for authentication with svn repositories * Added support for authentication with svn repositories
* Removed dependency on filter_var * Improved clones from GitHub which now automatically select between git/https/http protocols
* Improved `validate` command to give more feedback
* Improved the `search` & `show` commands output * Improved the `search` & `show` commands output
* Removed dependency on filter_var
* Various robustness & error handling improvements, docs fixes and more * Various robustness & error handling improvements, docs fixes and more
* 1.0.0-alpha1 (2012-03-01) * 1.0.0-alpha1 (2012-03-01)

10
composer.lock generated

@ -14,18 +14,20 @@
{ {
"package": "symfony/console", "package": "symfony/console",
"version": "dev-master", "version": "dev-master",
"source-reference": "2ee50c7c845ef7f8bce9c540709ecfd64cbcda87" "source-reference": "88a283f6c18d1e644ebca1e3df4c870eac8108dd"
}, },
{ {
"package": "symfony/finder", "package": "symfony/finder",
"version": "dev-master", "version": "dev-master",
"source-reference": "b3adc8d5c29593db93c0abc4711a1e25fd3a6fa0" "source-reference": "be30ecc95281d729ee51b9e89644d442bcf60451"
}, },
{ {
"package": "symfony/process", "package": "symfony/process",
"version": "dev-master", "version": "dev-master",
"source-reference": "6aceac404d8574cf7da57e7e29b00a665b7bd559" "source-reference": "0aad81ae9f884cf7df6387cb52a11b5b4f07b3d6"
} }
], ],
"aliases": [] "aliases": [
]
} }

@ -44,7 +44,7 @@ any version beginning with `1.0`.
## Installation ## Installation
### 1) Downloading the Composer Executable ### Downloading the Composer Executable
To actually get Composer, we need to do two things. The first one is installing To actually get Composer, we need to do two things. The first one is installing
Composer (again, this mean downloading it into your project): Composer (again, this mean downloading it into your project):
@ -65,7 +65,7 @@ You can place this file anywhere you wish. If you put it in your `PATH`,
you can access it globally. On unixy systems you can even make it you can access it globally. On unixy systems you can even make it
executable and invoke it without `php`. executable and invoke it without `php`.
### 2) Using Composer ### Using Composer
Next, run the command the `install` command to resolve and download dependencies: Next, run the command the `install` command to resolve and download dependencies:

@ -144,7 +144,7 @@ means that we can just start using classes from it, and they will be
autoloaded. autoloaded.
$log = new Monolog\Logger('name'); $log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Logger::WARNING)); $log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->addWarning('Foo'); $log->addWarning('Foo');

@ -179,10 +179,15 @@ directory to something other than `vendor/bin`.
This env var controls the time composer waits for commands (such as git This env var controls the time composer waits for commands (such as git
commands) to finish executing. The default value is 60 seconds. commands) to finish executing. The default value is 60 seconds.
### HTTP_PROXY ### http_proxy or HTTP_PROXY
If you are using composer from behind an HTTP proxy, you can use the standard If you are using composer from behind an HTTP proxy, you can use the standard
`HTTP_PROXY` or `http_proxy` env vars. Simply set it to the URL of your proxy. `http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy.
Many operating systems already set this variable for you. Many operating systems already set this variable for you.
Using `http_proxy` (lowercased) or even defining both might be preferrable since
some tools like git or curl will only use the lower-cased `http_proxy` version.
Alternatively you can also define the git proxy using
`git config --global http.proxy <proxy url>`.
&larr; [Libraries](02-libraries.md) | [Schema](04-schema.md) &rarr; &larr; [Libraries](02-libraries.md) | [Schema](04-schema.md) &rarr;

@ -181,8 +181,8 @@ class ClassLoader
$classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
foreach ($this->prefixes as $prefix => $dirs) { foreach ($this->prefixes as $prefix => $dirs) {
foreach ($dirs as $dir) { if (0 === strpos($class, $prefix)) {
if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) {
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
return $dir . DIRECTORY_SEPARATOR . $classPath; return $dir . DIRECTORY_SEPARATOR . $classPath;
} }

@ -29,4 +29,12 @@ abstract class Command extends BaseCommand
{ {
return $this->getApplication()->getComposer($required); return $this->getApplication()->getComposer($required);
} }
/**
* @return \Composer\IO\ConsoleIO
*/
protected function getIO()
{
return $this->getApplication()->getIO();
}
} }

@ -23,6 +23,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Util\RemoteFilesystem;
/** /**
* Install a package as new project into new directory. * Install a package as new project into new directory.
@ -84,11 +85,11 @@ EOT
} }
if (null === $repositoryUrl) { if (null === $repositoryUrl) {
$sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org')); $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO());
} elseif (".json" === substr($repositoryUrl, -5)) { } elseif (".json" === substr($repositoryUrl, -5)) {
$sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl)); $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io)));
} elseif (0 === strpos($repositoryUrl, 'http')) { } elseif (0 === strpos($repositoryUrl, 'http')) {
$sourceRepo = new ComposerRepository(array('url' => $repositoryUrl)); $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $this->getIO());
} else { } else {
throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url."); throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url.");
} }

@ -229,7 +229,7 @@ EOT
if (!$this->repos) { if (!$this->repos) {
$this->repos = new CompositeRepository(array( $this->repos = new CompositeRepository(array(
new PlatformRepository, new PlatformRepository,
new ComposerRepository(array('url' => 'http://packagist.org')) new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO())
)); ));
} }

@ -61,6 +61,6 @@ EOT
->setInstallSuggests($input->getOption('install-suggests')) ->setInstallSuggests($input->getOption('install-suggests'))
; ;
return $install->run(); return $install->run() ? 0 : 1;
} }
} }

@ -54,7 +54,8 @@ EOT
} else { } else {
$output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); $output->writeln('No composer.json found in the current directory, showing packages from packagist.org');
$installedRepo = $platformRepo; $installedRepo = $platformRepo;
$repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org')))); $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO());
$repos = new CompositeRepository(array($installedRepo, $packagist));
} }
$tokens = $input->getArgument('tokens'); $tokens = $input->getArgument('tokens');

@ -13,7 +13,7 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\Composer; use Composer\Composer;
use Composer\Util\StreamContextFactory; use Composer\Util\RemoteFilesystem;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -40,9 +40,8 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$ctx = StreamContextFactory::getContext(); $rfs = new RemoteFilesystem($this->getIO());
$latest = trim($rfs->getContents('getcomposer.org', 'http://getcomposer.org/version', false));
$latest = trim(file_get_contents('http://getcomposer.org/version', false, $ctx));
if (Composer::VERSION !== $latest) { if (Composer::VERSION !== $latest) {
$output->writeln(sprintf("Updating to version <info>%s</info>.", $latest)); $output->writeln(sprintf("Updating to version <info>%s</info>.", $latest));
@ -50,7 +49,7 @@ EOT
$remoteFilename = 'http://getcomposer.org/composer.phar'; $remoteFilename = 'http://getcomposer.org/composer.phar';
$localFilename = $_SERVER['argv'][0]; $localFilename = $_SERVER['argv'][0];
copy($remoteFilename, $localFilename, $ctx); $rfs->copy('getcomposer.org', $remoteFilename, $localFilename);
} else { } else {
$output->writeln("<info>You are using the latest composer version.</info>"); $output->writeln("<info>You are using the latest composer version.</info>");
} }

@ -65,7 +65,8 @@ EOT
} else { } else {
$output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); $output->writeln('No composer.json found in the current directory, showing packages from packagist.org');
$installedRepo = $platformRepo; $installedRepo = $platformRepo;
$repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org')))); $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO());
$repos = new CompositeRepository(array($installedRepo, $packagist));
} }
// show single package or single version // show single package or single version

@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Json\JsonValidationException; use Composer\Json\JsonValidationException;
use Composer\Util\RemoteFilesystem;
/** /**
* @author Robert Schönthal <seroscho@googlemail.com> * @author Robert Schönthal <seroscho@googlemail.com>
@ -55,7 +56,7 @@ EOT
$laxValid = false; $laxValid = false;
try { try {
$json = new JsonFile($file); $json = new JsonFile($file, new RemoteFilesystem($this->getIO()));
$json->read(); $json->read();
$json->validateSchema(JsonFile::LAX_SCHEMA); $json->validateSchema(JsonFile::LAX_SCHEMA);

@ -25,6 +25,7 @@ use Composer\Composer;
use Composer\Factory; use Composer\Factory;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\ConsoleIO; use Composer\IO\ConsoleIO;
use Composer\Util\ErrorHandler;
/** /**
* The console application that handles the commands * The console application that handles the commands
@ -40,6 +41,7 @@ class Application extends BaseApplication
public function __construct() public function __construct()
{ {
ErrorHandler::register();
parent::__construct('Composer', Composer::VERSION); parent::__construct('Composer', Composer::VERSION);
} }

@ -0,0 +1,150 @@
<?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\DependencyResolver;
/**
* Represents a problem detected while solving dependencies
*
* @author Nils Adermann <naderman@naderman.de>
*/
class Problem
{
/**
* A set of reasons for the problem, each is a rule or a job and a rule
* @var array
*/
protected $reasons;
/**
* Add a job as a reason
*
* @param array $job A job descriptor which is a reason for this problem
* @param Rule $rule An optional rule associated with the job
*/
public function addJobRule($job, Rule $rule = null)
{
$this->addReason(serialize($job), array(
'rule' => $rule,
'job' => $job,
));
}
/**
* Add a rule as a reason
*
* @param Rule $rule A rule which is a reason for this problem
*/
public function addRule(Rule $rule)
{
$this->addReason($rule->getId(), array(
'rule' => $rule,
'job' => null,
));
}
/**
* Retrieve all reasons for this problem
*
* @return array The problem's reasons
*/
public function getReasons()
{
return $this->reasons;
}
/**
* A human readable textual representation of the problem's reasons
*/
public function __toString()
{
if (count($this->reasons) === 1) {
reset($this->reasons);
$reason = current($this->reasons);
$rule = $reason['rule'];
$job = $reason['job'];
if ($job && $job['cmd'] === 'install' && empty($job['packages'])) {
// handle php extensions
if (0 === stripos($job['packageName'], 'ext-')) {
$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 'The requested package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'could not be found.';
}
}
$messages = array("Problem caused by:");
foreach ($this->reasons as $reason) {
$rule = $reason['rule'];
$job = $reason['job'];
if ($job) {
$messages[] = $this->jobToText($job);
} elseif ($rule) {
if ($rule instanceof Rule) {
$messages[] = $rule->toHumanReadableString();
}
}
}
return implode("\n\t\t\t- ", $messages);
}
/**
* Store a reason descriptor but ignore duplicates
*
* @param string $id A canonical identifier for the reason
* @param string $reason The reason descriptor
*/
protected function addReason($id, $reason)
{
if (!isset($this->reasons[$id])) {
$this->reasons[$id] = $reason;
}
}
/**
* Turns a job into a human readable description
*
* @param array $job
* @return string
*/
protected function jobToText($job)
{
switch ($job['cmd']) {
case 'install':
return 'Installation of package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'was requested. Satisfiable by packages ['.implode(', ', $job['packages']).'].';
case 'update':
return 'Update of package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'was requested.';
case 'remove':
return 'Removal of package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'was requested.';
}
return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])';
}
/**
* Turns a constraint into text usable in a sentence describing a job
*
* @param LinkConstraint $constraint
* @return string
*/
protected function constraintToText($constraint)
{
return ($constraint) ? 'with constraint '.$constraint.' ' : '';
}
}

@ -52,6 +52,7 @@ class Request
'packages' => $packages, 'packages' => $packages,
'cmd' => $cmd, 'cmd' => $cmd,
'packageName' => $packageName, 'packageName' => $packageName,
'constraint' => $constraint,
); );
} }

@ -17,6 +17,19 @@ namespace Composer\DependencyResolver;
*/ */
class Rule class Rule
{ {
const RULE_INTERNAL_ALLOW_UPDATE = 1;
const RULE_JOB_INSTALL = 2;
const RULE_JOB_REMOVE = 3;
const RULE_JOB_LOCK = 4;
const RULE_NOT_INSTALLABLE = 5;
const RULE_PACKAGE_CONFLICT = 6;
const RULE_PACKAGE_REQUIRES = 7;
const RULE_PACKAGE_OBSOLETES = 8;
const RULE_INSTALLED_PACKAGE_OBSOLETES = 9;
const RULE_PACKAGE_SAME_NAME = 10;
const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11;
const RULE_LEARNED = 12;
protected $disabled; protected $disabled;
protected $literals; protected $literals;
protected $type; protected $type;
@ -163,6 +176,68 @@ class Rule
} }
} }
public function toHumanReadableString()
{
$ruleText = '';
foreach ($this->literals as $i => $literal) {
if ($i != 0) {
$ruleText .= '|';
}
$ruleText .= $literal;
}
switch ($this->reason) {
case self::RULE_INTERNAL_ALLOW_UPDATE:
return $ruleText;
case self::RULE_JOB_INSTALL:
return "Install command rule ($ruleText)";
case self::RULE_JOB_REMOVE:
return "Remove command rule ($ruleText)";
case self::RULE_JOB_LOCK:
return "Lock command rule ($ruleText)";
case self::RULE_NOT_INSTALLABLE:
return $ruleText;
case self::RULE_PACKAGE_CONFLICT:
$package1 = $this->literals[0]->getPackage();
$package2 = $this->literals[1]->getPackage();
return 'Package "'.$package1.'" conflicts with "'.$package2.'"';
case self::RULE_PACKAGE_REQUIRES:
$literals = $this->literals;
$sourceLiteral = array_shift($literals);
$sourcePackage = $sourceLiteral->getPackage();
$requires = array();
foreach ($literals as $literal) {
$requires[] = $literal->getPackage();
}
$text = 'Package "'.$sourcePackage.'" contains the rule '.$this->reasonData.'. ';
if ($requires) {
$text .= 'Any of these packages satisfy the dependency: '.implode(', ', $requires).'.';
} else {
$text .= 'No package satisfies this dependency.';
}
return $text;
case self::RULE_PACKAGE_OBSOLETES:
return $ruleText;
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
return $ruleText;
case self::RULE_PACKAGE_SAME_NAME:
return $ruleText;
case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
return $ruleText;
case self::RULE_LEARNED:
return 'learned: '.$ruleText;
}
}
/** /**
* Formats a rule as a string of the format (Literal1|Literal2|...) * Formats a rule as a string of the format (Literal1|Literal2|...)
* *

@ -21,21 +21,6 @@ use Composer\DependencyResolver\Operation;
*/ */
class Solver class Solver
{ {
const RULE_INTERNAL_ALLOW_UPDATE = 1;
const RULE_JOB_INSTALL = 2;
const RULE_JOB_REMOVE = 3;
const RULE_JOB_LOCK = 4;
const RULE_NOT_INSTALLABLE = 5;
const RULE_NOTHING_PROVIDES_DEP = 6;
const RULE_PACKAGE_CONFLICT = 7;
const RULE_PACKAGE_NOT_EXIST = 8;
const RULE_PACKAGE_REQUIRES = 9;
const RULE_PACKAGE_OBSOLETES = 10;
const RULE_INSTALLED_PACKAGE_OBSOLETES = 11;
const RULE_PACKAGE_SAME_NAME = 12;
const RULE_PACKAGE_IMPLICIT_OBSOLETES = 13;
const RULE_LEARNED = 14;
protected $policy; protected $policy;
protected $pool; protected $pool;
protected $installed; protected $installed;
@ -235,7 +220,7 @@ class Solver
} }
if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installedMap, $package)) { if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installedMap, $package)) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, self::RULE_NOT_INSTALLABLE, (string) $package)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, Rule::RULE_NOT_INSTALLABLE, (string) $package));
continue; continue;
} }
@ -261,7 +246,7 @@ class Solver
} }
} }
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, self::RULE_PACKAGE_REQUIRES, (string) $link)); $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link));
foreach ($possibleRequires as $require) { foreach ($possibleRequires as $require) {
$workQueue->enqueue($require); $workQueue->enqueue($require);
@ -276,7 +261,7 @@ class Solver
continue; continue;
} }
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, self::RULE_PACKAGE_CONFLICT, (string) $link)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link));
} }
} }
@ -301,7 +286,7 @@ class Solver
continue; // don't repair installed/installed problems continue; // don't repair installed/installed problems
} }
$reason = ($isInstalled) ? self::RULE_INSTALLED_PACKAGE_OBSOLETES : self::RULE_PACKAGE_OBSOLETES; $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
} }
} }
@ -327,7 +312,7 @@ class Solver
continue; continue;
} }
$reason = ($package->getName() == $provider->getName()) ? self::RULE_PACKAGE_SAME_NAME : self::RULE_PACKAGE_IMPLICIT_OBSOLETES; $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package)); $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
} }
} }
@ -466,24 +451,29 @@ class Solver
$conflict = $this->findDecisionRule($literal->getPackage()); $conflict = $this->findDecisionRule($literal->getPackage());
/** TODO: handle conflict with systemsolvable? */ /** TODO: handle conflict with systemsolvable? */
$this->learnedPool[] = array($rule, $conflict);
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) { if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
$problem = new Problem;
if ($rule->getType() == RuleSet::TYPE_JOB) { if ($rule->getType() == RuleSet::TYPE_JOB) {
$why = $this->ruleToJob[$rule->getId()]; $job = $this->ruleToJob[$rule->getId()];
$problem->addJobRule($job, $rule);
$problem->addRule($conflict);
$this->disableProblem($job);
} else { } else {
$why = $rule; $problem->addRule($rule);
$problem->addRule($conflict);
$this->disableProblem($rule);
} }
$this->problems[] = array($why); $this->problems[] = $problem;
$this->disableProblem($why);
continue; continue;
} }
// conflict with another job or update/feature rule // conflict with another job or update/feature rule
$problem = new Problem;
$this->problems[] = array(); $problem->addRule($rule);
$problem->addRule($conflict);
// push all of our rules (can only be feature or job rules) // push all of our rules (can only be feature or job rules)
// asserting this literal on the problem stack // asserting this literal on the problem stack
@ -500,14 +490,16 @@ class Solver
} }
if ($assertRule->getType() === RuleSet::TYPE_JOB) { if ($assertRule->getType() === RuleSet::TYPE_JOB) {
$why = $this->ruleToJob[$assertRule->getId()]; $job = $this->ruleToJob[$assertRule->getId()];
$problem->addJobRule($job, $assertRule);
$this->disableProblem($job);
} else { } else {
$why = $assertRule; $problem->addRule($assertRule);
$this->disableProblem($assertRule);
} }
$this->problems[count($this->problems) - 1][] = $why;
$this->disableProblem($why);
} }
$this->problems[] = $problem;
// start over // start over
while (count($this->decisionQueue) > $decisionStart) { while (count($this->decisionQueue) > $decisionStart) {
@ -966,7 +958,7 @@ class Solver
foreach ($installedPackages as $package) { foreach ($installedPackages as $package) {
$updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
$rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
$rule->setWeak(true); $rule->setWeak(true);
$this->addRule(RuleSet::TYPE_FEATURE, $rule); $this->addRule(RuleSet::TYPE_FEATURE, $rule);
@ -977,9 +969,11 @@ class Solver
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': case 'install':
if (empty($job['packages'])) { if (empty($job['packages'])) {
$this->problems[] = array($job); $problem = new Problem();
$problem->addJobRule($job);
$this->problems[] = $problem;
} else { } else {
$rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']); $rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job['packageName']);
$this->addRule(RuleSet::TYPE_JOB, $rule); $this->addRule(RuleSet::TYPE_JOB, $rule);
$this->ruleToJob[$rule->getId()] = $job; $this->ruleToJob[$rule->getId()] = $job;
} }
@ -990,7 +984,7 @@ class Solver
// todo: cleandeps // todo: cleandeps
foreach ($job['packages'] as $package) { foreach ($job['packages'] as $package) {
$rule = $this->createRemoveRule($package, self::RULE_JOB_REMOVE); $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE);
$this->addRule(RuleSet::TYPE_JOB, $rule); $this->addRule(RuleSet::TYPE_JOB, $rule);
$this->ruleToJob[$rule->getId()] = $job; $this->ruleToJob[$rule->getId()] = $job;
} }
@ -998,9 +992,9 @@ class Solver
case 'lock': case 'lock':
foreach ($job['packages'] as $package) { foreach ($job['packages'] as $package) {
if (isset($this->installedMap[$package->getId()])) { if (isset($this->installedMap[$package->getId()])) {
$rule = $this->createInstallRule($package, self::RULE_JOB_LOCK); $rule = $this->createInstallRule($package, Rule::RULE_JOB_LOCK);
} else { } else {
$rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK); $rule = $this->createRemoveRule($package, Rule::RULE_JOB_LOCK);
} }
$this->addRule(RuleSet::TYPE_JOB, $rule); $this->addRule(RuleSet::TYPE_JOB, $rule);
$this->ruleToJob[$rule->getId()] = $job; $this->ruleToJob[$rule->getId()] = $job;
@ -1028,7 +1022,7 @@ class Solver
//solver_prepare_solutions(solv); //solver_prepare_solutions(solv);
if ($this->problems) { if ($this->problems) {
throw new SolverProblemsException($this->problems, $this->learnedPool); throw new SolverProblemsException($this->problems);
} }
return $this->createTransaction(); return $this->createTransaction();
@ -1487,22 +1481,21 @@ class Solver
$why = count($this->learnedPool) - 1; $why = count($this->learnedPool) - 1;
assert($learnedLiterals[0] !== null); assert($learnedLiterals[0] !== null);
$newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why); $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
return array($ruleLevel, $newRule, $why); return array($ruleLevel, $newRule, $why);
} }
private function analyzeUnsolvableRule($conflictRule, &$lastWeakWhy) private function analyzeUnsolvableRule($problem, $conflictRule, &$lastWeakWhy)
{ {
$why = $conflictRule->getId(); $why = $conflictRule->getId();
if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) { if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
$learnedWhy = $this->learnedWhy[$why]; $learnedWhy = $this->learnedWhy[$why];
$problem = $this->learnedPool[$learnedWhy]; $problemRules = $this->learnedPool[$learnedWhy];
foreach ($problem as $problemRule) { foreach ($problemRules as $problemRule) {
$this->analyzeUnsolvableRule($problemRule, $lastWeakWhy); $this->analyzeUnsolvableRule($problem, $problemRule, $lastWeakWhy);
} }
return; return;
} }
@ -1520,24 +1513,22 @@ class Solver
} }
if ($conflictRule->getType() == RuleSet::TYPE_JOB) { if ($conflictRule->getType() == RuleSet::TYPE_JOB) {
$why = $this->ruleToJob[$conflictRule->getId()]; $job = $this->ruleToJob[$conflictRule->getId()];
} $problem->addJobRule($job, $conflictRule);
} else {
// if this problem was already found skip it $problem->addRule($conflictRule);
if (in_array($why, $this->problems[count($this->problems) - 1], true)) {
return;
} }
$this->problems[count($this->problems) - 1][] = $why;
} }
private function analyzeUnsolvable($conflictRule, $disableRules) private function analyzeUnsolvable($conflictRule, $disableRules)
{ {
$lastWeakWhy = null; $lastWeakWhy = null;
$this->problems[] = array(); $problem = new Problem;
$this->learnedPool[] = array($conflictRule); $problem->addRule($conflictRule);
$this->analyzeUnsolvableRule($problem, $conflictRule, $lastWeakWhy);
$this->analyzeUnsolvableRule($conflictRule, $lastWeakWhy); $this->problems[] = $problem;
$seen = array(); $seen = array();
$literals = $conflictRule->getLiterals(); $literals = $conflictRule->getLiterals();
@ -1569,9 +1560,9 @@ class Solver
} }
$why = $this->decisionQueueWhy[$decisionId]; $why = $this->decisionQueueWhy[$decisionId];
$this->learnedPool[count($this->learnedPool) - 1][] = $why; $problem->addRule($why);
$this->analyzeUnsolvableRule($why, $lastWeakWhy); $this->analyzeUnsolvableRule($problem, $why, $lastWeakWhy);
$literals = $why->getLiterals(); $literals = $why->getLiterals();
/* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
@ -1591,7 +1582,6 @@ class Solver
if ($lastWeakWhy) { if ($lastWeakWhy) {
array_pop($this->problems); array_pop($this->problems);
array_pop($this->learnedPool);
if ($lastWeakWhy->getType() === RuleSet::TYPE_JOB) { if ($lastWeakWhy->getType() === RuleSet::TYPE_JOB) {
$why = $this->ruleToJob[$lastWeakWhy]; $why = $this->ruleToJob[$lastWeakWhy];
@ -1616,8 +1606,12 @@ class Solver
} }
if ($disableRules) { if ($disableRules) {
foreach ($this->problems[count($this->problems) - 1] as $why) { foreach ($this->problems[count($this->problems) - 1] as $reason) {
$this->disableProblem($why); if ($reason['job']) {
$this->disableProblem($reason['job']);
} else {
$this->disableProblem($reason['rule']);
}
} }
$this->resetSolver(); $this->resetSolver();
@ -1670,10 +1664,10 @@ class Solver
{ {
foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) { foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
$why = $this->learnedWhy[$rule->getId()]; $why = $this->learnedWhy[$rule->getId()];
$problem = $this->learnedPool[$why]; $problemRules = $this->learnedPool[$why];
$foundDisabled = false; $foundDisabled = false;
foreach ($problem as $problemRule) { foreach ($problemRules as $problemRule) {
if ($problemRule->disabled()) { if ($problemRule->disabled()) {
$foundDisabled = true; $foundDisabled = true;
break; break;

@ -19,47 +19,26 @@ class SolverProblemsException extends \RuntimeException
{ {
protected $problems; protected $problems;
public function __construct(array $problems, array $learnedPool) public function __construct(array $problems)
{ {
$message = ''; $this->problems = $problems;
foreach ($problems as $i => $problem) {
$message .= '[';
foreach ($problem as $why) {
if (is_int($why) && isset($learnedPool[$why])) { parent::__construct($this->createMessage());
$rules = $learnedPool[$why]; }
} else {
$rules = $why; protected function createMessage()
} {
$messages = array();
if (isset($rules['packages'])) { foreach ($this->problems as $problem) {
$message .= $this->jobToText($rules); $messages[] = (string) $problem;
} else {
$message .= '(';
foreach ($rules as $rule) {
if ($rule instanceof Rule) {
if ($rule->getType() == RuleSet::TYPE_LEARNED) {
$message .= 'learned: ';
}
$message .= $rule . ', ';
} else {
$message .= 'String(' . $rule . '), ';
}
}
$message .= ')';
}
$message .= ', ';
}
$message .= "]\n";
} }
parent::__construct($message); return "\n\tProblems:\n\t\t- ".implode("\n\t\t- ", $messages);
} }
public function jobToText($job) public function getProblems()
{ {
//$output = serialize($job); return $this->problems;
$output = 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])';
return $output;
} }
} }

@ -74,7 +74,6 @@ class FileDownloader implements DownloaderInterface
$url = $this->processUrl($url); $url = $this->processUrl($url);
$this->rfs->copy($package->getSourceUrl(), $url, $fileName); $this->rfs->copy($package->getSourceUrl(), $url, $fileName);
$this->io->write('');
if (!file_exists($fileName)) { if (!file_exists($fileName)) {
throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'

@ -16,6 +16,7 @@ use Composer\Json\JsonFile;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryManager;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
/** /**
* Creates an configured instance of composer. * Creates an configured instance of composer.
@ -38,7 +39,7 @@ class Factory
$composerFile = getenv('COMPOSER') ?: 'composer.json'; $composerFile = getenv('COMPOSER') ?: 'composer.json';
} }
$file = new JsonFile($composerFile); $file = new JsonFile($composerFile, new RemoteFilesystem($io));
if (!$file->exists()) { if (!$file->exists()) {
if ($composerFile === 'composer.json') { if ($composerFile === 'composer.json') {
$message = 'Composer could not find a composer.json file in '.getcwd(); $message = 'Composer could not find a composer.json file in '.getcwd();
@ -98,7 +99,7 @@ class Factory
// init locker // init locker
$lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock';
$locker = new Package\Locker(new JsonFile($lockFile), $rm, md5_file($composerFile)); $locker = new Package\Locker(new JsonFile($lockFile, new RemoteFilesystem($io)), $rm, md5_file($composerFile));
// initialize composer // initialize composer
$composer = new Composer(); $composer = new Composer();

@ -18,6 +18,7 @@ use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Downloader\DownloadManager; use Composer\Downloader\DownloadManager;
use Composer\Installer\InstallationManager; use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
@ -163,7 +164,7 @@ class Installer
$installFromLock = false; $installFromLock = false;
$request = new Request($pool); $request = new Request($pool);
if ($this->update) { if ($this->update) {
$this->io->write('<info>Updating dependencies</info>'); $this->io->write('Updating dependencies');
$request->updateAll(); $request->updateAll();
@ -174,7 +175,7 @@ class Installer
} }
} elseif ($this->locker->isLocked()) { } elseif ($this->locker->isLocked()) {
$installFromLock = true; $installFromLock = true;
$this->io->write('<info>Installing from lock file</info>'); $this->io->write('Installing from lock file');
if (!$this->locker->isFresh()) { if (!$this->locker->isFresh()) {
$this->io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>'); $this->io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>');
@ -192,7 +193,7 @@ class Installer
$request->install($package->getName(), $constraint); $request->install($package->getName(), $constraint);
} }
} else { } else {
$this->io->write('<info>Installing dependencies</info>'); $this->io->write('Installing dependencies');
$links = $this->collectLinks(); $links = $this->collectLinks();
@ -206,7 +207,14 @@ class Installer
$solver = new Solver($policy, $pool, $installedRepo); $solver = new Solver($policy, $pool, $installedRepo);
// solve dependencies // solve dependencies
$operations = $solver->solve($request); 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($e->getMessage());
return false;
}
// force dev packages to be updated to latest reference on update // force dev packages to be updated to latest reference on update
if ($this->update) { if ($this->update) {
@ -299,6 +307,8 @@ class Installer
$eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
$this->eventDispatcher->dispatchCommandEvent($eventName); $this->eventDispatcher->dispatchCommandEvent($eventName);
} }
return true;
} }
private function collectLinks() private function collectLinks()

@ -17,6 +17,7 @@ use Composer\Composer;
use JsonSchema\Validator; use JsonSchema\Validator;
use Seld\JsonLint\JsonParser; use Seld\JsonLint\JsonParser;
use Composer\Util\StreamContextFactory; use Composer\Util\StreamContextFactory;
use Composer\Util\RemoteFilesystem;
/** /**
* Reads/writes json files. * Reads/writes json files.
@ -34,15 +35,22 @@ class JsonFile
const JSON_UNESCAPED_UNICODE = 256; const JSON_UNESCAPED_UNICODE = 256;
private $path; private $path;
private $rfs;
/** /**
* Initializes json file reader/parser. * Initializes json file reader/parser.
* *
* @param string $lockFile path to a lockfile * @param string $lockFile path to a lockfile
* @param RemoteFilesystem $rfs required for loading http/https json files
*/ */
public function __construct($path) public function __construct($path, RemoteFilesystem $rfs = null)
{ {
$this->path = $path; $this->path = $path;
if (null === $rfs && preg_match('{^https?://}i', $path)) {
throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
}
$this->rfs = $rfs;
} }
public function getPath() public function getPath()
@ -67,15 +75,14 @@ class JsonFile
*/ */
public function read() public function read()
{ {
$ctx = StreamContextFactory::getContext(array( try {
'http' => array( if ($this->rfs) {
'header' => 'User-Agent: Composer/'.Composer::VERSION."\r\n" $json = $this->rfs->getContents($this->path, $this->path, false);
) } else {
)); $json = file_get_contents($this->path);
}
$json = file_get_contents($this->path, false, $ctx); } catch (\Exception $e) {
if (!$json) { throw new \RuntimeException('Could not read '.$this->path.', you are probably offline ('.$e->getMessage().')');
throw new \RuntimeException('Could not read '.$this->path.', you are probably offline');
} }
return static::parseJson($json); return static::parseJson($json);

@ -15,6 +15,8 @@ namespace Composer\Repository;
use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ArrayLoader;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Util\RemoteFilesystem;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
@ -22,9 +24,10 @@ use Composer\Json\JsonFile;
class ComposerRepository extends ArrayRepository class ComposerRepository extends ArrayRepository
{ {
protected $url; protected $url;
protected $io;
protected $packages; protected $packages;
public function __construct(array $config) public function __construct(array $config, IOInterface $io)
{ {
if (!preg_match('{^\w+://}', $config['url'])) { if (!preg_match('{^\w+://}', $config['url'])) {
// assume http as the default protocol // assume http as the default protocol
@ -36,12 +39,13 @@ class ComposerRepository extends ArrayRepository
} }
$this->url = $config['url']; $this->url = $config['url'];
$this->io = $io;
} }
protected function initialize() protected function initialize()
{ {
parent::initialize(); parent::initialize();
$json = new JsonFile($this->url.'/packages.json'); $json = new JsonFile($this->url.'/packages.json', new RemoteFilesystem($this->io));
$packages = $json->read(); $packages = $json->read();
if (!$packages) { if (!$packages) {
throw new \UnexpectedValueException('Could not parse package list from the '.$this->url.' repository'); throw new \UnexpectedValueException('Could not parse package list from the '.$this->url.' repository');

@ -12,8 +12,10 @@
namespace Composer\Repository; namespace Composer\Repository;
use Composer\IO\IOInterface;
use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ArrayLoader;
use Composer\Util\StreamContextFactory; use Composer\Util\RemoteFilesystem;
use Composer\Downloader\TransportException;
/** /**
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
@ -23,9 +25,10 @@ class PearRepository extends ArrayRepository
{ {
private $url; private $url;
private $channel; private $channel;
private $streamContext; private $io;
private $rfs;
public function __construct(array $config) public function __construct(array $config, IOInterface $io, RemoteFilesystem $rfs = null)
{ {
if (!preg_match('{^https?://}', $config['url'])) { if (!preg_match('{^https?://}', $config['url'])) {
$config['url'] = 'http://'.$config['url']; $config['url'] = 'http://'.$config['url'];
@ -36,20 +39,17 @@ class PearRepository extends ArrayRepository
} }
$this->url = rtrim($config['url'], '/'); $this->url = rtrim($config['url'], '/');
$this->channel = !empty($config['channel']) ? $config['channel'] : null; $this->channel = !empty($config['channel']) ? $config['channel'] : null;
$this->io = $io;
$this->rfs = $rfs ?: new RemoteFilesystem($this->io);
} }
protected function initialize() protected function initialize()
{ {
parent::initialize(); parent::initialize();
set_error_handler(function($severity, $message, $file, $line) { $this->io->write('Initializing PEAR repository '.$this->url);
throw new \ErrorException($message, $severity, $severity, $file, $line);
});
$this->streamContext = StreamContextFactory::getContext();
$this->fetchFromServer(); $this->fetchFromServer();
restore_error_handler();
} }
protected function fetchFromServer() protected function fetchFromServer()
@ -68,7 +68,7 @@ class PearRepository extends ArrayRepository
try { try {
$packagesLink = str_replace("info.xml", "packagesinfo.xml", $link); $packagesLink = str_replace("info.xml", "packagesinfo.xml", $link);
$this->fetchPear2Packages($this->url . $packagesLink); $this->fetchPear2Packages($this->url . $packagesLink);
} catch (\ErrorException $e) { } catch (TransportException $e) {
if (false === strpos($e->getMessage(), '404')) { if (false === strpos($e->getMessage(), '404')) {
throw $e; throw $e;
} }
@ -81,7 +81,7 @@ class PearRepository extends ArrayRepository
/** /**
* @param string $categoryLink * @param string $categoryLink
* @throws ErrorException * @throws TransportException
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
private function fetchPearPackages($categoryLink) private function fetchPearPackages($categoryLink)
@ -99,7 +99,7 @@ class PearRepository extends ArrayRepository
try { try {
$releasesXML = $this->requestXml($allReleasesLink); $releasesXML = $this->requestXml($allReleasesLink);
} catch (\ErrorException $e) { } catch (TransportException $e) {
if (strpos($e->getMessage(), '404')) { if (strpos($e->getMessage(), '404')) {
continue; continue;
} }
@ -120,8 +120,8 @@ class PearRepository extends ArrayRepository
); );
try { try {
$deps = file_get_contents($releaseLink . "/deps.".$pearVersion.".txt", false, $this->streamContext); $deps = $this->rfs->getContents($this->url, $releaseLink . "/deps.".$pearVersion.".txt", false);
} catch (\ErrorException $e) { } catch (TransportException $e) {
if (strpos($e->getMessage(), '404')) { if (strpos($e->getMessage(), '404')) {
continue; continue;
} }
@ -226,6 +226,7 @@ class PearRepository extends ArrayRepository
{ {
$loader = new ArrayLoader(); $loader = new ArrayLoader();
$packagesXml = $this->requestXml($packagesLink); $packagesXml = $this->requestXml($packagesLink);
$informations = $packagesXml->getElementsByTagName('pi'); $informations = $packagesXml->getElementsByTagName('pi');
foreach ($informations as $information) { foreach ($informations as $information) {
$package = $information->getElementsByTagName('p')->item(0); $package = $information->getElementsByTagName('p')->item(0);
@ -289,7 +290,7 @@ class PearRepository extends ArrayRepository
*/ */
private function requestXml($url) private function requestXml($url)
{ {
$content = file_get_contents($url, false, $this->streamContext); $content = $this->rfs->getContents($this->url, $url, false);
if (!$content) { if (!$content) {
throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.');
} }

@ -35,11 +35,10 @@ class GitDriver extends VcsDriver
$this->repoDir = $this->url; $this->repoDir = $this->url;
} else { } else {
$this->repoDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/'; $this->repoDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/';
$repoDir = escapeshellarg($this->repoDir);
if (is_dir($this->repoDir)) { if (is_dir($this->repoDir)) {
$this->process->execute(sprintf('cd %s && git fetch origin', $repoDir), $output); $this->process->execute('git fetch origin', $output, $this->repoDir);
} else { } else {
$this->process->execute(sprintf('git clone %s %s', $url, $repoDir), $output); $this->process->execute(sprintf('git clone %s %s', $url, escapeshellarg($this->repoDir)), $output);
} }
} }
@ -57,7 +56,7 @@ class GitDriver extends VcsDriver
if ($this->isLocal) { if ($this->isLocal) {
// select currently checked out branch if master is not available // select currently checked out branch if master is not available
$this->process->execute(sprintf('cd %s && git branch --no-color', escapeshellarg($this->repoDir)), $output); $this->process->execute('git branch --no-color', $output, $this->repoDir);
$branches = $this->process->splitLines($output); $branches = $this->process->splitLines($output);
if (!in_array('* master', $branches)) { if (!in_array('* master', $branches)) {
foreach ($branches as $branch) { foreach ($branches as $branch) {
@ -69,7 +68,7 @@ class GitDriver extends VcsDriver
} }
} else { } else {
// try to find a non-master remote HEAD branch // try to find a non-master remote HEAD branch
$this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->repoDir)), $output); $this->process->execute('git branch --no-color -r', $output, $this->repoDir);
foreach ($this->process->splitLines($output) as $branch) { foreach ($this->process->splitLines($output) as $branch) {
if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) { if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) {
$this->rootIdentifier = $match[1]; $this->rootIdentifier = $match[1];
@ -114,7 +113,7 @@ class GitDriver extends VcsDriver
public function getComposerInformation($identifier) public function getComposerInformation($identifier)
{ {
if (!isset($this->infoCache[$identifier])) { if (!isset($this->infoCache[$identifier])) {
$this->process->execute(sprintf('cd %s && git show %s:composer.json', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $composer); $this->process->execute(sprintf('git show %s:composer.json', escapeshellarg($identifier)), $composer, $this->repoDir);
if (!trim($composer)) { if (!trim($composer)) {
return; return;
@ -123,7 +122,7 @@ class GitDriver extends VcsDriver
$composer = JsonFile::parseJson($composer); $composer = JsonFile::parseJson($composer);
if (!isset($composer['time'])) { if (!isset($composer['time'])) {
$this->process->execute(sprintf('cd %s && git log -1 --format=%%at %s', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $output); $this->process->execute(sprintf('git log -1 --format=%%at %s', escapeshellarg($identifier)), $output, $this->repoDir);
$date = new \DateTime('@'.trim($output)); $date = new \DateTime('@'.trim($output));
$composer['time'] = $date->format('Y-m-d H:i:s'); $composer['time'] = $date->format('Y-m-d H:i:s');
} }
@ -139,7 +138,7 @@ class GitDriver extends VcsDriver
public function getTags() public function getTags()
{ {
if (null === $this->tags) { if (null === $this->tags) {
$this->process->execute(sprintf('cd %s && git tag', escapeshellarg($this->repoDir)), $output); $this->process->execute('git tag', $output, $this->repoDir);
$output = $this->process->splitLines($output); $output = $this->process->splitLines($output);
$this->tags = $output ? array_combine($output, $output) : array(); $this->tags = $output ? array_combine($output, $output) : array();
} }
@ -156,10 +155,9 @@ class GitDriver extends VcsDriver
$branches = array(); $branches = array();
$this->process->execute(sprintf( $this->process->execute(sprintf(
'cd %s && git branch --no-color --no-abbrev -v %s', 'git branch --no-color --no-abbrev -v %s',
escapeshellarg($this->repoDir),
$this->isLocal ? '' : '-r' $this->isLocal ? '' : '-r'
), $output); ), $output, $this->repoDir);
foreach ($this->process->splitLines($output) as $branch) { foreach ($this->process->splitLines($output) as $branch) {
if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
preg_match('{^(?:\* )? *(?:[^/]+/)?(\S+) *([a-f0-9]+) .*$}', $branch, $match); preg_match('{^(?:\* )? *(?:[^/]+/)?(\S+) *([a-f0-9]+) .*$}', $branch, $match);
@ -186,7 +184,7 @@ class GitDriver extends VcsDriver
if (static::isLocalUrl($url)) { if (static::isLocalUrl($url)) {
$process = new ProcessExecutor(); $process = new ProcessExecutor();
// check whether there is a git repo in that path // check whether there is a git repo in that path
if ($process->execute(sprintf('cd %s && git tag', escapeshellarg($url)), $output) === 0) { if ($process->execute('git tag', $output, $url) === 0) {
return true; return true;
} }
} }

@ -35,7 +35,7 @@ class VcsRepository extends ArrayRepository
$this->url = $config['url']; $this->url = $config['url'];
$this->io = $io; $this->io = $io;
$this->type = $config['type']; $this->type = isset($config['type']) ? $config['type'] : 'vcs';
} }
public function setDebug($debug) public function setDebug($debug)
@ -118,7 +118,7 @@ class VcsRepository extends ArrayRepository
} }
} catch (\Exception $e) { } catch (\Exception $e) {
if ($debug) { if ($debug) {
$this->io->write('Skipped tag '.$tag.', '.$e->getMessage()); $this->io->write('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()));
} }
continue; continue;
} }

@ -0,0 +1,52 @@
<?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\Util;
/**
* Convert PHP errors into exceptions
*
* @author Artem Lopata <biozshock@gmail.com>
*/
class ErrorHandler
{
/**
* Error handler
*
* @param int $level Level of the error raised
* @param string $message Error message
* @param string $file Filename that the error was raised in
* @param int $line Line number the error was raised at
*
* @static
* @throws \ErrorException
*/
public static function handle($level, $message, $file, $line)
{
// respect error_reporting being disabled
if (!error_reporting()) {
return;
}
throw new \ErrorException($message, 0, $level, $file, $line);
}
/**
* Register error handler
*
* @static
*/
public static function register()
{
set_error_handler(array(__CLASS__, 'handle'));
}
}

@ -26,15 +26,16 @@ class ProcessExecutor
/** /**
* runs a process on the commandline * runs a process on the commandline
* *
* @param $command the command to execute * @param string $command the command to execute
* @param null $output the output will be written into this var if passed * @param null $output the output will be written into this var if passed
* @param string $cwd the working directory
* @return int statuscode * @return int statuscode
*/ */
public function execute($command, &$output = null) public function execute($command, &$output = null, $cwd = null)
{ {
$captureOutput = count(func_get_args()) > 1; $captureOutput = count(func_get_args()) > 1;
$this->errorOutput = null; $this->errorOutput = null;
$process = new Process($command, null, null, null, static::getTimeout()); $process = new Process($command, $cwd, null, null, static::getTimeout());
$process->run(function($type, $buffer) use ($captureOutput) { $process->run(function($type, $buffer) use ($captureOutput) {
if ($captureOutput) { if ($captureOutput) {
return; return;

@ -12,6 +12,7 @@
namespace Composer\Util; namespace Composer\Util;
use Composer\Composer;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Downloader\TransportException; use Composer\Downloader\TransportException;
@ -101,26 +102,50 @@ class RemoteFilesystem
} }
$result = @file_get_contents($fileUrl, false, $ctx); $result = @file_get_contents($fileUrl, false, $ctx);
if (null !== $fileName) {
$result = @file_put_contents($fileName, $result) ? true : false;
}
// fix for 5.4.0 https://bugs.php.net/bug.php?id=61336 // 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])) { if (!empty($http_response_header[0]) && preg_match('{^HTTP/\S+ 404}i', $http_response_header[0])) {
$result = false; $result = false;
} }
// decode gzip
if (false !== $result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') {
$decode = false;
foreach ($http_response_header as $header) {
if (preg_match('{^content-encoding: *gzip *$}i', $header)) {
$decode = true;
continue;
} elseif (preg_match('{^HTTP/}i', $header)) {
$decode = false;
}
}
if ($decode) {
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$result = zlib_decode($result);
} else {
// work around issue with gzuncompress & co that do not work with all gzip checksums
$result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
}
}
}
// handle copy command if download was successful
if (false !== $result && null !== $fileName) {
$result = (Boolean) @file_put_contents($fileName, $result);
}
// avoid overriding if content was loaded by a sub-call to get() // avoid overriding if content was loaded by a sub-call to get()
if (null === $this->result) { if (null === $this->result) {
$this->result = $result; $this->result = $result;
} }
if ($this->progress) { if ($this->progress) {
$this->io->overwrite(" Downloading", false); $this->io->overwrite(" Downloading: <comment>100%</comment>");
} }
if (false === $this->result) { if (false === $this->result) {
throw new TransportException("The '$fileUrl' file could not be downloaded"); throw new TransportException('The "'.$fileUrl.'" file could not be downloaded');
} }
} }
@ -138,7 +163,7 @@ class RemoteFilesystem
{ {
switch ($notificationCode) { switch ($notificationCode) {
case STREAM_NOTIFY_FAILURE: case STREAM_NOTIFY_FAILURE:
throw new TransportException(trim($message), $messageCode); throw new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.trim($message).')', $messageCode);
break; break;
case STREAM_NOTIFY_AUTH_REQUIRED: case STREAM_NOTIFY_AUTH_REQUIRED:
@ -184,17 +209,21 @@ class RemoteFilesystem
} }
} }
protected function getOptionsForUrl($url) protected function getOptionsForUrl($originUrl)
{ {
$options = array(); $options['http']['header'] = 'User-Agent: Composer/'.Composer::VERSION."\r\n";
if ($this->io->hasAuthorization($url)) { if (extension_loaded('zlib')) {
$auth = $this->io->getAuthorization($url); $options['http']['header'] .= 'Accept-Encoding: gzip'."\r\n";
}
if ($this->io->hasAuthorization($originUrl)) {
$auth = $this->io->getAuthorization($originUrl);
$authStr = base64_encode($auth['username'] . ':' . $auth['password']); $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
$options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); $options['http']['header'] .= "Authorization: Basic $authStr\r\n";
} elseif (null !== $this->io->getLastUsername()) { } elseif (null !== $this->io->getLastUsername()) {
$authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword());
$options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); $options['http']['header'] .= "Authorization: Basic $authStr\r\n";
$this->io->setAuthorization($url, $this->io->getLastUsername(), $this->io->getLastPassword()); $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword());
} }
return $options; return $options;

@ -34,7 +34,7 @@ final class StreamContextFactory
// Handle system proxy // Handle system proxy
if (isset($_SERVER['HTTP_PROXY']) || isset($_SERVER['http_proxy'])) { if (isset($_SERVER['HTTP_PROXY']) || isset($_SERVER['http_proxy'])) {
// Some systems seem to rely on a lowercased version instead... // Some systems seem to rely on a lowercased version instead...
$proxy = isset($_SERVER['HTTP_PROXY']) ? $_SERVER['HTTP_PROXY'] : $_SERVER['http_proxy']; $proxy = isset($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY'];
// http(s):// is not supported in proxy // http(s):// is not supported in proxy
$proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxy); $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxy);

@ -40,9 +40,9 @@ class RequestTest extends TestCase
$this->assertEquals( $this->assertEquals(
array( array(
array('packages' => array($foo), 'cmd' => 'install', 'packageName' => 'foo'), array('packages' => array($foo), 'cmd' => 'install', 'packageName' => 'foo', 'constraint' => null),
array('packages' => array($bar), 'cmd' => 'install', 'packageName' => 'bar'), array('packages' => array($bar), 'cmd' => 'install', 'packageName' => 'bar', 'constraint' => null),
array('packages' => array($foobar), 'cmd' => 'remove', 'packageName' => 'foobar'), array('packages' => array($foobar), 'cmd' => 'remove', 'packageName' => 'foobar', 'constraint' => null),
), ),
$request->getJobs()); $request->getJobs());
} }
@ -63,11 +63,11 @@ class RequestTest extends TestCase
$pool->addRepository($repo2); $pool->addRepository($repo2);
$request = new Request($pool); $request = new Request($pool);
$request->install('foo', $this->getVersionConstraint('=', '1')); $request->install('foo', $constraint = $this->getVersionConstraint('=', '1'));
$this->assertEquals( $this->assertEquals(
array( array(
array('packages' => array($foo1, $foo2), 'cmd' => 'install', 'packageName' => 'foo'), array('packages' => array($foo1, $foo2), 'cmd' => 'install', 'packageName' => 'foo', 'constraint' => $constraint),
), ),
$request->getJobs() $request->getJobs()
); );

@ -59,13 +59,15 @@ class SolverTest extends TestCase
$this->repo->addPackage($this->getPackage('A', '1.0')); $this->repo->addPackage($this->getPackage('A', '1.0'));
$this->reposComplete(); $this->reposComplete();
$this->request->install('B'); $this->request->install('B', $this->getVersionConstraint('=', '1'));
try { try {
$transaction = $this->solver->solve($this->request); $transaction = $this->solver->solve($this->request);
$this->fail('Unsolvable conflict did not resolve in exception.'); $this->fail('Unsolvable conflict did not result in exception.');
} catch (SolverProblemsException $e) { } catch (SolverProblemsException $e) {
// TODO assert problem properties $problems = $e->getProblems();
$this->assertEquals(1, count($problems));
$this->assertEquals('The requested package "b" with constraint == 1.0.0.0 could not be found.', (string) $problems[0]);
} }
} }
@ -589,8 +591,10 @@ class SolverTest extends TestCase
try { try {
$transaction = $this->solver->solve($this->request); $transaction = $this->solver->solve($this->request);
$this->fail('Unsolvable conflict did not resolve in exception.'); $this->fail('Unsolvable conflict did not result in exception.');
} catch (SolverProblemsException $e) { } catch (SolverProblemsException $e) {
$problems = $e->getProblems();
$this->assertEquals(1, count($problems));
// TODO assert problem properties // TODO assert problem properties
} }
} }
@ -610,8 +614,10 @@ class SolverTest extends TestCase
try { try {
$transaction = $this->solver->solve($this->request); $transaction = $this->solver->solve($this->request);
$this->fail('Unsolvable conflict did not resolve in exception.'); $this->fail('Unsolvable conflict did not result in exception.');
} catch (SolverProblemsException $e) { } catch (SolverProblemsException $e) {
$problems = $e->getProblems();
$this->assertEquals(1, count($problems));
// TODO assert problem properties // TODO assert problem properties
} }
} }

@ -0,0 +1,49 @@
<?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\Test\Util;
use Composer\Util\ErrorHandler;
use Composer\Test\TestCase;
/**
* ErrorHandler test case
*
* @author Artem Lopata <biozshock@gmail.com>
*/
class ErrorHandlerTest extends TestCase
{
/**
* Test ErrorHandler handles notices
*/
public function testErrorHandlerCaptureNotice()
{
$this->setExpectedException('\ErrorException', 'Undefined index: baz');
ErrorHandler::register();
$array = array('foo' => 'bar');
$array['baz'];
}
/**
* Test ErrorHandler handles warnings
*/
public function testErrorHandlerCaptureWarning()
{
$this->setExpectedException('\ErrorException', 'array_merge(): Argument #2 is not an array');
ErrorHandler::register();
array_merge(array(), 'string');
}
}

@ -31,7 +31,8 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue(null)) ->will($this->returnValue(null))
; ;
$this->assertEquals(array(), $this->callGetOptionsForUrl($io, array('http://example.org'))); $res = $this->callGetOptionsForUrl($io, array('http://example.org'));
$this->assertTrue(isset($res['http']['header']) && false !== strpos($res['http']['header'], 'User-Agent'), 'getOptions must return an array with a header containing a User-Agent');
} }
public function testGetOptionsForUrlWithAuthorization() public function testGetOptionsForUrlWithAuthorization()

@ -57,13 +57,13 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
public function testHttpProxy() public function testHttpProxy()
{ {
$_SERVER['HTTP_PROXY'] = 'http://username:password@proxyserver.net:port/'; $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:port/';
$_SERVER['http_proxy'] = 'http://proxyserver/'; $_SERVER['HTTP_PROXY'] = 'http://proxyserver/';
$context = StreamContextFactory::getContext(array('http' => array('method' => 'GET'))); $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET')));
$options = stream_context_get_options($context); $options = stream_context_get_options($context);
$this->assertSame('http://proxyserver/', $_SERVER['http_proxy']); $this->assertSame('http://proxyserver/', $_SERVER['HTTP_PROXY']);
$this->assertEquals(array('http' => array( $this->assertEquals(array('http' => array(
'proxy' => 'tcp://username:password@proxyserver.net:port/', 'proxy' => 'tcp://username:password@proxyserver.net:port/',

Loading…
Cancel
Save