1
0
Fork 0

Merge branch 'refactoring'

pull/20/merge
Jordi Boggiano 2011-09-25 23:09:07 +02:00
commit c6d7760145
44 changed files with 2621 additions and 416 deletions

View File

@ -4,21 +4,43 @@
require __DIR__.'/../tests/bootstrap.php'; require __DIR__.'/../tests/bootstrap.php';
use Composer\Composer; use Composer\Composer;
use Composer\Downloader\GitDownloader; use Composer\Installer;
use Composer\Downloader\PearDownloader; use Composer\Downloader;
use Composer\Downloader\ZipDownloader; use Composer\Repository;
use Composer\Installer\LibraryInstaller; use Composer\Package;
use Composer\Console\Application; use Composer\Console\Application as ComposerApplication;
setlocale(LC_ALL, 'C'); // initialize repository manager
$rm = new Repository\RepositoryManager();
$localRepository = new Repository\WrapperRepository(array(
new Repository\ArrayRepository('.composer/installed.json'),
new Repository\PlatformRepository(),
));
$rm->setLocalRepository($localRepository);
$rm->setRepository('Packagist', new Repository\ComposerRepository('http://packagist.org'));
// initialize download manager
$dm = new Downloader\DownloadManager($preferSource = false);
$dm->setDownloader('git', new Downloader\GitDownloader());
//$dm->setDownloader('pear', new Downloader\PearDownloader());
//$dm->setDownloader('zip', new Downloader\ZipDownloader());
// initialize installation manager
$im = new Installer\InstallationManager();
$im->setInstaller('library', new Installer\LibraryInstaller('vendor', $dm, $rm->getLocalRepository()));
// load package
$loader = new Package\Loader\JsonLoader();
$package = $loader->load('composer.json');
// initialize composer // initialize composer
$composer = new Composer(); $composer = new Composer();
$composer->addDownloader('git', new GitDownloader); $composer->setPackage($package);
$composer->addDownloader('pear', new PearDownloader); $composer->setPackageLock(new Package\PackageLock('composer.lock'));
$composer->addDownloader('zip', new ZipDownloader); $composer->setRepositoryManager($rm);
$composer->addInstaller('library', new LibraryInstaller); $composer->setDownloadManager($dm);
$composer->setInstallationManager($im);
// run the command application // run the command application
$application = new Application($composer); $application = new ComposerApplication($composer);
$application->run(); $application->run();

View File

@ -9,7 +9,7 @@
"required": true "required": true
}, },
"type": { "type": {
"description": "Package type, either 'Library', or the parent project it applies to if it's a plugin for a framework or application (e.g. 'Symfony2', 'Typo3', 'Drupal', ..).", "description": "Package type, either 'Library', or the parent project it applies to if it's a plugin for a framework or application (e.g. 'Symfony2', 'Typo3', 'Drupal', ..), note that this has to be defined and communicated by any project implementing a custom composer installer, those are just unreliable examples.",
"type": "string", "type": "string",
"optional": true "optional": true
}, },

View File

@ -13,11 +13,17 @@
namespace Composer\Command; namespace Composer\Command;
use Symfony\Component\Console\Command\Command as BaseCommand; use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Solver;
use Composer\Installer\Operation;
/** /**
* Base class for Composer commands * Base class for Composer commands
* *
* @author Ryan Weaver <ryan@knplabs.com> * @author Ryan Weaver <ryan@knplabs.com>
* @authro Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
abstract class Command extends BaseCommand abstract class Command extends BaseCommand
{ {
@ -28,4 +34,4 @@ abstract class Command extends BaseCommand
{ {
return $this->getApplication()->getComposer(); return $this->getApplication()->getComposer();
} }
} }

View File

@ -12,20 +12,17 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\DependencyResolver;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Operation;
use Composer\DependencyResolver\Solver;
use Composer\Repository\PlatformRepository;
use Composer\Package\MemoryPackage;
use Composer\Package\LinkConstraint\VersionConstraint;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
* @author Ryan Weaver <ryan@knplabs.com> * @author Ryan Weaver <ryan@knplabs.com>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
class InstallCommand extends Command class InstallCommand extends Command
{ {
@ -48,130 +45,53 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
// TODO this needs a parameter to enable installing from source (i.e. git clone, instead of downloading archives) $composer = $this->getComposer();
$sourceInstall = false;
$config = $this->loadConfig(); if ($composer->getPackageLock()->isLocked()) {
$output->writeln('<info>Found lockfile. Reading</info>');
$output->writeln('<info>Loading repositories</info>'); $installationManager = $composer->getInstallationManager();
foreach ($composer->getPackageLock()->getLockedPackages() as $package) {
if (isset($config['repositories'])) { if (!$installationManager->isPackageInstalled($package)) {
foreach ($config['repositories'] as $name => $spec) { $operation = new Operation\InstallOperation($package, 'lock resolving');
$this->getComposer()->addRepository($name, $spec); $installationManager->execute($operation);
}
} }
return 0;
} }
// creating repository pool
$pool = new Pool; $pool = new Pool;
$pool->addRepository($composer->getRepositoryManager()->getLocalRepository());
$repoInstalled = new PlatformRepository; foreach ($composer->getRepositoryManager()->getRepositories() as $repository) {
$pool->addRepository($repoInstalled);
// TODO check the lock file to see what's currently installed
// $repoInstalled->addPackage(new MemoryPackage('$Package', '$Version'));
$output->writeln('Loading package list');
foreach ($this->getComposer()->getRepositories() as $repository) {
$pool->addRepository($repository); $pool->addRepository($repository);
} }
// creating requirements request
$request = new Request($pool); $request = new Request($pool);
foreach ($composer->getPackage()->getRequires() as $link) {
$output->writeln('Building up request'); $request->install($link->getTarget(), $link->getConstraint());
// TODO there should be an update flag or dedicated update command
// TODO check lock file to remove packages that disappeared from the requirements
foreach ($config['require'] as $name => $version) {
if ('latest' === $version) {
$request->install($name);
} else {
preg_match('#^([>=<~]*)([\d.]+.*)$#', $version, $match);
if (!$match[1]) {
$match[1] = '=';
}
$constraint = new VersionConstraint($match[1], $match[2]);
$request->install($name, $constraint);
}
} }
$output->writeln('Solving dependencies'); // prepare solver
$installationManager = $composer->getInstallationManager();
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
$policy = new DependencyResolver\DefaultPolicy();
$solver = new DependencyResolver\Solver($policy, $pool, $localRepo);
$policy = new DefaultPolicy; // solve dependencies and execute operations
$solver = new Solver($policy, $pool, $repoInstalled); foreach ($solver->solve($request) as $operation) {
$transaction = $solver->solve($request); $installationManager->execute($operation);
$lock = array();
foreach ($transaction as $task) {
switch ($task['job']) {
case 'install':
$package = $task['package'];
$output->writeln('> Installing '.$package->getPrettyName());
if ($sourceInstall) {
// TODO
} else {
if ($package->getDistType()) {
$downloaderType = $package->getDistType();
$type = 'dist';
} elseif ($package->getSourceType()) {
$output->writeln('Package '.$package->getPrettyName().' has no dist url, installing from source instead.');
$downloaderType = $package->getSourceType();
$type = 'source';
} else {
throw new \UnexpectedValueException('Package '.$package->getPrettyName().' has no source or dist URL.');
}
$downloader = $this->getComposer()->getDownloader($downloaderType);
$installer = $this->getComposer()->getInstaller($package->getType());
if (!$installer->install($package, $downloader, $type)) {
throw new \LogicException($package->getPrettyName().' could not be installed.');
}
}
$lock[$package->getName()] = array('version' => $package->getVersion());
break;
default:
throw new \UnexpectedValueException('Unhandled job type : '.$task['job']);
}
} }
if (false) {
$composer->getPackageLock()->lock($localRepo->getPackages());
$output->writeln('> Locked');
}
$localRepo->write();
$output->writeln('> Done'); $output->writeln('> Done');
$this->storeLockFile($lock, $output);
} }
}
protected function loadConfig()
{
if (!file_exists('composer.json')) {
throw new \UnexpectedValueException('composer.json config file not found in '.getcwd());
}
$config = json_decode(file_get_contents('composer.json'), true);
if (!$config) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
$msg = 'No error has occurred, is your composer.json file empty?';
break;
case JSON_ERROR_DEPTH:
$msg = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
}
throw new \UnexpectedValueException('Incorrect composer.json file: '.$msg);
}
return $config;
}
protected function storeLockFile(array $content, OutputInterface $output)
{
file_put_contents('composer.lock', json_encode($content, JSON_FORCE_OBJECT)."\n");
$output->writeln('> composer.lock dumped');
}
}

View File

@ -12,125 +12,74 @@
namespace Composer; namespace Composer;
use Composer\Downloader\DownloaderInterface; use Composer\Package\PackageInterface;
use Composer\Installer\InstallerInterface; use Composer\Package\PackageLock;
use Composer\Repository\ComposerRepository; use Composer\Repository\RepositoryManager;
use Composer\Repository\PlatformRepository; use Composer\Installer\InstallationManager;
use Composer\Repository\GitRepository; use Composer\Downloader\DownloadManager;
use Composer\Repository\PearRepository;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
*/ */
class Composer class Composer
{ {
const VERSION = '1.0.0-DEV'; const VERSION = '1.0.0-DEV';
protected $repositories = array(); private $package;
protected $downloaders = array(); private $lock;
protected $installers = array();
public function __construct() private $rm;
private $dm;
private $im;
public function setPackage(PackageInterface $package)
{ {
$this->addRepository('Packagist', array('composer' => 'http://packagist.org')); $this->package = $package;
} }
/** public function getPackage()
* Add downloader for type
*
* @param string $type
* @param DownloaderInterface $downloader
*/
public function addDownloader($type, DownloaderInterface $downloader)
{ {
$type = strtolower($type); return $this->package;
$this->downloaders[$type] = $downloader;
} }
/** public function setPackageLock($lock)
* Get type downloader
*
* @param string $type
*
* @return DownloaderInterface
*/
public function getDownloader($type)
{ {
$type = strtolower($type); $this->lock = $lock;
if (!isset($this->downloaders[$type])) {
throw new \UnexpectedValueException('Unknown source type: '.$type);
}
return $this->downloaders[$type];
} }
/** public function getPackageLock()
* Add installer for type
*
* @param string $type
* @param InstallerInterface $installer
*/
public function addInstaller($type, InstallerInterface $installer)
{ {
$type = strtolower($type); return $this->lock;
$this->installers[$type] = $installer;
} }
/** public function setRepositoryManager(RepositoryManager $manager)
* Get type installer
*
* @param string $type
*
* @return InstallerInterface
*/
public function getInstaller($type)
{ {
$type = strtolower($type); $this->rm = $manager;
if (!isset($this->installers[$type])) {
throw new \UnexpectedValueException('Unknown dependency type: '.$type);
}
return $this->installers[$type];
} }
public function addRepository($name, $spec) public function getRepositoryManager()
{ {
if (null === $spec) { return $this->rm;
unset($this->repositories[$name]);
}
if (is_array($spec) && count($spec) === 1) {
return $this->repositories[$name] = $this->createRepository($name, key($spec), current($spec));
}
throw new \UnexpectedValueException('Invalid repositories specification '.json_encode($spec).', should be: {"type": "url"}');
} }
public function getRepositories() public function setDownloadManager(DownloadManager $manager)
{ {
return $this->repositories; $this->dm = $manager;
} }
public function createRepository($name, $type, $spec) public function getDownloadManager()
{ {
if (is_string($spec)) { return $this->dm;
$spec = array('url' => $spec); }
}
$spec['url'] = rtrim($spec['url'], '/');
switch ($type) { public function setInstallationManager(InstallationManager $manager)
case 'git-bare': {
case 'git-multi': $this->im = $manager;
throw new \Exception($type.' repositories not supported yet'); }
break;
case 'git': public function getInstallationManager()
return new GitRepository($spec['url']); {
return $this->im;
case 'composer':
return new ComposerRepository($spec['url']);
case 'pear':
return new PearRepository($spec['url'], $name);
default:
throw new \UnexpectedValueException('Unknown repository type: '.$type.', could not create repository '.$name);
}
} }
} }

View File

@ -13,11 +13,13 @@
namespace Composer\Console; namespace Composer\Console;
use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Application as BaseApplication;
use Composer\Composer;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Composer\Command\InstallCommand; use Composer\Command\InstallCommand;
use Composer\Composer;
use Composer\Package\PackageInterface;
use Composer\Package\PackageLock;
/** /**
* The console application that handles the commands * The console application that handles the commands
@ -59,10 +61,10 @@ class Application extends BaseApplication
} }
/** /**
* Initializes all the composer commands * Looks for all *Command files in Composer's Command directory
*/ */
protected function registerCommands() protected function registerCommands()
{ {
$this->add(new InstallCommand()); $this->add(new InstallCommand());
} }
} }

View File

@ -0,0 +1,58 @@
<?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\Operation;
use Composer\Package\PackageInterface;
/**
* Solver install operation.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class InstallOperation extends SolverOperation
{
protected $package;
/**
* Initializes operation.
*
* @param PackageInterface $package package instance
* @param string $reason operation reason
*/
public function __construct(PackageInterface $package, $reason = null)
{
parent::__construct($reason);
$this->package = $package;
}
/**
* Returns package instance.
*
* @return PackageInterface
*/
public function getPackage()
{
return $this->package;
}
/**
* Returns job type.
*
* @return string
*/
public function getJobType()
{
return 'install';
}
}

View File

@ -0,0 +1,37 @@
<?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\Operation;
use Composer\Package\PackageInterface;
/**
* Solver operation interface.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OperationInterface
{
/**
* Returns job type.
*
* @return string
*/
function getJobType();
/**
* Returns operation reason.
*
* @return string
*/
function getReason();
}

View File

@ -0,0 +1,45 @@
<?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\Operation;
use Composer\Package\PackageInterface;
/**
* Abstract solver operation class.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
abstract class SolverOperation implements OperationInterface
{
protected $reason;
/**
* Initializes operation.
*
* @param string $reason operation reason
*/
public function __construct($reason = null)
{
$this->reason = $reason;
}
/**
* Returns operation reason.
*
* @return string
*/
public function getReason()
{
return $this->reason;
}
}

View File

@ -0,0 +1,58 @@
<?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\Operation;
use Composer\Package\PackageInterface;
/**
* Solver uninstall operation.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class UninstallOperation extends SolverOperation
{
protected $package;
/**
* Initializes operation.
*
* @param PackageInterface $package package instance
* @param string $reason operation reason
*/
public function __construct(PackageInterface $package, $reason = null)
{
parent::__construct($reason);
$this->package = $package;
}
/**
* Returns package instance.
*
* @return PackageInterface
*/
public function getPackage()
{
return $this->package;
}
/**
* Returns job type.
*
* @return string
*/
public function getJobType()
{
return 'uninstall';
}
}

View File

@ -0,0 +1,71 @@
<?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\Operation;
use Composer\Package\PackageInterface;
/**
* Solver update operation.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class UpdateOperation extends SolverOperation
{
protected $initialPackage;
protected $targetPackage;
/**
* Initializes update operation.
*
* @param PackageInterface $initial initial package
* @param PackageInterface $target target package (updated)
* @param string $reason update reason
*/
public function __construct(PackageInterface $initial, PackageInterface $target, $reason = null)
{
parent::__construct($reason);
$this->initialPackage = $initial;
$this->targetPackage = $target;
}
/**
* Returns initial package.
*
* @return PackageInterface
*/
public function getInitialPackage()
{
return $this->initialPackage;
}
/**
* Returns target package.
*
* @return PackageInterface
*/
public function getTargetPackage()
{
return $this->targetPackage;
}
/**
* Returns job type.
*
* @return string
*/
public function getJobType()
{
return 'update';
}
}

View File

@ -14,6 +14,7 @@ namespace Composer\DependencyResolver;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\DependencyResolver\Operation;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -49,6 +50,7 @@ class Solver
protected $watches = array(); protected $watches = array();
protected $removeWatches = array(); protected $removeWatches = array();
protected $decisionMap; protected $decisionMap;
protected $installedPackageMap;
protected $packageToUpdateRule = array(); protected $packageToUpdateRule = array();
protected $packageToFeatureRule = array(); protected $packageToFeatureRule = array();
@ -251,7 +253,7 @@ class Solver
$this->addedMap[$package->getId()] = true; $this->addedMap[$package->getId()] = true;
$dontFix = 0; $dontFix = 0;
if ($this->installed === $package->getRepository() && !isset($this->fixMap[$package->getId()])) { if (isset($this->installedPackageMap[$package->getId()]) && !isset($this->fixMap[$package->getId()])) {
$dontFix = 1; $dontFix = 1;
} }
@ -270,7 +272,7 @@ class Solver
if ($dontFix) { if ($dontFix) {
$foundInstalled = false; $foundInstalled = false;
foreach ($possibleRequires as $require) { foreach ($possibleRequires as $require) {
if ($this->installed === $require->getRepository()) { if (isset($this->installedPackageMap[$require->getId()])) {
$foundInstalled = true; $foundInstalled = true;
break; break;
} }
@ -293,7 +295,7 @@ class Solver
$possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($possibleConflicts as $conflict) { foreach ($possibleConflicts as $conflict) {
if ($dontFix && $this->installed === $conflict->getRepository()) { if ($dontFix && isset($this->installedPackageMap[$conflict->getId()])) {
continue; continue;
} }
@ -308,7 +310,7 @@ class Solver
/** @TODO: if ($this->noInstalledObsoletes) */ /** @TODO: if ($this->noInstalledObsoletes) */
if (true) { if (true) {
$noObsoletes = isset($this->noObsoletes[$package->getId()]); $noObsoletes = isset($this->noObsoletes[$package->getId()]);
$isInstalled = ($this->installed === $package->getRepository()); $isInstalled = (isset($this->installedPackageMap[$package->getId()]));
foreach ($package->getReplaces() as $link) { foreach ($package->getReplaces() as $link) {
$obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
@ -757,7 +759,7 @@ class Solver
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': case 'install':
foreach ($job['packages'] as $package) { foreach ($job['packages'] as $package) {
if ($this->installed === $package->getRepository()) { if (isset($this->installedPackageMap[$package->getId()])) {
$disableQueue[] = array('type' => 'update', 'package' => $package); $disableQueue[] = array('type' => 'update', 'package' => $package);
} }
@ -870,7 +872,7 @@ class Solver
case 'remove': case 'remove':
foreach ($job['packages'] as $package) { foreach ($job['packages'] as $package) {
if ($this->installed === $package->getRepository()) { if (isset($this->installedPackageMap[$package->getId()])) {
$disableQueue[] = array('type' => 'update', 'package' => $package); $disableQueue[] = array('type' => 'update', 'package' => $package);
} }
} }
@ -932,6 +934,10 @@ class Solver
{ {
$this->jobs = $request->getJobs(); $this->jobs = $request->getJobs();
$installedPackages = $this->installed->getPackages(); $installedPackages = $this->installed->getPackages();
$this->installedPackageMap = array();
foreach ($installedPackages as $package) {
$this->installedPackageMap[$package->getId()] = $package;
}
$this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1); $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1);
@ -953,12 +959,12 @@ class Solver
foreach ($job['packages'] as $package) { foreach ($job['packages'] as $package) {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'fix': case 'fix':
if ($this->installed === $package->getRepository()) { if (isset($this->installedPackageMap[$package->getId()])) {
$this->fixMap[$package->getId()] = true; $this->fixMap[$package->getId()] = true;
} }
break; break;
case 'update': case 'update':
if ($this->installed === $package->getRepository()) { if (isset($this->installedPackageMap[$package->getId()])) {
$this->updateMap[$package->getId()] = true; $this->updateMap[$package->getId()] = true;
} }
break; break;
@ -1038,7 +1044,7 @@ class Solver
break; break;
case 'lock': case 'lock':
foreach ($job['packages'] as $package) { foreach ($job['packages'] as $package) {
if ($this->installed === $package->getRepository()) { if (isset($this->installedPackageMap[$package->getId()])) {
$rule = $this->createInstallRule($package, self::RULE_JOB_LOCK); $rule = $this->createInstallRule($package, self::RULE_JOB_LOCK);
} else { } else {
$rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK); $rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK);
@ -1082,7 +1088,7 @@ class Solver
$package = $literal->getPackage(); $package = $literal->getPackage();
// !wanted & installed // !wanted & installed
if (!$literal->isWanted() && $this->installed === $package->getRepository()) { if (!$literal->isWanted() && isset($this->installedPackageMap[$package->getId()])) {
$updateRule = $this->packageToUpdateRule[$package->getId()]; $updateRule = $this->packageToUpdateRule[$package->getId()];
foreach ($updateRule->getLiterals() as $updateLiteral) { foreach ($updateRule->getLiterals() as $updateLiteral) {
@ -1097,7 +1103,7 @@ class Solver
$package = $literal->getPackage(); $package = $literal->getPackage();
// wanted & installed || !wanted & !installed // wanted & installed || !wanted & !installed
if ($literal->isWanted() == ($this->installed === $package->getRepository())) { if ($literal->isWanted() == (isset($this->installedPackageMap[$package->getId()]))) {
continue; continue;
} }
@ -1105,28 +1111,21 @@ class Solver
if (isset($installMeansUpdateMap[$literal->getPackageId()])) { if (isset($installMeansUpdateMap[$literal->getPackageId()])) {
$source = $installMeansUpdateMap[$literal->getPackageId()]; $source = $installMeansUpdateMap[$literal->getPackageId()];
$transaction[] = array( $transaction[] = new Operation\UpdateOperation(
'job' => 'update', $source, $package, $this->decisionQueueWhy[$i]
'from' => $source,
'to' => $package,
'why' => $this->decisionQueueWhy[$i],
); );
// avoid updates to one package from multiple origins // avoid updates to one package from multiple origins
unset($installMeansUpdateMap[$literal->getPackageId()]); unset($installMeansUpdateMap[$literal->getPackageId()]);
$ignoreRemove[$source->getId()] = true; $ignoreRemove[$source->getId()] = true;
} else { } else {
$transaction[] = array( $transaction[] = new Operation\InstallOperation(
'job' => 'install', $package, $this->decisionQueueWhy[$i]
'package' => $package,
'why' => $this->decisionQueueWhy[$i],
); );
} }
} else if (!isset($ignoreRemove[$package->getId()])) { } else if (!isset($ignoreRemove[$package->getId()])) {
$transaction[] = array( $transaction[] = new Operation\UninstallOperation(
'job' => 'remove', $package, $this->decisionQueueWhy[$i]
'package' => $package,
'why' => $this->decisionQueueWhy[$i],
); );
} }
} }
@ -2060,4 +2059,4 @@ class Solver
} }
echo "\n"; echo "\n";
} }
} }

View File

@ -0,0 +1,171 @@
<?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\Downloader;
use Composer\Package\PackageInterface;
use Composer\Downloader\DownloaderInterface;
/**
* Downloaders manager.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class DownloadManager
{
private $preferSource = false;
private $downloaders = array();
/**
* Initializes download manager.
*
* @param Boolean $preferSource prefer downloading from source
*/
public function __construct($preferSource = false)
{
$this->preferSource = $preferSource;
}
/**
* Makes downloader prefer source installation over the dist.
*
* @param Boolean $preferSource prefer downloading from source
*/
public function preferSource($preferSource = true)
{
$this->preferSource = $preferSource;
}
/**
* Sets installer downloader for a specific installation type.
*
* @param string $type installation type
* @param DownloaderInterface $downloader downloader instance
*/
public function setDownloader($type, DownloaderInterface $downloader)
{
$this->downloaders[$type] = $downloader;
}
/**
* Returns downloader for a specific installation type.
*
* @param string $type installation type
*
* @return DownloaderInterface
*
* @throws UnexpectedValueException if downloader for provided type is not registeterd
*/
public function getDownloader($type)
{
if (!isset($this->downloaders[$type])) {
throw new \UnexpectedValueException('Unknown source type: '.$type);
}
return $this->downloaders[$type];
}
/**
* Downloads package into target dir.
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* @param Boolean $preferSource prefer installation from source
*
* @return string downloader type (source/dist)
*
* @throws InvalidArgumentException if package have no urls to download from
*/
public function download(PackageInterface $package, $targetDir, $preferSource = null)
{
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
$sourceType = $package->getSourceType();
$distType = $package->getDistType();
if (!($preferSource && $sourceType) && $distType) {
$downloader = $this->getDownloader($distType);
$downloader->download(
$package, $targetDir,
$package->getDistUrl(), $package->getDistSha1Checksum(),
$preferSource
);
$package->setInstallationSource('dist');
} elseif ($sourceType) {
$downloader = $this->getDownloader($sourceType);
$downloader->download($package, $targetDir, $package->getSourceUrl(), $preferSource);
$package->setInstallationSource('source');
} else {
throw new \InvalidArgumentException('Package should have dist or source specified');
}
}
/**
* Updates package from initial to target version.
*
* @param PackageInterface $initial initial package version
* @param PackageInterface $target target package version
* @param string $targetDir target dir
*
* @throws InvalidArgumentException if initial package is not installed
*/
public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
{
if (null === $installationType = $initial->getInstallationSource()) {
throw new \InvalidArgumentException(
'Package '.$initial.' was not been installed propertly and can not be updated'
);
}
$useSource = 'source' === $installationType;
if (!$useSource) {
$initialType = $initial->getDistType();
$targetType = $target->getDistType();
} else {
$initialType = $initial->getSourceType();
$targetType = $target->getSourceType();
}
$downloader = $this->getDownloader($initialType);
if ($initialType === $targetType) {
$downloader->update($initial, $target, $targetDir, $useSource);
} else {
$downloader->remove($initial, $targetDir, $useSource);
$this->download($target, $targetDir, $useSource);
}
}
/**
* Removes package from target dir.
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
*/
public function remove(PackageInterface $package, $targetDir)
{
if (null === $installationType = $package->getInstallationSource()) {
throw new \InvalidArgumentException(
'Package '.$package.' was not been installed propertly and can not be removed'
);
}
$useSource = 'source' === $installationType;
// get proper downloader
if (!$useSource) {
$downloader = $this->getDownloader($package->getDistType());
} else {
$downloader = $this->getDownloader($package->getSourceType());
}
$downloader->remove($package, $targetDir, $useSource);
}
}

View File

@ -15,21 +15,39 @@ namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
/** /**
* Package Downloader * Downloader interface.
* *
* @author Kirill chEbba Chebunin <iam@chebba.org> * @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
interface DownloaderInterface interface DownloaderInterface
{ {
/** /**
* Download package * Downloads specific package into specific folder.
* *
* @param PackageInterface $package Downloaded package * @param PackageInterface $package package instance
* @param string $path Download to * @param string $path download path
* @param string $url Download from * @param string $url download url
* @param string|null $checksum Package checksum * @param string $checksum package checksum (for dists)
* * @param Boolean $useSource download as source
* @throws \UnexpectedValueException
*/ */
function download(PackageInterface $package, $path, $url, $checksum = null); function download(PackageInterface $package, $path, $url, $checksum = null, $useSource = false);
/**
* Updates specific package in specific folder from initial to target version.
*
* @param PackageInterface $initial initial package
* @param PackageInterface $target updated package
* @param string $path download path
* @param Boolean $useSource download as source
*/
function update(PackageInterface $initial, PackageInterface $target, $path, $useSource = false);
/**
* Removes specific package from specific folder.
*
* @param PackageInterface $package package instance
* @param string $path download path
* @param Boolean $useSource download as source
*/
function remove(PackageInterface $package, $path, $useSource = false);
} }

View File

@ -19,27 +19,33 @@ use Composer\Package\PackageInterface;
*/ */
class GitDownloader implements DownloaderInterface class GitDownloader implements DownloaderInterface
{ {
protected $clone; /**
* {@inheritDoc}
public function __construct($clone = true) */
public function download(PackageInterface $package, $path, $url, $checksum = null, $useSource = false)
{ {
$this->clone = $clone; system('git clone '.escapeshellarg($url).' -b master '.escapeshellarg($path));
// TODO non-source installs:
// system('git archive --format=tar --prefix='.escapeshellarg($package->getName()).' --remote='.escapeshellarg($url).' master | tar -xf -');
} }
public function download(PackageInterface $package, $path, $url, $checksum = null) /**
* {@inheritDoc}
*/
public function update(PackageInterface $initial, PackageInterface $target, $path, $useSource = false)
{ {
if (!is_dir($path)) { $cwd = getcwd();
if (file_exists($path)) { chdir($path);
throw new \UnexpectedValueException($path.' exists and is not a directory.'); system('git pull');
} chdir($cwd);
if (!mkdir($path, 0777, true)) {
throw new \UnexpectedValueException($path.' does not exist and could not be created.');
}
}
if ($this->clone) {
system('git clone '.escapeshellarg($url).' -b master '.escapeshellarg($path.'/'.$package->getName()));
} else {
system('git archive --format=tar --prefix='.escapeshellarg($package->getName()).' --remote='.escapeshellarg($url).' master | tar -xf -');
}
} }
}
/**
* {@inheritDoc}
*/
public function remove(PackageInterface $package, $path, $useSource = false)
{
echo 'rm -rf '.$path; // TODO
}
}

View File

@ -66,4 +66,4 @@ class PearDownloader implements DownloaderInterface
} }
chdir($cwd); chdir($cwd);
} }
} }

View File

@ -73,4 +73,4 @@ class ZipDownloader implements DownloaderInterface
throw new \UnexpectedValueException($zipName.' is not a valid zip archive, got error code '.$retval); throw new \UnexpectedValueException($zipName.' is not a valid zip archive, got error code '.$retval);
} }
} }
} }

View File

@ -0,0 +1,131 @@
<?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\Installer;
use Composer\Package\PackageInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
/**
* Package operation manager.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class InstallationManager
{
private $installers = array();
/**
* Sets installer for a specific package type.
*
* @param string $type package type (library f.e.)
* @param InstallerInterface $installer installer instance
*/
public function setInstaller($type, InstallerInterface $installer)
{
$this->installers[$type] = $installer;
}
/**
* Returns installer for a specific package type.
*
* @param string $type package type
*
* @return InstallerInterface
*
* @throws InvalidArgumentException if installer for provided type is not registered
*/
public function getInstaller($type)
{
if (!isset($this->installers[$type])) {
throw new \InvalidArgumentException('Unknown installer type: '.$type);
}
return $this->installers[$type];
}
/**
* Checks whether provided package is installed in one of the registered installers.
*
* @param PackageInterface $package package instance
*
* @return Boolean
*/
public function isPackageInstalled(PackageInterface $package)
{
foreach ($this->installers as $installer) {
if ($installer->isInstalled($package)) {
return true;
}
}
return false;
}
/**
* Executes solver operation.
*
* @param OperationInterface $operation operation instance
*/
public function execute(OperationInterface $operation)
{
$method = $operation->getJobType();
$this->$method($operation);
}
/**
* Executes install operation.
*
* @param InstallOperation $operation operation instance
*/
public function install(InstallOperation $operation)
{
$installer = $this->getInstaller($operation->getPackage()->getType());
$installer->install($operation->getPackage());
}
/**
* Executes update operation.
*
* @param InstallOperation $operation operation instance
*/
public function update(UpdateOperation $operation)
{
$initial = $operation->getInitialPackage();
$target = $operation->getTargetPackage();
$initialType = $initial->getType();
$targetType = $target->getType();
if ($initialType === $targetType) {
$installer = $this->getInstaller($initialType);
$installer->update($initial, $target);
} else {
$this->getInstaller($initialType)->uninstall($initial);
$this->getInstaller($targetType)->install($target);
}
}
/**
* Uninstalls package.
*
* @param UninstallOperation $operation operation instance
*/
public function uninstall(UninstallOperation $operation)
{
$installer = $this->getInstaller($operation->getPackage()->getType());
$installer->uninstall($operation->getPackage());
}
}

View File

@ -12,22 +12,46 @@
namespace Composer\Installer; namespace Composer\Installer;
use Composer\Downloader\DownloaderInterface; use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
/** /**
* Package Installer * Interface for the package installation manager.
* *
* @author Kirill chEbba Chebunin <iam@chebba.org> * @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
interface InstallerInterface interface InstallerInterface
{ {
/** /**
* Install package * Checks that provided package is installed.
* *
* @param PackageInterface $package * @param PackageInterface $package package instance
* @param DownloaderInterface $downloader *
* @param string $type * @return Boolean
*/ */
function install(PackageInterface $package, DownloaderInterface $downloader, $type); function isInstalled(PackageInterface $package);
/**
* Installs specific package.
*
* @param PackageInterface $package package instance
*/
function install(PackageInterface $package);
/**
* Updates specific package.
*
* @param PackageInterface $initial already installed package version
* @param PackageInterface $target updated version
*
* @throws InvalidArgumentException if $from package is not installed
*/
function update(PackageInterface $initial, PackageInterface $target);
/**
* Uninstalls specific package.
*
* @param PackageInterface $package package instance
*/
function uninstall(PackageInterface $package);
} }

View File

@ -12,30 +12,115 @@
namespace Composer\Installer; namespace Composer\Installer;
use Composer\Downloader\DownloaderInterface; use Composer\Downloader\DownloadManager;
use Composer\Repository\WritableRepositoryInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
/** /**
* Package installation manager.
*
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
class LibraryInstaller implements InstallerInterface class LibraryInstaller implements InstallerInterface
{ {
protected $dir; private $dir;
private $dm;
private $repository;
public function __construct($dir = 'vendor') /**
* Initializes library installer.
*
* @param string $dir relative path for packages home
* @param DownloadManager $dm download manager
* @param WritableRepositoryInterface $repository repository controller
*/
public function __construct($dir, DownloadManager $dm, WritableRepositoryInterface $repository)
{ {
$this->dir = $dir; $this->dir = $dir;
$this->dm = $dm;
if (!is_dir($this->dir)) {
if (file_exists($this->dir)) {
throw new \UnexpectedValueException(
$this->dir.' exists and is not a directory.'
);
}
if (!mkdir($this->dir, 0777, true)) {
throw new \UnexpectedValueException(
$this->dir.' does not exist and could not be created.'
);
}
}
$this->repository = $repository;
} }
public function install(PackageInterface $package, DownloaderInterface $downloader, $type) /**
* Checks that specific package is installed.
*
* @param PackageInterface $package package instance
*
* @return Boolean
*/
public function isInstalled(PackageInterface $package)
{ {
if ($type === 'dist') { return $this->repository->hasPackage($package);
$downloader->download($package, $this->dir, $package->getDistUrl(), $package->getDistSha1Checksum());
} elseif ($type === 'source') {
$downloader->download($package, $this->dir, $package->getSourceUrl());
} else {
throw new \InvalidArgumentException('Type must be one of (dist, source), '.$type.' given.');
}
return true;
} }
}
/**
* Installs specific package.
*
* @param PackageInterface $package package instance
*
* @throws InvalidArgumentException if provided package have no urls to download from
*/
public function install(PackageInterface $package)
{
$downloadPath = $this->dir.DIRECTORY_SEPARATOR.$package->getName();
$this->dm->download($package, $downloadPath);
$this->repository->addPackage($package);
}
/**
* Updates specific package.
*
* @param PackageInterface $initial already installed package version
* @param PackageInterface $target updated version
*
* @throws InvalidArgumentException if $from package is not installed
*/
public function update(PackageInterface $initial, PackageInterface $target)
{
if (!$this->repository->hasPackage($initial)) {
throw new \InvalidArgumentException('Package is not installed: '.$initial);
}
$downloadPath = $this->dir.DIRECTORY_SEPARATOR.$initial->getName();
$this->dm->update($initial, $target, $downloadPath);
$this->repository->removePackage($initial);
$this->repository->addPackage($target);
}
/**
* Uninstalls specific package.
*
* @param PackageInterface $package package instance
*
* @throws InvalidArgumentException if package is not installed
*/
public function uninstall(PackageInterface $package)
{
if (!$this->repository->hasPackage($package)) {
throw new \InvalidArgumentException('Package is not installed: '.$package);
}
$downloadPath = $this->dir.DIRECTORY_SEPARATOR.$package->getName();
$this->dm->remove($package, $downloadPath);
$this->repository->removePackage($package);
}
}

View File

@ -134,6 +134,16 @@ abstract class BasePackage implements PackageInterface
$this->repository = $repository; $this->repository = $repository;
} }
/**
* Returns package unique name, constructed from name, version and release type.
*
* @return string
*/
public function getUniqueName()
{
return $this->getName().'-'.$this->getVersion().'-'.$this->getReleaseType();
}
/** /**
* Converts the package into a readable and unique string * Converts the package into a readable and unique string
* *
@ -141,27 +151,6 @@ abstract class BasePackage implements PackageInterface
*/ */
public function __toString() public function __toString()
{ {
return $this->getName().'-'.$this->getVersion().'-'.$this->getReleaseType(); return $this->getUniqueName();
}
/**
* Parses a version string and returns an array with the version, its type (alpha, beta, RC, stable) and a dev flag (for development branches tracking)
*
* @param string $version
* @return array
*/
public static function parseVersion($version)
{
if (!preg_match('#^v?(\d+)(\.\d+)?(\.\d+)?-?((?:beta|RC|alpha)\d*)?-?(dev)?$#i', $version, $matches)) {
throw new \UnexpectedValueException('Invalid version string '.$version);
}
return array(
'version' => $matches[1]
.(!empty($matches[2]) ? $matches[2] : '.0')
.(!empty($matches[3]) ? $matches[3] : '.0'),
'type' => !empty($matches[4]) ? strtolower($matches[4]) : 'stable',
'dev' => !empty($matches[5]),
);
} }
} }

View File

@ -0,0 +1,58 @@
<?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\Dumper;
use Composer\Package\PackageInterface;
/**
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
*/
class ArrayDumper
{
public function dump(PackageInterface $package)
{
$keys = array(
'type',
'names',
'extra',
'installationSource',
'sourceType',
'sourceUrl',
'distType',
'distUrl',
'distSha1Checksum',
'releaseType',
'version',
'license',
'requires',
'conflicts',
'provides',
'replaces',
'recommends',
'suggests'
);
$data = array();
$data['name'] = $package->getPrettyName();
foreach ($keys as $key) {
$getter = 'get'.ucfirst($key);
$value = $package->$getter();
if (null !== $value && !(is_array($value) && 0 === count($value))) {
$data[$key] = $value;
}
}
return $data;
}
}

View File

@ -0,0 +1,114 @@
<?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;
/**
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
*/
class ArrayLoader
{
protected $supportedLinkTypes = array(
'require' => 'requires',
'conflict' => 'conflicts',
'provide' => 'provides',
'replace' => 'replaces',
'recommend' => 'recommends',
'suggest' => 'suggests',
);
public function load($config)
{
$this->validateConfig($config);
$versionParser = new Package\Version\VersionParser();
$version = $versionParser->parse($config['version']);
$package = new Package\MemoryPackage($config['name'], $version['version'], $version['type']);
$package->setType(isset($config['type']) ? $config['type'] : 'library');
if (isset($config['extra'])) {
$package->setExtra($config['extra']);
}
if (isset($config['license'])) {
$package->setLicense($config['license']);
}
if (isset($config['source'])) {
if (!isset($config['source']['type']) || !isset($config['source']['url'])) {
throw new \UnexpectedValueException(sprintf(
"package source should be specified as {\"type\": ..., \"url\": ...},\n%s given",
json_encode($config['source'])
));
}
$package->setSourceType($config['source']['type']);
$package->setSourceUrl($config['source']['url']);
}
if (isset($config['dist'])) {
if (!isset($config['dist']['type'])
|| !isset($config['dist']['url'])
|| !isset($config['dist']['shasum'])) {
throw new \UnexpectedValueException(sprintf(
"package dist should be specified as ".
"{\"type\": ..., \"url\": ..., \"shasum\": ...},\n%s given",
json_encode($config['source'])
));
}
$package->setDistType($config['dist']['type']);
$package->setDistUrl($config['dist']['url']);
$package->setDistSha1Checksum($config['dist']['shasum']);
}
foreach ($this->supportedLinkTypes as $type => $description) {
if (isset($config[$type])) {
$method = 'set'.ucfirst($description);
$package->{$method}(
$this->loadLinksFromConfig($package->getName(), $description, $config['require'])
);
}
}
return $package;
}
private function loadLinksFromConfig($srcPackageName, $description, array $linksSpecs)
{
$links = array();
foreach ($linksSpecs as $packageName => $version) {
$name = strtolower($packageName);
preg_match('#^([>=<~]*)([\d.]+.*)$#', $version, $match);
if (!$match[1]) {
$match[1] = '=';
}
$constraint = new Package\LinkConstraint\VersionConstraint($match[1], $match[2]);
$links[] = new Package\Link($srcPackageName, $packageName, $constraint, $description);
}
return $links;
}
private function validateConfig(array $config)
{
if (!isset($config['name'])) {
throw new \UnexpectedValueException('name is required for package');
}
if (!isset($config['version'])) {
throw new \UnexpectedValueException('version is required for package');
}
}
}

View File

@ -0,0 +1,60 @@
<?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;
/**
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
*/
class JsonLoader extends ArrayLoader
{
public function load($json)
{
$config = $this->loadJsonConfig($json);
return parent::load($config);
}
private function loadJsonConfig($json)
{
if (is_file($json)) {
$json = file_get_contents($json);
}
$config = json_decode($json, true);
if (!$config) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
$msg = 'No error has occurred, is your composer.json file empty?';
break;
case JSON_ERROR_DEPTH:
$msg = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
}
throw new \UnexpectedValueException('Incorrect composer.json file: '.$msg);
}
return $config;
}
}

View File

@ -20,6 +20,7 @@ namespace Composer\Package;
class MemoryPackage extends BasePackage class MemoryPackage extends BasePackage
{ {
protected $type; protected $type;
protected $installationSource;
protected $sourceType; protected $sourceType;
protected $sourceUrl; protected $sourceUrl;
protected $distType; protected $distType;
@ -84,6 +85,22 @@ class MemoryPackage extends BasePackage
return $this->extra; return $this->extra;
} }
/**
* {@inheritDoc}
*/
public function setInstallationSource($type)
{
$this-> installationSource = $type;
}
/**
* {@inheritDoc}
*/
public function getInstallationSource()
{
return $this->installationSource;
}
/** /**
* @param string $type * @param string $type
*/ */

View File

@ -82,6 +82,20 @@ interface PackageInterface
*/ */
function getExtra(); function getExtra();
/**
* Sets source from which this package was installed (source/dist).
*
* @param string $type source/dist
*/
function setInstallationSource($type);
/**
* Returns source from which this package was installed (source/dist).
*
* @param string $type source/dist
*/
function getInstallationSource();
/** /**
* Returns the repository type of this package, e.g. git, svn * Returns the repository type of this package, e.g. git, svn
* *
@ -202,6 +216,13 @@ interface PackageInterface
*/ */
function getRepository(); function getRepository();
/**
* Returns package unique name, constructed from name, version and release type.
*
* @return string
*/
function getUniqueName();
/** /**
* Converts the package into a readable and unique string * Converts the package into a readable and unique string
* *

View File

@ -0,0 +1,91 @@
<?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;
use Composer\Package\MemoryPackage;
use Composer\Package\Version\VersionParser;
/**
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
*/
class PackageLock
{
private $file;
private $isLocked = false;
public function __construct($file = 'composer.lock')
{
if (file_exists($file)) {
$this->file = $file;
$this->isLocked = true;
}
}
public function isLocked()
{
return $this->isLocked;
}
public function getLockedPackages()
{
$lockList = $this->loadJsonConfig($this->file);
$versionParser = new VersionParser();
$packages = array();
foreach ($lockList as $info) {
$version = $versionParser->parse($info['version']);
$packages[] = new MemoryPackage($info['package'], $version['version'], $version['type']);
}
return $packages;
}
public function lock(array $packages)
{
// TODO: write installed packages info into $this->file
}
private function loadJsonConfig($json)
{
if (is_file($json)) {
$json = file_get_contents($json);
}
$config = json_decode($json, true);
if (!$config) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
$msg = 'No error has occurred, is your composer.json file empty?';
break;
case JSON_ERROR_DEPTH:
$msg = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
}
throw new \UnexpectedValueException('Incorrect composer.json file: '.$msg);
}
return $config;
}
}

View File

@ -0,0 +1,43 @@
<?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\Version;
/**
* Version parser
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Nils Adermann <naderman@naderman.de>
*/
class VersionParser
{
/**
* Parses a version string and returns an array with the version, its type (alpha, beta, RC, stable) and a dev flag (for development branches tracking)
*
* @param string $version
* @return array
*/
public function parse($version)
{
if (!preg_match('#^v?(\d+)(\.\d+)?(\.\d+)?-?((?:beta|RC|alpha)\d*)?-?(dev)?$#i', $version, $matches)) {
throw new \UnexpectedValueException('Invalid version string '.$version);
}
return array(
'version' => $matches[1]
.(!empty($matches[2]) ? $matches[2] : '.0')
.(!empty($matches[3]) ? $matches[3] : '.0'),
'type' => strtolower(!empty($matches[4]) ? $matches[4] : 'stable'),
'dev' => !empty($matches[5]),
);
}
}

View File

@ -23,6 +23,26 @@ class ArrayRepository implements RepositoryInterface
{ {
protected $packages; protected $packages;
/**
* Checks if specified package in this repository.
*
* @param PackageInterface $package package instance
*
* @return Boolean
*/
public function hasPackage(PackageInterface $package)
{
$packageId = $package->getUniqueName();
foreach ($this->getPackages() as $repoPackage) {
if ($packageId === $repoPackage->getUniqueName()) {
return true;
}
}
return false;
}
/** /**
* Adds a new package to the repository * Adds a new package to the repository
* *
@ -37,6 +57,24 @@ class ArrayRepository implements RepositoryInterface
$this->packages[] = $package; $this->packages[] = $package;
} }
/**
* Removes package from repository.
*
* @param PackageInterface $package package instance
*/
public function removePackage(PackageInterface $package)
{
$packageId = $package->getUniqueName();
foreach ($this->getPackages() as $key => $repoPackage) {
if ($packageId === $repoPackage->getUniqueName()) {
array_splice($this->packages, $key, 1);
return;
}
}
}
/** /**
* Returns all contained packages * Returns all contained packages
* *

View File

@ -12,9 +12,7 @@
namespace Composer\Repository; namespace Composer\Repository;
use Composer\Package\MemoryPackage; use Composer\Package\Loader\ArrayLoader;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
/** /**
@ -22,10 +20,16 @@ use Composer\Package\LinkConstraint\VersionConstraint;
*/ */
class ComposerRepository extends ArrayRepository class ComposerRepository extends ArrayRepository
{ {
protected $url;
protected $packages; protected $packages;
public function __construct($url) public function __construct($url)
{ {
$url = rtrim($url, '/');
if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$url);
}
$this->url = $url; $this->url = $url;
} }
@ -42,60 +46,11 @@ class ComposerRepository extends ArrayRepository
} }
} }
protected function createPackages($data) private function createPackages($data)
{ {
foreach ($data['versions'] as $rev) { foreach ($data['versions'] as $rev) {
$version = BasePackage::parseVersion($rev['version']); $loader = new ArrayLoader();
$this->addPackage($loader->load($rev));
$package = new MemoryPackage($rev['name'], $version['version'], $version['type']);
$package->setSourceType($rev['source']['type']);
$package->setSourceUrl($rev['source']['url']);
$package->setDistType($rev['dist']['type']);
$package->setDistUrl($rev['dist']['url']);
$package->setDistSha1Checksum($rev['dist']['shasum']);
if (isset($rev['type'])) {
$package->setType($rev['type']);
}
if (isset($rev['extra'])) {
$package->setExtra($rev['extra']);
}
if (isset($rev['license'])) {
$package->setLicense($rev['license']);
}
$links = array(
'require',
'conflict',
'provide',
'replace',
'recommend',
'suggest',
);
foreach ($links as $link) {
if (isset($rev[$link])) {
$method = 'set'.$link.'s';
$package->{$method}($this->createLinks($rev['name'], $link.'s', $rev[$link]));
}
}
$this->addPackage($package);
} }
} }
}
protected function createLinks($name, $description, $linkSpecs)
{
$links = array();
foreach ($linkSpecs as $dep => $ver) {
preg_match('#^([>=<~]*)([\d.]+.*)$#', $ver, $match);
if (!$match[1]) {
$match[1] = '=';
}
$constraint = new VersionConstraint($match[1], $match[2]);
$links[] = new Link($name, $dep, $constraint, $description);
}
return $links;
}
}

View File

@ -0,0 +1,82 @@
<?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\Repository;
use Composer\Package\PackageInterface;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
/**
* Filesystem repository.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class FilesystemRepository extends ArrayRepository implements WritableRepositoryInterface
{
private $file;
/**
* Initializes filesystem repository.
*
* @param string $group registry (installer) group
*/
public function __construct($repositoryFile)
{
$this->file = $repositoryFile;
$path = dirname($this->file);
if (!is_dir($path)) {
if (file_exists($path)) {
throw new \UnexpectedValueException(
$path.' exists and is not a directory.'
);
}
if (!mkdir($path, 0777, true)) {
throw new \UnexpectedValueException(
$path.' does not exist and could not be created.'
);
}
}
}
/**
* Initializes repository (reads file, or remote address).
*/
protected function initialize()
{
parent::initialize();
$packages = @json_decode(file_get_contents($this->file), true);
if (is_array($packages)) {
$loader = new ArrayLoader();
foreach ($packages as $package) {
$this->addPackage($loader->load($package));
}
}
}
/**
* Writes writable repository.
*/
public function write()
{
$packages = array();
$dumper = new ArrayDumper();
foreach ($this->getPackages() as $package) {
$packages[] = $dumper->dump($package);
}
file_put_contents($this->file, json_encode($packages));
}
}

View File

@ -24,11 +24,13 @@ use Composer\Package\LinkConstraint\VersionConstraint;
*/ */
class GitRepository extends ArrayRepository class GitRepository extends ArrayRepository
{ {
protected $packages; protected $url;
protected $cacheDir;
public function __construct($url) public function __construct($url, $cacheDir)
{ {
$this->url = $url; $this->url = $url;
$this->cacheDir = $cacheDir;
} }
protected function initialize() protected function initialize()
@ -98,4 +100,4 @@ class GitRepository extends ArrayRepository
} }
return $links; return $links;
} }
} }

View File

@ -23,16 +23,17 @@ use Composer\Package\LinkConstraint\VersionConstraint;
*/ */
class PearRepository extends ArrayRepository class PearRepository extends ArrayRepository
{ {
private $name; protected $url;
private $url; protected $cacheDir;
public function __construct($url, $name = '') public function __construct($url, $cacheDir)
{ {
if (!filter_var($url, FILTER_VALIDATE_URL)) { if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new \UnexpectedValueException('Invalid url given for PEAR repository "'.$name.'": '.$url); throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$url);
} }
$this->url = $url; $this->url = $url;
$this->cacheDir = $cacheDir;
} }
protected function initialize() protected function initialize()

View File

@ -14,22 +14,23 @@ namespace Composer\Repository;
use Composer\Package\MemoryPackage; use Composer\Package\MemoryPackage;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
*/ */
class PlatformRepository extends ArrayRepository class PlatformRepository extends ArrayRepository
{ {
protected $packages;
protected function initialize() protected function initialize()
{ {
parent::initialize(); parent::initialize();
$versionParser = new VersionParser();
try { try {
$version = BasePackage::parseVersion(PHP_VERSION); $version = $versionParser->parse(PHP_VERSION);
} catch (\UnexpectedValueException $e) { } catch (\UnexpectedValueException $e) {
$version = BasePackage::parseVersion(preg_replace('#^(.+?)(-.+)?$#', '$1', PHP_VERSION)); $version = $versionParser->parse(preg_replace('#^(.+?)(-.+)?$#', '$1', PHP_VERSION));
} }
$php = new MemoryPackage('php', $version['version'], $version['type']); $php = new MemoryPackage('php', $version['version'], $version['type']);
@ -42,7 +43,7 @@ class PlatformRepository extends ArrayRepository
$reflExt = new \ReflectionExtension($ext); $reflExt = new \ReflectionExtension($ext);
try { try {
$version = BasePackage::parseVersion($reflExt->getVersion()); $version = $versionParser->parse($reflExt->getVersion());
} catch (\UnexpectedValueException $e) { } catch (\UnexpectedValueException $e) {
$version = array('version' => '0', 'type' => 'stable'); $version = array('version' => '0', 'type' => 'stable');
} }
@ -51,4 +52,4 @@ class PlatformRepository extends ArrayRepository
$this->addPackage($ext); $this->addPackage($ext);
} }
} }
} }

View File

@ -12,10 +12,29 @@
namespace Composer\Repository; namespace Composer\Repository;
use Composer\Package\PackageInterface;
/** /**
* Repository interface.
*
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
interface RepositoryInterface extends \Countable interface RepositoryInterface extends \Countable
{ {
/**
* Checks if specified package registered (installed).
*
* @param PackageInterface $package package instance
*
* @return Boolean
*/
function hasPackage(PackageInterface $package);
/**
* Returns list of registered packages.
*
* @return array
*/
function getPackages(); function getPackages();
} }

View File

@ -0,0 +1,83 @@
<?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\Repository;
/**
* Repositories manager.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class RepositoryManager
{
private $localRepository;
private $repositories = array();
/**
* Sets repository with specific name.
*
* @param string $type repository name
* @param RepositoryInterface $repository repository instance
*/
public function setRepository($type, RepositoryInterface $repository)
{
$this->repositories[$type] = $repository;
}
/**
* Returns repository for a specific installation type.
*
* @param string $type installation type
*
* @return RepositoryInterface
*
* @throws InvalidArgumentException if repository for provided type is not registeterd
*/
public function getRepository($type)
{
if (!isset($this->repositories[$type])) {
throw new \InvalidArgumentException('Repository is not registered: '.$type);
}
return $this->repositories[$type];
}
/**
* Returns all repositories, except local one.
*
* @return array
*/
public function getRepositories()
{
return $this->repositories;
}
/**
* Sets local repository for the project.
*
* @param RepositoryInterface $repository repository instance
*/
public function setLocalRepository(RepositoryInterface $repository)
{
$this->localRepository = $repository;
}
/**
* Returns local repository for the project.
*
* @return RepositoryInterface
*/
public function getLocalRepository()
{
return $this->localRepository;
}
}

View File

@ -0,0 +1,64 @@
<?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\Repository;
use Composer\Package\PackageInterface;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class WrapperRepository extends ArrayRepository implements WritableRepositoryInterface
{
private $repositories;
public function __construct(array $repositories)
{
$this->repositories = $repositories;
}
protected function initialize()
{
parent::initialize();
foreach ($this->repositories as $repo) {
foreach ($repo->getPackages() as $package) {
$this->packages[] = $package;
}
}
}
/**
* {@inheritDoc}
*/
public function addPackage(PackageInterface $package)
{
throw new \LogicException('Can not add packages to a wrapper repository');
}
/**
* {@inheritDoc}
*/
public function removePackage(PackageInterface $package)
{
throw new \LogicException('Can not remove packages to a wrapper repository');
}
public function write()
{
foreach ($this->repositories as $repo) {
if ($repo instanceof WritableRepositoryInterface) {
$repo->write();
}
}
}
}

View File

@ -0,0 +1,42 @@
<?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\Repository;
use Composer\Package\PackageInterface;
/**
* Writable repository interface.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface WritableRepositoryInterface extends RepositoryInterface
{
/**
* Writes repository (f.e. to the disc).
*/
function write();
/**
* Adds package to the repository.
*
* @param PackageInterface $package package instance
*/
function addPackage(PackageInterface $package);
/**
* Removes package from the repository.
*
* @param PackageInterface $package package instance
*/
function removePackage(PackageInterface $package);
}

View File

@ -213,25 +213,6 @@ class SolverTest extends \PHPUnit_Framework_TestCase
)); ));
} }
public function testSolverWithComposerRepo()
{
$this->repoInstalled = new PlatformRepository;
// overwrite solver with custom installed repo
$this->solver = new Solver($this->policy, $this->pool, $this->repoInstalled);
$this->repo = new ComposerRepository('http://packagist.org');
list($monolog) = $this->repo->getPackages();
$this->reposComplete();
$this->request->install('Monolog');
$this->checkSolverResult(array(
array('job' => 'install', 'package' => $monolog),
));
}
protected function reposComplete() protected function reposComplete()
{ {
$this->pool->addRepository($this->repoInstalled); $this->pool->addRepository($this->repoInstalled);
@ -240,10 +221,23 @@ class SolverTest extends \PHPUnit_Framework_TestCase
protected function checkSolverResult(array $expected) protected function checkSolverResult(array $expected)
{ {
$result = $this->solver->solve($this->request); $transaction = $this->solver->solve($this->request);
foreach ($result as &$step) { $result = array();
unset($step['why']); foreach ($transaction as $operation) {
if ('update' === $operation->getJobType()) {
$result[] = array(
'job' => 'update',
'from' => $operation->getInitialPackage(),
'to' => $operation->getTargetPackage()
);
} else {
$job = ('uninstall' === $operation->getJobType() ? 'remove' : 'install');
$result[] = array(
'job' => $job,
'package' => $operation->getPackage()
);
}
} }
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);

View File

@ -0,0 +1,499 @@
<?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\Downloader;
use Composer\Downloader\DownloadManager;
class DownloadManagerTest extends \PHPUnit_Framework_TestCase
{
public function testSetGetDownloader()
{
$downloader = $this->createDownloaderMock();
$manager = new DownloadManager();
$manager->setDownloader('test', $downloader);
$this->assertSame($downloader, $manager->getDownloader('test'));
$this->setExpectedException('UnexpectedValueException');
$manager->getDownloader('unregistered');
}
public function testFullPackageDownload()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('git'));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$package
->expects($this->once())
->method('getDistUrl')
->will($this->returnValue('dist_url'));
$package
->expects($this->once())
->method('getDistSha1Checksum')
->will($this->returnValue('sha1'));
$package
->expects($this->once())
->method('setInstallationSource')
->with('dist');
$pearDownloader = $this->createDownloaderMock();
$pearDownloader
->expects($this->once())
->method('download')
->with($package, 'target_dir', 'dist_url', 'sha1', false);
$manager = new DownloadManager();
$manager->setDownloader('pear', $pearDownloader);
$manager->download($package, 'target_dir');
}
public function testBadPackageDownload()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue(null));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue(null));
$manager = new DownloadManager();
$this->setExpectedException('InvalidArgumentException');
$manager->download($package, 'target_dir');
}
public function testDistOnlyPackageDownload()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue(null));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$package
->expects($this->once())
->method('getDistUrl')
->will($this->returnValue('dist_url'));
$package
->expects($this->once())
->method('getDistSha1Checksum')
->will($this->returnValue('sha1'));
$package
->expects($this->once())
->method('setInstallationSource')
->with('dist');
$pearDownloader = $this->createDownloaderMock();
$pearDownloader
->expects($this->once())
->method('download')
->with($package, 'target_dir', 'dist_url', 'sha1', false);
$manager = new DownloadManager();
$manager->setDownloader('pear', $pearDownloader);
$manager->download($package, 'target_dir');
}
public function testSourceOnlyPackageDownload()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('git'));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue(null));
$package
->expects($this->once())
->method('getSourceUrl')
->will($this->returnValue('source_url'));
$package
->expects($this->once())
->method('setInstallationSource')
->with('source');
$gitDownloader = $this->createDownloaderMock();
$gitDownloader
->expects($this->once())
->method('download')
->with($package, 'vendor/pkg', 'source_url', false);
$manager = new DownloadManager();
$manager->setDownloader('git', $gitDownloader);
$manager->download($package, 'vendor/pkg');
}
public function testFullPackageDownloadWithSourcePreferred()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('git'));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$package
->expects($this->once())
->method('getSourceUrl')
->will($this->returnValue('source_url'));
$package
->expects($this->once())
->method('setInstallationSource')
->with('source');
$gitDownloader = $this->createDownloaderMock();
$gitDownloader
->expects($this->once())
->method('download')
->with($package, 'vendor/pkg', 'source_url', true);
$manager = new DownloadManager();
$manager->setDownloader('git', $gitDownloader);
$manager->preferSource();
$manager->download($package, 'vendor/pkg');
}
public function testDistOnlyPackageDownloadWithSourcePreferred()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue(null));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$package
->expects($this->once())
->method('getDistUrl')
->will($this->returnValue('dist_url'));
$package
->expects($this->once())
->method('getDistSha1Checksum')
->will($this->returnValue('sha1'));
$package
->expects($this->once())
->method('setInstallationSource')
->with('dist');
$pearDownloader = $this->createDownloaderMock();
$pearDownloader
->expects($this->once())
->method('download')
->with($package, 'target_dir', 'dist_url', 'sha1', true);
$manager = new DownloadManager();
$manager->setDownloader('pear', $pearDownloader);
$manager->preferSource();
$manager->download($package, 'target_dir');
}
public function testSourceOnlyPackageDownloadWithSourcePreferred()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('git'));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue(null));
$package
->expects($this->once())
->method('getSourceUrl')
->will($this->returnValue('source_url'));
$package
->expects($this->once())
->method('setInstallationSource')
->with('source');
$gitDownloader = $this->createDownloaderMock();
$gitDownloader
->expects($this->once())
->method('download')
->with($package, 'vendor/pkg', 'source_url', true);
$manager = new DownloadManager();
$manager->setDownloader('git', $gitDownloader);
$manager->preferSource();
$manager->download($package, 'vendor/pkg');
}
public function testBadPackageDownloadWithSourcePreferred()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue(null));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue(null));
$manager = new DownloadManager();
$manager->preferSource();
$this->setExpectedException('InvalidArgumentException');
$manager->download($package, 'target_dir');
}
public function testUpdateDistWithEqualTypes()
{
$initial = $this->createPackageMock();
$initial
->expects($this->once())
->method('getInstallationSource')
->will($this->returnValue('dist'));
$initial
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$target = $this->createPackageMock();
$target
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$pearDownloader = $this->createDownloaderMock();
$pearDownloader
->expects($this->once())
->method('update')
->with($initial, $target, 'vendor/bundles/FOS/UserBundle', false);
$manager = new DownloadManager();
$manager->setDownloader('pear', $pearDownloader);
$manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle');
}
public function testUpdateDistWithNotEqualTypes()
{
$initial = $this->createPackageMock();
$initial
->expects($this->once())
->method('getInstallationSource')
->will($this->returnValue('dist'));
$initial
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$target = $this->createPackageMock();
$target
->expects($this->once())
->method('getDistType')
->will($this->returnValue('composer'));
$pearDownloader = $this->createDownloaderMock();
$pearDownloader
->expects($this->once())
->method('remove')
->with($initial, 'vendor/bundles/FOS/UserBundle', false);
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setMethods(array('download'))
->getMock();
$manager
->expects($this->once())
->method('download')
->with($target, 'vendor/bundles/FOS/UserBundle', false);
$manager->setDownloader('pear', $pearDownloader);
$manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle');
}
public function testUpdateSourceWithEqualTypes()
{
$initial = $this->createPackageMock();
$initial
->expects($this->once())
->method('getInstallationSource')
->will($this->returnValue('source'));
$initial
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('svn'));
$target = $this->createPackageMock();
$target
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('svn'));
$svnDownloader = $this->createDownloaderMock();
$svnDownloader
->expects($this->once())
->method('update')
->with($initial, $target, 'vendor/pkg', true);
$manager = new DownloadManager();
$manager->setDownloader('svn', $svnDownloader);
$manager->update($initial, $target, 'vendor/pkg');
}
public function testUpdateSourceWithNotEqualTypes()
{
$initial = $this->createPackageMock();
$initial
->expects($this->once())
->method('getInstallationSource')
->will($this->returnValue('source'));
$initial
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('svn'));
$target = $this->createPackageMock();
$target
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('git'));
$svnDownloader = $this->createDownloaderMock();
$svnDownloader
->expects($this->once())
->method('remove')
->with($initial, 'vendor/pkg', true);
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setMethods(array('download'))
->getMock();
$manager
->expects($this->once())
->method('download')
->with($target, 'vendor/pkg', true);
$manager->setDownloader('svn', $svnDownloader);
$manager->update($initial, $target, 'vendor/pkg');
}
public function testUpdateBadlyInstalledPackage()
{
$initial = $this->createPackageMock();
$target = $this->createPackageMock();
$this->setExpectedException('InvalidArgumentException');
$manager = new DownloadManager();
$manager->update($initial, $target, 'vendor/pkg');
}
public function testRemoveDist()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getInstallationSource')
->will($this->returnValue('dist'));
$package
->expects($this->once())
->method('getDistType')
->will($this->returnValue('pear'));
$pearDownloader = $this->createDownloaderMock();
$pearDownloader
->expects($this->once())
->method('remove')
->with($package, 'vendor/bundles/FOS/UserBundle');
$manager = new DownloadManager();
$manager->setDownloader('pear', $pearDownloader);
$manager->remove($package, 'vendor/bundles/FOS/UserBundle');
}
public function testRemoveSource()
{
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getInstallationSource')
->will($this->returnValue('source'));
$package
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('svn'));
$svnDownloader = $this->createDownloaderMock();
$svnDownloader
->expects($this->once())
->method('remove')
->with($package, 'vendor/pkg');
$manager = new DownloadManager();
$manager->setDownloader('svn', $svnDownloader);
$manager->remove($package, 'vendor/pkg');
}
public function testRemoveBadlyInstalledPackage()
{
$package = $this->createPackageMock();
$manager = new DownloadManager();
$this->setExpectedException('InvalidArgumentException');
$manager->remove($package, 'vendor/pkg');
}
private function createDownloaderMock()
{
return $this->getMockBuilder('Composer\Downloader\DownloaderInterface')
->getMock();
}
private function createPackageMock()
{
return $this->getMockBuilder('Composer\Package\PackageInterface')
->getMock();
}
}

View File

@ -0,0 +1,180 @@
<?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\Installer;
use Composer\Installer\InstallationManager;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
class InstallationManagerTest extends \PHPUnit_Framework_TestCase
{
public function testSetGetInstaller()
{
$installer = $this->createInstallerMock();
$manager = new InstallationManager();
$manager->setInstaller('vendor', $installer);
$this->assertSame($installer, $manager->getInstaller('vendor'));
$this->setExpectedException('InvalidArgumentException');
$manager->getInstaller('unregistered');
}
public function testExecute()
{
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')
->setMethods(array('install', 'update', 'uninstall'))
->getMock();
$installOperation = new InstallOperation($this->createPackageMock());
$removeOperation = new UninstallOperation($this->createPackageMock());
$updateOperation = new UpdateOperation(
$this->createPackageMock(), $this->createPackageMock()
);
$manager
->expects($this->once())
->method('install')
->with($installOperation);
$manager
->expects($this->once())
->method('uninstall')
->with($removeOperation);
$manager
->expects($this->once())
->method('update')
->with($updateOperation);
$manager->execute($installOperation);
$manager->execute($removeOperation);
$manager->execute($updateOperation);
}
public function testInstall()
{
$installer = $this->createInstallerMock();
$manager = new InstallationManager();
$manager->setInstaller('library', $installer);
$package = $this->createPackageMock();
$operation = new InstallOperation($package, 'test');
$package
->expects($this->once())
->method('getType')
->will($this->returnValue('library'));
$installer
->expects($this->once())
->method('install')
->with($package);
$manager->install($operation);
}
public function testUpdateWithEqualTypes()
{
$installer = $this->createInstallerMock();
$manager = new InstallationManager();
$manager->setInstaller('library', $installer);
$initial = $this->createPackageMock();
$target = $this->createPackageMock();
$operation = new UpdateOperation($initial, $target, 'test');
$initial
->expects($this->once())
->method('getType')
->will($this->returnValue('library'));
$target
->expects($this->once())
->method('getType')
->will($this->returnValue('library'));
$installer
->expects($this->once())
->method('update')
->with($initial, $target);
$manager->update($operation);
}
public function testUpdateWithNotEqualTypes()
{
$installer1 = $this->createInstallerMock();
$installer2 = $this->createInstallerMock();
$manager = new InstallationManager();
$manager->setInstaller('library', $installer1);
$manager->setInstaller('bundles', $installer2);
$initial = $this->createPackageMock();
$target = $this->createPackageMock();
$operation = new UpdateOperation($initial, $target, 'test');
$initial
->expects($this->once())
->method('getType')
->will($this->returnValue('library'));
$target
->expects($this->once())
->method('getType')
->will($this->returnValue('bundles'));
$installer1
->expects($this->once())
->method('uninstall')
->with($initial);
$installer2
->expects($this->once())
->method('install')
->with($target);
$manager->update($operation);
}
public function testUninstall()
{
$installer = $this->createInstallerMock();
$manager = new InstallationManager();
$manager->setInstaller('library', $installer);
$package = $this->createPackageMock();
$operation = new UninstallOperation($package, 'test');
$package
->expects($this->once())
->method('getType')
->will($this->returnValue('library'));
$installer
->expects($this->once())
->method('uninstall')
->with($package);
$manager->uninstall($operation);
}
private function createInstallerMock()
{
return $this->getMockBuilder('Composer\Installer\InstallerInterface')
->getMock();
}
private function createPackageMock()
{
return $this->getMockBuilder('Composer\Package\PackageInterface')
->getMock();
}
}

View File

@ -0,0 +1,169 @@
<?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\Installer;
use Composer\Installer\LibraryInstaller;
use Composer\DependencyResolver\Operation;
class LibraryInstallerTest extends \PHPUnit_Framework_TestCase
{
private $dir;
private $dm;
private $repository;
private $library;
protected function setUp()
{
$this->dir = sys_get_temp_dir().'/composer';
if (is_dir($this->dir)) {
rmdir($this->dir);
}
$this->dm = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->disableOriginalConstructor()
->getMock();
$this->repository = $this->getMockBuilder('Composer\Repository\WritableRepositoryInterface')
->disableOriginalConstructor()
->getMock();
}
public function testInstallerCreation()
{
$library = new LibraryInstaller($this->dir, $this->dm, $this->repository);
$this->assertTrue(is_dir($this->dir));
$file = sys_get_temp_dir().'/file';
touch($file);
$this->setExpectedException('UnexpectedValueException');
$library = new LibraryInstaller($file, $this->dm, $this->repository);
}
public function testIsInstalled()
{
$library = new LibraryInstaller($this->dir, $this->dm, $this->repository);
$package = $this->createPackageMock();
$this->repository
->expects($this->exactly(2))
->method('hasPackage')
->with($package)
->will($this->onConsecutiveCalls(true, false));
$this->assertTrue($library->isInstalled($package));
$this->assertFalse($library->isInstalled($package));
}
public function testInstall()
{
$library = new LibraryInstaller($this->dir, $this->dm, $this->repository);
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getName')
->will($this->returnValue('some/package'));
$this->dm
->expects($this->once())
->method('download')
->with($package, $this->dir.'/some/package');
$this->repository
->expects($this->once())
->method('addPackage')
->with($package);
$library->install($package);
}
public function testUpdate()
{
$library = new LibraryInstaller($this->dir, $this->dm, $this->repository);
$initial = $this->createPackageMock();
$target = $this->createPackageMock();
$initial
->expects($this->once())
->method('getName')
->will($this->returnValue('package1'));
$this->repository
->expects($this->exactly(2))
->method('hasPackage')
->with($initial)
->will($this->onConsecutiveCalls(true, false));
$this->dm
->expects($this->once())
->method('update')
->with($initial, $target, $this->dir.'/package1');
$this->repository
->expects($this->once())
->method('removePackage')
->with($initial);
$this->repository
->expects($this->once())
->method('addPackage')
->with($target);
$library->update($initial, $target);
$this->setExpectedException('InvalidArgumentException');
$library->update($initial, $target);
}
public function testUninstall()
{
$library = new LibraryInstaller($this->dir, $this->dm, $this->repository);
$package = $this->createPackageMock();
$package
->expects($this->once())
->method('getName')
->will($this->returnValue('pkg'));
$this->repository
->expects($this->exactly(2))
->method('hasPackage')
->with($package)
->will($this->onConsecutiveCalls(true, false));
$this->dm
->expects($this->once())
->method('remove')
->with($package, $this->dir.'/pkg');
$this->repository
->expects($this->once())
->method('removePackage')
->with($package);
$library->uninstall($package);
$this->setExpectedException('InvalidArgumentException');
$library->uninstall($package);
}
private function createPackageMock()
{
return $this->getMockBuilder('Composer\Package\MemoryPackage')
->setConstructorArgs(array(md5(rand()), '1.0.0'))
->getMock();
}
}

View File

@ -17,11 +17,37 @@ use Composer\Package\MemoryPackage;
class ArrayRepositoryTest extends \PHPUnit_Framework_TestCase class ArrayRepositoryTest extends \PHPUnit_Framework_TestCase
{ {
public function testAddLiteral() public function testAddPackage()
{ {
$repo = new ArrayRepository; $repo = new ArrayRepository;
$repo->addPackage(new MemoryPackage('foo', '1')); $repo->addPackage(new MemoryPackage('foo', '1'));
$this->assertEquals(1, count($repo)); $this->assertEquals(1, count($repo));
} }
public function testRemovePackage()
{
$package = new MemoryPackage('bar', '2');
$repo = new ArrayRepository;
$repo->addPackage(new MemoryPackage('foo', '1'));
$repo->addPackage($package);
$this->assertEquals(2, count($repo));
$repo->removePackage(new MemoryPackage('foo', '1'));
$this->assertEquals(1, count($repo));
$this->assertEquals(array($package), $repo->getPackages());
}
public function testHasPackage()
{
$repo = new ArrayRepository;
$repo->addPackage(new MemoryPackage('foo', '1'));
$repo->addPackage(new MemoryPackage('bar', '2'));
$this->assertTrue($repo->hasPackage(new MemoryPackage('foo', '1')));
$this->assertFalse($repo->hasPackage(new MemoryPackage('bar', '1')));
}
} }

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Repository;
use Composer\Repository\FilesystemRepository;
class FilesystemRepositoryTest extends \PHPUnit_Framework_TestCase
{
private $dir;
private $repositoryFile;
protected function setUp()
{
$this->dir = sys_get_temp_dir().'/.composer';
$this->repositoryFile = $this->dir.'/some_registry-reg.json';
if (file_exists($this->repositoryFile)) {
unlink($this->repositoryFile);
}
}
public function testRepositoryReadWrite()
{
$this->assertFileNotExists($this->repositoryFile);
$repository = new FilesystemRepository($this->repositoryFile);
$repository->getPackages();
$repository->write();
$this->assertFileExists($this->repositoryFile);
file_put_contents($this->repositoryFile, json_encode(array(
array('name' => 'package1', 'version' => '1.0.0-beta', 'type' => 'vendor')
)));
$repository = new FilesystemRepository($this->repositoryFile);
$repository->getPackages();
$repository->write();
$this->assertFileExists($this->repositoryFile);
$data = json_decode(file_get_contents($this->repositoryFile), true);
$this->assertEquals(array(
array('name' => 'package1', 'type' => 'vendor', 'version' => '1.0.0', 'releaseType' => 'beta', 'names' => array('package1'))
), $data);
}
}