Browse Source

Merge remote-tracking branch 'trivago/add_exclude'

Conflicts:
	doc/04-schema.md
	src/Composer/Autoload/AutoloadGenerator.php
Jordi Boggiano 9 years ago
parent
commit
084f6de24e

+ 18 - 2
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 <span>([root-only](04-schema.md#root-package))</span>
 
 This section allows to define autoload rules for development purposes.
@@ -730,7 +746,7 @@ override packages from it.
 
 ### config <span>([root-only](04-schema.md#root-package))</span>
 
-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 <span>([root-only](04-schema.md#root-package))</span>
@@ -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"

+ 4 - 0
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/\"]"
                 }
             }
         },

+ 42 - 12
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('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '<dirsep>', $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') {

+ 3 - 3
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;
             }
 

+ 44 - 0
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', '<?php namespace Lala; class ClassMapMain {}');
+
+        $this->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', '<?php namespace Acme\Cake; class ClassMapBar {}');
+
+        $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc');
+        $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc/tests');
+        file_put_contents($this->workingDir.'/composersrc/foo.php', '<?php class ClassMapFoo {}');
+
+        // this classes should not be found in the classmap
+        file_put_contents($this->workingDir.'/composersrc/tests/bar.php', '<?php class ClassExcludeMapFoo {}');
+        file_put_contents($this->workingDir.'/composersrc/ClassToExclude.php', '<?php class ClassClassToExclude {}');
+
+        $this->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';