From 1e1ecb80b7d9d52fee4fa5438bb749326926d603 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 16 Sep 2011 22:14:06 +0300 Subject: [PATCH] initial refactoring --- bin/composer | 2 +- src/Composer/Command/InstallCommand.php | 146 +++---------- src/Composer/Composer.php | 108 ++-------- src/Composer/ConfigurableComposer.php | 200 ++++++++++++++++++ .../Console/Package/VerboseManager.php | 40 ++++ src/Composer/Installer/LibraryInstaller.php | 17 +- src/Composer/Package/Manager.php | 93 ++++++++ 7 files changed, 401 insertions(+), 205 deletions(-) create mode 100644 src/Composer/ConfigurableComposer.php create mode 100644 src/Composer/Console/Package/VerboseManager.php create mode 100644 src/Composer/Package/Manager.php diff --git a/bin/composer b/bin/composer index c9af1edbd..2c2376a19 100755 --- a/bin/composer +++ b/bin/composer @@ -13,7 +13,7 @@ use Composer\Console\Application; setlocale(LC_ALL, 'C'); // initialize composer -$composer = new Composer(); +$composer = new ConfigurableComposer('composer.json', 'composer.lock'); $composer->addDownloader('git', new GitDownloader); $composer->addDownloader('pear', new PearDownloader); $composer->addDownloader('zip', new ZipDownloader); diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 07b85f163..f183c049b 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -12,20 +12,14 @@ namespace Composer\Command; -use Composer\DependencyResolver\Pool; -use Composer\DependencyResolver\Request; -use Composer\DependencyResolver\DefaultPolicy; -use Composer\DependencyResolver\Solver; -use Composer\Repository\PlatformRepository; -use Composer\Package\MemoryPackage; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\DependencyResolver; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - /** * @author Jordi Boggiano * @author Ryan Weaver + * @author Konstantin Kudryashov */ class InstallCommand extends Command { @@ -48,130 +42,48 @@ EOT 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) - $sourceInstall = false; + if ($this->getLock()->isLocked()) { + $this->writeln('Found lockfile. Reading'); - $config = $this->loadConfig(); - - $output->writeln('Loading repositories'); - - if (isset($config['repositories'])) { - foreach ($config['repositories'] as $name => $spec) { - $this->getComposer()->addRepository($name, $spec); + foreach ($this->getLock()->getLockedPackages() as $package) { + $installer = $this->getComposer()->getInstaller($package->getType()); + if (!$installer->isInstalled($package)) { + $installer->install($package); + } } + + return 0; } + // creating repository pool $pool = new Pool; - - $repoInstalled = new PlatformRepository; - $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); } + // creating requirements request $request = new Request($pool); - - $output->writeln('Building up request'); - - // 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); - } + foreach ($this->getPackage()->getRequires() as $link) { + $request->install($link->getTarget(), $link->getConstraint()); } - $output->writeln('Solving dependencies'); + // prepare solver + $platform = $this->getComposer()->getRepository('Platform'); + $policy = new DependencyResolver\DefaultPolicy(); + $solver = new DependencyResolver\Solver($policy, $pool, $platform); - $policy = new DefaultPolicy; - $solver = new Solver($policy, $pool, $repoInstalled); - $transaction = $solver->solve($request); - - $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']); - } + // solve dependencies and execute operations + $operations = $this->solveDependencies($request, $solver); + foreach ($operations as $operation) { + $operation->execute(); + // TODO: collect installable packages into $installed } + $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()); + if (false) { + $config->lock($installed); + $output->writeln('> Locked'); } - $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'); - } -} \ No newline at end of file +} diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4da5de68d..ceeb16735 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -12,125 +12,61 @@ namespace Composer; -use Composer\Downloader\DownloaderInterface; use Composer\Installer\InstallerInterface; -use Composer\Repository\ComposerRepository; -use Composer\Repository\PlatformRepository; -use Composer\Repository\GitRepository; -use Composer\Repository\PearRepository; /** * @author Jordi Boggiano + * @author Konstantin Kudryashiv */ class Composer { const VERSION = '1.0.0-DEV'; - protected $repositories = array(); - protected $downloaders = array(); - protected $installers = array(); + private $repositories = array(); + private $installers = array(); - public function __construct() + public function setInstaller($type, InstallerInterface $installer = null) { - $this->addRepository('Packagist', array('composer' => 'http://packagist.org')); - } + if (null === $installer) { + unset($this->installers[$type]); - /** - * Add downloader for type - * - * @param string $type - * @param DownloaderInterface $downloader - */ - public function addDownloader($type, DownloaderInterface $downloader) - { - $type = strtolower($type); - $this->downloaders[$type] = $downloader; - } - - /** - * Get type downloader - * - * @param string $type - * - * @return DownloaderInterface - */ - public function getDownloader($type) - { - $type = strtolower($type); - if (!isset($this->downloaders[$type])) { - throw new \UnexpectedValueException('Unknown source type: '.$type); + return; } - return $this->downloaders[$type]; - } - /** - * Add installer for type - * - * @param string $type - * @param InstallerInterface $installer - */ - public function addInstaller($type, InstallerInterface $installer) - { - $type = strtolower($type); $this->installers[$type] = $installer; } - /** - * Get type installer - * - * @param string $type - * - * @return InstallerInterface - */ public function getInstaller($type) { - $type = strtolower($type); if (!isset($this->installers[$type])) { throw new \UnexpectedValueException('Unknown dependency type: '.$type); } + return $this->installers[$type]; } - public function addRepository($name, $spec) + public function setRepository($name, RepositoryInterface $repository = null) { - if (null === $spec) { + if (null === $repository) { unset($this->repositories[$name]); + + return; } - if (is_array($spec) && count($spec) === 1) { - return $this->repositories[$name] = $this->createRepository($name, key($spec), current($spec)); + + $this->repositories[$name] = $repository; + } + + public function getRepository($name) + { + if (!isset($this->repositories[$name])) { + throw new \UnexpectedValueException('Unknown repository: '.$name); } - throw new \UnexpectedValueException('Invalid repositories specification '.json_encode($spec).', should be: {"type": "url"}'); + + return $this->repositories[$name]; } public function getRepositories() { return $this->repositories; } - - public function createRepository($name, $type, $spec) - { - if (is_string($spec)) { - $spec = array('url' => $spec); - } - $spec['url'] = rtrim($spec['url'], '/'); - - switch ($type) { - case 'git-bare': - case 'git-multi': - throw new \Exception($type.' repositories not supported yet'); - break; - - case 'git': - return new GitRepository($spec['url']); - - 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); - } - } } diff --git a/src/Composer/ConfigurableComposer.php b/src/Composer/ConfigurableComposer.php new file mode 100644 index 000000000..1d0443c09 --- /dev/null +++ b/src/Composer/ConfigurableComposer.php @@ -0,0 +1,200 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Repository; +use Composer\Package; +use Composer\Installer\LibraryInstaller; + +/** + * @author Konstantin Kudryashiv + */ +class ConfigurableComposer extends Composer +{ + private $configFile; + private $lockFile; + private $isLocked = false; + private $lockedPackages = array(); + + public function __construct($configFile = 'composer.json', $lockFile = 'composer.lock') + { + $this->configFile = $configFile; + $this->lockFile = $lockFile; + $this->setRepository('Platform', new Repository\PlatformRepository()); + + if (!file_exists($configFile)) { + throw new \UnexpectedValueException('Can not find composer config file'); + } + + $config = $this->loadJsonConfig($configFile); + + if (isset($config['path'])) { + $this->setInstaller('library', new LibraryInstaller($config['path'])); + } else { + $this->setInstaller('library', new LibraryInstaller()); + } + + if (isset($config['repositories'])) { + $repositories = $this->loadRepositoriesFromConfig($config['repositories']) + foreach ($repositories as $name => $repository) { + $this->setRepository($name, $repository); + } + } + + if (isset($config['require'])) { + $requirements = $this->loadRequirementsFromConfig($config['require']); + foreach ($requirements as $name => $constraint) { + $this->setRequirement($name, $constraint); + } + } + + if (file_exists($lockFile)) { + $lock = $this->loadJsonConfig($lockFile); + $platform = $this->getRepository('Platform'); + $packages = $this->loadPackagesFromLock($lock); + foreach ($packages as $package) { + if ($this->hasRequirement($package->getName())) { + $platform->addPackage($package); + $this->lockedPackages[] = $package; + } + } + $this->isLocked = true; + } + } + + public function isLocked() + { + return $this->isLocked; + } + + public function getLockedPackages() + { + return $this->lockedPackages; + } + + public function lock(array $packages) + { + // TODO: write installed packages info into $this->lockFile + } + + private function loadPackagesFromLock(array $lockList) + { + $packages = array(); + foreach ($lockList as $info) { + $packages[] = new Package\MemoryPackage($info['package'], $info['version']); + } + + return $packages; + } + + private function loadRepositoriesFromConfig(array $repositoriesList) + { + $repositories = array(); + foreach ($repositoriesList as $name => $spec) { + if (is_array($spec) && count($spec) === 1) { + $repositories[$name] = $this->createRepository($name, key($spec), current($spec)); + } elseif (null === $spec) { + $repositories[$name] = null; + } else { + throw new \UnexpectedValueException( + 'Invalid repositories specification '. + json_encode($spec).', should be: {"type": "url"}' + ); + } + } + + return $repositories; + } + + private function loadRequirementsFromConfig(array $requirementsList) + { + $requirements = array(); + foreach ($requirementsList as $name => $version) { + $name = $this->lowercase($name); + if ('latest' === $version) { + $requirements[$name] = null; + } else { + preg_match('#^([>=<~]*)([\d.]+.*)$#', $version, $match); + if (!$match[1]) { + $match[1] = '='; + } + $constraint = new Package\LinkConstraint\VersionConstraint($match[1], $match[2]); + $requirements[$name] = $constraint; + } + } + + return $requirements; + } + + private function loadJsonConfig($configFile) + { + $config = json_decode(file_get_contents($configFile), 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; + } + + private function lowercase($str) + { + if (function_exists('mb_strtolower')) { + return mb_strtolower($str, 'UTF-8'); + } + return strtolower($str, 'UTF-8'); + } + + private function createRepository($name, $type, $spec) + { + if (is_string($spec)) { + $spec = array('url' => $spec); + } + $spec['url'] = rtrim($spec['url'], '/'); + + switch ($type) { + case 'git-bare': + case 'git-multi': + throw new \Exception($type.' repositories not supported yet'); + case 'git': + return new Repository\GitRepository($spec['url']); + case 'composer': + return new Repository\ComposerRepository($spec['url']); + case 'pear': + return new Repository\PearRepository($spec['url'], $name); + default: + throw new \UnexpectedValueException( + 'Unknown repository type: '.$type.', could not create repository '.$name + ); + } + } +} diff --git a/src/Composer/Console/Package/VerboseManager.php b/src/Composer/Console/Package/VerboseManager.php new file mode 100644 index 000000000..7562ec57d --- /dev/null +++ b/src/Composer/Console/Package/VerboseManager.php @@ -0,0 +1,40 @@ +composer = $composer; + } + + public function install(PackageInterface $package) + { + $this->output->writeln('> Installing '.$package->getName()); + + parent::install($package); + } + + public function update(PackageInterface $package) + { + $this->output->writeln('> Updating '.$package->getName()); + + parent::update($package); + } + + public function remove(PackageInterface $package) + { + $this->output->writeln('> Removing '.$package->getName()); + + parent::remove($package); + } +} diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index ad3fd451e..1e7ed2583 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -38,4 +38,19 @@ class LibraryInstaller implements InstallerInterface } return true; } -} \ No newline at end of file + + public function isInstalled(PackageInterface $package, $downloader, $type) + { + // TODO: implement installation check + } + + public function update(PackageInterface $package, $downloader, $type) + { + // TODO: implement package update + } + + public function remove(PackageInterface $package, $downloader, $type) + { + // TODO: implement package removal + } +} diff --git a/src/Composer/Package/Manager.php b/src/Composer/Package/Manager.php new file mode 100644 index 000000000..03482602b --- /dev/null +++ b/src/Composer/Package/Manager.php @@ -0,0 +1,93 @@ +composer = $composer; + } + + public function isInstalled(PackageInterface $package) + { + $installer = $this->composer->getInstaller($package->getType()); + $downloader = $this->getDownloaderForPackage($package); + $packageType = $this->getTypeForPackage($package); + + return $installer->isInstalled($package, $downloader, $packageType); + } + + public function install(PackageInterface $package) + { + $output->writeln('> Installing '.$package->getName()); + + $installer = $this->composer->getInstaller($package->getType()); + $downloader = $this->getDownloaderForPackage($package); + $packageType = $this->getTypeForPackage($package); + + if (!$installer->install($package, $downloader, $packageType)) { + throw new \LogicException($package->getName().' could not be installed.'); + } + } + + public function update(PackageInterface $package) + { + $output->writeln('> Updating '.$package->getName()); + + $installer = $this->composer->getInstaller($package->getType()); + $downloader = $this->getDownloaderForPackage($package); + $packageType = $this->getTypeForPackage($package); + + if (!$installer->update($package, $downloader, $packageType)) { + throw new \LogicException($package->getName().' could not be updated.'); + } + } + + public function remove(PackageInterface $package) + { + $output->writeln('> Removing '.$package->getName()); + + $installer = $this->composer->getInstaller($package->getType()); + $downloader = $this->getDownloaderForPackage($package); + $packageType = $this->getTypeForPackage($package); + + if (!$installer->remove($package, $downloader, $packageType)) { + throw new \LogicException($package->getName().' could not be removed.'); + } + } + + private function getDownloaderForPackage(PackageInterface $package) + { + if ($package->getDistType()) { + $downloader = $this->composer->getDownloader($package->getDistType); + } elseif ($package->getSourceType()) { + $downloader = $this->copmoser->getDownloader($package->getSourceType()); + } else { + throw new \UnexpectedValueException( + 'Package '.$package->getName().' has no source or dist URL.' + ); + } + + return $downloader; + } + + private function getTypeForPackage(PackageInterface $package) + { + if ($package->getDistType()) { + $type = 'dist'; + } elseif ($package->getSourceType()) { + $type = 'source'; + } else { + throw new \UnexpectedValueException( + 'Package '.$package->getName().' has no source or dist URL.' + ); + } + + return $type; + } +}