diff --git a/doc/04-schema.md b/doc/04-schema.md
index 85f8aff2d..33f82769d 100644
--- a/doc/04-schema.md
+++ b/doc/04-schema.md
@@ -552,6 +552,22 @@ Example:
}
```
+#### Exclude files from classmaps
+
+If you want to exclude some files or folders from the classmap you can use the 'exclude-from-classmap' property.
+This might be useful to exclude test classes in your live environment, for example.
+
+The classmap generator will ignore all files in the paths configured here.
+
+Example:
+
+```json
+{
+ "autoload": {
+ "exclude-from-classmap": ["/Tests/", "/test/", "/tests/"]
+ }
+}
+
### autoload-dev ([root-only](04-schema.md#root-package))
This section allows to define autoload rules for development purposes.
@@ -730,7 +746,7 @@ override packages from it.
### config ([root-only](04-schema.md#root-package))
-A set of configuration options. It is only used for projects. See
+A set of configuration options. It is only used for projects. See
[Config](06-config.md) for a description of each individual option.
### scripts ([root-only](04-schema.md#root-package))
@@ -791,7 +807,7 @@ Optional.
### non-feature-branches
-A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something),
+A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something),
that will NOT be handled as feature branches. This is an array of strings.
If you have non-numeric branch names, for example like "latest", "current", "latest-stable"
diff --git a/res/composer-schema.json b/res/composer-schema.json
index f74a9c9b8..80ce27718 100644
--- a/res/composer-schema.json
+++ b/res/composer-schema.json
@@ -257,6 +257,10 @@
"files": {
"type": "array",
"description": "This is an array of files that are always required on every request."
+ },
+ "exclude-from-classmap": {
+ "type": "array",
+ "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]"
}
}
},
diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php
index c170d41f6..ec604bcd2 100644
--- a/src/Composer/Autoload/AutoloadGenerator.php
+++ b/src/Composer/Autoload/AutoloadGenerator.php
@@ -28,6 +28,8 @@ use Composer\Script\ScriptEvents;
*/
class AutoloadGenerator
{
+ const EXCLUDE_PATTERN = '.*%s';
+
/**
* @var EventDispatcher
*/
@@ -193,6 +195,11 @@ EOF;
EOF;
}
+ $blacklist = null;
+ if (!empty($autoloads['exclude-from-classmap'])) {
+ $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}';
+ }
+
// flatten array
$classMap = array();
if ($scanPsr0Packages) {
@@ -215,21 +222,21 @@ EOF;
if (!is_dir($dir)) {
continue;
}
- $whitelist = sprintf(
- '{%s/%s.+$}',
- preg_quote($dir),
- ($psrType === 'psr-0' && strpos($namespace, '_') === false) ? preg_quote(strtr($namespace, '\\', '/')) : ''
- );
+// $whitelist = sprintf(
+// '{%s/%s.+$}',
+// preg_quote($dir),
+// ($psrType === 'psr-0' && strpos($namespace, '_') === false) ? preg_quote(strtr($namespace, '\\', '/')) : ''
+// );
$namespaceFilter = $namespace === '' ? null : $namespace;
- $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $whitelist, $namespaceFilter, $classMap);
+ $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
}
}
}
}
foreach ($autoloads['classmap'] as $dir) {
- $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, null, null, $classMap);
+ $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap);
}
ksort($classMap);
@@ -277,9 +284,9 @@ EOF;
));
}
- private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $whitelist = null, $namespaceFilter = null, array $classMap = array())
+ private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array())
{
- foreach ($this->generateClassMap($dir, $whitelist, $namespaceFilter) as $class => $path) {
+ foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) {
$pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
if (!isset($classMap[$class])) {
$classMap[$class] = $pathCode;
@@ -294,9 +301,9 @@ EOF;
return $classMap;
}
- private function generateClassMap($dir, $whitelist = null, $namespaceFilter = null)
+ private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null)
{
- return ClassMapGenerator::createMap($dir, $whitelist, $this->io, $namespaceFilter);
+ return ClassMapGenerator::createMap($dir, $blacklist, $this->io, $namespaceFilter);
}
public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@@ -359,11 +366,18 @@ EOF;
$psr4 = $this->parseAutoloadsType($packageMap, 'psr-4', $mainPackage);
$classmap = $this->parseAutoloadsType(array_reverse($sortedPackageMap), 'classmap', $mainPackage);
$files = $this->parseAutoloadsType($sortedPackageMap, 'files', $mainPackage);
+ $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $mainPackage);
krsort($psr0);
krsort($psr4);
- return array('psr-0' => $psr0, 'psr-4' => $psr4, 'classmap' => $classmap, 'files' => $files);
+ return array(
+ 'psr-0' => $psr0,
+ 'psr-4' => $psr4,
+ 'classmap' => $classmap,
+ 'files' => $files,
+ 'exclude-from-classmap' => $exclude
+ );
}
/**
@@ -674,6 +688,22 @@ FOOTER;
}
}
+ if ($type === 'exclude-from-classmap') {
+ // first escape user input
+ $path = sprintf(self::EXCLUDE_PATTERN, preg_quote($path));
+
+ if ($package === $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
+ // remove target-dir from classmap entries of the root package
+ $targetDir = str_replace('\\', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '', $package->getTargetDir())));
+ $path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
+ } elseif ($package !== $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
+ // add target-dir to exclude entries that don't have it
+ $path = preg_quote($package->getTargetDir()) . '/' . $path;
+ }
+ $autoloads[] = empty($installPath) ? $path : preg_quote($installPath) . '/' . $path;
+ continue;
+ }
+
$relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path;
if ($type === 'files' || $type === 'classmap') {
diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php
index 7f7e7e1eb..b487ed6ed 100644
--- a/src/Composer/Autoload/ClassMapGenerator.php
+++ b/src/Composer/Autoload/ClassMapGenerator.php
@@ -50,14 +50,14 @@ class ClassMapGenerator
* Iterate over all files in the given directory searching for classes
*
* @param \Iterator|string $path The path to search in or an iterator
- * @param string $whitelist Regex that matches against the file path
+ * @param string $blacklist Regex that matches against the file path that exclude from the classmap.
* @param IOInterface $io IO object
* @param string $namespace Optional namespace prefix to filter by
*
* @throws \RuntimeException When the path is neither an existing file nor directory
* @return array A class map array
*/
- public static function createMap($path, $whitelist = null, IOInterface $io = null, $namespace = null)
+ public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null)
{
if (is_string($path)) {
if (is_file($path)) {
@@ -81,7 +81,7 @@ class ClassMapGenerator
continue;
}
- if ($whitelist && !preg_match($whitelist, strtr($filePath, '\\', '/'))) {
+ if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) {
continue;
}
diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
index 8c7727058..efc3a6a93 100644
--- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
+++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -1261,6 +1261,50 @@ EOF;
$this->assertEquals($expectedPsr4, file_get_contents($this->vendorDir.'/composer/autoload_psr4.php'));
}
+ public function testExcludeFromClassmap()
+ {
+ $package = new Package('a', '1.0', '1.0');
+ $package->setAutoload(array(
+ 'psr-0' => array(
+ 'Main' => 'src/',
+ 'Lala' => array('src/', 'lib/'),
+ ),
+ 'psr-4' => array(
+ 'Acme\Fruit\\' => 'src-fruit/',
+ 'Acme\Cake\\' => array('src-cake/', 'lib-cake/'),
+ ),
+ 'classmap' => array('composersrc/'),
+ 'exclude-from-classmap' => array('/tests/', 'Exclude.php'),
+ ));
+
+ $this->repository->expects($this->once())
+ ->method('getCanonicalPackages')
+ ->will($this->returnValue(array()));
+
+ $this->fs->ensureDirectoryExists($this->workingDir.'/composer');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/src/Lala');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/lib');
+ file_put_contents($this->workingDir.'/src/Lala/ClassMapMain.php', 'fs->ensureDirectoryExists($this->workingDir.'/src-fruit');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/src-cake');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/lib-cake');
+ file_put_contents($this->workingDir.'/src-cake/ClassMapBar.php', 'fs->ensureDirectoryExists($this->workingDir.'/composersrc');
+ $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc/tests');
+ file_put_contents($this->workingDir.'/composersrc/foo.php', 'workingDir.'/composersrc/tests/bar.php', 'workingDir.'/composersrc/ClassToExclude.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1');
+
+ // Assert that autoload_classmap.php was correctly generated.
+ $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap');
+ }
+
private function assertAutoloadFiles($name, $dir, $type = 'namespaces')
{
$a = __DIR__.'/Fixtures/autoload_'.$name.'.php';