commit
78c250da19
|
@ -9,5 +9,7 @@ php:
|
||||||
before_script:
|
before_script:
|
||||||
- echo '' > ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
|
- echo '' > ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
|
||||||
- composer install --dev --prefer-source
|
- composer install --dev --prefer-source
|
||||||
|
- git config --global user.name travis-ci
|
||||||
|
- git config --global user.email travis@example.com
|
||||||
|
|
||||||
script: ./vendor/bin/phpunit -c tests/complete.phpunit.xml
|
script: ./vendor/bin/phpunit -c tests/complete.phpunit.xml
|
||||||
|
|
|
@ -656,4 +656,29 @@ See [Vendor Binaries](articles/vendor-binaries.md) for more details.
|
||||||
|
|
||||||
Optional.
|
Optional.
|
||||||
|
|
||||||
|
### archive
|
||||||
|
|
||||||
|
A set of options for creating package archives.
|
||||||
|
|
||||||
|
The following options are supported:
|
||||||
|
|
||||||
|
* **exclude:** Allows configuring a list of patterns for excluded paths. The
|
||||||
|
pattern syntax matches .gitignore files. A leading exclamation mark (!) will
|
||||||
|
result in any matching files to be included even if a previous pattern
|
||||||
|
excluded them. A leading slash will only match at the beginning of the project
|
||||||
|
relative path. An asterisk will not expand to a directory separator.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{
|
||||||
|
"archive": {
|
||||||
|
"exclude": ["/foo/bar", "baz", "/*.test", "!/foo/bar/baz"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The example will include `/dir/foo/bar/file`, `/foo/bar/baz`, `/file.php`,
|
||||||
|
`/foo/my.test` but it will exclude `/foo/bar/any`, `/foo/baz`, and `/my.test`.
|
||||||
|
|
||||||
|
Optional.
|
||||||
|
|
||||||
← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) →
|
← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) →
|
||||||
|
|
|
@ -202,6 +202,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"archive": {
|
||||||
|
"type": ["object"],
|
||||||
|
"description": "Options for creating package archives for distribution.",
|
||||||
|
"properties": {
|
||||||
|
"exclude": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repositories": {
|
"repositories": {
|
||||||
"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.",
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Command;
|
||||||
|
|
||||||
|
use Composer\Factory;
|
||||||
|
use Composer\IO\IOInterface;
|
||||||
|
use Composer\DependencyResolver\Pool;
|
||||||
|
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||||
|
use Composer\Repository\CompositeRepository;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an archive of a package for distribution.
|
||||||
|
*
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class ArchiveCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('archive')
|
||||||
|
->setDescription('Create an archive of this composer package')
|
||||||
|
->setDefinition(array(
|
||||||
|
new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project'),
|
||||||
|
new InputArgument('version', InputArgument::OPTIONAL, 'The package version to archive'),
|
||||||
|
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar or zip', 'tar'),
|
||||||
|
new InputOption('dir', false, InputOption::VALUE_REQUIRED, 'Write the archive to this directory', '.'),
|
||||||
|
))
|
||||||
|
->setHelp(<<<EOT
|
||||||
|
The <info>archive</info> command creates an archive of the specified format
|
||||||
|
containing the files and directories of the Composer project or the specified
|
||||||
|
package in the specified version and writes it to the specified directory.
|
||||||
|
|
||||||
|
<info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info>
|
||||||
|
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
return $this->archive(
|
||||||
|
$this->getIO(),
|
||||||
|
$input->getArgument('package'),
|
||||||
|
$input->getArgument('version'),
|
||||||
|
$input->getOption('format'),
|
||||||
|
$input->getOption('dir')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function archive(IOInterface $io, $packageName = null, $version = null, $format = 'tar', $dest = '.')
|
||||||
|
{
|
||||||
|
$config = Factory::createConfig();
|
||||||
|
$factory = new Factory;
|
||||||
|
$archiveManager = $factory->createArchiveManager($config);
|
||||||
|
|
||||||
|
if ($packageName) {
|
||||||
|
$package = $this->selectPackage($io, $packageName, $version);
|
||||||
|
|
||||||
|
if (!$package) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$package = $this->getComposer()->getPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->write('<info>Creating the archive.</info>');
|
||||||
|
$archiveManager->archive($package, $format, $dest);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function selectPackage(IOInterface $io, $packageName, $version = null)
|
||||||
|
{
|
||||||
|
$io->write('<info>Searching for the specified package.</info>');
|
||||||
|
|
||||||
|
if ($composer = $this->getComposer(false)) {
|
||||||
|
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||||
|
$repos = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories()));
|
||||||
|
} else {
|
||||||
|
$defaultRepos = Factory::createDefaultRepositories($this->getIO());
|
||||||
|
$output->writeln('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos)));
|
||||||
|
$repos = new CompositeRepository($defaultRepos);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pool = new Pool();
|
||||||
|
$pool->addRepository($repos);
|
||||||
|
|
||||||
|
$constraint = ($version) ? new VersionConstraint('>=', $version) : null;
|
||||||
|
$packages = $pool->whatProvides($packageName, $constraint);
|
||||||
|
|
||||||
|
if (count($packages) > 1) {
|
||||||
|
$package = $packages[0];
|
||||||
|
$io->write('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>');
|
||||||
|
$io->write('Alternatives were '.implode(', ', array_map(function ($p) { return $p->getPrettyString(); }, $packages)).'.');
|
||||||
|
$io->write('<comment>Please use a more specific constraint to pick a different package.</comment>');
|
||||||
|
} elseif ($packages) {
|
||||||
|
$package = $packages[0];
|
||||||
|
$io->write('<info>Found an exact match '.$package->getPrettyString().'.</info>');
|
||||||
|
} else {
|
||||||
|
$io->write('<error>Could not find a package matching '.$packageName.'.</error>');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $package;
|
||||||
|
}
|
||||||
|
}
|
|
@ -194,6 +194,7 @@ class Application extends BaseApplication
|
||||||
$commands[] = new Command\RequireCommand();
|
$commands[] = new Command\RequireCommand();
|
||||||
$commands[] = new Command\DumpAutoloadCommand();
|
$commands[] = new Command\DumpAutoloadCommand();
|
||||||
$commands[] = new Command\StatusCommand();
|
$commands[] = new Command\StatusCommand();
|
||||||
|
$commands[] = new Command\ArchiveCommand();
|
||||||
|
|
||||||
if ('phar:' === substr(__FILE__, 0, 5)) {
|
if ('phar:' === substr(__FILE__, 0, 5)) {
|
||||||
$commands[] = new Command\SelfUpdateCommand();
|
$commands[] = new Command\SelfUpdateCommand();
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer;
|
||||||
use Composer\Config\JsonConfigSource;
|
use Composer\Config\JsonConfigSource;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
use Composer\Package\Archiver;
|
||||||
use Composer\Repository\ComposerRepository;
|
use Composer\Repository\ComposerRepository;
|
||||||
use Composer\Repository\RepositoryManager;
|
use Composer\Repository\RepositoryManager;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
@ -317,6 +318,24 @@ class Factory
|
||||||
return $dm;
|
return $dm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Config $config The configuration
|
||||||
|
* @param Downloader\DownloadManager $dm Manager use to download sources
|
||||||
|
*
|
||||||
|
* @return Archiver\ArchiveManager
|
||||||
|
*/
|
||||||
|
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
|
||||||
|
{
|
||||||
|
if (null === $dm) {
|
||||||
|
$dm = $this->createDownloadManager(new IO\NullIO(), $config);
|
||||||
|
}
|
||||||
|
|
||||||
|
$am = new Archiver\ArchiveManager($dm);
|
||||||
|
$am->addArchiver(new Archiver\PharArchiver);
|
||||||
|
|
||||||
|
return $am;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Installer\InstallationManager
|
* @return Installer\InstallationManager
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -311,6 +311,10 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
|
||||||
{
|
{
|
||||||
return $this->aliasOf->getNotificationUrl();
|
return $this->aliasOf->getNotificationUrl();
|
||||||
}
|
}
|
||||||
|
public function getArchiveExcludes()
|
||||||
|
{
|
||||||
|
return $this->aliasOf->getArchiveExcludes();
|
||||||
|
}
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')';
|
return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')';
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Package\BasePackage;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
|
||||||
|
use Symfony\Component\Finder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Symfony Finder wrapper which locates files that should go into archives
|
||||||
|
*
|
||||||
|
* Handles .gitignore, .gitattributes and .hgignore files as well as composer's
|
||||||
|
* own exclude rules from composer.json
|
||||||
|
*
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class ArchivableFilesFinder extends \FilterIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Symfony\Component\Finder\Finder
|
||||||
|
*/
|
||||||
|
protected $finder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the internal Symfony Finder with appropriate filters
|
||||||
|
*
|
||||||
|
* @param string $sources Path to source files to be archived
|
||||||
|
* @param array $excludes Composer's own exclude rules from composer.json
|
||||||
|
*/
|
||||||
|
public function __construct($sources, array $excludes)
|
||||||
|
{
|
||||||
|
$sources = realpath($sources);
|
||||||
|
|
||||||
|
$filters = array(
|
||||||
|
new HgExcludeFilter($sources),
|
||||||
|
new GitExcludeFilter($sources),
|
||||||
|
new ComposerExcludeFilter($sources, $excludes),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->finder = new Finder\Finder();
|
||||||
|
$this->finder
|
||||||
|
->in($sources)
|
||||||
|
->filter(function (\SplFileInfo $file) use ($sources, $filters) {
|
||||||
|
$relativePath = preg_replace(
|
||||||
|
'#^'.preg_quote($sources, '#').'#',
|
||||||
|
'',
|
||||||
|
str_replace(DIRECTORY_SEPARATOR, '/', $file->getRealPath())
|
||||||
|
);
|
||||||
|
|
||||||
|
$exclude = false;
|
||||||
|
foreach ($filters as $filter) {
|
||||||
|
$exclude = $filter->filter($relativePath, $exclude);
|
||||||
|
}
|
||||||
|
return !$exclude;
|
||||||
|
})
|
||||||
|
->ignoreVCS(true)
|
||||||
|
->ignoreDotFiles(false);
|
||||||
|
|
||||||
|
parent::__construct($this->finder->getIterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accept()
|
||||||
|
{
|
||||||
|
return !$this->getInnerIterator()->current()->isDir();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Downloader\DownloadManager;
|
||||||
|
use Composer\Factory;
|
||||||
|
use Composer\IO\NullIO;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Package\RootPackage;
|
||||||
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Matthieu Moquet <matthieu@moquet.net>
|
||||||
|
* @author Till Klampaeckel <till@php.net>
|
||||||
|
*/
|
||||||
|
class ArchiveManager
|
||||||
|
{
|
||||||
|
protected $downloadManager;
|
||||||
|
|
||||||
|
protected $archivers = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $overwriteFiles = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DownloadManager $downloadManager A manager used to download package sources
|
||||||
|
*/
|
||||||
|
public function __construct(DownloadManager $downloadManager)
|
||||||
|
{
|
||||||
|
$this->downloadManager = $downloadManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ArchiverInterface $archiver
|
||||||
|
*/
|
||||||
|
public function addArchiver(ArchiverInterface $archiver)
|
||||||
|
{
|
||||||
|
$this->archivers[] = $archiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether existing archives should be overwritten
|
||||||
|
*
|
||||||
|
* @param bool $overwriteFiles New setting
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setOverwriteFiles($overwriteFiles)
|
||||||
|
{
|
||||||
|
$this->overwriteFiles = $overwriteFiles;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a distinct filename for a particular version of a package.
|
||||||
|
*
|
||||||
|
* @param PackageInterface $package The package to get a name for
|
||||||
|
*
|
||||||
|
* @return string A filename without an extension
|
||||||
|
*/
|
||||||
|
public function getPackageFilename(PackageInterface $package)
|
||||||
|
{
|
||||||
|
$nameParts = array(preg_replace('#[^a-z0-9-_.]#i', '-', $package->getName()));
|
||||||
|
|
||||||
|
if (preg_match('{^[a-f0-9]{40}$}', $package->getDistReference())) {
|
||||||
|
$nameParts = array_merge($nameParts, array($package->getDistReference(), $package->getDistType()));
|
||||||
|
} else {
|
||||||
|
$nameParts = array_merge($nameParts, array($package->getPrettyVersion(), $package->getDistReference()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($package->getSourceReference()) {
|
||||||
|
$nameParts[] = substr(sha1($package->getSourceReference()), 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('-', array_filter($nameParts, function ($p) {
|
||||||
|
return !empty($p);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an archive of the specified package.
|
||||||
|
*
|
||||||
|
* @param PackageInterface $package The package to archive
|
||||||
|
* @param string $format The format of the archive (zip, tar, ...)
|
||||||
|
* @param string $targetDir The diretory where to build the archive
|
||||||
|
*
|
||||||
|
* @return string The path of the created archive
|
||||||
|
*/
|
||||||
|
public function archive(PackageInterface $package, $format, $targetDir)
|
||||||
|
{
|
||||||
|
if (empty($format)) {
|
||||||
|
throw new \InvalidArgumentException('Format must be specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the most appropriate archiver
|
||||||
|
$usableArchiver = null;
|
||||||
|
foreach ($this->archivers as $archiver) {
|
||||||
|
if ($archiver->supports($format, $package->getSourceType())) {
|
||||||
|
$usableArchiver = $archiver;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks the format/source type are supported before downloading the package
|
||||||
|
if (null === $usableArchiver) {
|
||||||
|
throw new \RuntimeException(sprintf('No archiver found to support %s format', $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$packageName = $this->getPackageFilename($package);
|
||||||
|
|
||||||
|
// Archive filename
|
||||||
|
$filesystem->ensureDirectoryExists($targetDir);
|
||||||
|
$target = realpath($targetDir).'/'.$packageName.'.'.$format;
|
||||||
|
$filesystem->ensureDirectoryExists(dirname($target));
|
||||||
|
|
||||||
|
if (!$this->overwriteFiles && file_exists($target)) {
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($package instanceof RootPackage) {
|
||||||
|
$sourcePath = realpath('.');
|
||||||
|
} else {
|
||||||
|
// Directory used to download the sources
|
||||||
|
$sourcePath = sys_get_temp_dir().'/composer_archiver/'.$packageName;
|
||||||
|
$filesystem->ensureDirectoryExists($sourcePath);
|
||||||
|
|
||||||
|
// Download sources
|
||||||
|
$this->downloadManager->download($package, $sourcePath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the archive
|
||||||
|
return $usableArchiver->archive($sourcePath, $target, $format, $package->getArchiveExcludes());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Till Klampaeckel <till@php.net>
|
||||||
|
* @author Matthieu Moquet <matthieu@moquet.net>
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
interface ArchiverInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an archive from the sources.
|
||||||
|
*
|
||||||
|
* @param string $sources The sources directory
|
||||||
|
* @param string $target The target file
|
||||||
|
* @param string $format The format used for archive
|
||||||
|
* @param array $excludes A list of patterns for files to exclude
|
||||||
|
*
|
||||||
|
* @return string The path to the written archive file
|
||||||
|
*/
|
||||||
|
public function archive($sources, $target, $format, array $excludes = array());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format supported by the archiver.
|
||||||
|
*
|
||||||
|
* @param string $format The archive format
|
||||||
|
* @param string $sourceType The source type (git, svn, hg, etc.)
|
||||||
|
*
|
||||||
|
* @return boolean true if the format is supported by the archiver
|
||||||
|
*/
|
||||||
|
public function supports($format, $sourceType);
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Symfony\Component\Finder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
abstract class BaseExcludeFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sourcePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $excludePatterns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sourcePath Directory containing sources to be filtered
|
||||||
|
*/
|
||||||
|
public function __construct($sourcePath)
|
||||||
|
{
|
||||||
|
$this->sourcePath = $sourcePath;
|
||||||
|
$this->excludePatterns = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the given path against all exclude patterns in this filter
|
||||||
|
*
|
||||||
|
* Negated patterns overwrite exclude decisions of previous filters.
|
||||||
|
*
|
||||||
|
* @param string $relativePath The file's path relative to the sourcePath
|
||||||
|
* @param bool $exclude Whether a previous filter wants to exclude this file
|
||||||
|
*
|
||||||
|
* @return bool Whether the file should be excluded
|
||||||
|
*/
|
||||||
|
public function filter($relativePath, $exclude)
|
||||||
|
{
|
||||||
|
foreach ($this->excludePatterns as $patternData) {
|
||||||
|
list($pattern, $negate, $stripLeadingSlash) = $patternData;
|
||||||
|
|
||||||
|
if ($stripLeadingSlash) {
|
||||||
|
$path = substr($relativePath, 1);
|
||||||
|
} else {
|
||||||
|
$path = $relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match($pattern, $path)) {
|
||||||
|
$exclude = !$negate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a file containing exclude rules of different formats per line
|
||||||
|
*
|
||||||
|
* @param array $lines A set of lines to be parsed
|
||||||
|
* @param callback $lineParser The parser to be used on each line
|
||||||
|
*
|
||||||
|
* @return array Exclude patterns to be used in filter()
|
||||||
|
*/
|
||||||
|
protected function parseLines(array $lines, $lineParser)
|
||||||
|
{
|
||||||
|
return array_filter(
|
||||||
|
array_map(
|
||||||
|
function ($line) use ($lineParser) {
|
||||||
|
$line = trim($line);
|
||||||
|
|
||||||
|
$commentHash = strpos($line, '#');
|
||||||
|
if ($commentHash !== false) {
|
||||||
|
$line = substr($line, 0, $commentHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($line) {
|
||||||
|
return call_user_func($lineParser, $line);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, $lines),
|
||||||
|
function ($pattern) {
|
||||||
|
return $pattern !== null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a set of exclude patterns for filter() from gitignore rules
|
||||||
|
*
|
||||||
|
* @param array $rules A list of exclude rules in gitignore syntax
|
||||||
|
*
|
||||||
|
* @return array Exclude patterns
|
||||||
|
*/
|
||||||
|
protected function generatePatterns($rules)
|
||||||
|
{
|
||||||
|
$patterns = array();
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
$patterns[] = $this->generatePattern($rule);
|
||||||
|
}
|
||||||
|
return $patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an exclude pattern for filter() from a gitignore rule
|
||||||
|
*
|
||||||
|
* @param string An exclude rule in gitignore syntax
|
||||||
|
*
|
||||||
|
* @param array An exclude pattern
|
||||||
|
*/
|
||||||
|
protected function generatePattern($rule)
|
||||||
|
{
|
||||||
|
$negate = false;
|
||||||
|
$pattern = '#';
|
||||||
|
|
||||||
|
if (strlen($rule) && $rule[0] === '!') {
|
||||||
|
$negate = true;
|
||||||
|
$rule = substr($rule, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($rule) && $rule[0] === '/') {
|
||||||
|
$pattern .= '^/';
|
||||||
|
$rule = substr($rule, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern .= substr(Finder\Glob::toRegex($rule), 2, -2);
|
||||||
|
return array($pattern . '#', $negate, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exclude filter which processes composer's own exclude rules
|
||||||
|
*
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class ComposerExcludeFilter extends BaseExcludeFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $sourcePath Directory containing sources to be filtered
|
||||||
|
* @param array $excludeRules An array of exclude rules from composer.json
|
||||||
|
*/
|
||||||
|
public function __construct($sourcePath, array $excludeRules)
|
||||||
|
{
|
||||||
|
parent::__construct($sourcePath);
|
||||||
|
$this->excludePatterns = $this->generatePatterns($excludeRules);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exclude filter that processes gitignore and gitattributes
|
||||||
|
*
|
||||||
|
* It respects export-ignore git attributes
|
||||||
|
*
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class GitExcludeFilter extends BaseExcludeFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parses .gitignore and .gitattributes files if they exist
|
||||||
|
*
|
||||||
|
* @param string $sourcePath
|
||||||
|
*/
|
||||||
|
public function __construct($sourcePath)
|
||||||
|
{
|
||||||
|
parent::__construct($sourcePath);
|
||||||
|
|
||||||
|
if (file_exists($sourcePath.'/.gitignore')) {
|
||||||
|
$this->excludePatterns = $this->parseLines(
|
||||||
|
file($sourcePath.'/.gitignore'),
|
||||||
|
array($this, 'parseGitIgnoreLine')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (file_exists($sourcePath.'/.gitattributes')) {
|
||||||
|
$this->excludePatterns = array_merge(
|
||||||
|
$this->excludePatterns,
|
||||||
|
$this->parseLines(
|
||||||
|
file($sourcePath.'/.gitattributes'),
|
||||||
|
array($this, 'parseGitAttributesLine')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback line parser which process gitignore lines
|
||||||
|
*
|
||||||
|
* @param string $line A line from .gitignore
|
||||||
|
*
|
||||||
|
* @return array An exclude pattern for filter()
|
||||||
|
*/
|
||||||
|
public function parseGitIgnoreLine($line)
|
||||||
|
{
|
||||||
|
return $this->generatePattern($line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback parser which finds export-ignore rules in git attribute lines
|
||||||
|
*
|
||||||
|
* @param string $line A line from .gitattributes
|
||||||
|
*
|
||||||
|
* @return array An exclude pattern for filter()
|
||||||
|
*/
|
||||||
|
public function parseGitAttributesLine($line)
|
||||||
|
{
|
||||||
|
$parts = preg_split('#\s+#', $line);
|
||||||
|
|
||||||
|
if (count($parts) != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parts[1] === 'export-ignore') {
|
||||||
|
return $this->generatePattern($parts[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Symfony\Component\Finder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exclude filter that processes hgignore files
|
||||||
|
*
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class HgExcludeFilter extends BaseExcludeFilter
|
||||||
|
{
|
||||||
|
const HG_IGNORE_REGEX = 1;
|
||||||
|
const HG_IGNORE_GLOB = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either HG_IGNORE_REGEX or HG_IGNORE_GLOB
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $patternMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses .hgignore file if it exist
|
||||||
|
*
|
||||||
|
* @param string $sourcePath
|
||||||
|
*/
|
||||||
|
public function __construct($sourcePath)
|
||||||
|
{
|
||||||
|
parent::__construct($sourcePath);
|
||||||
|
|
||||||
|
$this->patternMode = self::HG_IGNORE_REGEX;
|
||||||
|
|
||||||
|
if (file_exists($sourcePath.'/.hgignore')) {
|
||||||
|
$this->excludePatterns = $this->parseLines(
|
||||||
|
file($sourcePath.'/.hgignore'),
|
||||||
|
array($this, 'parseHgIgnoreLine')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback line parser which process hgignore lines
|
||||||
|
*
|
||||||
|
* @param string $line A line from .hgignore
|
||||||
|
*
|
||||||
|
* @return array An exclude pattern for filter()
|
||||||
|
*/
|
||||||
|
public function parseHgIgnoreLine($line)
|
||||||
|
{
|
||||||
|
if (preg_match('#^syntax\s*:\s*(glob|regexp)$#', $line, $matches)) {
|
||||||
|
if ($matches[1] === 'glob') {
|
||||||
|
$this->patternMode = self::HG_IGNORE_GLOB;
|
||||||
|
} else {
|
||||||
|
$this->patternMode = self::HG_IGNORE_REGEX;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->patternMode == self::HG_IGNORE_GLOB) {
|
||||||
|
return $this->patternFromGlob($line);
|
||||||
|
} else {
|
||||||
|
return $this->patternFromRegex($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an exclude pattern for filter() from a hg glob expression
|
||||||
|
*
|
||||||
|
* @param string $line A line from .hgignore in glob mode
|
||||||
|
*
|
||||||
|
* @return array An exclude pattern for filter()
|
||||||
|
*/
|
||||||
|
protected function patternFromGlob($line)
|
||||||
|
{
|
||||||
|
$pattern = '#'.substr(Finder\Glob::toRegex($line), 2, -1).'#';
|
||||||
|
$pattern = str_replace('[^/]*', '.*', $pattern);
|
||||||
|
return array($pattern, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an exclude pattern for filter() from a hg regexp expression
|
||||||
|
*
|
||||||
|
* @param string $line A line from .hgignore in regexp mode
|
||||||
|
*
|
||||||
|
* @return array An exclude pattern for filter()
|
||||||
|
*/
|
||||||
|
public function patternFromRegex($line)
|
||||||
|
{
|
||||||
|
// WTF need to escape the delimiter safely
|
||||||
|
$pattern = '#'.preg_replace('/((?:\\\\\\\\)*)(\\\\?)#/', '\1\2\2\\#', $line).'#';
|
||||||
|
return array($pattern, false, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Package\BasePackage;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Till Klampaeckel <till@php.net>
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
* @author Matthieu Moquet <matthieu@moquet.net>
|
||||||
|
*/
|
||||||
|
class PharArchiver implements ArchiverInterface
|
||||||
|
{
|
||||||
|
protected static $formats = array(
|
||||||
|
'zip' => \Phar::ZIP,
|
||||||
|
'tar' => \Phar::TAR,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function archive($sources, $target, $format, array $excludes = array())
|
||||||
|
{
|
||||||
|
$sources = realpath($sources);
|
||||||
|
|
||||||
|
// Phar would otherwise load the file which we don't want
|
||||||
|
if (file_exists($target)) {
|
||||||
|
unlink($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$phar = new \PharData($target, null, null, static::$formats[$format]);
|
||||||
|
$files = new ArchivableFilesFinder($sources, $excludes);
|
||||||
|
$phar->buildFromIterator($files, $sources);
|
||||||
|
return $target;
|
||||||
|
} catch (\UnexpectedValueException $e) {
|
||||||
|
$message = sprintf("Could not create archive '%s' from '%s': %s",
|
||||||
|
$target,
|
||||||
|
$sources,
|
||||||
|
$e->getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new \RuntimeException($message, $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supports($format, $sourceType)
|
||||||
|
{
|
||||||
|
return isset(static::$formats[$format]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,10 @@ class ArrayDumper
|
||||||
$data['dist']['shasum'] = $package->getDistSha1Checksum();
|
$data['dist']['shasum'] = $package->getDistSha1Checksum();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($package->getArchiveExcludes()) {
|
||||||
|
$data['archive']['exclude'] = $package->getArchiveExcludes();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
|
foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
|
||||||
if ($links = $package->{'get'.ucfirst($opts['method'])}()) {
|
if ($links = $package->{'get'.ucfirst($opts['method'])}()) {
|
||||||
foreach ($links as $link) {
|
foreach ($links as $link) {
|
||||||
|
|
|
@ -150,6 +150,10 @@ class ArrayLoader implements LoaderInterface
|
||||||
$package->setNotificationUrl($config['notification-url']);
|
$package->setNotificationUrl($config['notification-url']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($config['archive']['exclude'])) {
|
||||||
|
$package->setArchiveExcludes($config['archive']['exclude']);
|
||||||
|
}
|
||||||
|
|
||||||
if ($package instanceof Package\CompletePackageInterface) {
|
if ($package instanceof Package\CompletePackageInterface) {
|
||||||
if (isset($config['scripts']) && is_array($config['scripts'])) {
|
if (isset($config['scripts']) && is_array($config['scripts'])) {
|
||||||
foreach ($config['scripts'] as $event => $listeners) {
|
foreach ($config['scripts'] as $event => $listeners) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ class Package extends BasePackage
|
||||||
protected $suggests = array();
|
protected $suggests = array();
|
||||||
protected $autoload = array();
|
protected $autoload = array();
|
||||||
protected $includePaths = array();
|
protected $includePaths = array();
|
||||||
|
protected $archiveExcludes = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new in memory package.
|
* Creates a new in memory package.
|
||||||
|
@ -525,4 +526,22 @@ class Package extends BasePackage
|
||||||
{
|
{
|
||||||
return $this->notificationUrl;
|
return $this->notificationUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a list of patterns to be excluded from archives
|
||||||
|
*
|
||||||
|
* @param array $excludes
|
||||||
|
*/
|
||||||
|
public function setArchiveExcludes(array $excludes)
|
||||||
|
{
|
||||||
|
$this->archiveExcludes = $excludes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getArchiveExcludes()
|
||||||
|
{
|
||||||
|
return $this->archiveExcludes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,4 +308,11 @@ interface PackageInterface
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getPrettyString();
|
public function getPrettyString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of patterns to exclude from package archives
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getArchiveExcludes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,9 +55,8 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
$fs->ensureDirectoryExists(dirname(self::$pharPath));
|
$fs->ensureDirectoryExists(dirname(self::$pharPath));
|
||||||
chdir(dirname(self::$pharPath));
|
chdir(dirname(self::$pharPath));
|
||||||
|
|
||||||
$proc = new Process('php '.escapeshellarg(__DIR__.'/../../../bin/compile'));
|
$proc = new Process('php '.escapeshellarg(__DIR__.'/../../../bin/compile'), dirname(self::$pharPath));
|
||||||
$exitcode = $proc->run();
|
$exitcode = $proc->run();
|
||||||
|
|
||||||
if ($exitcode !== 0 || trim($proc->getOutput())) {
|
if ($exitcode !== 0 || trim($proc->getOutput())) {
|
||||||
$this->fail($proc->getOutput());
|
$this->fail($proc->getOutput());
|
||||||
}
|
}
|
||||||
|
@ -76,7 +75,7 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
putenv('COMPOSER_HOME='.$this->testDir.'home');
|
putenv('COMPOSER_HOME='.$this->testDir.'home');
|
||||||
|
|
||||||
$cmd = 'php '.escapeshellarg(self::$pharPath).' --no-ansi '.$testData['RUN'];
|
$cmd = 'php '.escapeshellarg(self::$pharPath).' --no-ansi '.$testData['RUN'];
|
||||||
$proc = new Process($cmd);
|
$proc = new Process($cmd, __DIR__.'/Fixtures/functional');
|
||||||
$exitcode = $proc->run();
|
$exitcode = $proc->run();
|
||||||
|
|
||||||
if (isset($testData['EXPECT'])) {
|
if (isset($testData['EXPECT'])) {
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Package\Archiver\ArchivableFilesFinder;
|
||||||
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
protected $sources;
|
||||||
|
protected $finder;
|
||||||
|
|
||||||
|
protected function setup()
|
||||||
|
{
|
||||||
|
$fs = new Filesystem;
|
||||||
|
|
||||||
|
$this->sources = sys_get_temp_dir().
|
||||||
|
'/composer_archiver_test'.uniqid(mt_rand(), true);
|
||||||
|
|
||||||
|
$fileTree = array(
|
||||||
|
'A/prefixA.foo',
|
||||||
|
'A/prefixB.foo',
|
||||||
|
'A/prefixC.foo',
|
||||||
|
'A/prefixD.foo',
|
||||||
|
'A/prefixE.foo',
|
||||||
|
'A/prefixF.foo',
|
||||||
|
'B/sub/prefixA.foo',
|
||||||
|
'B/sub/prefixB.foo',
|
||||||
|
'B/sub/prefixC.foo',
|
||||||
|
'B/sub/prefixD.foo',
|
||||||
|
'B/sub/prefixE.foo',
|
||||||
|
'B/sub/prefixF.foo',
|
||||||
|
'toplevelA.foo',
|
||||||
|
'toplevelB.foo',
|
||||||
|
'prefixA.foo',
|
||||||
|
'prefixB.foo',
|
||||||
|
'prefixC.foo',
|
||||||
|
'prefixD.foo',
|
||||||
|
'prefixE.foo',
|
||||||
|
'prefixF.foo',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($fileTree as $relativePath) {
|
||||||
|
$path = $this->sources.'/'.$relativePath;
|
||||||
|
$fs->ensureDirectoryExists(dirname($path));
|
||||||
|
file_put_contents($path, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown()
|
||||||
|
{
|
||||||
|
$fs = new Filesystem;
|
||||||
|
$fs->removeDirectory($this->sources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManualExcludes()
|
||||||
|
{
|
||||||
|
$excludes = array(
|
||||||
|
'prefixB.foo',
|
||||||
|
'!/prefixB.foo',
|
||||||
|
'/prefixA.foo',
|
||||||
|
'prefixC.*',
|
||||||
|
'!*/*/*/prefixC.foo'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->finder = new ArchivableFilesFinder($this->sources, $excludes);
|
||||||
|
|
||||||
|
$this->assertArchivableFiles(array(
|
||||||
|
'/A/prefixA.foo',
|
||||||
|
'/A/prefixD.foo',
|
||||||
|
'/A/prefixE.foo',
|
||||||
|
'/A/prefixF.foo',
|
||||||
|
'/B/sub/prefixA.foo',
|
||||||
|
'/B/sub/prefixC.foo',
|
||||||
|
'/B/sub/prefixD.foo',
|
||||||
|
'/B/sub/prefixE.foo',
|
||||||
|
'/B/sub/prefixF.foo',
|
||||||
|
'/prefixB.foo',
|
||||||
|
'/prefixD.foo',
|
||||||
|
'/prefixE.foo',
|
||||||
|
'/prefixF.foo',
|
||||||
|
'/toplevelA.foo',
|
||||||
|
'/toplevelB.foo',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGitExcludes()
|
||||||
|
{
|
||||||
|
file_put_contents($this->sources.'/.gitignore', implode("\n", array(
|
||||||
|
'# gitignore rules with comments and blank lines',
|
||||||
|
'',
|
||||||
|
'prefixE.foo',
|
||||||
|
'# and more',
|
||||||
|
'# comments',
|
||||||
|
'',
|
||||||
|
'!/prefixE.foo',
|
||||||
|
'/prefixD.foo',
|
||||||
|
'prefixF.*',
|
||||||
|
'!/*/*/prefixF.foo',
|
||||||
|
'',
|
||||||
|
)));
|
||||||
|
|
||||||
|
// git does not currently support negative git attributes
|
||||||
|
file_put_contents($this->sources.'/.gitattributes', implode("\n", array(
|
||||||
|
'',
|
||||||
|
'# gitattributes rules with comments and blank lines',
|
||||||
|
'prefixB.foo export-ignore',
|
||||||
|
//'!/prefixB.foo export-ignore',
|
||||||
|
'/prefixA.foo export-ignore',
|
||||||
|
'prefixC.* export-ignore',
|
||||||
|
//'!/*/*/prefixC.foo export-ignore'
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->finder = new ArchivableFilesFinder($this->sources, array());
|
||||||
|
|
||||||
|
$this->assertArchivableFiles($this->getArchivedFiles('git init && '.
|
||||||
|
'git add .git* && '.
|
||||||
|
'git commit -m "ignore rules" && '.
|
||||||
|
'git add . && '.
|
||||||
|
'git commit -m "init" && '.
|
||||||
|
'git archive --format=zip --prefix=archive/ -o archive.zip HEAD'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHgExcludes()
|
||||||
|
{
|
||||||
|
file_put_contents($this->sources.'/.hgignore', implode("\n", array(
|
||||||
|
'# hgignore rules with comments, blank lines and syntax changes',
|
||||||
|
'',
|
||||||
|
'pre*A.foo',
|
||||||
|
'prefixE.foo',
|
||||||
|
'# and more',
|
||||||
|
'# comments',
|
||||||
|
'',
|
||||||
|
'^prefixD.foo',
|
||||||
|
'syntax: glob',
|
||||||
|
'prefixF.*',
|
||||||
|
'B/*',
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->finder = new ArchivableFilesFinder($this->sources, array());
|
||||||
|
|
||||||
|
$expectedFiles = $this->getArchivedFiles('hg init && '.
|
||||||
|
'hg add && '.
|
||||||
|
'hg commit -m "init" && '.
|
||||||
|
'hg archive archive.zip'
|
||||||
|
);
|
||||||
|
|
||||||
|
array_shift($expectedFiles); // remove .hg_archival.txt
|
||||||
|
|
||||||
|
$this->assertArchivableFiles($expectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getArchivableFiles()
|
||||||
|
{
|
||||||
|
$files = array();
|
||||||
|
foreach ($this->finder as $file) {
|
||||||
|
if (!$file->isDir()) {
|
||||||
|
$files[] = preg_replace('#^'.preg_quote($this->sources, '#').'#', '', $file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($files);
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getArchivedFiles($command)
|
||||||
|
{
|
||||||
|
$process = new Process($command, $this->sources);
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
$archive = new \PharData($this->sources.'/archive.zip');
|
||||||
|
$iterator = new \RecursiveIteratorIterator($archive);
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
$files[] = preg_replace('#^phar://'.preg_quote($this->sources, '#').'/archive\.zip/archive#', '', $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($this->sources.'/archive.zip');
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertArchivableFiles($expectedFiles)
|
||||||
|
{
|
||||||
|
$actualFiles = $this->getArchivableFiles();
|
||||||
|
|
||||||
|
$this->assertEquals($expectedFiles, $actualFiles);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Factory;
|
||||||
|
use Composer\IO\NullIO;
|
||||||
|
use Composer\Package\Archiver;
|
||||||
|
use Composer\Package\Archiver\ArchiveManager;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Till Klampaeckel <till@php.net>
|
||||||
|
* @author Matthieu Moquet <matthieu@moquet.net>
|
||||||
|
*/
|
||||||
|
class ArchiveManagerTest extends ArchiverTest
|
||||||
|
{
|
||||||
|
protected $manager;
|
||||||
|
protected $targetDir;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$factory = new Factory();
|
||||||
|
$this->manager = $factory->createArchiveManager($factory->createConfig());
|
||||||
|
$this->targetDir = $this->testDir.'/composer_archiver_tests';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownFormat()
|
||||||
|
{
|
||||||
|
$this->setExpectedException('RuntimeException');
|
||||||
|
|
||||||
|
$package = $this->setupPackage();
|
||||||
|
|
||||||
|
$this->manager->archive($package, '__unknown_format__', $this->targetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testArchiveTar()
|
||||||
|
{
|
||||||
|
$this->setupGitRepo();
|
||||||
|
|
||||||
|
$package = $this->setupPackage();
|
||||||
|
|
||||||
|
$this->manager->archive($package, 'tar', $this->targetDir);
|
||||||
|
|
||||||
|
$target = $this->getTargetName($package, 'tar');
|
||||||
|
$this->assertFileExists($target);
|
||||||
|
|
||||||
|
unlink($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTargetName(PackageInterface $package, $format)
|
||||||
|
{
|
||||||
|
$packageName = $this->manager->getPackageFilename($package);
|
||||||
|
$target = $this->targetDir.'/'.$packageName.'.'.$format;
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create local git repository to run tests against!
|
||||||
|
*/
|
||||||
|
protected function setupGitRepo()
|
||||||
|
{
|
||||||
|
$currentWorkDir = getcwd();
|
||||||
|
chdir($this->testDir);
|
||||||
|
|
||||||
|
$output = null;
|
||||||
|
$result = $this->process->execute('git init -q', $output, $this->testDir);
|
||||||
|
if ($result > 0) {
|
||||||
|
chdir($currentWorkDir);
|
||||||
|
throw new \RuntimeException('Could not init: '.$this->process->getErrorOutput());
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = file_put_contents('b', 'a');
|
||||||
|
if (false === $result) {
|
||||||
|
chdir($currentWorkDir);
|
||||||
|
throw new \RuntimeException('Could not save file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->process->execute('git add b && git commit -m "commit b" -q', $output, $this->testDir);
|
||||||
|
if ($result > 0) {
|
||||||
|
chdir($currentWorkDir);
|
||||||
|
throw new \RuntimeException('Could not commit: '.$this->process->getErrorOutput());
|
||||||
|
}
|
||||||
|
|
||||||
|
chdir($currentWorkDir);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\ProcessExecutor;
|
||||||
|
use Composer\Package\Package;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Till Klampaeckel <till@php.net>
|
||||||
|
* @author Matthieu Moquet <matthieu@moquet.net>
|
||||||
|
*/
|
||||||
|
abstract class ArchiverTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Composer\Util\Filesystem
|
||||||
|
*/
|
||||||
|
protected $filesystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Composer\Util\ProcessExecutor
|
||||||
|
*/
|
||||||
|
protected $process;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $testDir;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->filesystem = new Filesystem();
|
||||||
|
$this->process = new ProcessExecutor();
|
||||||
|
$this->testDir = sys_get_temp_dir().'/composer_archiver_test_'.mt_rand();
|
||||||
|
$this->filesystem->ensureDirectoryExists($this->testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
$this->filesystem->removeDirectory($this->testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util method to quickly setup a package using the source path built.
|
||||||
|
*
|
||||||
|
* @return \Composer\Package\Package
|
||||||
|
*/
|
||||||
|
protected function setupPackage()
|
||||||
|
{
|
||||||
|
$package = new Package('archivertest/archivertest', 'master', 'master');
|
||||||
|
$package->setSourceUrl(realpath($this->testDir));
|
||||||
|
$package->setSourceReference('master');
|
||||||
|
$package->setSourceType('git');
|
||||||
|
|
||||||
|
return $package;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Test\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Package\Archiver\HgExcludeFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class HgExcludeFilterTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider patterns
|
||||||
|
*/
|
||||||
|
public function testPatternEscape($ignore, $expected)
|
||||||
|
{
|
||||||
|
$filter = new HgExcludeFilter('/');
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $filter->patternFromRegex($ignore));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function patterns()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('.#', array('#.\\##', false, true)),
|
||||||
|
array('.\\#', array('#.\\\\\\##', false, true)),
|
||||||
|
array('\\.#', array('#\\.\\##', false, true)),
|
||||||
|
array('\\\\.\\\\\\\\#', array('#\\\\.\\\\\\\\\\##', false, true)),
|
||||||
|
array('.\\\\\\\\\\#', array('#.\\\\\\\\\\\\\\##', false, true)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?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\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\Package\Archiver\PharArchiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Till Klampaeckel <till@php.net>
|
||||||
|
* @author Matthieu Moquet <matthieu@moquet.net>
|
||||||
|
*/
|
||||||
|
class PharArchiverTest extends ArchiverTest
|
||||||
|
{
|
||||||
|
public function testTarArchive()
|
||||||
|
{
|
||||||
|
// Set up repository
|
||||||
|
$this->setupDummyRepo();
|
||||||
|
$package = $this->setupPackage();
|
||||||
|
$target = sys_get_temp_dir().'/composer_archiver_test.tar';
|
||||||
|
|
||||||
|
// Test archive
|
||||||
|
$archiver = new PharArchiver();
|
||||||
|
$archiver->archive($package->getSourceUrl(), $target, 'tar', array('foo/bar', 'baz', '!/foo/bar/baz'));
|
||||||
|
$this->assertFileExists($target);
|
||||||
|
|
||||||
|
unlink($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testZipArchive()
|
||||||
|
{
|
||||||
|
// Set up repository
|
||||||
|
$this->setupDummyRepo();
|
||||||
|
$package = $this->setupPackage();
|
||||||
|
$target = sys_get_temp_dir().'/composer_archiver_test.zip';
|
||||||
|
|
||||||
|
// Test archive
|
||||||
|
$archiver = new PharArchiver();
|
||||||
|
$archiver->archive($package->getSourceUrl(), $target, 'zip');
|
||||||
|
$this->assertFileExists($target);
|
||||||
|
|
||||||
|
unlink($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a local dummy repository to run tests against!
|
||||||
|
*/
|
||||||
|
protected function setupDummyRepo()
|
||||||
|
{
|
||||||
|
$currentWorkDir = getcwd();
|
||||||
|
chdir($this->testDir);
|
||||||
|
|
||||||
|
$this->writeFile('file.txt', 'content', $currentWorkDir);
|
||||||
|
$this->writeFile('foo/bar/baz', 'content', $currentWorkDir);
|
||||||
|
$this->writeFile('foo/bar/ignoreme', 'content', $currentWorkDir);
|
||||||
|
$this->writeFile('x/baz', 'content', $currentWorkDir);
|
||||||
|
$this->writeFile('x/includeme', 'content', $currentWorkDir);
|
||||||
|
|
||||||
|
chdir($currentWorkDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function writeFile($path, $content, $currentWorkDir)
|
||||||
|
{
|
||||||
|
if (!file_exists(dirname($path))) {
|
||||||
|
mkdir(dirname($path), 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = file_put_contents($path, 'a');
|
||||||
|
if (false === $result) {
|
||||||
|
chdir($currentWorkDir);
|
||||||
|
throw new \RuntimeException('Could not save file.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -130,6 +130,14 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase
|
||||||
'extra',
|
'extra',
|
||||||
array('class' => 'MyVendor\\Installer')
|
array('class' => 'MyVendor\\Installer')
|
||||||
),
|
),
|
||||||
|
array(
|
||||||
|
'archive',
|
||||||
|
array('/foo/bar', 'baz', '!/foo/bar/baz'),
|
||||||
|
'archiveExcludes',
|
||||||
|
array(
|
||||||
|
'exclude' => array('/foo/bar', 'baz', '!/foo/bar/baz'),
|
||||||
|
),
|
||||||
|
),
|
||||||
array(
|
array(
|
||||||
'require',
|
'require',
|
||||||
array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')),
|
array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')),
|
||||||
|
|
|
@ -114,6 +114,9 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
|
||||||
'target-dir' => 'some/prefix',
|
'target-dir' => 'some/prefix',
|
||||||
'extra' => array('random' => array('things' => 'of', 'any' => 'shape')),
|
'extra' => array('random' => array('things' => 'of', 'any' => 'shape')),
|
||||||
'bin' => array('bin1', 'bin/foo'),
|
'bin' => array('bin1', 'bin/foo'),
|
||||||
|
'archive' => array(
|
||||||
|
'exclude' => array('/foo/bar', 'baz', '!/foo/bar/baz'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
$package = $this->loader->load($config);
|
$package = $this->loader->load($config);
|
||||||
|
|
|
@ -123,6 +123,9 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
|
||||||
'vendor-dir' => 'vendor',
|
'vendor-dir' => 'vendor',
|
||||||
'process-timeout' => 10000,
|
'process-timeout' => 10000,
|
||||||
),
|
),
|
||||||
|
'archive' => array(
|
||||||
|
'exclude' => array('/foo/bar', 'baz', '!/foo/bar/baz'),
|
||||||
|
),
|
||||||
'scripts' => array(
|
'scripts' => array(
|
||||||
'post-update-cmd' => 'Foo\\Bar\\Baz::doSomething',
|
'post-update-cmd' => 'Foo\\Bar\\Baz::doSomething',
|
||||||
'post-install-cmd' => array(
|
'post-install-cmd' => array(
|
||||||
|
|
Loading…
Reference in New Issue