everzet 13 жил өмнө
parent
commit
1e1ecb80b7

+ 1 - 1
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);

+ 29 - 117
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 <j.boggiano@seld.be>
  * @author Ryan Weaver <ryan@knplabs.com>
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
  */
 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;
-
-        $config = $this->loadConfig();
+        if ($this->getLock()->isLocked()) {
+            $this->writeln('<info>Found lockfile. Reading</info>');
 
-        $output->writeln('<info>Loading repositories</info>');
-
-        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);
-    }
+        $output->writeln('> Done');
 
-    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);
+        if (false) {
+            $config->lock($installed);
+            $output->writeln('> Locked');
         }
-        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');
     }
-}
+}

+ 21 - 85
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 <j.boggiano@seld.be>
+ * @author Konstantin Kudryashiv <ever.zet@gmail.com>
  */
 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'));
-    }
-
-    /**
-     * Add downloader for type
-     *
-     * @param string              $type
-     * @param DownloaderInterface $downloader
-     */
-    public function addDownloader($type, DownloaderInterface $downloader)
-    {
-        $type = strtolower($type);
-        $this->downloaders[$type] = $downloader;
-    }
+        if (null === $installer) {
+            unset($this->installers[$type]);
 
-    /**
-     * 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));
-        }
-        throw new \UnexpectedValueException('Invalid repositories specification '.json_encode($spec).', should be: {"type": "url"}');
-    }
 
-    public function getRepositories()
-    {
-        return $this->repositories;
+        $this->repositories[$name] = $repository;
     }
 
-    public function createRepository($name, $type, $spec)
+    public function getRepository($name)
     {
-        if (is_string($spec)) {
-            $spec = array('url' => $spec);
+        if (!isset($this->repositories[$name])) {
+            throw new \UnexpectedValueException('Unknown repository: '.$name);
         }
-        $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);
+        return $this->repositories[$name];
+    }
 
-        default:
-            throw new \UnexpectedValueException('Unknown repository type: '.$type.', could not create repository '.$name);
-        }
+    public function getRepositories()
+    {
+        return $this->repositories;
     }
 }

+ 200 - 0
src/Composer/ConfigurableComposer.php

@@ -0,0 +1,200 @@
+<?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;
+
+use Composer\Repository;
+use Composer\Package;
+use Composer\Installer\LibraryInstaller;
+
+/**
+ * @author Konstantin Kudryashiv <ever.zet@gmail.com>
+ */
+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
+                );
+        }
+    }
+}

+ 40 - 0
src/Composer/Console/Package/VerboseManager.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Composer\Console\Package;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Composer\Package\PackageInterface;
+use Composer\Package\Manager;
+
+class VerboseManager extends Manager
+{
+    private $output;
+
+    public function __construct(Composer $composer, OutputInterface $output)
+    {
+        parent::__construct($output);
+
+        $this->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);
+    }
+}

+ 16 - 1
src/Composer/Installer/LibraryInstaller.php

@@ -38,4 +38,19 @@ class LibraryInstaller implements InstallerInterface
         }
         return true;
     }
-}
+
+    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
+    }
+}

+ 93 - 0
src/Composer/Package/Manager.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace Composer\Package;
+
+use Composer\Package\PackageInterface;
+
+class Manager
+{
+    private $composer;
+
+    public function __construct(Composer $composer)
+    {
+        $this->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;
+    }
+}