Merge pull request #736 from Seldaek/require-update
Require command & update <package>pull/739/head
commit
242323cba4
|
@ -1,6 +1,8 @@
|
|||
* 1.0.0-alpha4
|
||||
|
||||
* Schema: Added references for dev versions, requiring `dev-master#abcdef` for example will force the abcdef commit
|
||||
* Added `require` command to add a package to your requirements and install it
|
||||
* Added a whitelist to `update`. Calling `composer update foo/bar foo/baz` allows you to update only those packages
|
||||
* Added caching of GitHub metadata (faster startup time with custom GitHub VCS repos)
|
||||
* Added support for file:// URLs to GitDriver
|
||||
* Added --dev flag to `create-project` command
|
||||
|
|
|
@ -53,12 +53,36 @@ In order to get the latest versions of the dependencies and to update the
|
|||
This will resolve all dependencies of the project and write the exact versions
|
||||
into `composer.lock`.
|
||||
|
||||
If you just want to update a few packages and not all, you can list them as such:
|
||||
|
||||
$ php composer.phar update vendor/package vendor/package2
|
||||
|
||||
### Options
|
||||
|
||||
* **--prefer-source:** Install packages from `source` when available.
|
||||
* **--dry-run:** Simulate the command without actually doing anything.
|
||||
* **--dev:** Install packages listed in `require-dev`.
|
||||
|
||||
## require
|
||||
|
||||
The `require` command adds new packages to the `composer.json` file from
|
||||
the current directory.
|
||||
|
||||
$ php composer.phar require
|
||||
|
||||
After adding/changing the requirements, the modified requirements will be
|
||||
installed or updated.
|
||||
|
||||
If you do not want to choose requirements interactively, you can just pass them
|
||||
to the command.
|
||||
|
||||
$ php composer.phar require vendor/package:2.* vendor/package2:dev-master
|
||||
|
||||
### Options
|
||||
|
||||
* **--prefer-source:** Install packages from `source` when available.
|
||||
* **--dev:** Add packages to `require-dev`.
|
||||
|
||||
## search
|
||||
|
||||
The search command allows you to search through the current project's package
|
||||
|
|
|
@ -60,7 +60,8 @@ class InitCommand extends Command
|
|||
new InputOption('author', null, InputOption::VALUE_NONE, 'Author name of package'),
|
||||
// new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'),
|
||||
new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'),
|
||||
new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'An array required packages'),
|
||||
new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
|
||||
new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>init</info> command creates a basic composer.json file
|
||||
|
@ -216,10 +217,15 @@ EOT
|
|||
));
|
||||
|
||||
$requirements = array();
|
||||
if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dependencies interactively', 'yes', '?'), true)) {
|
||||
$requirements = $this->determineRequirements($input, $output);
|
||||
if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dependencies (require) interactively', 'yes', '?'), true)) {
|
||||
$requirements = $this->determineRequirements($input, $output, $input->getOption('require'));
|
||||
}
|
||||
$input->setOption('require', $requirements);
|
||||
$devRequirements = array();
|
||||
if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dev dependencies (require-dev) interactively', 'yes', '?'), true)) {
|
||||
$devRequirements = $this->determineRequirements($input, $output, $input->getOption('require-dev'));
|
||||
}
|
||||
$input->setOption('require-dev', $devRequirements);
|
||||
}
|
||||
|
||||
protected function findPackages($name)
|
||||
|
@ -246,12 +252,27 @@ EOT
|
|||
return $packages;
|
||||
}
|
||||
|
||||
protected function determineRequirements(InputInterface $input, OutputInterface $output)
|
||||
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
|
||||
{
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
$prompt = $dialog->getQuestion('Search for a package', false, ':');
|
||||
|
||||
$requires = $input->getOption('require') ?: array();
|
||||
if ($requires) {
|
||||
foreach ($requires as $key => $requirement) {
|
||||
$requires[$key] = preg_replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', $requirement);
|
||||
if (false === strpos($requires[$key], ' ') && $input->isInteractive()) {
|
||||
$question = $dialog->getQuestion('Please provide a version constraint for the '.$requirement.' requirement');
|
||||
if ($constraint = $dialog->ask($output, $question)) {
|
||||
$requires[$key] .= ' ' . $constraint;
|
||||
}
|
||||
}
|
||||
if (false === strpos($requires[$key], ' ')) {
|
||||
throw new \InvalidArgumentException('The requirement '.$requirement.' must contain a version constraint');
|
||||
}
|
||||
}
|
||||
|
||||
return $requires;
|
||||
}
|
||||
|
||||
while (null !== $package = $dialog->ask($output, $prompt)) {
|
||||
$matches = $this->findPackages($package);
|
||||
|
@ -287,7 +308,7 @@ EOT
|
|||
return sprintf('%s %s', $package->getName(), $package->getPrettyVersion());
|
||||
};
|
||||
|
||||
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or a <package> <version> couple if it is not listed', false, ':'), $validator, 3);
|
||||
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or a "[package] [version]" couple if it is not listed', false, ':'), $validator, 3);
|
||||
|
||||
if (false !== $package) {
|
||||
$requires[] = $package;
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?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\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Factory;
|
||||
use Composer\Installer;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Json\JsonValidationException;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
/**
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class RequireCommand extends InitCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('require')
|
||||
->setDescription('Adds required packages to your composer.json and installs them')
|
||||
->setDefinition(array(
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
|
||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
|
||||
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The require command adds required packages to your composer.json and installs them
|
||||
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$factory = new Factory;
|
||||
$file = $factory->getComposerFile();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
$output->writeln('<error>'.$file.' not found.</error>');
|
||||
return 1;
|
||||
}
|
||||
if (!is_readable($file)) {
|
||||
$output->writeln('<error>'.$file.' is not readable.</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
|
||||
$json = new JsonFile($file);
|
||||
$composer = $json->read();
|
||||
|
||||
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
|
||||
|
||||
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
|
||||
$baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
|
||||
$requirements = $this->formatRequirements($requirements);
|
||||
|
||||
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey)) {
|
||||
foreach ($requirements as $package => $version) {
|
||||
$baseRequirements[$package] = $version;
|
||||
}
|
||||
|
||||
$composer[$requireKey] = $baseRequirements;
|
||||
$json->write($composer);
|
||||
}
|
||||
|
||||
$output->writeln('<info>'.$file.' has been updated</info>');
|
||||
|
||||
// Update packages
|
||||
$composer = $this->getComposer();
|
||||
$io = $this->getIO();
|
||||
$install = Installer::create($io, $composer);
|
||||
|
||||
$install
|
||||
->setVerbose($input->getOption('verbose'))
|
||||
->setPreferSource($input->getOption('prefer-source'))
|
||||
->setDevMode($input->getOption('dev'))
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($requirements);
|
||||
;
|
||||
|
||||
return $install->run() ? 0 : 1;
|
||||
}
|
||||
|
||||
private function updateFileCleanly($json, array $base, array $new, $requireKey)
|
||||
{
|
||||
$contents = file_get_contents($json->getPath());
|
||||
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
|
||||
foreach ($new as $package => $constraint) {
|
||||
if (!$manipulator->addLink($requireKey, $package, $constraint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ namespace Composer\Command;
|
|||
use Composer\Installer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
|
@ -28,6 +29,7 @@ class UpdateCommand extends Command
|
|||
->setName('update')
|
||||
->setDescription('Updates your dependencies to the latest version, and updates the composer.lock file.')
|
||||
->setDefinition(array(
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'),
|
||||
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
|
||||
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
|
||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
|
||||
|
@ -58,6 +60,7 @@ EOT
|
|||
->setDevMode($input->getOption('dev'))
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($input->getArgument('packages'))
|
||||
;
|
||||
|
||||
return $install->run() ? 0 : 1;
|
||||
|
|
|
@ -64,7 +64,6 @@ class Application extends BaseApplication
|
|||
*/
|
||||
public function doRun(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->registerCommands();
|
||||
$this->io = new ConsoleIO($input, $output, $this->getHelperSet());
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.3.2', '<')) {
|
||||
|
@ -106,21 +105,25 @@ class Application extends BaseApplication
|
|||
/**
|
||||
* Initializes all the composer commands
|
||||
*/
|
||||
protected function registerCommands()
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$this->add(new Command\AboutCommand());
|
||||
$this->add(new Command\DependsCommand());
|
||||
$this->add(new Command\InitCommand());
|
||||
$this->add(new Command\InstallCommand());
|
||||
$this->add(new Command\CreateProjectCommand());
|
||||
$this->add(new Command\UpdateCommand());
|
||||
$this->add(new Command\SearchCommand());
|
||||
$this->add(new Command\ValidateCommand());
|
||||
$this->add(new Command\ShowCommand());
|
||||
$commands = parent::getDefaultCommands();
|
||||
$commands[] = new Command\AboutCommand();
|
||||
$commands[] = new Command\DependsCommand();
|
||||
$commands[] = new Command\InitCommand();
|
||||
$commands[] = new Command\InstallCommand();
|
||||
$commands[] = new Command\CreateProjectCommand();
|
||||
$commands[] = new Command\UpdateCommand();
|
||||
$commands[] = new Command\SearchCommand();
|
||||
$commands[] = new Command\ValidateCommand();
|
||||
$commands[] = new Command\ShowCommand();
|
||||
$commands[] = new Command\RequireCommand();
|
||||
|
||||
if ('phar:' === substr(__FILE__, 0, 5)) {
|
||||
$this->add(new Command\SelfUpdateCommand());
|
||||
$commands[] = new Command\SelfUpdateCommand();
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,6 +62,11 @@ class Factory
|
|||
return $config;
|
||||
}
|
||||
|
||||
public function getComposerFile()
|
||||
{
|
||||
return getenv('COMPOSER') ?: 'composer.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Composer instance
|
||||
*
|
||||
|
@ -73,7 +78,7 @@ class Factory
|
|||
{
|
||||
// load Composer configuration
|
||||
if (null === $localConfig) {
|
||||
$localConfig = getenv('COMPOSER') ?: 'composer.json';
|
||||
$localConfig = $this->getComposerFile();
|
||||
}
|
||||
|
||||
if (is_string($localConfig)) {
|
||||
|
|
|
@ -89,6 +89,7 @@ class Installer
|
|||
protected $verbose = false;
|
||||
protected $update = false;
|
||||
protected $runScripts = true;
|
||||
protected $updateWhitelist = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
@ -219,6 +220,8 @@ class Installer
|
|||
$stabilityFlags = $this->locker->getStabilityFlags();
|
||||
}
|
||||
|
||||
$this->whitelistUpdateDependencies($localRepo, $devMode);
|
||||
|
||||
// creating repository pool
|
||||
$pool = new Pool($minimumStability, $stabilityFlags);
|
||||
$pool->addRepository($installedRepo);
|
||||
|
@ -275,8 +278,11 @@ class Installer
|
|||
// fix the version of all installed packages (+ platform) that are not
|
||||
// in the current local repo to prevent rogue updates (e.g. non-dev
|
||||
// updating when in dev)
|
||||
//
|
||||
// if the updateWhitelist is enabled, packages not in it are also fixed
|
||||
// to their currently installed version
|
||||
foreach ($installedRepo->getPackages() as $package) {
|
||||
if ($package->getRepository() === $localRepo) {
|
||||
if ($package->getRepository() === $localRepo && (!$this->updateWhitelist || $this->isUpdateable($package))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -331,6 +337,11 @@ class Installer
|
|||
} else {
|
||||
// force update to latest on update
|
||||
if ($this->update) {
|
||||
// skip package if the whitelist is enabled and it is not in it
|
||||
if ($this->updateWhitelist && !$this->isUpdateable($package)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newPackage = $this->repositoryManager->findPackage($package->getName(), $package->getVersion());
|
||||
if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
|
||||
$operations[] = new UpdateOperation($package, $newPackage);
|
||||
|
@ -441,6 +452,64 @@ class Installer
|
|||
return $aliases;
|
||||
}
|
||||
|
||||
private function isUpdateable(PackageInterface $package)
|
||||
{
|
||||
if (!$this->updateWhitelist) {
|
||||
throw new \LogicException('isUpdateable should only be called when a whitelist is present');
|
||||
}
|
||||
|
||||
return isset($this->updateWhitelist[$package->getName()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all dependencies of the update whitelist to the whitelist, too.
|
||||
*
|
||||
* @param RepositoryInterface $localRepo
|
||||
* @param boolean $devMode
|
||||
*/
|
||||
private function whitelistUpdateDependencies($localRepo, $devMode)
|
||||
{
|
||||
if (!$this->updateWhitelist) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pool = new Pool;
|
||||
$pool->addRepository($localRepo);
|
||||
|
||||
$seen = array();
|
||||
|
||||
foreach ($this->updateWhitelist as $packageName => $void) {
|
||||
$packageQueue = new \SplQueue;
|
||||
|
||||
foreach ($pool->whatProvides($packageName) as $depPackage) {
|
||||
$packageQueue->enqueue($depPackage);
|
||||
}
|
||||
|
||||
while (!$packageQueue->isEmpty()) {
|
||||
$package = $packageQueue->dequeue();
|
||||
if (isset($seen[$package->getId()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[$package->getId()] = true;
|
||||
$this->updateWhitelist[$package->getName()] = true;
|
||||
|
||||
$requires = $package->getRequires();
|
||||
if ($devMode) {
|
||||
$requires = array_merge($requires, $package->getDevRequires());
|
||||
}
|
||||
|
||||
foreach ($requires as $require) {
|
||||
$requirePackages = $pool->whatProvides($require->getTarget());
|
||||
|
||||
foreach ($requirePackages as $requirePackage) {
|
||||
$packageQueue->enqueue($requirePackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Installer
|
||||
*
|
||||
|
@ -551,4 +620,18 @@ class Installer
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* restrict the update operation to a few packages, all other packages
|
||||
* that are already installed will be kept at their current version
|
||||
*
|
||||
* @param array $packages
|
||||
* @return Installer
|
||||
*/
|
||||
public function setUpdateWhitelist(array $packages)
|
||||
{
|
||||
$this->updateWhitelist = array_flip(array_map('strtolower', $packages));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
<?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\Json;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class JsonManipulator
|
||||
{
|
||||
private $contents;
|
||||
private $newline;
|
||||
private $indent;
|
||||
|
||||
public function __construct($contents)
|
||||
{
|
||||
$contents = trim($contents);
|
||||
if (!preg_match('#^\{(.*)\}$#s', $contents)) {
|
||||
throw new \InvalidArgumentException('The json file must be an object ({})');
|
||||
}
|
||||
$this->newline = false !== strpos("\r\n", $contents) ? "\r\n": "\n";
|
||||
$this->contents = $contents;
|
||||
$this->detectIndenting();
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
return $this->contents . $this->newline;
|
||||
}
|
||||
|
||||
public function addLink($type, $package, $constraint)
|
||||
{
|
||||
// no link of that type yet
|
||||
if (!preg_match('#"'.$type.'":\s*\{#', $this->contents)) {
|
||||
$this->addMainKey($type, $this->format(array($package => $constraint)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$linksRegex = '#("'.$type.'":\s*\{)([^}]+)(\})#s';
|
||||
if (!preg_match($linksRegex, $this->contents, $match)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$links = $match[2];
|
||||
$packageRegex = str_replace('/', '\\\\?/', preg_quote($package));
|
||||
|
||||
// link exists already
|
||||
if (preg_match('{"'.$packageRegex.'"\s*:}i', $links)) {
|
||||
$links = preg_replace('{"'.$packageRegex.'"(\s*:\s*)"[^"]+"}i', JsonFile::encode($package).'$1"'.$constraint.'"', $links);
|
||||
} elseif (preg_match('#[^\s](\s*)$#', $links, $match)) {
|
||||
// link missing but non empty links
|
||||
$links = preg_replace(
|
||||
'#'.$match[1].'$#',
|
||||
',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1],
|
||||
$links
|
||||
);
|
||||
} else {
|
||||
// links empty
|
||||
$links = $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $links;
|
||||
}
|
||||
|
||||
$this->contents = preg_replace($linksRegex, '$1'.$links.'$3', $this->contents);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addMainKey($key, $content)
|
||||
{
|
||||
if (preg_match('#[^{\s](\s*)\}$#', $this->contents, $match)) {
|
||||
$this->contents = preg_replace(
|
||||
'#'.$match[1].'\}$#',
|
||||
',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}',
|
||||
$this->contents
|
||||
);
|
||||
} else {
|
||||
$this->contents = preg_replace(
|
||||
'#\}$#',
|
||||
$this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}',
|
||||
$this->contents
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function format($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
reset($data);
|
||||
|
||||
if (is_numeric(key($data))) {
|
||||
return '['.implode(', ', $data).']';
|
||||
}
|
||||
|
||||
$out = '{' . $this->newline;
|
||||
foreach ($data as $key => $val) {
|
||||
$elems[] = $this->indent . $this->indent . JsonFile::encode($key). ': '.$this->format($val);
|
||||
}
|
||||
return $out . implode(','.$this->newline, $elems) . $this->newline . $this->indent . '}';
|
||||
}
|
||||
|
||||
return JsonFile::encode($data);
|
||||
}
|
||||
|
||||
protected function detectIndenting()
|
||||
{
|
||||
if (preg_match('{^(\s+)"}', $this->contents, $match)) {
|
||||
$this->indent = $match[1];
|
||||
} else {
|
||||
$this->indent = ' ';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,5 +10,7 @@
|
|||
<installed.json file definition>
|
||||
--INSTALLED:DEV--
|
||||
<installed_dev.json file definition>
|
||||
--EXPECT-- or --EXPECT:UPDATE-- or --EXPECT:DEV-- or --EXPECT:UPDATE:DEV--
|
||||
--RUN--
|
||||
install
|
||||
--EXPECT--
|
||||
<output (stringified operations)>
|
|
@ -42,6 +42,8 @@ Aliases take precedence over default package even if default is selected
|
|||
"a/req": "dev-feature-foo as dev-master"
|
||||
}
|
||||
}
|
||||
--RUN--
|
||||
install
|
||||
--EXPECT--
|
||||
Marking a/req (dev-master feat.f) as installed, alias of a/req (dev-feature-foo feat.f)
|
||||
Installing a/req (dev-feature-foo feat.f)
|
||||
|
|
|
@ -44,6 +44,8 @@ Aliases take precedence over default package
|
|||
"a/c": "dev-feature-foo as dev-master"
|
||||
}
|
||||
}
|
||||
--RUN--
|
||||
install
|
||||
--EXPECT--
|
||||
Installing a/b (dev-master forked)
|
||||
Marking a/b (1.0.x-dev forked) as installed, alias of a/b (dev-master forked)
|
||||
|
|
|
@ -18,6 +18,8 @@ Installs a package in dev env
|
|||
"a/b": "1.0.0"
|
||||
}
|
||||
}
|
||||
--EXPECT:DEV--
|
||||
--RUN--
|
||||
install --dev
|
||||
--EXPECT--
|
||||
Installing a/a (1.0.0)
|
||||
Installing a/b (1.0.0)
|
|
@ -17,5 +17,7 @@ Installs a dev package forcing it's reference
|
|||
"a/a": "dev-master#def000"
|
||||
}
|
||||
}
|
||||
--RUN--
|
||||
install
|
||||
--EXPECT--
|
||||
Installing a/a (dev-master def000)
|
||||
|
|
|
@ -14,5 +14,7 @@ Installs a simple package with exact match requirement
|
|||
"a/a": "1.0.0"
|
||||
}
|
||||
}
|
||||
--RUN--
|
||||
install
|
||||
--EXPECT--
|
||||
Installing a/a (1.0.0)
|
|
@ -36,6 +36,8 @@ Updates updateable packages
|
|||
[
|
||||
{ "name": "a/b", "version": "1.0.0" }
|
||||
]
|
||||
--EXPECT:UPDATE:DEV--
|
||||
--RUN--
|
||||
update --dev
|
||||
--EXPECT--
|
||||
Updating a/a (1.0.0) to a/a (1.0.1)
|
||||
Updating a/b (1.0.0) to a/b (2.0.0)
|
|
@ -24,5 +24,7 @@ Updates a dev package forcing it's reference
|
|||
"source": { "reference": "abc123", "url": "", "type": "git" }
|
||||
}
|
||||
]
|
||||
--RUN--
|
||||
install
|
||||
--EXPECT--
|
||||
Updating a/a (dev-master abc123) to a/a (dev-master def000)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
--TEST--
|
||||
Update with a package whitelist only updates those packages and their dependencies if they are not present in composer.json
|
||||
--COMPOSER--
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "fixed", "version": "1.1.0" },
|
||||
{ "name": "fixed", "version": "1.0.0" },
|
||||
{ "name": "whitelisted", "version": "1.1.0", "require": { "dependency": "1.1.0" } },
|
||||
{ "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
|
||||
{ "name": "dependency", "version": "1.1.0" },
|
||||
{ "name": "dependency", "version": "1.0.0" },
|
||||
{ "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } },
|
||||
{ "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
|
||||
{ "name": "unrelated-dependency", "version": "1.1.0" },
|
||||
{ "name": "unrelated-dependency", "version": "1.0.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"fixed": "1.*",
|
||||
"whitelisted": "1.*",
|
||||
"unrelated": "1.*"
|
||||
}
|
||||
}
|
||||
--INSTALLED--
|
||||
[
|
||||
{ "name": "fixed", "version": "1.0.0" },
|
||||
{ "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
|
||||
{ "name": "dependency", "version": "1.0.0" },
|
||||
{ "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
|
||||
{ "name": "unrelated-dependency", "version": "1.0.0" }
|
||||
]
|
||||
--RUN--
|
||||
update whitelisted
|
||||
--EXPECT--
|
||||
Updating dependency (1.0.0) to dependency (1.1.0)
|
||||
Updating whitelisted (1.0.0) to whitelisted (1.1.0)
|
|
@ -12,6 +12,7 @@
|
|||
namespace Composer\Test;
|
||||
|
||||
use Composer\Installer;
|
||||
use Composer\Console\Application;
|
||||
use Composer\Config;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
|
@ -24,6 +25,8 @@ use Composer\Test\Mock\FactoryMock;
|
|||
use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
|
||||
use Composer\Test\Mock\InstallationManagerMock;
|
||||
use Composer\Test\Mock\WritableRepositoryMock;
|
||||
use Symfony\Component\Console\Input\StringInput;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
|
||||
class InstallerTest extends TestCase
|
||||
{
|
||||
|
@ -121,7 +124,7 @@ class InstallerTest extends TestCase
|
|||
/**
|
||||
* @dataProvider getIntegrationTests
|
||||
*/
|
||||
public function testIntegration($file, $message, $condition, $composer, $lock, $installed, $installedDev, $update, $dev, $expect)
|
||||
public function testIntegration($file, $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expect)
|
||||
{
|
||||
if ($condition) {
|
||||
eval('$res = '.$condition.';');
|
||||
|
@ -177,14 +180,31 @@ class InstallerTest extends TestCase
|
|||
$autoloadGenerator
|
||||
);
|
||||
|
||||
$installer->setDevMode($dev)->setUpdate($update);
|
||||
$application = new Application;
|
||||
$application->get('install')->setCode(function ($input, $output) use ($installer) {
|
||||
$installer->setDevMode($input->getOption('dev'));
|
||||
|
||||
$result = $installer->run();
|
||||
$this->assertTrue($result, $output);
|
||||
return $installer->run();
|
||||
});
|
||||
|
||||
$expectedInstalled = isset($options['install']) ? $options['install'] : array();
|
||||
$expectedUpdated = isset($options['update']) ? $options['update'] : array();
|
||||
$expectedUninstalled = isset($options['uninstall']) ? $options['uninstall'] : array();
|
||||
$application->get('update')->setCode(function ($input, $output) use ($installer) {
|
||||
$installer
|
||||
->setDevMode($input->getOption('dev'))
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($input->getArgument('packages'));
|
||||
|
||||
return $installer->run();
|
||||
});
|
||||
|
||||
if (!preg_match('{^(install|update)\b}', $run)) {
|
||||
throw new \UnexpectedValueException('The run command only supports install and update');
|
||||
}
|
||||
|
||||
$application->setAutoExit(false);
|
||||
$appOutput = fopen('php://memory', 'w+');
|
||||
$result = $application->run(new StringInput($run), new StreamOutput($appOutput));
|
||||
fseek($appOutput, 0);
|
||||
$this->assertEquals(0, $result, $output . stream_get_contents($appOutput));
|
||||
|
||||
$installationManager = $composer->getInstallationManager();
|
||||
$this->assertSame($expect, implode("\n", $installationManager->getTrace()));
|
||||
|
@ -210,7 +230,8 @@ class InstallerTest extends TestCase
|
|||
(?:--LOCK--\s*(?P<lock>'.$content.'))?\s*
|
||||
(?:--INSTALLED--\s*(?P<installed>'.$content.'))?\s*
|
||||
(?:--INSTALLED:DEV--\s*(?P<installedDev>'.$content.'))?\s*
|
||||
--EXPECT(?P<update>:UPDATE)?(?P<dev>:DEV)?--\s*(?P<expect>.*?)\s*
|
||||
--RUN--\s*(?P<run>.*?)\s*
|
||||
--EXPECT--\s*(?P<expect>.*?)\s*
|
||||
$}xs';
|
||||
|
||||
$installed = array();
|
||||
|
@ -231,8 +252,7 @@ class InstallerTest extends TestCase
|
|||
if (!empty($match['installedDev'])) {
|
||||
$installedDev = JsonFile::parseJson($match['installedDev']);
|
||||
}
|
||||
$update = !empty($match['update']);
|
||||
$dev = !empty($match['dev']);
|
||||
$run = $match['run'];
|
||||
$expect = $match['expect'];
|
||||
} catch (\Exception $e) {
|
||||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||
|
@ -241,7 +261,7 @@ class InstallerTest extends TestCase
|
|||
die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file)));
|
||||
}
|
||||
|
||||
$tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $update, $dev, $expect);
|
||||
$tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expect);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
<?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\Json;
|
||||
|
||||
use Composer\Json\JsonManipulator;
|
||||
|
||||
class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider linkProvider
|
||||
*/
|
||||
public function testAddLink($json, $type, $package, $constraint, $expected)
|
||||
{
|
||||
$manipulator = new JsonManipulator($json);
|
||||
$this->assertTrue($manipulator->addLink($type, $package, $constraint));
|
||||
$this->assertEquals($expected, $manipulator->getContents());
|
||||
}
|
||||
|
||||
public function linkProvider()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'{
|
||||
}',
|
||||
'require',
|
||||
'vendor/baz',
|
||||
'qux',
|
||||
'{
|
||||
"require": {
|
||||
"vendor/baz": "qux"
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
array(
|
||||
'{
|
||||
"foo": "bar"
|
||||
}',
|
||||
'require',
|
||||
'vendor/baz',
|
||||
'qux',
|
||||
'{
|
||||
"foo": "bar",
|
||||
"require": {
|
||||
"vendor/baz": "qux"
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
array(
|
||||
'{
|
||||
"require": {
|
||||
}
|
||||
}',
|
||||
'require',
|
||||
'vendor/baz',
|
||||
'qux',
|
||||
'{
|
||||
"require": {
|
||||
"vendor/baz": "qux"
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
array(
|
||||
'{
|
||||
"require": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}',
|
||||
'require',
|
||||
'vendor/baz',
|
||||
'qux',
|
||||
'{
|
||||
"require": {
|
||||
"foo": "bar",
|
||||
"vendor/baz": "qux"
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
array(
|
||||
'{
|
||||
"require":
|
||||
{
|
||||
"foo": "bar",
|
||||
"vendor/baz": "baz"
|
||||
}
|
||||
}',
|
||||
'require',
|
||||
'vendor/baz',
|
||||
'qux',
|
||||
'{
|
||||
"require":
|
||||
{
|
||||
"foo": "bar",
|
||||
"vendor/baz": "qux"
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
array(
|
||||
'{
|
||||
"require":
|
||||
{
|
||||
"foo": "bar",
|
||||
"vendor\/baz": "baz"
|
||||
}
|
||||
}',
|
||||
'require',
|
||||
'vendor/baz',
|
||||
'qux',
|
||||
'{
|
||||
"require":
|
||||
{
|
||||
"foo": "bar",
|
||||
"vendor/baz": "qux"
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue