Merge remote-tracking branch 'github-seldaek/stability'

* github-seldaek/stability:
  Add stability docs
  Add test for Pool handling and refactor a couple things
  Add support for stabilities in lock file
  Infer stability flags for requirements that have an explicit version required of a lower stability
  Add InstalledArrayRepository to make sure the root package is not purged by the Pool because of a lower stability
  Basic handling of stability flags
  Add list of stabilities to base package
  Add minimum-stability flag on root package to filter packages by stability
  Add CompositeRepo::getRepositories
  Add package stability
main
Nils Adermann 12 years ago
commit 7c7cac61b6

@ -200,22 +200,10 @@ An example:
Optional, but highly recommended.
### Package links <span>(require, require-dev, conflict, replace, provide)</span>
Each of these takes an object which maps package names to version constraints.
* **require:** Packages required by this package.
* **require-dev:** Packages required for developing this package, or running
tests, etc. They are installed if install or update is ran with `--dev`.
* **conflict:** Mark this version of this package as conflicting with other
packages.
* **replace:** Packages that can be replaced by this package. This is useful
for large repositories with subtree splits. It allows the main package to
replace all of it's child packages.
* **provide:** List of other packages that are provided by this package. This
is mostly useful for common interfaces. A package could depend on some virtual
`logger` package, any library that provides this logger, would simply list it
in `provide`.
### Package links
All of the following take an object which maps package names to
[version constraints](01-basic-usage.md#package-versions).
Example:
@ -225,7 +213,59 @@ Example:
}
}
Optional.
All links are optional fields.
`require` and `require-dev` additionally support stability flags (root-only).
These allow you to further restrict or expand the stability of a package beyond
the scope of the [minimum-stability](#minimum-stability) setting. You can apply
them to a constraint, or just apply them to an empty constraint if you want to
allow unstable packages of a dependency's dependency for example.
Example:
{
"require": {
"monolog/monolog": "1.0.*@beta"
"acme/foo": "@dev"
}
}
#### require
Lists packages required by this package. The package will not be installed
unless those requirements can be met.
#### require-dev
Lists packages required for developing this package, or running
tests, etc. They are installed if install or update is ran with `--dev`.
#### conflict
Lists packages that conflict with this version of this package. They
will not be allowed to be installed together with your package.
#### replace
Lists packages that are replaced by this package.
This is useful for packages that contain sub-packages, for example the main
symfony/symfony package contains all the Symfony Components which are also
available as individual packages. If you require the main package it will
automatically fulfill any requirement of one of the individual components,
since it replaces them.
Caution is advised when using replace however, for the sub-package example
above you should typically only replace using `self.version` as a version
constraint, to make sure the main package only replaces the sub-packages of
that exact version, and not any other version, which would be incorrect.
#### provide
List of other packages that are provided by this package. This is mostly
useful for common interfaces. A package could depend on some virtual
`logger` package, any library that implements this logger interface would
simply list it in `provide`.
### suggest
@ -340,6 +380,19 @@ To do that, `autoload` and `target-dir` are defined as follows:
Optional.
### minimum-stability <span>(root-only)</span>
This defines the default behavior for filtering packages by stability. This
defaults to `dev` but in the future will be switched to `stable`. As such if
you rely on a default of `dev` you should specify it in your file to avoid
surprises.
All versions of each package is checked for stability, and those that are less
stable than the `minimum-stability` setting will be ignored when resolving
your project dependencies. Specific changes to the stability requirements of
a given package can be done in `require` or `require-dev` (see
[package links](#package-links)).
### repositories <span>(root-only)</span>
Custom package repositories to use.

@ -143,6 +143,10 @@
"description": "A set of additional repositories where packages can be found.",
"additionalProperties": true
},
"minimum-stability": {
"type": ["string"],
"description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable."
},
"bin": {
"type": ["array"],
"description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).",

@ -12,19 +12,39 @@
namespace Composer\DependencyResolver;
use Composer\Package\BasePackage;
use Composer\Package\LinkConstraint\LinkConstraintInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\PlatformRepository;
/**
* A package pool contains repositories that provide packages.
*
* @author Nils Adermann <naderman@naderman.de>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class Pool
{
protected $repositories = array();
protected $packages = array();
protected $packageByName = array();
protected $acceptableStabilities;
protected $stabilityFlags;
// TODO BC change to stable end of june?
public function __construct($minimumStability = 'dev', array $stabilityFlags = array())
{
$stabilities = BasePackage::$stabilities;
$this->acceptableStabilities = array();
foreach (BasePackage::$stabilities as $stability => $value) {
if ($value <= BasePackage::$stabilities[$minimumStability]) {
$this->acceptableStabilities[$stability] = $value;
}
}
$this->stabilityFlags = $stabilityFlags;
}
/**
* Adds a repository and its packages to this package pool
@ -33,14 +53,38 @@ class Pool
*/
public function addRepository(RepositoryInterface $repo)
{
$this->repositories[] = $repo;
foreach ($repo->getPackages() as $package) {
$package->setId(count($this->packages) + 1);
$this->packages[] = $package;
if ($repo instanceof CompositeRepository) {
$repos = $repo->getRepositories();
} else {
$repos = array($repo);
}
foreach ($package->getNames() as $name) {
$this->packageByName[$name][] = $package;
$id = count($this->packages) + 1;
foreach ($repos as $repo) {
$this->repositories[] = $repo;
$exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
foreach ($repo->getPackages() as $package) {
$name = $package->getName();
$stability = $package->getStability();
if (
// always allow exempt repos
$exempt
// allow if package matches the global stability requirement and has no exception
|| (!isset($this->stabilityFlags[$name])
&& isset($this->acceptableStabilities[$stability]))
// allow if package matches the package-specific stability flag
|| (isset($this->stabilityFlags[$name])
&& BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$name]
)
) {
$package->setId($id++);
$this->packages[] = $package;
foreach ($package->getNames() as $name) {
$this->packageByName[$name][] = $package;
}
}
}
}
}

@ -29,6 +29,7 @@ use Composer\Package\Locker;
use Composer\Package\PackageInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
@ -139,7 +140,7 @@ class Installer
$repos = array_merge(
$this->repositoryManager->getLocalRepositories(),
array(
new ArrayRepository(array($this->package)),
new InstalledArrayRepository(array($this->package)),
new PlatformRepository(),
)
);
@ -179,7 +180,9 @@ class Installer
$updatedLock = $this->locker->setLockData(
$this->repositoryManager->getLocalRepository()->getPackages(),
$this->devMode ? $this->repositoryManager->getLocalDevRepository()->getPackages() : null,
$aliases
$aliases,
$this->package->getMinimumStability(),
$this->package->getStabilityFlags()
);
if ($updatedLock) {
$this->io->write('<info>Writing lock file</info>');
@ -201,13 +204,18 @@ class Installer
protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = false)
{
$minimumStability = $this->package->getMinimumStability();
$stabilityFlags = $this->package->getStabilityFlags();
// initialize locker to create aliased packages
if (!$this->update && $this->locker->isLocked($devMode)) {
$lockedPackages = $this->locker->getLockedPackages($devMode);
$minimumStability = $this->locker->getMinimumStability();
$stabilityFlags = $this->locker->getStabilityFlags();
}
// creating repository pool
$pool = new Pool;
$pool = new Pool($minimumStability, $stabilityFlags);
$pool->addRepository($installedRepo);
foreach ($this->repositoryManager->getRepositories() as $repository) {
$pool->addRepository($repository);

@ -28,6 +28,7 @@ class AliasPackage extends BasePackage
protected $dev;
protected $aliasOf;
protected $rootPackageAlias = false;
protected $stability;
protected $requires;
protected $conflicts;
@ -50,7 +51,8 @@ class AliasPackage extends BasePackage
$this->version = $version;
$this->prettyVersion = $prettyVersion;
$this->aliasOf = $aliasOf;
$this->dev = VersionParser::isDev($version);
$this->stability = VersionParser::parseStability($version);
$this->dev = $this->stability === 'dev';
// replace self.version dependencies
foreach (array('requires', 'devRequires') as $type) {
@ -91,6 +93,14 @@ class AliasPackage extends BasePackage
return $this->version;
}
/**
* {@inheritDoc}
*/
public function getStability()
{
return $this->stability;
}
/**
* {@inheritDoc}
*/

@ -32,6 +32,20 @@ abstract class BasePackage implements PackageInterface
'require-dev' => array('description' => 'requires (for development)', 'method' => 'devRequires'),
);
const STABILITY_STABLE = 0;
const STABILITY_RC = 5;
const STABILITY_BETA = 10;
const STABILITY_ALPHA = 15;
const STABILITY_DEV = 20;
public static $stabilities = array(
'stable' => self::STABILITY_STABLE,
'RC' => self::STABILITY_RC,
'beta' => self::STABILITY_BETA,
'alpha' => self::STABILITY_ALPHA,
'dev' => self::STABILITY_DEV,
);
protected $name;
protected $prettyName;

@ -12,6 +12,7 @@
namespace Composer\Package\Loader;
use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositoryManager;
use Composer\Util\ProcessExecutor;
@ -60,20 +61,22 @@ class RootPackageLoader extends ArrayLoader
$package = parent::load($config);
$aliases = array();
$stabilityFlags = array();
if (isset($config['require'])) {
$aliases = array();
foreach ($config['require'] as $reqName => $reqVersion) {
if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) {
$aliases[] = array(
'package' => strtolower($reqName),
'version' => $this->versionParser->normalize($match[1]),
'alias' => $match[2],
'alias_normalized' => $this->versionParser->normalize($match[2]),
);
}
}
$aliases = $this->extractAliases($config['require'], $aliases);
$stabilityFlags = $this->extractStabilityFlags($config['require'], $stabilityFlags);
}
if (isset($config['require-dev'])) {
$aliases = $this->extractAliases($config['require-dev'], $aliases);
$stabilityFlags = $this->extractStabilityFlags($config['require-dev'], $stabilityFlags);
}
$package->setAliases($aliases);
$package->setAliases($aliases);
$package->setStabilityFlags($stabilityFlags);
if (isset($config['minimum-stability'])) {
$package->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
}
if (isset($config['repositories'])) {
@ -95,4 +98,51 @@ class RootPackageLoader extends ArrayLoader
return $package;
}
private function extractAliases(array $requires, array $aliases)
{
foreach ($requires as $reqName => $reqVersion) {
if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) {
$aliases[] = array(
'package' => strtolower($reqName),
'version' => $this->versionParser->normalize($match[1]),
'alias' => $match[2],
'alias_normalized' => $this->versionParser->normalize($match[2]),
);
}
}
return $aliases;
}
private function extractStabilityFlags(array $requires, array $stabilityFlags)
{
$stabilities = BasePackage::$stabilities;
foreach ($requires as $reqName => $reqVersion) {
// parse explicit stability flags
if (preg_match('{^[^,\s]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
$name = strtolower($reqName);
$stability = $stabilities[VersionParser::normalizeStability($match[1])];
if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
continue;
}
$stabilityFlags[$name] = $stability;
continue;
}
// infer flags for requirements that have an explicit -dev or -beta version specified for example
if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
$name = strtolower($reqName);
$stability = $stabilities[$stabilityName];
if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
continue;
}
$stabilityFlags[$name] = $stability;
}
}
return $stabilityFlags;
}
}

@ -82,10 +82,10 @@ class Locker
*/
public function getLockedPackages($dev = false)
{
$lockList = $this->getLockData();
$lockData = $this->getLockData();
$packages = array();
$lockedPackages = $dev ? $lockList['packages-dev'] : $lockList['packages'];
$lockedPackages = $dev ? $lockData['packages-dev'] : $lockData['packages'];
$repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository();
foreach ($lockedPackages as $info) {
@ -128,22 +128,38 @@ class Locker
return $packages;
}
public function getMinimumStability()
{
$lockData = $this->getLockData();
// TODO BC change dev to stable end of june?
return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'dev';
}
public function getStabilityFlags()
{
$lockData = $this->getLockData();
return isset($lockData['stability-flags']) ? $lockData['stability-flags'] : array();
}
public function getAliases()
{
$lockList = $this->getLockData();
return isset($lockList['aliases']) ? $lockList['aliases'] : array();
$lockData = $this->getLockData();
return isset($lockData['aliases']) ? $lockData['aliases'] : array();
}
public function getLockData()
{
if (!$this->lockFile->exists()) {
throw new \LogicException('No lockfile found. Unable to read locked packages');
}
if (null !== $this->lockDataCache) {
return $this->lockDataCache;
}
if (!$this->lockFile->exists()) {
throw new \LogicException('No lockfile found. Unable to read locked packages');
}
return $this->lockDataCache = $this->lockFile->read();
}
@ -156,13 +172,15 @@ class Locker
*
* @return Boolean
*/
public function setLockData(array $packages, $devPackages, array $aliases)
public function setLockData(array $packages, $devPackages, array $aliases, $minimumStability, array $stabilityFlags)
{
$lock = array(
'hash' => $this->hash,
'packages' => null,
'packages-dev' => null,
'aliases' => $aliases,
'minimum-stability' => $minimumStability,
'stability-flags' => $stabilityFlags,
);
$lock['packages'] = $this->lockPackages($packages);

@ -48,6 +48,10 @@ class MemoryPackage extends BasePackage
protected $prettyAlias;
protected $dev;
// TODO BC change dev to stable end of june?
protected $minimumStability = 'dev';
protected $stabilityFlags = array();
protected $requires = array();
protected $conflicts = array();
protected $provides = array();
@ -71,7 +75,8 @@ class MemoryPackage extends BasePackage
$this->version = $version;
$this->prettyVersion = $prettyVersion;
$this->dev = VersionParser::isDev($version);
$this->stability = VersionParser::parseStability($version);
$this->dev = $this->stability === 'dev';
}
/**
@ -98,6 +103,14 @@ class MemoryPackage extends BasePackage
return $this->type ?: 'library';
}
/**
* {@inheritDoc}
*/
public function getStability()
{
return $this->stability;
}
/**
* @param string $targetDir
*/
@ -588,6 +601,42 @@ class MemoryPackage extends BasePackage
return $this->homepage;
}
/**
* Set the minimumStability
*
* @param string $minimumStability
*/
public function setMinimumStability($minimumStability)
{
$this->minimumStability = $minimumStability;
}
/**
* {@inheritDoc}
*/
public function getMinimumStability()
{
return $this->minimumStability;
}
/**
* Set the stabilityFlags
*
* @param array $stabilityFlags
*/
public function setStabilityFlags(array $stabilityFlags)
{
$this->stabilityFlags = $stabilityFlags;
}
/**
* {@inheritDoc}
*/
public function getStabilityFlags()
{
return $this->stabilityFlags;
}
/**
* Set the autoload mapping
*

@ -180,6 +180,13 @@ interface PackageInterface
*/
function getPrettyVersion();
/**
* Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
*
* @return string
*/
function getStability();
/**
* Returns the package license, e.g. MIT, BSD, GPL
*

@ -12,6 +12,7 @@
namespace Composer\Package\Version;
use Composer\Package\BasePackage;
use Composer\Package\LinkConstraint\MultiConstraint;
use Composer\Package\LinkConstraint\VersionConstraint;
@ -22,17 +23,37 @@ use Composer\Package\LinkConstraint\VersionConstraint;
*/
class VersionParser
{
private $modifierRegex = '[.-]?(?:(beta|RC|alpha|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
private static $modifierRegex = '[.-]?(?:(beta|RC|alpha|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
/**
* Checks if a version is dev or not
* Returns the stability of a version
*
* @param string $version
* @return Boolean
* @return string
*/
static public function isDev($version)
static public function parseStability($version)
{
return 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4);
if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
return 'dev';
}
preg_match('{'.self::$modifierRegex.'$}', $version, $match);
if (!empty($match[3])) {
return 'dev';
}
if (!empty($match[1]) && ($match[1] === 'beta' || $match[1] === 'alpha' || $match[1] === 'RC')) {
return $match[1];
}
return 'stable';
}
static public function normalizeStability($stability)
{
$stability = strtolower($stability);
return $stability === 'rc' ? 'RC' : $stability;
}
/**
@ -60,13 +81,13 @@ class VersionParser
}
// 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+)?'.self::$modifierRegex.'$}i', $version, $matches)) {
$version = $matches[1]
.(!empty($matches[2]) ? $matches[2] : '.0')
.(!empty($matches[3]) ? $matches[3] : '.0')
.(!empty($matches[4]) ? $matches[4] : '.0');
$index = 5;
} elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)'.$this->modifierRegex.'$}i', $version, $matches)) { // match date-based versioning
} elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)'.self::$modifierRegex.'$}i', $version, $matches)) { // match date-based versioning
$version = preg_replace('{\D}', '-', $matches[1]);
$index = 2;
}
@ -130,6 +151,10 @@ class VersionParser
*/
public function parseConstraints($constraints)
{
if (preg_match('{^([^,\s]*?)@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $constraints, $match)) {
$constraints = empty($match[1]) ? '*' : $match[1];
}
$constraints = preg_split('{\s*,\s*}', trim($constraints));
if (count($constraints) > 1) {

@ -36,6 +36,16 @@ class CompositeRepository implements RepositoryInterface
$this->repositories = $repositories;
}
/**
* Returns all the wrapped repositories
*
* @return array
*/
public function getRepositories()
{
return $this->repositories;
}
/**
* {@inheritdoc}
*/

@ -0,0 +1,42 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Repository;
use Composer\Json\JsonFile;
use Composer\Package\PackageInterface;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
/**
* Installed array repository.
*
* This is used for serving the RootPackage inside an in-memory InstalledRepository
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class InstalledArrayRepository extends ArrayRepository implements InstalledRepositoryInterface
{
/**
* {@inheritDoc}
*/
public function write()
{
}
/**
* {@inheritDoc}
*/
public function reload()
{
}
}

@ -14,6 +14,7 @@ namespace Composer\Test\DependencyResolver;
use Composer\DependencyResolver\Pool;
use Composer\Repository\ArrayRepository;
use Composer\Package\BasePackage;
use Composer\Test\TestCase;
class PoolTest extends TestCase
@ -31,6 +32,22 @@ class PoolTest extends TestCase
$this->assertEquals(array($package), $pool->whatProvides('foo'));
}
public function testPoolIgnoresIrrelevantPackages()
{
$pool = new Pool('stable', array('bar' => BasePackage::STABILITY_BETA));
$repo = new ArrayRepository;
$repo->addPackage($package = $this->getPackage('bar', '1'));
$repo->addPackage($betaPackage = $this->getPackage('bar', '1-beta'));
$repo->addPackage($alphaPackage = $this->getPackage('bar', '1-alpha'));
$repo->addPackage($package2 = $this->getPackage('foo', '1'));
$repo->addPackage($rcPackage2 = $this->getPackage('foo', '1rc'));
$pool->addRepository($repo);
$this->assertEquals(array($package, $betaPackage), $pool->whatProvides('bar'));
$this->assertEquals(array($package2), $pool->whatProvides('foo'));
}
/**
* @expectedException \RuntimeException
*/

@ -157,9 +157,11 @@ class LockerTest extends \PHPUnit_Framework_TestCase
),
'packages-dev' => array(),
'aliases' => array(),
'minimum-stability' => 'dev',
'stability-flags' => array(),
));
$locker->setLockData(array($package1, $package2), array(), array());
$locker->setLockData(array($package1, $package2), array(), array(), 'dev', array());
}
public function testLockBadPackages()
@ -177,7 +179,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('LogicException');
$locker->setLockData(array($package1), array(), array());
$locker->setLockData(array($package1), array(), array(), 'dev', array());
}
public function testIsFresh()

@ -106,6 +106,12 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
);
}
public function testParseConstraintsIgnoresStabilityFlag()
{
$parser = new VersionParser;
$this->assertSame((string) new VersionConstraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0@dev'));
}
/**
* @dataProvider simpleConstraints
*/
@ -195,21 +201,25 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
}
/**
* @dataProvider dataIsDev
* @dataProvider stabilityProvider
*/
public function testIsDev($expected, $version)
public function testParseStability($expected, $version)
{
$this->assertSame($expected, VersionParser::isDev($version));
$this->assertSame($expected, VersionParser::parseStability($version));
}
public function dataIsDev()
public function stabilityProvider()
{
return array(
array(false, '1.0'),
array(false, 'v2.0.*'),
array(false, '3.0dev'),
array(true, 'dev-master'),
array(true, '3.1.2-dev'),
array('stable', '1.0'),
array('dev', 'v2.0.x-dev'),
array('RC', '3.0-RC2'),
array('dev', 'dev-master'),
array('dev', '3.1.2-dev'),
array('stable', '3.1.2-pl2'),
array('stable', '3.1.2-patch'),
array('alpha', '3.1.2-alpha5'),
array('beta', '3.1.2-beta'),
);
}
}

Loading…
Cancel
Save