Merge pull request #329 from Seldaek/new_dev

[BC Break] New dev handling
main
Nils Adermann 13 years ago
commit 39aa5c0752

@ -30,6 +30,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\Solver;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
@ -113,6 +114,7 @@ EOT
} }
// creating requirements request // creating requirements request
$installFromLock = false;
$request = new Request($pool); $request = new Request($pool);
if ($update) { if ($update) {
$io->write('<info>Updating dependencies</info>'); $io->write('<info>Updating dependencies</info>');
@ -125,6 +127,7 @@ EOT
$request->install($link->getTarget(), $link->getConstraint()); $request->install($link->getTarget(), $link->getConstraint());
} }
} elseif ($composer->getLocker()->isLocked()) { } elseif ($composer->getLocker()->isLocked()) {
$installFromLock = true;
$io->write('<info>Installing from lock file</info>'); $io->write('<info>Installing from lock file</info>');
if (!$composer->getLocker()->isFresh()) { if (!$composer->getLocker()->isFresh()) {
@ -185,13 +188,59 @@ EOT
if (!$operations) { if (!$operations) {
$io->write('<info>Nothing to install/update</info>'); $io->write('<info>Nothing to install/update</info>');
} }
// force dev packages to be updated to latest reference on update
if ($update) {
foreach ($localRepo->getPackages() as $package) {
// skip non-dev packages
if (!$package->isDev()) {
continue;
}
// skip packages that will be updated/uninstalled
foreach ($operations as $operation) {
if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage())
|| ('uninstall' === $operation->getJobType() && $package === $operation->getPackage())
) {
continue 2;
}
}
// force update
$newPackage = $composer->getRepositoryManager()->findPackage($package->getName(), $package->getVersion());
if ($newPackage->getSourceReference() !== $package->getSourceReference()) {
$operations[] = new UpdateOperation($package, $newPackage);
}
}
}
foreach ($operations as $operation) { foreach ($operations as $operation) {
if ($verbose) { if ($verbose) {
$io->write((string) $operation); $io->write((string) $operation);
} }
if (!$dryRun) { if (!$dryRun) {
$eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation); $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
// if installing from lock, restore dev packages' references to their locked state
if ($installFromLock) {
$package = null;
if ('update' === $operation->getJobType()) {
$package = $operation->getTargetPackage();
} elseif ('install' === $operation->getJobType()) {
$package = $operation->getPackage();
}
if ($package && $package->isDev()) {
$lockData = $composer->getLocker()->getLockData();
foreach ($lockData['packages'] as $lockedPackage) {
if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) {
$package->setSourceReference($lockedPackage['source-reference']);
break;
}
}
}
}
$installationManager->execute($operation); $installationManager->execute($operation);
$eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation); $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
} }
} }

@ -125,14 +125,14 @@ class DownloadManager
$sourceType = $package->getSourceType(); $sourceType = $package->getSourceType();
$distType = $package->getDistType(); $distType = $package->getDistType();
if (!($preferSource && $sourceType) && $distType) { if (!$package->isDev() && !($preferSource && $sourceType) && $distType) {
$package->setInstallationSource('dist'); $package->setInstallationSource('dist');
} elseif ($sourceType) { } elseif ($sourceType) {
$package->setInstallationSource('source'); $package->setInstallationSource('source');
} elseif ($package->isDev()) {
throw new \InvalidArgumentException('Dev package '.$package.' must have a source specified');
} else { } else {
throw new \InvalidArgumentException( throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
'Package '.$package.' should have source or dist specified'
);
} }
$fs = new Filesystem(); $fs = new Filesystem();

@ -109,7 +109,9 @@ class LibraryInstaller implements InstallerInterface
$this->downloadManager->update($initial, $target, $downloadPath); $this->downloadManager->update($initial, $target, $downloadPath);
$this->installBinaries($target); $this->installBinaries($target);
$this->repository->removePackage($initial); $this->repository->removePackage($initial);
$this->repository->addPackage(clone $target); if (!$this->repository->hasPackage($target)) {
$this->repository->addPackage(clone $target);
}
} }
/** /**

@ -122,6 +122,8 @@ class ArrayLoader
$package->setSourceType($config['source']['type']); $package->setSourceType($config['source']['type']);
$package->setSourceUrl($config['source']['url']); $package->setSourceUrl($config['source']['url']);
$package->setSourceReference($config['source']['reference']); $package->setSourceReference($config['source']['reference']);
} elseif ($package->isDev()) {
throw new \UnexpectedValueException('Dev package '.$package.' must have a source specified');
} }
if (isset($config['dist'])) { if (isset($config['dist'])) {

@ -38,7 +38,7 @@ class RootPackageLoader extends ArrayLoader
$config['name'] = '__root__'; $config['name'] = '__root__';
} }
if (!isset($config['version'])) { if (!isset($config['version'])) {
$config['version'] = '1.0.0-dev'; $config['version'] = '1.0.0';
} }
$package = parent::load($config); $package = parent::load($config);

@ -69,11 +69,7 @@ class Locker
*/ */
public function getLockedPackages() public function getLockedPackages()
{ {
if (!$this->isLocked()) { $lockList = $this->getLockData();
throw new \LogicException('No lockfile found. Unable to read locked packages');
}
$lockList = $this->lockFile->read();
$packages = array(); $packages = array();
foreach ($lockList['packages'] as $info) { foreach ($lockList['packages'] as $info) {
$package = $this->repositoryManager->getLocalRepository()->findPackage($info['package'], $info['version']); $package = $this->repositoryManager->getLocalRepository()->findPackage($info['package'], $info['version']);
@ -95,6 +91,15 @@ class Locker
return $packages; return $packages;
} }
public function getLockData()
{
if (!$this->isLocked()) {
throw new \LogicException('No lockfile found. Unable to read locked packages');
}
return $this->lockFile->read();
}
/** /**
* Locks provided packages into lockfile. * Locks provided packages into lockfile.
* *
@ -116,7 +121,13 @@ class Locker
)); ));
} }
$lock['packages'][] = array('package' => $name, 'version' => $version); $spec = array('package' => $name, 'version' => $version);
if ($package->isDev()) {
$spec['source-reference'] = $package->getSourceReference();
}
$lock['packages'][] = $spec;
} }
$this->lockFile->write($lock); $this->lockFile->write($lock);

@ -41,6 +41,7 @@ class MemoryPackage extends BasePackage
protected $extra = array(); protected $extra = array();
protected $binaries = array(); protected $binaries = array();
protected $scripts = array(); protected $scripts = array();
protected $dev;
protected $requires = array(); protected $requires = array();
protected $conflicts = array(); protected $conflicts = array();
@ -63,6 +64,16 @@ class MemoryPackage extends BasePackage
$this->version = $version; $this->version = $version;
$this->prettyVersion = $prettyVersion; $this->prettyVersion = $prettyVersion;
$this->dev = 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4);
}
/**
* {@inheritDoc}
*/
public function isDev()
{
return $this->dev;
} }
/** /**

@ -68,6 +68,13 @@ interface PackageInterface
*/ */
function matches($name, LinkConstraintInterface $constraint); function matches($name, LinkConstraintInterface $constraint);
/**
* Returns whether the package is a development virtual package or a concrete one
*
* @return Boolean
*/
function isDev();
/** /**
* Returns the package type, e.g. library * Returns the package type, e.g. library
* *

@ -34,10 +34,15 @@ class VersionParser
{ {
$version = trim($version); $version = trim($version);
if (preg_match('{^(?:master|trunk|default)(?:[.-]?dev)?$}i', $version)) { // match master-like branches
if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
return '9999999-dev'; return '9999999-dev';
} }
if ('dev-' === strtolower(substr($version, 0, 4))) {
return strtolower($version);
}
// match classical versioning // match classical versioning
if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.$this->modifierRegex.'$}i', $version, $matches)) { if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.$this->modifierRegex.'$}i', $version, $matches)) {
$version = $matches[1] $version = $matches[1]
@ -53,7 +58,7 @@ class VersionParser
// add version modifiers if a version was matched // add version modifiers if a version was matched
if (isset($index)) { if (isset($index)) {
if (!empty($matches[$index])) { if (!empty($matches[$index])) {
$mod = array('{^pl?$}', '{^rc$}'); $mod = array('{^pl?$}i', '{^rc$}i');
$modNormalized = array('patch', 'RC'); $modNormalized = array('patch', 'RC');
$version .= '-'.preg_replace($mod, $modNormalized, strtolower($matches[$index])) $version .= '-'.preg_replace($mod, $modNormalized, strtolower($matches[$index]))
. (!empty($matches[$index+1]) ? $matches[$index+1] : ''); . (!empty($matches[$index+1]) ? $matches[$index+1] : '');
@ -97,7 +102,7 @@ class VersionParser
return str_replace('x', '9999999', $version).'-dev'; return str_replace('x', '9999999', $version).'-dev';
} }
throw new \UnexpectedValueException('Invalid branch name '.$name); return 'dev-'.$name;
} }
/** /**

@ -76,20 +76,22 @@ class VcsRepository extends ArrayRepository
} }
foreach ($driver->getTags() as $tag => $identifier) { foreach ($driver->getTags() as $tag => $identifier) {
$this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)', false); $msg = 'Get composer info for <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)';
if ($debug) {
$this->io->write($msg);
} else {
$this->io->overwrite($msg, false);
}
$parsedTag = $this->validateTag($versionParser, $tag); $parsedTag = $this->validateTag($versionParser, $tag);
if ($parsedTag && $driver->hasComposerFile($identifier)) { if ($parsedTag && $driver->hasComposerFile($identifier)) {
try { try {
$data = $driver->getComposerInformation($identifier); $data = $driver->getComposerInformation($identifier);
} catch (\Exception $e) { } catch (\Exception $e) {
if (strpos($e->getMessage(), 'JSON Parse Error') !== false) { if ($debug) {
if ($debug) { $this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
$this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
}
continue;
} else {
throw $e;
} }
continue;
} }
// manually versioned package // manually versioned package
@ -103,7 +105,7 @@ class VcsRepository extends ArrayRepository
// make sure tag packages have no -dev flag // make sure tag packages have no -dev flag
$data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']); $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
$data['version_normalized'] = preg_replace('{[.-]?dev$}i', '', $data['version_normalized']); $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
// broken package, version doesn't match tag // broken package, version doesn't match tag
if ($data['version_normalized'] !== $parsedTag) { if ($data['version_normalized'] !== $parsedTag) {
@ -126,39 +128,33 @@ class VcsRepository extends ArrayRepository
$this->io->overwrite('', false); $this->io->overwrite('', false);
foreach ($driver->getBranches() as $branch => $identifier) { foreach ($driver->getBranches() as $branch => $identifier) {
$this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)', false); $msg = 'Get composer info for <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)';
if ($debug) {
$this->io->write($msg);
} else {
$this->io->overwrite($msg, false);
}
$parsedBranch = $this->validateBranch($versionParser, $branch); $parsedBranch = $this->validateBranch($versionParser, $branch);
if ($driver->hasComposerFile($identifier)) { if ($driver->hasComposerFile($identifier)) {
$data = $driver->getComposerInformation($identifier); $data = $driver->getComposerInformation($identifier);
// manually versioned package if (!$parsedBranch) {
if (isset($data['version'])) {
$data['version_normalized'] = $versionParser->normalize($data['version']);
} elseif ($parsedBranch) {
// auto-versionned package, read value from branch name
$data['version'] = $branch;
$data['version_normalized'] = $parsedBranch;
} else {
if ($debug) { if ($debug) {
$this->io->write('Skipped branch '.$branch.', invalid name and no composer file was found'); $this->io->write('Skipped branch '.$branch.', invalid name and no composer file was found');
} }
continue; continue;
} }
// make sure branch packages have a -dev flag // branches are always auto-versionned, read value from branch name
$normalizedStableVersion = preg_replace('{[.-]?dev$}i', '', $data['version_normalized']); $data['version'] = $branch;
$data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']) . '-dev'; $data['version_normalized'] = $parsedBranch;
$data['version_normalized'] = $normalizedStableVersion . '-dev';
// Skip branches that contain a version that has been tagged already // make sure branch packages have a dev flag
foreach ($this->getPackages() as $package) { if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
if ($normalizedStableVersion === $package->getVersion()) { $data['version'] = 'dev-' . $data['version'];
if ($debug) { } else {
$this->io->write('Skipped branch '.$branch.', already tagged'); $data['version'] = $data['version'] . '-dev';
}
continue 2;
}
} }
if ($debug) { if ($debug) {

@ -67,9 +67,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
->method('getPackages') ->method('getPackages')
->will($this->returnValue(array($this->packages[0]))); ->will($this->returnValue(array($this->packages[0])));
$this->repository $this->repository
->expects($this->once()) ->expects($this->exactly(2))
->method('hasPackage') ->method('hasPackage')
->will($this->returnValue(true)); ->will($this->onConsecutiveCalls(true, false));
$installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im); $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
$test = $this; $test = $this;
@ -90,9 +90,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
->method('getPackages') ->method('getPackages')
->will($this->returnValue(array($this->packages[1]))); ->will($this->returnValue(array($this->packages[1])));
$this->repository $this->repository
->expects($this->once()) ->expects($this->exactly(2))
->method('hasPackage') ->method('hasPackage')
->will($this->returnValue(true)); ->will($this->onConsecutiveCalls(true, false));
$installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im); $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
$test = $this; $test = $this;

@ -128,10 +128,9 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue('package1')); ->will($this->returnValue('package1'));
$this->repository $this->repository
->expects($this->exactly(2)) ->expects($this->exactly(3))
->method('hasPackage') ->method('hasPackage')
->with($initial) ->will($this->onConsecutiveCalls(true, false, false));
->will($this->onConsecutiveCalls(true, false));
$this->dm $this->dm
->expects($this->once()) ->expects($this->once())

@ -49,9 +49,10 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'parses datetime' => array('20100102-203040', '20100102-203040'), 'parses datetime' => array('20100102-203040', '20100102-203040'),
'parses dt+number' => array('20100102203040-10', '20100102203040-10'), 'parses dt+number' => array('20100102203040-10', '20100102203040-10'),
'parses dt+patch' => array('20100102-203040-p1', '20100102-203040-patch1'), 'parses dt+patch' => array('20100102-203040-p1', '20100102-203040-patch1'),
'parses master' => array('master', '9999999-dev'), 'parses master' => array('dev-master', '9999999-dev'),
'parses trunk' => array('trunk', '9999999-dev'), 'parses trunk' => array('dev-trunk', '9999999-dev'),
'parses trunk/2' => array('trunk-dev', '9999999-dev'), 'parses arbitrary' => array('dev-feature-foo', 'dev-feature-foo'),
'parses arbitrary2' => array('DEV-FOOBAR', 'dev-foobar'),
); );
} }
@ -72,6 +73,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'invalid chars' => array('a'), 'invalid chars' => array('a'),
'invalid type' => array('1.0.0-meh'), 'invalid type' => array('1.0.0-meh'),
'too many bits' => array('1.0.0.0.0'), 'too many bits' => array('1.0.0.0.0'),
'non-dev arbitrary' => array('feature-foo'),
); );
} }
@ -97,6 +99,8 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'parses long digits/2' => array('2.4.4', '2.4.4.9999999-dev'), 'parses long digits/2' => array('2.4.4', '2.4.4.9999999-dev'),
'parses master' => array('master', '9999999-dev'), 'parses master' => array('master', '9999999-dev'),
'parses trunk' => array('trunk', '9999999-dev'), 'parses trunk' => array('trunk', '9999999-dev'),
'parses arbitrary' => array('feature-a', 'dev-feature-a'),
'parses arbitrary/2' => array('foobar', 'dev-foobar'),
); );
} }
@ -121,8 +125,9 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'no op means eq' => array('1.2.3', new VersionConstraint('=', '1.2.3.0')), 'no op means eq' => array('1.2.3', new VersionConstraint('=', '1.2.3.0')),
'completes version' => array('=1.0', new VersionConstraint('=', '1.0.0.0')), 'completes version' => array('=1.0', new VersionConstraint('=', '1.0.0.0')),
'accepts spaces' => array('>= 1.2.3', new VersionConstraint('>=', '1.2.3.0')), 'accepts spaces' => array('>= 1.2.3', new VersionConstraint('>=', '1.2.3.0')),
'accepts master' => array('>=master-dev', new VersionConstraint('>=', '9999999-dev')), 'accepts master' => array('>=dev-master', new VersionConstraint('>=', '9999999-dev')),
'accepts master/2' => array('master-dev', new VersionConstraint('=', '9999999-dev')), 'accepts master/2' => array('dev-master', new VersionConstraint('=', '9999999-dev')),
'accepts arbitrary' => array('dev-feature-a', new VersionConstraint('=', 'dev-feature-a')),
); );
} }

@ -0,0 +1,140 @@
<?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 Symfony\Component\Process\ExecutableFinder;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Repository\VcsRepository;
use Composer\Repository\Vcs\GitDriver;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;
use Composer\IO\NullIO;
class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
{
private static $gitRepo;
private static $skipped;
public static function setUpBeforeClass()
{
$oldCwd = getcwd();
self::$gitRepo = sys_get_temp_dir() . '/composer-git-'.rand().'/';
$locator = new ExecutableFinder();
if (!$locator->find('git')) {
self::$skipped = 'This test needs a git binary in the PATH to be able to run';
return;
}
if (!mkdir(self::$gitRepo) || !chdir(self::$gitRepo)) {
self::$skipped = 'Could not create and move into the temp git repo '.self::$gitRepo;
return;
}
// init
$process = new ProcessExecutor;
$process->execute('git init', $null);
touch('foo');
$process->execute('git add foo', $null);
$process->execute('git commit -m init', $null);
// non-composed tag & branch
$process->execute('git tag 0.5.0', $null);
$process->execute('git branch oldbranch', $null);
// add composed tag & master branch
$composer = array('name' => 'a/b');
file_put_contents('composer.json', json_encode($composer));
$process->execute('git add composer.json', $null);
$process->execute('git commit -m addcomposer', $null);
$process->execute('git tag 0.6.0', $null);
// add feature-a branch
$process->execute('git checkout -b feature-a', $null);
file_put_contents('foo', 'bar feature');
$process->execute('git add foo', $null);
$process->execute('git commit -m change-a', $null);
// add version to composer.json
$process->execute('git checkout master', $null);
$composer['version'] = '1.0.0';
file_put_contents('composer.json', json_encode($composer));
$process->execute('git add composer.json', $null);
$process->execute('git commit -m addversion', $null);
// create tag with wrong version in it
$process->execute('git tag 0.9.0', $null);
// create tag with correct version in it
$process->execute('git tag 1.0.0', $null);
// add feature-b branch
$process->execute('git checkout -b feature-b', $null);
file_put_contents('foo', 'baz feature');
$process->execute('git add foo', $null);
$process->execute('git commit -m change-b', $null);
// add 1.0 branch
$process->execute('git checkout master', $null);
$process->execute('git branch 1.0', $null);
// add 1.0.x branch
$process->execute('git branch 1.0.x', $null);
// update master to 2.0
$composer['version'] = '2.0.0';
file_put_contents('composer.json', json_encode($composer));
$process->execute('git add composer.json', $null);
$process->execute('git commit -m bump-version', $null);
chdir($oldCwd);
}
public function setUp()
{
if (self::$skipped) {
$this->markTestSkipped(self::$skipped);
}
}
public static function tearDownAfterClass()
{
$fs = new Filesystem;
$fs->removeDirectory(self::$gitRepo);
}
public function testLoadVersions()
{
$expected = array(
'0.6.0' => true,
'1.0.0' => true,
'1.0-dev' => true,
'1.0.x-dev' => true,
'dev-feature-b' => true,
'dev-feature-a' => true,
'dev-master' => true,
);
$repo = new VcsRepository(array('url' => self::$gitRepo), new NullIO);
$packages = $repo->getPackages();
$dumper = new ArrayDumper();
foreach ($packages as $package) {
if (isset($expected[$package->getPrettyVersion()])) {
unset($expected[$package->getPrettyVersion()]);
} else {
$this->fail('Unexpected version '.$package->getPrettyVersion().' in '.json_encode($dumper->dump($package)));
}
}
$this->assertEmpty($expected, 'Missing versions: '.implode(', ', array_keys($expected)));
}
}
Loading…
Cancel
Save