1
0
Fork 0
pull/151/head
digitalkaoz 2012-02-20 22:31:49 +01:00
commit 1cf92eb6c8
46 changed files with 2116 additions and 282 deletions

View File

@ -97,6 +97,10 @@ merged. This is to ensure proper review of all the code.
Fork the project, create a feature branch, and send us a pull request. Fork the project, create a feature branch, and send us a pull request.
To ensure a consistent code base, you should make sure the code follows
the [Coding Standards](http://symfony.com/doc/2.0/contributing/code/standards.html)
which we borrowed from Symfony.
If you would like to help take a look at the [list of issues](http://github.com/composer/composer/issues). If you would like to help take a look at the [list of issues](http://github.com/composer/composer/issues).
Community Community

View File

@ -19,9 +19,9 @@
], ],
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0",
"symfony/console": "2.1.0-dev", "symfony/console": "dev-master",
"symfony/finder": ">2.0,<2.2-dev", "symfony/finder": "dev-master",
"symfony/process": ">2.0,<2.2-dev" "symfony/process": "dev-master"
}, },
"recommend": { "recommend": {
"ext-zip": "*" "ext-zip": "*"

19
composer.lock generated
View File

@ -1,17 +1,20 @@
{ {
"hash": "9c243b2c15fdc7c3e35c5200d704ba53", "hash": "4ba2fad397e186b6bc453b4417c2ab00",
"packages": [ "packages": [
{ {
"package": "symfony\/process", "package": "symfony/console",
"version": "2.1.0-dev" "version": "dev-master",
"source-reference": "75ca31776bd98ad427f759cbe8a62400e40c73a1"
}, },
{ {
"package": "symfony\/finder", "package": "symfony/finder",
"version": "2.1.0-dev" "version": "dev-master",
"source-reference": "dd56fc9f1f0baa006d7491d5c17eb3e2dd8a066c"
}, },
{ {
"package": "symfony\/console", "package": "symfony/process",
"version": "2.1.0-dev" "version": "dev-master",
"source-reference": "f381eeee3733ca0fd374491fab56dce0f3ca8e34"
} }
] ]
} }

65
doc/00-intro.md Normal file
View File

@ -0,0 +1,65 @@
# Introduction
Composer is a tool for dependency management in PHP. It allows you to declare
the dependencies of your project and will install these dependencies for you.
## Dependency management
One important distinction to make is that composer is not a package manager. It
deals with packages, but it manages them on a per-project basis. By default it
will never install anything globally. Thus, it is a dependency manager.
This idea is not new by any means. Composer is strongly inspired by
node's [npm](http://npmjs.org/) and ruby's [bundler](http://gembundler.com/).
But there has not been such a tool for PHP so far.
The problem that composer solves is the following. You have a project that
depends on a number of libraries. Some of those libraries have dependencies of
their own. You declare the things you depend on. Composer will then go ahead
and find out which versions of which packages need to be installed, and
install them.
## Declaring dependencies
Let's say you are creating a project, and you need a library that does logging.
You decide to use [monolog](https://github.com/Seldaek/monolog). In order to
add it to your project, all you need to do is create a `composer.json` file
which describes the project's dependencies.
```json
{
"require": {
"monolog/monolog": "1.0.*"
}
}
```
We are simply stating that our project requires the `monolog/monolog` package,
any version beginning with `1.0`.
## Installation
To actually get it, we need to do two things. The first one is installing
composer:
$ curl -s http://getcomposer.org/installer | php
This will just check a few PHP settings and then download `composer.phar` to
your working directory. This is the composer binary.
After that we run the command for installing all dependencies:
$ php composer.phar install
This will download monolog and dump it into `vendor/monolog/monolog`.
## Autoloading
After this you can just add the following line to your bootstrap code to get
autoloading:
```php
require 'vendor/.composer/autoload.php';
```
That's all it takes to have a basic setup.

189
doc/01-basic-usage.md Normal file
View File

@ -0,0 +1,189 @@
# Basic usage
## Installation
To install composer, simply run this command on the command line:
$ curl -s http://getcomposer.org/installer | php
This will perform some checks on your environment to make sure you can
actually run it.
This will download `composer.phar` and place it in your working directory.
`composer.phar` is the composer binary. It is a PHAR (PHP archive), which
is an archive format for PHP which can be run on the command line, amongst
other things.
You can place this file anywhere you wish. If you put it in your `PATH`,
you can access it globally. On unixy systems you can even make it
executable and invoke it without `php`.
To check if composer is working, just run the PHAR through `php`:
$ php composer.phar
This should give you a list of available commands.
> **Note:** You can also perform the checks only without downloading composer
> by using the `--check` option. For more information, just use `--help`.
>
> $ curl -s http://getcomposer.org/installer | php -- --help
## Project setup
To start using composer in your project, all you need is a `composer.json`
file. This file describes the dependencies of your project and may contain
other metadata as well.
The [JSON format](http://json.org/) is quite easy to write. It allows you to
define nested structures.
The first (and often only) thing you specify in `composer.json` is the
`require` key. You're simply telling composer which packages your project
depends on.
```json
{
"require": {
"monolog/monolog": "1.0.*"
}
}
```
As you can see, `require` takes an object that maps package names to versions.
## Package names
The package name consists of a vendor name and the project's name. Often these
will be identical. The vendor name exists to prevent naming clashes. It allows
two different people to create a library named `json`, which would then just be
named `igorw/json` and `seldaek/json`.
Here we are requiring `monolog/monolog`, so the vendor name is the same as the
project's name. For projects with a unique name this is recommended. It also
allows adding more related projects under the same namespace later on. If you
are maintaining a library, this would make it really easy to split it up into
smaller decoupled parts.
## Package versions
We are also requiring the version `1.0.*` of monolog. This means any version
in the `1.0` development branch. It would match `1.0.0`, `1.0.2` and `1.0.20`.
Version constraints can be specified in a few different ways.
* **Exact version:** You can specify the exact version of a package, for
example `1.0.2`. This is not used very often, but can be useful.
* **Range:** By using comparison operators you can specify ranges of valid
versions. Valid operators are `>`, `>=`, `<`, `<=`. An example range would be
`>=1.0`. You can define multiple of these, separated by comma: `>=1.0,<2.0`.
* **Wildcard:** You can specify a pattern with a `*` wildcard. `1.0.*` is the
equivalent of `>=1.0,<1.1-dev`.
## Installing dependencies
To fetch the defined dependencies into the local project, you simply run the
`install` command of `composer.phar`.
$ php composer.phar install
This will find the latest version of `monolog/monolog` that matches the
supplied version constraint and download it into the the `vendor` directory.
It's a convention to put third party code into a directory named `vendor`.
In case of monolog it will put it into `vendor/monolog/monolog`.
**Tip:** If you are using git for your project, you probably want to add
`vendor` into your `.gitignore`. You really don't want to add all of that
code to your repository.
Another thing that the `install` command does is it adds a `composer.lock`
file into your project root.
## Lock file
After installing the dependencies, composer writes the list of the exact
versions it installed into a `composer.lock` file. This locks the project
to those specific versions.
**Commit your project's `composer.lock` into version control.**
The reason is that anyone who sets up the project should get the same version.
The `install` command will check if a lock file is present. If it is, it will
use the versions specified there. If not, it will resolve the dependencies and
create a lock file.
If any of the dependencies gets a new version, you can update to that version
by using the `update` command. This will fetch the latest matching versions and
also update the lock file.
$ php composer.phar update
## Packagist
[Packagist](http://packagist.org/) is the main composer repository. A composer
repository is basically a package source. A place where you can get packages
from. Packagist aims to be the central repository that everybody uses. This
means that you can automatically `require` any package that is available
there.
If you go to the [packagist website](http://packagist.org/) (packagist.org),
you can browse and search for packages.
Any open source project using composer should publish their packages on
packagist.
## Autoloading
For libraries that follow the [PSR-0](https://github.com/php-fig/fig-
standards/blob/master/accepted/PSR-0.md) naming standard, composer generates a
`vendor/.composer/autoload.php` file for autoloading. You can simply include
this file and you will get autoloading for free.
```php
require 'vendor/.composer/autoload.php';
```
This makes it really easy to use third party code, because you really just
have to add one line to `composer.json` and run `install`. For monolog, it
means that we can just start using classes from it, and they will be
autoloaded.
```php
$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Logger::WARNING));
$log->addWarning('Foo');
```
You can even add your own code to the autoloader by adding an `autoload` key
to `composer.json`.
```json
{
"autoload": {
"psr-0": {"Acme": "src/"}
}
}
```
This is a mapping from namespaces to directories. The `src` directory would be
in your project root. An example filename would be `src/Acme/Foo.php`
containing a `Acme\Foo` class.
After adding the `autoload` key, you have to re-run `install` to re-generate
the `vendor/.composer/autoload.php` file.
Including that file will also return the autoloader instance, so you can add
retrieve it and add more namespaces. This can be useful for autoloading
classes in a test suite, for example.
```php
$loader = require 'vendor/.composer/autoload.php';
$loader->add('Acme\Test', __DIR__);
```
> **Note:** Composer provides its own autoloader. If you don't want to use
that one, you can just include `vendor/.composer/autoload_namespaces.php`,
which returns an associative array mapping namespaces to directories.

168
doc/02-libraries.md Normal file
View File

@ -0,0 +1,168 @@
# Libraries
This chapter will tell you how to make your library installable through composer.
## Every project is a package
As soon as you have a `composer.json` in a directory, that directory is a
package. When you add a `require` to a project, you are making a package that
depends on other packages. The only difference between your project and
libraries is that your project is a package without a name.
In order to make that package installable you need to give it a name. You do
this by adding a `name` to `composer.json`:
```json
{
"name": "acme/hello-world",
"require": {
"monolog/monolog": "1.0.*"
}
}
```
In this case the project name is `acme/hello-world`, where `acme` is the
vendor name. Supplying a vendor name is mandatory.
> **Note:** If you don't know what to use as a vendor name, your GitHub
username is usually a good bet. While package names are case insensitive, the
convention is all lowercase and dashes for word separation.
## Specifying the version
You need to specify the version some way. Depending on the type of repository
you are using, it might be possible to omit it from `composer.json`, because
the repository is able to infer the version from elsewhere.
If you do want to specify it explicitly, you can just add a `version` field:
```json
{
"version": "1.0.0"
}
```
However if you are using git, svn or hg, you don't have to specify it.
Composer will detect versions as follows:
### Tags
For every tag that looks like a version, a package version of that tag will be
created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix for RC,
beta, alpha or patch.
Here are a few examples of valid tag names:
1.0.0
v1.0.0
1.10.5-RC1
v4.4.4beta2
v2.0.0-alpha
v2.0.4-p1
> **Note:** If you specify an explicit version in `composer.json`, the tag name must match the specified version.
### Branches
For every branch, a package development version will be created. If the branch
name looks like a version, the version will be `{branchname}-dev`. For example
a branch `2.0` will get a version `2.0-dev`. If the branch does not look like
a version, it will be `dev-{branchname}`. `master` results in a `dev-master`
version.
Here are some examples of version branch names:
1.0
1.*
1.1.x
1.1.*
> **Note:** When you install a dev version, it will install it from source.
See [Repositories] for more information.
## Lock file
For projects it is recommended to commit the `composer.lock` file into version
control. For libraries this is not the case. You do not want your library to
be tied to exact versions of the dependencies. It should work with any
compatible version, so make sure you specify your version constraints so that
they include all compatible versions.
**Do not commit your library's `composer.lock` into version control.**
If you are using git, add it to the `.gitignore`.
## Publishing to a VCS
Once you have a vcs repository (version control system, e.g. git) containing a
`composer.json` file, your library is already composer-installable. In this
example we will publish the `acme/hello-world` library on GitHub under
`github.com/composer/hello-world`.
Now, To test installing the `acme/hello-world` package, we create a new
project locally. We will call it `acme/blog`. This blog will depend on `acme
/hello-world`, which in turn depends on `monolog/monolog`. We can accomplish
this by creating a new `blog` directory somewhere, containing a
`composer.json`:
```json
{
"name": "acme/blog",
"require": {
"acme/hello-world": "dev-master"
}
}
```
The name is not needed in this case, since we don't want to publish the blog
as a library. It is added here to clarify which `composer.json` is being
described.
Now we need to tell the blog app where to find the `hello-world` dependency.
We do this by adding a package repository specification to the blog's
`composer.json`:
```json
{
"name": "acme/blog",
"repositories": {
"acme/hello-world": {
"vcs": { "url": "https://github.com/composer/hello-world" }
}
},
"require": {
"acme/hello-world": "dev-master"
}
}
```
For more details on how package repositories work and what other types are
available, see [Repositories].
That's all. You can now install the dependencies by running composer's
`install` command!
**Recap:** Any git/svn/hg repository containing a `composer.json` can be added
to your project by specifying the package repository and declaring the
dependency in the `require` field.
## Publishing to packagist
Alright, so now you can publish packages. But specifying the vcs repository
every time is cumbersome. You don't want to force all your users to do that.
The other thing that you may have noticed is that we did not specify a package
repository for `monolog/monolog`. How did that work? The answer is packagist.
[Packagist](http://packagist.org/) is the main package repository for
composer, and it is enabled by default. Anything that is published on
packagist is available automatically through composer. Since monolog
[is on packagist](http://packagist.org/packages/monolog/monolog), we can depend
on it without having to specify any additional repositories.
Assuming we want to share `hello-world` with the world, we would want to
publish it on packagist as well. And this is really easy.
You simply hit the big "Submit Package" button and sign up. Then you submit
the URL to your VCS repository, at which point packagist will start crawling
it. Once it is done, your package will be available to anyone.

139
doc/03-cli.md Normal file
View File

@ -0,0 +1,139 @@
# Command-line interface
You've already learned how to use the command-line interface to do some
things. This chapter documents all the available commands.
## init
In the [Libraries] chapter we looked at how to create a `composer.json` by
hand. There is also an `init` command available that makes it a bit easier to
do this.
When you run the command it will interactively ask you to fill in the fields,
while using some smart defaults.
$ php composer.phar init
## install
The `install` command reads the `composer.json` file from the current
directory, resolves the dependencies, and installs them into `vendor`.
$ php composer.phar install
If there is a `composer.lock` file in the current directory, it will use the
exact versions from there instead of resolving them. This ensures that
everyone using the library will get the same versions of the dependencies.
If there is no `composer.lock` file, composer will create one after dependency
resolution.
### Options
* **--prefer-source:** There are two ways of downloading a package: `source` and `dist`. For stable versions composer will use the `dist` by default. The `source` is a version control repository. If `--prefer-source` is enabled, composer will install from `source` if there is one. This is useful if you want to make a bugfix to a project and get a local git clone of the dependency directly.
* **--dry-run:** If you want to run through an installation without actually installing a package, you can use `--dry-run`. This will simulate the installation and show you what would happen.
* **--no-install-recommends:** By default composer will install all packages that are referenced by `recommend`. By passing this option you can disable that.
* **--install-suggests:** The packages referenced by `suggest` will not be installed by default. By passing this option, you can install them.
## update
In order to get the latest versions of the dependencies and to update the
`composer.lock` file, you should use the `update` command.
$ php composer.phar update
This will resolve all dependencies of the project and write the exact versions
into `composer.lock`.
### Options
* **--prefer-source:** Install packages from `source` when available.
* **--dry-run:** Simulate the command without actually doing anything.
* **--no-install-recommends:** Do not install packages referenced by `recommend`.
* **--install-suggests:** Install packages referenced by `suggest`.
## search
The search command allows you to search through the current project's package
repositories. Usually this will be just packagist. You simply pass it the
terms you want to search for.
$ php composer.phar search monolog
You can also search for more than one term by passing multiple arguments.
## show
To list all of the available packages, you can use the `show` command.
$ php composer.phar show
If you want to see the details of a certain package, you can pass the package
name.
$ php composer.phar show monolog/monolog
name : monolog/monolog
versions : master-dev, 1.0.2, 1.0.1, 1.0.0, 1.0.0-RC1
type : library
names : monolog/monolog
source : [git] http://github.com/Seldaek/monolog.git 3d4e60d0cbc4b888fe5ad223d77964428b1978da
dist : [zip] http://github.com/Seldaek/monolog/zipball/3d4e60d0cbc4b888fe5ad223d77964428b1978da 3d4e60d0cbc4b888fe5ad223d77964428b1978da
license : MIT
autoload
psr-0
Monolog : src/
requires
php >=5.3.0
You can even pass the package version, which will tell you the details of that
specific version.
$ php composer.phar show monolog/monolog 1.0.2
### Options
* **--installed:** Will list the packages that are installed.
* **--platform:** Will list only [Platform packages].
## depends
The `depends` command tells you which other packages depend on a certain
package. You can specify which link types (`require`, `recommend`, `suggest`)
should be included in the listing.
$ php composer.phar depends --link-type=require monolog/monolog
nrk/monolog-fluent
poc/poc
propel/propel
symfony/monolog-bridge
symfony/symfony
### Options
* **--link-type:** The link types to match on, can be specified multiple
times.
## validate
You should always run the `validate` command before you commit your
`composer.json` file, and before you tag a release. It will check if your
`composer.json` is valid.
$ php composer.phar validate
## self-update
To update composer itself to the latest version, just run the `self-update`
command. It will replace your `composer.phar` with the latest version.
$ php composer.phar self-update
## help
To get more information about a certain command, just use `help`.
$ php composer.phar help install

417
doc/04-schema.md Normal file
View File

@ -0,0 +1,417 @@
# composer.json
This chapter will explain all of the options available in `composer.json`.
## JSON schema
We have a [JSON schema](http://json-schema.org) that documents the format and
can also be used to validate your `composer.json`. In fact, it is used by the
`validate` command. You can find it at: [`Resources/composer-
schema.json`](https://github.com/composer/composer/blob/master/res
/composer-schema.json).
## Package root
The root of the package definition is a JSON object.
## name
The name of the package. It consists of vendor name and project name,
separated by `/`.
Examples:
* monolog/monolog
* igorw/event-source
Required for published packages (libraries).
## description
A short description of the package. Usually this is just one line long.
Optional but recommended.
## version
The version of the package.
This must follow the format of `X.Y.Z` with an optional suffix of `-dev`,
`alphaN`, `-betaN` or `-RCN`.
Examples:
1.0.0
1.0.2
1.1.0
0.2.5
1.0.0-dev
1.0.0-beta2
1.0.0-RC5
Optional if the package repository can infer the version from somewhere, such
as the VCS tag name in the VCS repository. In that case it is also recommended
to omit it.
## type
The type of the package. It defaults to `library`.
Package types are used for custom installation logic. If you have a package
that needs some special logic, you can define a custom type. This could be a
`symfony-bundle`, a `wordpress-plugin` or a `typo3-module`. These will all be
specific to certain projects, and they will need to provide an installer
capable of installing packages of that type.
Out of the box, composer supports two types:
* **library:** This is the default. It will simply copy the files to `vendor`.
* **composer-installer:** A package of type `composer-installer` provides an
installer for other packages that have a custom type. Symfony could supply a
`symfony/bundle-installer` package, which every bundle would depend on.
Whenever you install a bundle, it will fetch the installer and register it, in
order to be able to install the bundle.
Only use a custom type if you need custom logic during installation. It is
recommended to omit this field and have it just default to `library`.
## keywords
An array of keywords that the package is related to. These can be used for
searching and filtering.
Examples:
logging
events
database
redis
templating
Optional.
## homepage
An URL to the website of the project.
Optional.
## time
Release date of the version.
Must be in `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS` format.
Optional.
## license
The license of the package. This can be either a string or an array of strings.
The recommended notation for the most common licenses is:
MIT
BSD-2
BSD-3
BSD-4
GPLv2
GPLv3
LGPLv2
LGPLv3
Apache2
WTFPL
Optional, but it is highly recommended to supply this.
## authors
The authors of the package. This is an array of objects.
Each author object can have following properties:
* **name:** The author's name. Usually his real name.
* **email:** The author's email address.
* **homepage:** An URL to the author's website.
An example:
```json
{
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
]
}
```
Optional, but highly recommended.
## Link types
Each of these takes an object which maps package names to version constraints.
* **require:** Packages required by this package.
* **recommend:** Recommended packages, installed by default.
* **suggest:** Suggested packages. These are displayed after installation,
but not installed by default.
* **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`.
Example:
```json
{
"require": {
"monolog/monolog": "1.0.*"
}
}
```
Optional.
## autoload
Autoload mapping for a PHP autoloader.
Currently only [PSR-0](https://github.com/php-fig/fig-
standards/blob/master/accepted/PSR-0.md) autoloading is supported. Under the
`psr-0` key you define a mapping from namespaces to paths, relative to the
package root.
Example:
```json
{
"autoload": {
"psr-0": { "Monolog": "src/" }
}
}
```
Optional, but it is highly recommended that you follow PSR-0 and use this.
## target-dir
Defines the installation target.
In case the package root is below the namespace declaration you cannot
autoload properly. `target-dir` solves this problem.
An example is Symfony. There are individual packages for the components. The
Yaml component is under `Symfony\Component\Yaml`. The package root is that
`Yaml` directory. To make autoloading possible, we need to make sure that it
is not installed into `vendor/symfony/yaml`, but instead into
`vendor/symfony/yaml/Symfony/Component/Yaml`, so that the autoloader can load
it from `vendor/symfony/yaml`.
To do that, `autoload` and `target-dir` are defined as follows:
```json
{
"autoload": {
"psr-0": { "Symfony\\Component\\Yaml": "" }
},
"target-dir": "Symfony/Component/Yaml"
}
```
Optional.
## repositories
Custom package repositories to use.
By default composer just uses the packagist repository. By specifying
repositories you can get packages from elsewhere.
Repositories are not resolved recursively. You can only add them to your main
`composer.json`. Repository declarations of dependencies' `composer.json`s are
ignored.
Following repository types are supported:
* **composer:** A composer repository is simply a `packages.json` file served
via HTTP that contains a list of `composer.json` objects with additional
`dist` and/or `source` information.
* **vcs:** The version control system repository can fetch packages from git,
svn and hg repositories. Note the distinction between package repository and
version control repository.
* **pear:** With this you can import any pear repository into your composer
project.
* **package:** If you depend on a project that does not have any support for
composer whatsoever you can define the package inline using a `package`
repository. You basically just inline the `composer.json` object.
For more information on any of these, see [Repositories].
Example:
```json
{
"repositories": [
{
"type": "composer",
"url": "http://packages.example.com"
},
{
"type": "vcs",
"url": "https://github.com/Seldaek/monolog"
},
{
"type": "pear",
"url": "http://pear2.php.net"
},
{
"type": "package",
"package": {
"name": "smarty/smarty",
"version": "3.1.7",
"dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip"
},
"source": {
"url": "http://smarty-php.googlecode.com/svn/",
"type": "svn",
"reference": "tags/Smarty_3_1_7/distribution/"
}
}
}
]
}
```
> **Note:** Order is significant here. Repositories added later will take
precedence. This also means that custom repositories can override packages
that exist on packagist.
You can also disable the packagist repository by setting `packagist` to
`false`.
```json
{
"repositories": [
{
"packagist": false
}
]
}
```
## config
A set of configuration options. It is only used for projects.
The following options are supported:
* **vendor-dir:** Defaults to `vendor`. You can install dependencies into a
different directory if you want to.
* **bin-dir:** Defaults to `vendor/bin`. If a project includes binaries, they
will be symlinked into this directory.
Example:
```json
{
"config": {
"bin-dir": "bin"
}
}
```
## scripts
Composer allows you to hook into various parts of the installation process through the use of scripts.
These events are supported:
* **pre-install-cmd:** Occurs before the install command is executed, contains
one or more Class::method callables.
* **post-install-cmd:** Occurs after the install command is executed, contains
one or more Class::method callables.
* **pre-update-cmd:** Occurs before the update command is executed, contains
one or more Class::method callables.
* **post-update-cmd:** Occurs after the update command is executed, contains
one or more Class::method callables.
* **pre-package-install:** Occurs before a package is installed, contains one
or more Class::method callables.
* **post-package-install:** Occurs after a package is installed, contains one
or more Class::method callables.
* **pre-package-update:** Occurs before a package is updated, contains one or
more Class::method callables.
* **post-package-update:** Occurs after a package is updated, contains one or
more Class::method callables.
* **pre-package-uninstall:** Occurs before a package has been uninstalled,
contains one or more Class::method callables.
* **post-package-uninstall:** Occurs after a package has been uninstalled,
contains one or more Class::method callables.
For each of these events you can provide a static method on a class that will
handle it.
Example:
```json
{
"scripts": {
"post-install-cmd": [
"Acme\\ScriptHandler::doSomething"
]
}
}
```
The event handler receives a `Composer\Script\Event` object as an argument,
which gives you access to the `Composer\Composer` instance through the
`getComposer` method.
```php
namespace Acme;
use Composer\Script\Event;
class ScriptHandler
{
static public function doSomething(Event $event)
{
// custom logic
}
}
```
## extra
Arbitrary extra data for consumption by `scripts`.
This can be virtually anything. To access it from within a script event
handler, you can do:
```php
$extra = $event->getComposer()->getPackage()->getExtra();
```
Optional.
## bin
A set of files that should be treated as binaries and symlinked into the `bin-
dir` (from config).
See [articles/bin.md] for more details.
Optional.

263
doc/05-repositories.md Normal file
View File

@ -0,0 +1,263 @@
# Repositories
This chapter will explain the concept of packages and repositories, what kinds
of repositories are available, and how they work.
## Concepts
Before we look at the different types of repositories that we can have, we
need to understand some of the basic concepts that composer is built on.
### Package
Composer is a dependency manager. It installs packages. A package is
essentially just a directory containing something. In this case it is PHP
code, but in theory it could be anything. And it contains a package
description which has a name and a version. The name and the version are used
to identify the package.
In fact, internally composer sees every version as a separate package. While
this distinction does not matter when you are using composer, it's quite
important when you want to change it.
In addition to the name and the version, there is useful data. The only really
important piece of information is the package source, that describes where to
get the package contents. The package data points to the contents of the
package. And there are two options here: dist and source.
**Dist:** The dist is a packaged version of the package data. Usually a
released version, usually a stable release.
**Source:** The source is used for development. This will usually originate
from a source code repository, such as git. You can fetch this when you want
to modify the downloaded package.
Packages can supply either of these, or even both. Depending on certain
factors, such as user-supplied options and stability of the package, one will
be preferred.
### Repository
A repository is a package source. It's a list of packages, of which you can
pick some to install.
You can also add more repositories to your project by declaring them in
`composer.json`.
## Types
### Composer
The main repository type is the `composer` repository. It uses a single
`packages.json` file that contains all of the package metadata. The JSON
format is as follows:
```json
{
"vendor/packageName": {
"name": "vendor/packageName",
"description": "Package description",
"versions": {
"master-dev": { @composer.json },
"1.0.0": { @composer.json }
}
}
}
```
The `@composer.json` marker would be the contents of the `composer.json` from
that package version including as a minimum:
* name
* version
* dist or source
Here is a minimal package definition:
```json
{
"name": "smarty/smarty",
"version": "3.1.7",
"dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip"
}
}
```
It may include any of the other fields specified in the [schema].
The `composer` repository is also what packagist uses. To reference a
`composer` repository, just supply the path before the `packages.json` file.
In case of packagist, that file is located at `/packages.json`, so the URL of
the repository would be `http://packagist.org`. For
`http://example.org/packages.org` the repository URL would be
`http://example.org`.
### VCS
VCS stands for version control system. This includes versioning systems like
git, svn or hg. Composer has a repository type for installing packages from
these systems.
There are a few use cases for this. The most common one is maintaining your
own fork of a third party library. If you are using a certain library for your
project and you decide to change something in the library, you will want your
project to use the patched version. If the library is on GitHub (this is the
case most of the time), you can simply fork it there and push your changes to
your fork. After that you update the project's `composer.json`. All you have
to do is add your fork as a repository and update the version constraint to
point to your custom branch.
Example assuming you patched monolog to fix a bug in the `bugfix` branch:
```json
{
"repositories": [
{
"type": "vcs",
"url": "http://github.com/igorw/monolog"
}
],
"require": {
"monolog/monolog": "dev-bugfix"
}
}
```
When you run `php composer.phar update`, you should get your modified version
of `monolog/monolog` instead of the one from packagist.
Git is not the only version control system supported by the VCS repository.
The following are supported:
* **Git:** [git-scm.com](http://git-scm.com)
* **Subversion:** [subversion.apache.org](http://subversion.apache.org)
* **Mercurial:** [mercurial.selenic.com](http://mercurial.selenic.com)
To use these systems you need to have them installed. That can be
invonvenient. And for this reason there is special support for GitHub and
BitBucket that use the APIs provided by these sites, to fetch the packages
without having to install the version control system. The VCS repository
provides `dist`s for them that fetch the packages as zips.
* **GitHub:** [github.com](https://github.com) (Git)
* **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial)
The VCS driver to be used is detected automatically based on the URL.
### PEAR
It is possible to install packages from any PEAR channel by using the `pear`
repository. Composer will prefix all package names with `pear-{channelName}/` to
avoid conflicts.
Example using `pear2.php.net`:
```json
{
"repositories": [
{
"type": "pear",
"url": "http://pear2.php.net"
}
],
"require": {
"pear-pear2/PEAR2_HTTP_Request": "*"
}
}
```
In this case the short name of the channel is `pear2`, so the
`PEAR2_HTTP_Request` package name becomes `pear-pear2/PEAR2_HTTP_Request`.
> **Note:** The `pear` repository requires doing quite a few requests per
> package, so this may considerably slow down the installation process.
### Package
If you want to use a project that does not support composer through any of the
means above, you still can define the package yourself using a `package`
repository.
Basically, you define the same information that is included in the `composer`
repository's `packages.json`, but only for a single package. Again, the
minimally required fields are `name`, `version`, and either of `dist` or
`source`.
Here is an example for the smarty template engine:
```json
{
"repositories": [
{
"type": "package",
"package": {
"name": "smarty/smarty",
"version": "3.1.7",
"dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip"
},
"source": {
"url": "http://smarty-php.googlecode.com/svn/",
"type": "svn",
"reference": "tags/Smarty_3_1_7/distribution/"
}
}
}
],
"require": {
"smarty/smarty": "3.1.*"
}
}
```
Typically you would leave the source part off, as you don't really need it.
## Hosting your own
While you will probably want to put your packages on packagist most of the time,
there are some use cases for hosting your own repository.
* **Private company packages:** If you are part of a company that uses composer
for their packages internally, you might want to keep those packages private.
* **Separate ecosystem:** If you have a project which has its own ecosystem,
and the packages aren't really reusable by the greater PHP community, you
might want to keep them separate to packagist. An example of this would be
wordpress plugins.
When hosting your own package repository it is recommended to use a `composer`
one. This is type that is native to composer and yields the best performance.
There are a few different tools that can help you create a `composer`
repository.
### Packagist
The underlying application used by packagist is open source. This means that you
can just install your own copy of packagist, re-brand, and use it. It's really
quite straight-forward to do.
Packagist is a Symfony2 application, and it is [available on
GitHub](https://github.com/composer/packagist). It uses composer internally and
acts as a proxy between VCS repositories and the composer users. It holds a list
of all VCS packages, periodically re-crawls them, and exposes them as a composer
repository.
To set your own copy, simply follow the instructions from the [packagist
github repository](https://github.com/composer/packagist).
### Satis
Satis is a static `composer` repository generator. It is a bit like a ultra-
lightweight, file-based version of packagist.
You give it a `composer.json` containing repositories, typically VCS and package
repository definitions. It will fetch all the packages that are `require`d from
these repositories and dump a `packages.json` that is your `composer`
repository.
Check [the satis GitHub repository](https://github.com/composer/satis) for more
information.

28
doc/06-community.md Normal file
View File

@ -0,0 +1,28 @@
# Community
We have a lot of people using composer, and also many contributors to the
project.
## Contributing
If you would like to contribute to composer, please read the
[README](https://github.com/composer/composer).
The most important guidelines are described as follows:
> All code contributions - including those of people having commit access - must
> go through a pull request and approved by a core developer before being
> merged. This is to ensure proper review of all the code.
>
> Fork the project, create a feature branch, and send us a pull request.
>
> To ensure a consistent code base, you should make sure the code follows
> the [Coding Standards](http://symfony.com/doc/2.0/contributing/code/standards.html)
> which we borrowed from Symfony.
## IRC / mailing list
The developer mailing list is on [google groups](http://groups.google.com/group
/composer-dev) IRC channels are available for discussion as well, on
irc.freenode.org [#composer](irc://irc.freenode.org/composer) for users and
[#composer-dev](irc://irc.freenode.org/composer-dev) for development.

View File

@ -101,6 +101,20 @@
"description": "This is a hash of package name (keys) and version constraints (values) that this package suggests work well with it (typically this will only be suggested to the user).", "description": "This is a hash of package name (keys) and version constraints (values) that this package suggests work well with it (typically this will only be suggested to the user).",
"additionalProperties": true "additionalProperties": true
}, },
"config": {
"type": ["object"],
"description": "Composer options.",
"properties": {
"vendor-dir": {
"type": "string",
"description": "The location where all packages are installed, defaults to \"vendor\"."
},
"bin-dir": {
"type": "string",
"description": "The location where all binaries are linked, defaults to \"vendor/bin\"."
}
}
},
"extra": { "extra": {
"type": ["object", "array"], "type": ["object", "array"],
"description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.", "description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.",
@ -121,6 +135,59 @@
"type": ["object", "array"], "type": ["object", "array"],
"description": "A set of additional repositories where packages can be found.", "description": "A set of additional repositories where packages can be found.",
"additionalProperties": true "additionalProperties": true
},
"bin": {
"type": ["array"],
"description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).",
"items": {
"type": "string"
}
},
"scripts": {
"type": ["object"],
"description": "Scripts listeners that will be executed before/after some events.",
"properties": {
"pre-install-cmd": {
"type": ["array", "string"],
"description": "Occurs before the install command is executed, contains one or more Class::method callables."
},
"post-install-cmd": {
"type": ["array", "string"],
"description": "Occurs after the install command is executed, contains one or more Class::method callables."
},
"pre-update-cmd": {
"type": ["array", "string"],
"description": "Occurs before the update command is executed, contains one or more Class::method callables."
},
"post-update-cmd": {
"type": ["array", "string"],
"description": "Occurs after the update command is executed, contains one or more Class::method callables."
},
"pre-package-install": {
"type": ["array", "string"],
"description": "Occurs before a package is installed, contains one or more Class::method callables."
},
"post-package-install": {
"type": ["array", "string"],
"description": "Occurs after a package is installed, contains one or more Class::method callables."
},
"pre-package-update": {
"type": ["array", "string"],
"description": "Occurs before a package is updated, contains one or more Class::method callables."
},
"post-package-update": {
"type": ["array", "string"],
"description": "Occurs after a package is updated, contains one or more Class::method callables."
},
"pre-package-uninstall": {
"type": ["array", "string"],
"description": "Occurs before a package has been uninstalled, contains one or more Class::method callables."
},
"post-package-uninstall": {
"type": ["array", "string"],
"description": "Occurs after a package has been uninstalled, contains one or more Class::method callables."
}
}
} }
} }
} }

View File

@ -25,6 +25,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class DependsCommand extends Command class DependsCommand extends Command
{ {
protected $linkTypes = array('require', 'recommend', 'suggest');
protected function configure() protected function configure()
{ {
$this $this
@ -32,7 +34,7 @@ class DependsCommand extends Command
->setDescription('Shows which packages depend on the given package') ->setDescription('Shows which packages depend on the given package')
->setDefinition(array( ->setDefinition(array(
new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', array('requires', 'recommends', 'suggests')) new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', $this->linkTypes)
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
Displays detailed information about where a package is referenced. Displays detailed information about where a package is referenced.
@ -74,10 +76,15 @@ EOT
$repos = $composer->getRepositoryManager()->getRepositories(); $repos = $composer->getRepositoryManager()->getRepositories();
$types = $input->getOption('link-type'); $types = $input->getOption('link-type');
foreach ($repos as $repository) { foreach ($repos as $repository) {
foreach ($repository->getPackages() as $package) { foreach ($repository->getPackages() as $package) {
foreach ($types as $type) { foreach ($types as $type) {
foreach ($package->{'get'.$type}() as $link) { $type = rtrim($type, 's');
if (!in_array($type, $this->linkTypes)) {
throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', $this->linkTypes));
}
foreach ($package->{'get'.$type.'s'}() as $link) {
if ($link->getTarget() === $needle) { if ($link->getTarget() === $needle) {
if ($verbose) { if ($verbose) {
$references[] = array($type, $package, $link); $references[] = array($type, $package, $link);

View File

@ -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,23 +114,20 @@ 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>');
$installedPackages = $installedRepo->getPackages(); $installedPackages = $installedRepo->getPackages();
$links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests); $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
foreach ($links as $link) { $request->updateAll();
foreach ($installedPackages as $package) {
if ($package->getName() === $link->getTarget()) {
$request->update($package->getName(), new VersionConstraint('=', $package->getVersion()));
break;
}
}
foreach ($links as $link) {
$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()) {
@ -158,45 +156,63 @@ EOT
// solve dependencies // solve dependencies
$operations = $solver->solve($request); $operations = $solver->solve($request);
// check for missing deps
// TODO this belongs in the solver, but this will do for now to report top-level deps missing at least
foreach ($request->getJobs() as $job) {
if ('install' === $job['cmd']) {
foreach ($installedRepo->getPackages() as $package ) {
if ($installedRepo->hasPackage($package) && !$package->isPlatform() && !$installationManager->isPackageInstalled($package)) {
$operations[$job['packageName']] = new InstallOperation($package, Solver::RULE_PACKAGE_NOT_EXIST);
}
if (in_array($job['packageName'], $package->getNames())) {
continue 2;
}
}
foreach ($operations as $operation) {
if ('install' === $operation->getJobType() && in_array($job['packageName'], $operation->getPackage()->getNames())) {
continue 2;
}
if ('update' === $operation->getJobType() && in_array($job['packageName'], $operation->getTargetPackage()->getNames())) {
continue 2;
}
}
if ($pool->whatProvides($job['packageName'])) {
throw new \UnexpectedValueException('Package '.$job['packageName'].' can not be installed, either because its version constraint is incorrect, or because one of its dependencies was not found.');
}
throw new \UnexpectedValueException('Package '.$job['packageName'].' was not found in the package pool, check the name for typos.');
}
}
// execute operations // execute operations
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 && $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);
} }
} }

View File

@ -21,11 +21,6 @@ use Composer\Package\LinkConstraint\VersionConstraint;
*/ */
class DefaultPolicy implements PolicyInterface class DefaultPolicy implements PolicyInterface
{ {
public function allowUninstall()
{
return true;
}
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
{ {
$constraint = new VersionConstraint($operator, $b->getVersion()); $constraint = new VersionConstraint($operator, $b->getVersion());

View File

@ -20,7 +20,6 @@ use Composer\Package\PackageInterface;
*/ */
interface PolicyInterface interface PolicyInterface
{ {
function allowUninstall();
function versionCompare(PackageInterface $a, PackageInterface $b, $operator); function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);
function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);

View File

@ -55,6 +55,11 @@ class Request
); );
} }
public function updateAll()
{
$this->jobs[] = array('cmd' => 'update-all', 'packages' => array());
}
public function getJobs() public function getJobs()
{ {
return $this->jobs; return $this->jobs;

View File

@ -20,7 +20,6 @@ class RuleSet implements \IteratorAggregate, \Countable
// highest priority => lowest number // highest priority => lowest number
const TYPE_PACKAGE = 0; const TYPE_PACKAGE = 0;
const TYPE_JOB = 1; const TYPE_JOB = 1;
const TYPE_UPDATE = 2;
const TYPE_FEATURE = 3; const TYPE_FEATURE = 3;
const TYPE_CHOICE = 4; const TYPE_CHOICE = 4;
const TYPE_LEARNED = 5; const TYPE_LEARNED = 5;
@ -29,7 +28,6 @@ class RuleSet implements \IteratorAggregate, \Countable
-1 => 'UNKNOWN', -1 => 'UNKNOWN',
self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_PACKAGE => 'PACKAGE',
self::TYPE_FEATURE => 'FEATURE', self::TYPE_FEATURE => 'FEATURE',
self::TYPE_UPDATE => 'UPDATE',
self::TYPE_JOB => 'JOB', self::TYPE_JOB => 'JOB',
self::TYPE_CHOICE => 'CHOICE', self::TYPE_CHOICE => 'CHOICE',
self::TYPE_LEARNED => 'LEARNED', self::TYPE_LEARNED => 'LEARNED',

View File

@ -52,7 +52,6 @@ class Solver
protected $decisionMap; protected $decisionMap;
protected $installedMap; protected $installedMap;
protected $packageToUpdateRule = array();
protected $packageToFeatureRule = array(); protected $packageToFeatureRule = array();
public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
@ -148,10 +147,6 @@ class Solver
*/ */
protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null) protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
{ {
if (empty($packages)) {
return $this->createImpossibleRule($reason, $reasonData);
}
$literals = array(); $literals = array();
foreach ($packages as $package) { foreach ($packages as $package) {
$literals[] = new Literal($package, true); $literals[] = new Literal($package, true);
@ -201,22 +196,6 @@ class Solver
return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData); return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData);
} }
/**
* Intentionally creates a rule impossible to solve
*
* The rule is an empty one so it can never be satisfied.
*
* @param int $reason A RULE_* constant describing the reason for
* generating this rule
* @param mixed $reasonData Any data, e.g. the package name, that goes with
* the reason
* @return Rule An empty rule
*/
protected function createImpossibleRule($reason, $reasonData = null)
{
return new Rule(array(), $reason, $reasonData);
}
/** /**
* Adds a rule unless it duplicates an existing one of any type * Adds a rule unless it duplicates an existing one of any type
* *
@ -305,7 +284,7 @@ class Solver
// if ignoreinstalledsobsoletes is not set, we're also checking // if ignoreinstalledsobsoletes is not set, we're also checking
// obsoletes of installed packages (like newer rpm versions) // obsoletes of installed packages (like newer rpm versions)
// //
/** @TODO: if ($this->noInstalledObsoletes) */ /** TODO if ($this->noInstalledObsoletes) */
if (true) { if (true) {
$noObsoletes = isset($this->noObsoletes[$package->getId()]); $noObsoletes = isset($this->noObsoletes[$package->getId()]);
$isInstalled = (isset($this->installedMap[$package->getId()])); $isInstalled = (isset($this->installedMap[$package->getId()]));
@ -508,7 +487,7 @@ class Solver
// push all of our rules (can only be feature or job rules) // push all of our rules (can only be feature or job rules)
// asserting this literal on the problem stack // asserting this literal on the problem stack
foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_UPDATE, RuleSet::TYPE_FEATURE)) as $assertRule) { foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) {
if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) { if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
continue; continue;
} }
@ -882,11 +861,6 @@ class Solver
protected function disableUpdateRule($package) protected function disableUpdateRule($package)
{ {
// find update & feature rule and disable
if (isset($this->packageToUpdateRule[$package->getId()])) {
$this->packageToUpdateRule[$package->getId()]->disable();
}
if (isset($this->packageToFeatureRule[$package->getId()])) { if (isset($this->packageToFeatureRule[$package->getId()])) {
$this->packageToFeatureRule[$package->getId()]->disable(); $this->packageToFeatureRule[$package->getId()]->disable();
} }
@ -958,6 +932,14 @@ class Solver
break; break;
} }
} }
switch ($job['cmd']) {
case 'update-all':
foreach ($installedPackages as $package) {
$this->updateMap[$package->getId()] = true;
}
break;
}
} }
foreach ($installedPackages as $package) { foreach ($installedPackages as $package) {
@ -986,22 +968,21 @@ class Solver
$updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
$rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
if ($this->policy->allowUninstall()) { $rule->setWeak(true);
$rule->setWeak(true); $this->addRule(RuleSet::TYPE_FEATURE, $rule);
$this->addRule(RuleSet::TYPE_FEATURE, $featureRule); $this->packageToFeatureRule[$package->getId()] = $rule;
$this->packageToFeatureRule[$package->getId()] = $rule;
} else {
$this->addRule(RuleSet::TYPE_UPDATE, $rule);
$this->packageToUpdateRule[$package->getId()] = $rule;
}
} }
foreach ($this->jobs as $job) { foreach ($this->jobs as $job) {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': case 'install':
$rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']); if (empty($job['packages'])) {
$this->addRule(RuleSet::TYPE_JOB, $rule); $this->problems[] = array($job);
$this->ruleToJob[$rule->getId()] = $job; } else {
$rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']);
$this->addRule(RuleSet::TYPE_JOB, $rule);
$this->ruleToJob[$rule->getId()] = $job;
}
break; break;
case 'remove': case 'remove':
// remove all packages with this name including uninstalled // remove all packages with this name including uninstalled
@ -1046,6 +1027,10 @@ class Solver
//findrecommendedsuggested(solv); //findrecommendedsuggested(solv);
//solver_prepare_solutions(solv); //solver_prepare_solutions(solv);
if ($this->problems) {
throw new SolverProblemsException($this->problems, $this->learnedPool);
}
return $this->createTransaction(); return $this->createTransaction();
} }
@ -1061,9 +1046,6 @@ class Solver
if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) { if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
$literals = array(); $literals = array();
if (isset($this->packageToUpdateRule[$package->getId()])) {
$literals = array_merge($literals, $this->packageToUpdateRule[$package->getId()]->getLiterals());
}
if (isset($this->packageToFeatureRule[$package->getId()])) { if (isset($this->packageToFeatureRule[$package->getId()])) {
$literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals()); $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
} }
@ -1127,6 +1109,8 @@ class Solver
protected function addDecision(Literal $l, $level) protected function addDecision(Literal $l, $level)
{ {
assert($this->decisionMap[$l->getPackageId()] == 0);
if ($l->isWanted()) { if ($l->isWanted()) {
$this->decisionMap[$l->getPackageId()] = $level; $this->decisionMap[$l->getPackageId()] = $level;
} else { } else {
@ -1137,6 +1121,9 @@ class Solver
protected function addDecisionId($literalId, $level) protected function addDecisionId($literalId, $level)
{ {
$packageId = abs($literalId); $packageId = abs($literalId);
assert($this->decisionMap[$packageId] == 0);
if ($literalId > 0) { if ($literalId > 0) {
$this->decisionMap[$packageId] = $level; $this->decisionMap[$packageId] = $level;
} else { } else {
@ -1179,8 +1166,8 @@ class Solver
{ {
$packageId = abs($literalId); $packageId = abs($literalId);
return ( return (
$this->decisionMap[$packageId] > 0 && !($literalId < 0) || ($this->decisionMap[$packageId] > 0 && $literalId < 0) ||
$this->decisionMap[$packageId] < 0 && $literalId > 0 ($this->decisionMap[$packageId] < 0 && $literalId > 0)
); );
} }
@ -1227,7 +1214,8 @@ class Solver
continue; continue;
} }
for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) { $prevRule = null;
for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
$nextRule = $rule->getNext($literal); $nextRule = $rule->getNext($literal);
if ($rule->isDisabled()) { if ($rule->isDisabled()) {
@ -1247,16 +1235,27 @@ class Solver
if ($otherWatch !== $ruleLiteral->getId() && if ($otherWatch !== $ruleLiteral->getId() &&
!$this->decisionsConflict($ruleLiteral)) { !$this->decisionsConflict($ruleLiteral)) {
if ($literal->getId() === $rule->watch1) { if ($literal->getId() === $rule->watch1) {
$rule->watch1 = $ruleLiteral->getId(); $rule->watch1 = $ruleLiteral->getId();
$rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ; $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
} else { } else {
$rule->watch2 = $ruleLiteral->getId(); $rule->watch2 = $ruleLiteral->getId();
$rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ; $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
}
if ($prevRule) {
if ($prevRule->next1 == $rule) {
$prevRule->next1 = $nextRule;
} else {
$prevRule->next2 = $nextRule;
}
} else {
$this->watches[$literal->getId()] = $nextRule;
} }
$this->watches[$ruleLiteral->getId()] = $rule; $this->watches[$ruleLiteral->getId()] = $rule;
$rule = $prevRule;
continue 2; continue 2;
} }
} }
@ -1487,7 +1486,7 @@ class Solver
} }
$why = count($this->learnedPool) - 1; $why = count($this->learnedPool) - 1;
assert($learnedLiterals[0] !== null);
$newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why); $newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why);
return array($ruleLevel, $newRule, $why); return array($ruleLevel, $newRule, $why);
@ -1813,11 +1812,7 @@ class Solver
$rule = null; $rule = null;
if (isset($this->packageToUpdateRule[$literal->getPackageId()])) { if (isset($this->packageToFeatureRule[$literal->getPackageId()])) {
$rule = $this->packageToUpdateRule[$literal->getPackageId()];
}
if ((!$rule || $rule->isDisabled()) && isset($this->packageToFeatureRule[$literal->getPackageId()])) {
$rule = $this->packageToFeatureRule[$literal->getPackageId()]; $rule = $this->packageToFeatureRule[$literal->getPackageId()];
} }
@ -2027,8 +2022,10 @@ class Solver
} }
if ($level > 0) { if ($level > 0) {
echo ' +' . $this->pool->packageById($packageId)."\n"; echo ' +' . $this->pool->packageById($packageId)."\n";
} else { } elseif ($level < 0) {
echo ' -' . $this->pool->packageById($packageId)."\n"; echo ' -' . $this->pool->packageById($packageId)."\n";
} else {
echo ' ?' . $this->pool->packageById($packageId)."\n";
} }
} }
echo "\n"; echo "\n";
@ -2042,4 +2039,41 @@ class Solver
} }
echo "\n"; echo "\n";
} }
private function printWatches()
{
echo "\nWatches:\n";
foreach ($this->watches as $literalId => $watch) {
echo ' '.$this->literalFromId($literalId)."\n";
$queue = array(array(' ', $watch));
while (!empty($queue)) {
list($indent, $watch) = array_pop($queue);
echo $indent.$watch;
if ($watch) {
echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]";
}
echo "\n";
if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) {
if ($watch->next1 == $watch) {
echo $indent." 1 *RECURSION*";
}
if ($watch->next2 == $watch) {
echo $indent." 2 *RECURSION*";
}
} elseif ($watch && ($watch->next1 || $watch->next2)) {
$indent = str_replace(array('1', '2'), ' ', $indent);
array_push($queue, array($indent.' 2 ', $watch->next2));
array_push($queue, array($indent.' 1 ', $watch->next1));
}
}
echo "\n";
}
}
} }

View File

@ -0,0 +1,65 @@
<?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\DependencyResolver;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
class SolverProblemsException extends \RuntimeException
{
protected $problems;
public function __construct(array $problems, array $learnedPool)
{
$message = '';
foreach ($problems as $i => $problem) {
$message .= '[';
foreach ($problem as $why) {
if (is_int($why) && isset($learnedPool[$why])) {
$rules = $learnedPool[$why];
} else {
$rules = $why;
}
if (isset($rules['packages'])) {
$message .= $this->jobToText($rules);
} else {
$message .= '(';
foreach ($rules as $rule) {
if ($rule instanceof Rule) {
if ($rule->getType() == RuleSet::TYPE_LEARNED) {
$message .= 'learned: ';
}
$message .= $rule . ', ';
} else {
$message .= 'String(' . $rule . '), ';
}
}
$message .= ')';
}
$message .= ', ';
}
$message .= "]\n";
}
parent::__construct($message);
}
public function jobToText($job)
{
//$output = serialize($job);
$output = 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])';
return $output;
}
}

View File

@ -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();

View File

@ -71,7 +71,15 @@ class Factory
$rm = $this->createRepositoryManager($io); $rm = $this->createRepositoryManager($io);
// load default repository unless it's explicitly disabled // load default repository unless it's explicitly disabled
if (!isset($packageConfig['repositories']['packagist']) || $packageConfig['repositories']['packagist'] !== false) { $loadPackagist = true;
if (isset($packageConfig['repositories'])) {
foreach ($packageConfig['repositories'] as $repo) {
if (isset($repo['packagist']) && $repo['packagist'] === false) {
$loadPackagist = false;
}
}
}
if ($loadPackagist) {
$this->addPackagistRepository($rm); $this->addPackagistRepository($rm);
} }

View File

@ -78,11 +78,9 @@ class LibraryInstaller implements InstallerInterface
*/ */
public function install(PackageInterface $package) public function install(PackageInterface $package)
{ {
$this->initializeDirs();
$downloadPath = $this->getInstallPath($package); $downloadPath = $this->getInstallPath($package);
$this->filesystem->ensureDirectoryExists($this->vendorDir);
$this->filesystem->ensureDirectoryExists($this->binDir);
// remove the binaries if it appears the package files are missing // remove the binaries if it appears the package files are missing
if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) { if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) {
$this->removeBinaries($package); $this->removeBinaries($package);
@ -104,16 +102,16 @@ class LibraryInstaller implements InstallerInterface
throw new \InvalidArgumentException('Package is not installed: '.$initial); throw new \InvalidArgumentException('Package is not installed: '.$initial);
} }
$this->initializeDirs();
$downloadPath = $this->getInstallPath($initial); $downloadPath = $this->getInstallPath($initial);
$this->filesystem->ensureDirectoryExists($this->vendorDir);
$this->filesystem->ensureDirectoryExists($this->binDir);
$this->removeBinaries($initial); $this->removeBinaries($initial);
$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);
}
} }
/** /**
@ -191,6 +189,14 @@ class LibraryInstaller implements InstallerInterface
} }
} }
protected function initializeDirs()
{
$this->filesystem->ensureDirectoryExists($this->vendorDir);
$this->filesystem->ensureDirectoryExists($this->binDir);
$this->vendorDir = realpath($this->vendorDir);
$this->binDir = realpath($this->binDir);
}
private function generateWindowsProxyCode($bin, $link) private function generateWindowsProxyCode($bin, $link)
{ {
$binPath = $this->filesystem->findShortestPath($link, $bin); $binPath = $this->filesystem->findShortestPath($link, $bin);

View File

@ -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'])) {

View File

@ -38,20 +38,23 @@ 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);
if (isset($config['repositories'])) { if (isset($config['repositories'])) {
foreach ($config['repositories'] as $repoName => $repo) { foreach ($config['repositories'] as $index => $repo) {
if (false === $repo && 'packagist' === $repoName) { if (isset($repo['packagist']) && $repo['packagist'] === false) {
continue; continue;
} }
if (!is_array($repo)) { if (!is_array($repo)) {
throw new \UnexpectedValueException('Repository '.$repoName.' in '.$package->getPrettyName().' '.$package->getVersion().' should be an array, '.gettype($repo).' given'); throw new \UnexpectedValueException('Repository '.$index.' should be an array, '.gettype($repo).' given');
} }
$repository = $this->manager->createRepository(key($repo), current($repo)); if (!isset($repo['type'])) {
throw new \UnexpectedValueException('Repository '.$index.' must have a type defined');
}
$repository = $this->manager->createRepository($repo['type'], $repo);
$this->manager->addRepository($repository); $this->manager->addRepository($repository);
} }
$package->setRepositories($config['repositories']); $package->setRepositories($config['repositories']);

View File

@ -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,8 +121,17 @@ 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;
} }
usort($lock['packages'], function ($a, $b) {
return strcmp($a['package'], $b['package']);
});
$this->lockFile->write($lock); $this->lockFile->write($lock);
} }

View File

@ -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;
} }
/** /**

View File

@ -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
* *

View File

@ -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;
} }
/** /**

View File

@ -33,7 +33,7 @@ class PackageRepository extends ArrayRepository
*/ */
public function __construct(array $config) public function __construct(array $config)
{ {
$this->config = $config; $this->config = $config['package'];
} }
/** /**

View File

@ -22,6 +22,7 @@ use Composer\Util\StreamContextFactory;
class PearRepository extends ArrayRepository class PearRepository extends ArrayRepository
{ {
private $url; private $url;
private $channel;
private $streamContext; private $streamContext;
public function __construct(array $config) public function __construct(array $config)
@ -29,11 +30,14 @@ class PearRepository extends ArrayRepository
if (!preg_match('{^https?://}', $config['url'])) { if (!preg_match('{^https?://}', $config['url'])) {
$config['url'] = 'http://'.$config['url']; $config['url'] = 'http://'.$config['url'];
} }
if (!filter_var($config['url'], FILTER_VALIDATE_URL)) { if (!filter_var($config['url'], FILTER_VALIDATE_URL)) {
throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']); throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']);
} }
$this->url = rtrim($config['url'], '/'); $this->url = rtrim($config['url'], '/');
$this->channel = !empty($config['channel']) ? $config['channel'] : null;
} }
protected function initialize() protected function initialize()
@ -50,6 +54,12 @@ class PearRepository extends ArrayRepository
protected function fetchFromServer() protected function fetchFromServer()
{ {
if (!$this->channel) {
$channelXML = $this->requestXml($this->url . "/channel.xml");
$this->channel = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue
?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue;
}
$categoryXML = $this->requestXml($this->url . "/rest/c/categories.xml"); $categoryXML = $this->requestXml($this->url . "/rest/c/categories.xml");
$categories = $categoryXML->getElementsByTagName("c"); $categories = $categoryXML->getElementsByTagName("c");
@ -81,6 +91,7 @@ class PearRepository extends ArrayRepository
$loader = new ArrayLoader(); $loader = new ArrayLoader();
foreach ($packages as $package) { foreach ($packages as $package) {
$packageName = $package->nodeValue; $packageName = $package->nodeValue;
$fullName = 'pear-'.$this->channel.'/'.$packageName;
$packageLink = $package->getAttribute('xlink:href'); $packageLink = $package->getAttribute('xlink:href');
$releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink); $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink);
@ -102,7 +113,7 @@ class PearRepository extends ArrayRepository
$pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue; $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue;
$packageData = array( $packageData = array(
'name' => $packageName, 'name' => $fullName,
'type' => 'library', 'type' => 'library',
'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"), 'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"),
'version' => $pearVersion, 'version' => $pearVersion,
@ -220,8 +231,9 @@ class PearRepository extends ArrayRepository
$package = $information->getElementsByTagName('p')->item(0); $package = $information->getElementsByTagName('p')->item(0);
$packageName = $package->getElementsByTagName('n')->item(0)->nodeValue; $packageName = $package->getElementsByTagName('n')->item(0)->nodeValue;
$fullName = 'pear-'.$this->channel.'/'.$packageName;
$packageData = array( $packageData = array(
'name' => $packageName, 'name' => $fullName,
'type' => 'library' 'type' => 'library'
); );
$packageKeys = array('l' => 'license', 'd' => 'description'); $packageKeys = array('l' => 'license', 'd' => 'description');

View File

@ -15,6 +15,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
protected $branches; protected $branches;
protected $rootIdentifier; protected $rootIdentifier;
protected $infoCache = array(); protected $infoCache = array();
protected $isLocal = false;
public function __construct($url, IOInterface $io, ProcessExecutor $process = null) public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
{ {
@ -30,10 +31,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
{ {
$url = escapeshellarg($this->url); $url = escapeshellarg($this->url);
$tmpDir = escapeshellarg($this->tmpDir); $tmpDir = escapeshellarg($this->tmpDir);
if (is_dir($this->tmpDir)) {
$this->process->execute(sprintf('cd %s && git fetch origin', $tmpDir), $output); if (static::isLocalUrl($url)) {
$this->isLocal = true;
} else { } else {
$this->process->execute(sprintf('git clone %s %s', $url, $tmpDir), $output); if (is_dir($this->tmpDir)) {
$this->process->execute(sprintf('cd %s && git fetch origin', $tmpDir), $output);
} else {
$this->process->execute(sprintf('git clone %s %s', $url, $tmpDir), $output);
}
} }
$this->getTags(); $this->getTags();
@ -47,11 +53,27 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
{ {
if (null === $this->rootIdentifier) { if (null === $this->rootIdentifier) {
$this->rootIdentifier = 'master'; $this->rootIdentifier = 'master';
$this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->tmpDir)), $output);
foreach ($this->process->splitLines($output) as $branch) { if ($this->isLocal) {
if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) { // select currently checked out branch if master is not available
$this->rootIdentifier = $match[1]; $this->process->execute(sprintf('cd %s && git branch --no-color', escapeshellarg($this->tmpDir)), $output);
break; $branches = $this->process->splitLines($output);
if (!in_array('* master', $branches)) {
foreach ($branches as $branch) {
if ($branch && preg_match('{^\* +(\S+)}', $branch, $match)) {
$this->rootIdentifier = $match[1];
break;
}
}
}
} else {
// try to find a non-master remote HEAD branch
$this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->tmpDir)), $output);
foreach ($this->process->splitLines($output) as $branch) {
if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) {
$this->rootIdentifier = $match[1];
break;
}
} }
} }
} }
@ -132,7 +154,11 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
if (null === $this->branches) { if (null === $this->branches) {
$branches = array(); $branches = array();
$this->process->execute(sprintf('cd %s && git branch --no-color -rv', escapeshellarg($this->tmpDir)), $output); $this->process->execute(sprintf(
'cd %s && git branch --no-color --no-abbrev -v %s',
escapeshellarg($this->tmpDir),
$this->isLocal ? '' : '-r'
), $output);
foreach ($this->process->splitLines($output) as $branch) { foreach ($this->process->splitLines($output) as $branch) {
if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
preg_match('{^ *[^/]+/(\S+) *([a-f0-9]+) .*$}', $branch, $match); preg_match('{^ *[^/]+/(\S+) *([a-f0-9]+) .*$}', $branch, $match);
@ -170,7 +196,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
} }
// local filesystem // local filesystem
if (preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url)) { if (static::isLocalUrl($url)) {
$process = new ProcessExecutor(); $process = new ProcessExecutor();
// check whether there is a git repo in that path // check whether there is a git repo in that path
if ($process->execute(sprintf('cd %s && git show', escapeshellarg($url)), $output) === 0) { if ($process->execute(sprintf('cd %s && git show', escapeshellarg($url)), $output) === 0) {

View File

@ -68,4 +68,9 @@ abstract class VcsDriver
$rfs = new RemoteFilesystem($this->io); $rfs = new RemoteFilesystem($this->io);
return $rfs->getContents($this->url, $url, false); return $rfs->getContents($this->url, $url, false);
} }
protected static function isLocalUrl($url)
{
return (Boolean) preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url);
}
} }

View File

@ -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) {

View File

@ -46,4 +46,16 @@ class RequestTest extends TestCase
), ),
$request->getJobs()); $request->getJobs());
} }
public function testUpdateAll()
{
$pool = new Pool;
$request = new Request($pool);
$request->updateAll();
$this->assertEquals(
array(array('cmd' => 'update-all', 'packages' => array())),
$request->getJobs());
}
} }

View File

@ -27,7 +27,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
new Rule(array(), 'job1', null), new Rule(array(), 'job1', null),
new Rule(array(), 'job2', null), new Rule(array(), 'job2', null),
), ),
RuleSet::TYPE_UPDATE => array( RuleSet::TYPE_FEATURE => array(
new Rule(array(), 'update1', null), new Rule(array(), 'update1', null),
), ),
RuleSet::TYPE_PACKAGE => array(), RuleSet::TYPE_PACKAGE => array(),
@ -46,7 +46,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
$expected = array( $expected = array(
$this->rules[RuleSet::TYPE_JOB][0], $this->rules[RuleSet::TYPE_JOB][0],
$this->rules[RuleSet::TYPE_JOB][1], $this->rules[RuleSet::TYPE_JOB][1],
$this->rules[RuleSet::TYPE_UPDATE][0], $this->rules[RuleSet::TYPE_FEATURE][0],
); );
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
@ -64,7 +64,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
$expected = array( $expected = array(
RuleSet::TYPE_JOB, RuleSet::TYPE_JOB,
RuleSet::TYPE_JOB, RuleSet::TYPE_JOB,
RuleSet::TYPE_UPDATE, RuleSet::TYPE_FEATURE,
); );
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);

View File

@ -27,10 +27,9 @@ class RuleSetTest extends TestCase
new Rule(array(), 'job1', null), new Rule(array(), 'job1', null),
new Rule(array(), 'job2', null), new Rule(array(), 'job2', null),
), ),
RuleSet::TYPE_UPDATE => array( RuleSet::TYPE_FEATURE => array(
new Rule(array(), 'update1', null), new Rule(array(), 'update1', null),
), ),
RuleSet::TYPE_FEATURE => array(),
RuleSet::TYPE_LEARNED => array(), RuleSet::TYPE_LEARNED => array(),
RuleSet::TYPE_CHOICE => array(), RuleSet::TYPE_CHOICE => array(),
); );
@ -38,7 +37,7 @@ class RuleSetTest extends TestCase
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB); $ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB);
$ruleSet->add($rules[RuleSet::TYPE_UPDATE][0], RuleSet::TYPE_UPDATE); $ruleSet->add($rules[RuleSet::TYPE_FEATURE][0], RuleSet::TYPE_FEATURE);
$ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB); $ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB);
$this->assertEquals($rules, $ruleSet->getRules()); $this->assertEquals($rules, $ruleSet->getRules());
@ -81,7 +80,7 @@ class RuleSetTest extends TestCase
$rule1 = new Rule(array(), 'job1', null); $rule1 = new Rule(array(), 'job1', null);
$rule2 = new Rule(array(), 'job1', null); $rule2 = new Rule(array(), 'job1', null);
$ruleSet->add($rule1, RuleSet::TYPE_JOB); $ruleSet->add($rule1, RuleSet::TYPE_JOB);
$ruleSet->add($rule2, RuleSet::TYPE_UPDATE); $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
$iterator = $ruleSet->getIterator(); $iterator = $ruleSet->getIterator();
@ -97,9 +96,9 @@ class RuleSetTest extends TestCase
$rule2 = new Rule(array(), 'job1', null); $rule2 = new Rule(array(), 'job1', null);
$ruleSet->add($rule1, RuleSet::TYPE_JOB); $ruleSet->add($rule1, RuleSet::TYPE_JOB);
$ruleSet->add($rule2, RuleSet::TYPE_UPDATE); $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
$iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_UPDATE); $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_FEATURE);
$this->assertSame($rule2, $iterator->current()); $this->assertSame($rule2, $iterator->current());
} }
@ -111,7 +110,7 @@ class RuleSetTest extends TestCase
$rule2 = new Rule(array(), 'job1', null); $rule2 = new Rule(array(), 'job1', null);
$ruleSet->add($rule1, RuleSet::TYPE_JOB); $ruleSet->add($rule1, RuleSet::TYPE_JOB);
$ruleSet->add($rule2, RuleSet::TYPE_UPDATE); $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
$iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB); $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB);
@ -143,7 +142,7 @@ class RuleSetTest extends TestCase
->method('equal') ->method('equal')
->will($this->returnValue(false)); ->will($this->returnValue(false));
$ruleSet->add($rule, RuleSet::TYPE_UPDATE); $ruleSet->add($rule, RuleSet::TYPE_FEATURE);
$this->assertTrue($ruleSet->containsEqual($rule)); $this->assertTrue($ruleSet->containsEqual($rule));
$this->assertFalse($ruleSet->containsEqual($rule2)); $this->assertFalse($ruleSet->containsEqual($rule2));
@ -156,9 +155,9 @@ class RuleSetTest extends TestCase
$literal = new Literal($this->getPackage('foo', '2.1'), true); $literal = new Literal($this->getPackage('foo', '2.1'), true);
$rule = new Rule(array($literal), 'job1', null); $rule = new Rule(array($literal), 'job1', null);
$ruleSet->add($rule, RuleSet::TYPE_UPDATE); $ruleSet->add($rule, RuleSet::TYPE_FEATURE);
$this->assertContains('UPDATE : (+foo-2.1.0.0)', $ruleSet->__toString()); $this->assertContains('FEATURE : (+foo-2.1.0.0)', $ruleSet->__toString());
} }
private function getRuleMock() private function getRuleMock()

View File

@ -19,6 +19,7 @@ use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Test\TestCase; use Composer\Test\TestCase;
@ -54,13 +55,28 @@ class SolverTest extends TestCase
)); ));
} }
public function testInstallNonExistingPackageFails()
{
$this->repo->addPackage($this->getPackage('A', '1.0'));
$this->reposComplete();
$this->request->install('B');
try {
$transaction = $this->solver->solve($this->request);
$this->fail('Unsolvable conflict did not resolve in exception.');
} catch (SolverProblemsException $e) {
// TODO assert problem properties
}
}
public function testSolverInstallWithDeps() public function testSolverInstallWithDeps()
{ {
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
$this->reposComplete(); $this->reposComplete();
@ -122,12 +138,12 @@ class SolverTest extends TestCase
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$this->reposComplete(); $this->reposComplete();
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0.0.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0.0.0'), 'requires')));
$this->request->install('A', new VersionConstraint('=', '1.0.0.0')); $this->request->install('A', $this->getVersionConstraint('=', '1.0.0.0'));
$this->request->install('B', new VersionConstraint('=', '1.1.0.0')); $this->request->install('B', $this->getVersionConstraint('=', '1.1.0.0'));
$this->request->update('A', new VersionConstraint('=', '1.0.0.0')); $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
$this->request->update('B', new VersionConstraint('=', '1.0.0.0')); $this->request->update('B', $this->getVersionConstraint('=', '1.0.0.0'));
$this->checkSolverResult(array( $this->checkSolverResult(array(
array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB), array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB),
@ -147,6 +163,26 @@ class SolverTest extends TestCase
)); ));
} }
public function testSolverUpdateAll()
{
$this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$packageA->setRequires(array(new Link('A', 'B', null, 'requires')));
$this->reposComplete();
$this->request->install('A');
$this->request->updateAll();
$this->checkSolverResult(array(
array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB),
array('job' => 'update', 'from' => $packageA, 'to' => $newPackageA),
));
}
public function testSolverUpdateCurrent() public function testSolverUpdateCurrent()
{ {
$this->repoInstalled->addPackage($this->getPackage('A', '1.0')); $this->repoInstalled->addPackage($this->getPackage('A', '1.0'));
@ -181,7 +217,7 @@ class SolverTest extends TestCase
$this->repo->addPackage($this->getPackage('A', '2.0')); $this->repo->addPackage($this->getPackage('A', '2.0'));
$this->reposComplete(); $this->reposComplete();
$this->request->install('A', new VersionConstraint('<', '2.0.0.0')); $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
$this->request->update('A'); $this->request->update('A');
$this->checkSolverResult(array(array( $this->checkSolverResult(array(array(
@ -198,8 +234,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($this->getPackage('A', '2.0')); $this->repo->addPackage($this->getPackage('A', '2.0'));
$this->reposComplete(); $this->reposComplete();
$this->request->install('A', new VersionConstraint('<', '2.0.0.0')); $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
$this->request->update('A', new VersionConstraint('=', '1.0.0.0')); $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
$this->checkSolverResult(array(array( $this->checkSolverResult(array(array(
'job' => 'update', 'job' => 'update',
@ -216,8 +252,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($this->getPackage('A', '2.0')); $this->repo->addPackage($this->getPackage('A', '2.0'));
$this->reposComplete(); $this->reposComplete();
$this->request->install('A', new VersionConstraint('<', '2.0.0.0')); $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
$this->request->update('A', new VersionConstraint('=', '1.0.0.0')); $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
$this->checkSolverResult(array(array( $this->checkSolverResult(array(array(
'job' => 'update', 'job' => 'update',
@ -236,7 +272,7 @@ class SolverTest extends TestCase
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$this->repo->addPackage($packageC = $this->getPackage('C', '1.1')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.1'));
$this->repo->addPackage($this->getPackage('D', '1.0')); $this->repo->addPackage($this->getPackage('D', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
$this->reposComplete(); $this->reposComplete();
@ -258,8 +294,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
$this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9')); $this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
$packageA->setConflicts(array(new Link('A', 'B', new VersionConstraint('<', '1.0'), 'conflicts'))); $packageA->setConflicts(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.0'), 'conflicts')));
$this->reposComplete(); $this->reposComplete();
@ -305,8 +341,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '0.8')); $this->repo->addPackage($packageB = $this->getPackage('B', '0.8'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setProvides(array(new Link('Q', 'B', new VersionConstraint('=', '1.0'), 'provides'))); $packageQ->setProvides(array(new Link('Q', 'B', $this->getVersionConstraint('=', '1.0'), 'provides')));
$this->reposComplete(); $this->reposComplete();
@ -323,8 +359,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -340,8 +376,8 @@ class SolverTest extends TestCase
{ {
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -358,8 +394,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0')); $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces'))); $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -376,24 +412,24 @@ class SolverTest extends TestCase
{ {
$this->repo->addPackage($packageX = $this->getPackage('X', '1.0')); $this->repo->addPackage($packageX = $this->getPackage('X', '1.0'));
$packageX->setRequires(array( $packageX->setRequires(array(
new Link('X', 'A', new VersionConstraint('>=', '2.0.0.0'), 'requires'), new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'),
new Link('X', 'B', new VersionConstraint('>=', '2.0.0.0'), 'requires'))); new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires')));
$this->repo->addPackage($packageA = $this->getPackage('A', '2.0.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '2.0.0'));
$this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0')); $this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0'));
$this->repo->addPackage($newPackageB = $this->getPackage('B', '2.1.0')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '2.1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '2.0.0.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires')));
// new package A depends on version of package B that does not exist // new package A depends on version of package B that does not exist
// => new package A is not installable // => new package A is not installable
$newPackageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '2.2.0.0'), 'requires'))); $newPackageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.2.0.0'), 'requires')));
// add a package S replacing both A and B, so that S and B or S and A cannot be simultaneously installed // add a package S replacing both A and B, so that S and B or S and A cannot be simultaneously installed
// but an alternative option for A and B both exists // but an alternative option for A and B both exists
// this creates a more difficult so solve conflict // this creates a more difficult so solve conflict
$this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0')); $this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0'));
$packageS->setReplaces(array(new Link('S', 'A', new VersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', new VersionConstraint('>=', '2.0.0.0'), 'replaces'))); $packageS->setReplaces(array(new Link('S', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces')));
$this->reposComplete(); $this->reposComplete();
@ -411,8 +447,8 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9')); $this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9'));
$this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1')); $this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageB2->setRequires(array(new Link('B', 'A', new VersionConstraint('>=', '1.0'), 'requires'))); $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$this->reposComplete(); $this->reposComplete();
@ -426,16 +462,17 @@ class SolverTest extends TestCase
public function testInstallAlternativeWithCircularRequire() public function testInstallAlternativeWithCircularRequire()
{ {
$this->markTestIncomplete();
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$this->repo->addPackage($packageC = $this->getPackage('C', '1.0')); $this->repo->addPackage($packageC = $this->getPackage('C', '1.0'));
$this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); $this->repo->addPackage($packageD = $this->getPackage('D', '1.0'));
$packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'))); $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageB->setRequires(array(new Link('B', 'Virtual', new VersionConstraint('>=', '1.0'), 'requires'))); $packageB->setRequires(array(new Link('B', 'Virtual', $this->getVersionConstraint('>=', '1.0'), 'requires')));
$packageC->setRequires(array(new Link('C', 'Virtual', new VersionConstraint('==', '1.0'), 'provides'))); $packageC->setProvides(array(new Link('C', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides')));
$packageD->setRequires(array(new Link('D', 'Virtual', new VersionConstraint('==', '1.0'), 'provides'))); $packageD->setProvides(array(new Link('D', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides')));
$packageC->setRequires(array(new Link('C', 'A', $this->getVersionConstraint('==', '1.0'), 'requires')));
$packageD->setRequires(array(new Link('D', 'A', $this->getVersionConstraint('==', '1.0'), 'requires')));
$this->reposComplete(); $this->reposComplete();
@ -460,18 +497,18 @@ class SolverTest extends TestCase
$this->repo->addPackage($packageD2 = $this->getPackage('D', '1.1')); $this->repo->addPackage($packageD2 = $this->getPackage('D', '1.1'));
$packageA->setRequires(array( $packageA->setRequires(array(
new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'), new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'),
new Link('A', 'C', new VersionConstraint('>=', '1.0'), 'requires'), new Link('A', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'),
)); ));
$packageD->setReplaces(array( $packageD->setReplaces(array(
new Link('D', 'B', new VersionConstraint('>=', '1.0'), 'replaces'), new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
new Link('D', 'C', new VersionConstraint('>=', '1.0'), 'replaces'), new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
)); ));
$packageD2->setReplaces(array( $packageD2->setReplaces(array(
new Link('D', 'B', new VersionConstraint('>=', '1.0'), 'replaces'), new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
new Link('D', 'C', new VersionConstraint('>=', '1.0'), 'replaces'), new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
)); ));
$this->reposComplete(); $this->reposComplete();
@ -484,6 +521,83 @@ class SolverTest extends TestCase
)); ));
} }
public function testIssue265()
{
$this->repo->addPackage($packageA1 = $this->getPackage('A', '2.0.999999-dev'));
$this->repo->addPackage($packageA2 = $this->getPackage('A', '2.1-dev'));
$this->repo->addPackage($packageA3 = $this->getPackage('A', '2.2-dev'));
$this->repo->addPackage($packageB1 = $this->getPackage('B', '2.0.10'));
$this->repo->addPackage($packageB2 = $this->getPackage('B', '2.0.9'));
$this->repo->addPackage($packageC = $this->getPackage('C', '2.0-dev'));
$this->repo->addPackage($packageD = $this->getPackage('D', '2.0.9'));
$packageC->setRequires(array(
new Link('C', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'),
new Link('C', 'D', $this->getVersionConstraint('>=', '2.0'), 'requires'),
));
$packageD->setRequires(array(
new Link('D', 'A', $this->getVersionConstraint('>=', '2.1'), 'requires'),
new Link('D', 'B', $this->getVersionConstraint('>=', '2.0-dev'), 'requires'),
));
$packageB1->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires')));
$packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires')));
$packageB2->setReplaces(array(new Link('B', 'D', $this->getVersionConstraint('==', '2.0.9.0'), 'replaces')));
$this->reposComplete();
$this->request->install('C', $this->getVersionConstraint('==', '2.0.0.0-dev'));
$this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
$this->solver->solve($this->request);
}
public function testConflictResultEmpty()
{
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));;
$packageA->setConflicts(array(
new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'conflicts'),
));
$this->reposComplete();
$this->request->install('A');
$this->request->install('B');
try {
$transaction = $this->solver->solve($this->request);
$this->fail('Unsolvable conflict did not resolve in exception.');
} catch (SolverProblemsException $e) {
// TODO assert problem properties
}
}
public function testUnsatisfiableRequires()
{
$this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
$packageA->setRequires(array(
new Link('A', 'B', $this->getVersionConstraint('>=', '2.0'), 'requires'),
));
$this->reposComplete();
$this->request->install('A');
try {
$transaction = $this->solver->solve($this->request);
$this->fail('Unsolvable conflict did not resolve in exception.');
} catch (SolverProblemsException $e) {
// TODO assert problem properties
}
}
protected function reposComplete() protected function reposComplete()
{ {
$this->pool->addRepository($this->repoInstalled); $this->pool->addRepository($this->repoInstalled);
@ -513,5 +627,4 @@ class SolverTest extends TestCase
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
} }
} }

View File

@ -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;

View File

@ -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())

View File

@ -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')),
); );
} }

View File

@ -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)));
}
}

View File

@ -1,34 +1,43 @@
<?php <?php
/* /*
* This file is part of Composer. * This file is part of Composer.
* *
* (c) Nils Adermann <naderman@naderman.de> * (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be> * Jordi Boggiano <j.boggiano@seld.be>
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace Composer\Test; namespace Composer\Test;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Package\MemoryPackage; use Composer\Package\MemoryPackage;
use Composer\Package\LinkConstraint\VersionConstraint;
abstract class TestCase extends \PHPUnit_Framework_TestCase
{ abstract class TestCase extends \PHPUnit_Framework_TestCase
private static $versionParser; {
private static $versionParser;
public static function setUpBeforeClass()
{ public static function setUpBeforeClass()
if (!self::$versionParser) { {
self::$versionParser = new VersionParser(); if (!self::$versionParser) {
} self::$versionParser = new VersionParser();
} }
}
protected function getPackage($name, $version)
{ protected function getVersionConstraint($operator, $version)
$normVersion = self::$versionParser->normalize($version); {
return new MemoryPackage($name, $normVersion, $version); return new VersionConstraint(
} $operator,
} self::$versionParser->normalize($version)
);
}
protected function getPackage($name, $version)
{
$normVersion = self::$versionParser->normalize($version);
return new MemoryPackage($name, $normVersion, $version);
}
}