diff --git a/composer.json b/composer.json index b72856837..ea526e9c7 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,9 @@ "autoload": { "psr-0": { "Composer": "src/" } }, + "autoload-dev": { + "psr-0": { "Composer\\Test": "tests/" } + }, "bin": ["bin/composer"], "extra": { "branch-alias": { diff --git a/doc/04-schema.md b/doc/04-schema.md index effec5386..ad8b53623 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -516,6 +516,27 @@ Example: } } +### autoload-dev (root-only) + +This section allows to define autoload rules for development purpose. + +If you're generating classmaps from your PSR-0 namespaces, you're probably concerned +about performance, if so, you'll also don't want your test classes to be mixed up +with your regular classes in those classmaps. + +Therefore, it is a good idea to rely on a dedicated path for your unit tests. + +Example: + + { + "autoload": { + "psr-0": { "MyLibrary": "src/" } + }, + "autoload-dev": { + "psr-0": { "MyLibrary\\Tests": "tests/" } + } + } + ### include-path > **DEPRECATED**: This is only present to support legacy projects, and all new code diff --git a/res/composer-schema.json b/res/composer-schema.json index 905199247..69d4dc5a1 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -226,6 +226,30 @@ } } }, + "autoload-dev": { + "type": "object", + "description": "Description of additional autoload rules for development purpose (eg. a test suite).", + "properties": { + "psr-0": { + "type": "object", + "description": "This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", + "additionalProperties": true + }, + "psr-4": { + "type": "object", + "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", + "additionalProperties": true + }, + "classmap": { + "type": "array", + "description": "This is an array of directories that contain classes to be included in the class-map generation process." + }, + "files": { + "type": "array", + "description": "This is an array of files that are always required on every request." + } + } + }, "archive": { "type": ["object"], "description": "Options for creating package archives for distribution.", diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 4fc900187..a0bf8c29f 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -32,11 +32,18 @@ class AutoloadGenerator */ private $eventDispatcher; + private $devMode = false; + public function __construct(EventDispatcher $eventDispatcher) { $this->eventDispatcher = $eventDispatcher; } + public function setDevMode($devMode = true) + { + $this->devMode = (boolean) $devMode; + } + public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '') { $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP); @@ -567,6 +574,9 @@ FOOTER; list($package, $installPath) = $item; $autoload = $package->getAutoload(); + if ($this->devMode && $package === $mainPackage) { + $autoload = array_merge_recursive($autoload, $package->getDevAutoload()); + } // skip misconfigured packages if (!isset($autoload[$type]) || !is_array($autoload[$type])) { diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index d228fb150..cd3f1b71d 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -31,6 +31,7 @@ class DumpAutoloadCommand extends Command ->setDescription('Dumps the autoloader') ->setDefinition(array( new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 packages to be loaded with classmaps too, good for production.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables dev autoload.'), )) ->setHelp(<<php composer.phar dump-autoload @@ -59,6 +60,8 @@ EOT $output->writeln('Generating autoload files'); } - $composer->getAutoloadGenerator()->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); + $generator = $composer->getAutoloadGenerator(); + $generator->setDevMode($input->getOption('dev')); + $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); } } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index d373beb43..839fc45e4 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -290,6 +290,7 @@ class Installer $this->io->write('Generating autoload files'); } + $this->autoloadGenerator->setDevMode($this->devMode); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); if ($this->runScripts) { diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 6f1fb5095..631b035a7 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -245,6 +245,10 @@ class AliasPackage extends BasePackage implements CompletePackageInterface { return $this->aliasOf->getAutoload(); } + public function getDevAutoload() + { + return $this->aliasOf->getDevAutoload(); + } public function getIncludePaths() { return $this->aliasOf->getIncludePaths(); diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index be94adec4..0a183742f 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -31,6 +31,7 @@ class ArrayDumper 'extra', 'installationSource' => 'installation-source', 'autoload', + 'devAutoload' => 'autoload-dev', 'notificationUrl' => 'notification-url', 'includePaths' => 'include-path', ); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 3940bdeb0..6ef4e7c03 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -130,6 +130,10 @@ class ArrayLoader implements LoaderInterface $package->setAutoload($config['autoload']); } + if (isset($config['autoload-dev'])) { + $package->setDevAutoload($config['autoload-dev']); + } + if (isset($config['include-path'])) { $package->setIncludePaths($config['include-path']); } diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index ba3f611c1..8fab59a8a 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -47,6 +47,7 @@ class Package extends BasePackage protected $devRequires = array(); protected $suggests = array(); protected $autoload = array(); + protected $devAutoload = array(); protected $includePaths = array(); protected $archiveExcludes = array(); @@ -440,6 +441,24 @@ class Package extends BasePackage return $this->autoload; } + /** + * Set the dev autoload mapping + * + * @param array $autoload Mapping of dev autoloading rules + */ + public function setDevAutoload(array $devAutoload) + { + $this->devAutoload = $devAutoload; + } + + /** + * {@inheritDoc} + */ + public function getDevAutoload() + { + return $this->devAutoload; + } + /** * Sets the list of paths added to PHP's include path. * diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index a3c8a2793..fd7393992 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -231,13 +231,25 @@ interface PackageInterface * * {"": {""}} * - * Type is either "psr-0" or "pear". Namespaces are mapped to directories - * for autoloading using the type specified. + * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to + * directories for autoloading using the type specified. * * @return array Mapping of autoloading rules */ public function getAutoload(); + /** + * Returns an associative array of dev autoloading rules + * + * {"": {""}} + * + * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to + * directories for autoloading using the type specified. + * + * @return array Mapping of dev autoloading rules + */ + public function getDevAutoload(); + /** * Returns a list of directories which should get added to PHP's * include path. diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 9f1e2fb16..1bbf0b577 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -170,7 +170,75 @@ class AutoloadGeneratorTest extends TestCase // Assert that autoload_classmap.php was correctly generated. $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); } + + public function testMainPackageDevAutoloading() + { + $package = new Package('a', '1.0', '1.0'); + $package->setAutoload(array( + 'psr-0' => array( + 'Main' => 'src/', + ), + )); + $package->setDevAutoload(array( + 'files' => array('devfiles/foo.php'), + )); + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue(array())); + + $this->fs->ensureDirectoryExists($this->workingDir.'/composer'); + $this->fs->ensureDirectoryExists($this->workingDir.'/src/Main'); + file_put_contents($this->workingDir.'/src/Main/ClassMain.php', 'fs->ensureDirectoryExists($this->workingDir.'/devfiles'); + file_put_contents($this->workingDir.'/devfiles/foo.php', 'generator->setDevMode(true); + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + + // check standard autoload + $this->assertAutoloadFiles('main4', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('classmap7', $this->vendorDir.'/composer', 'classmap'); + + // make sure dev autoload is correctly dumped + $this->assertAutoloadFiles('files2', $this->vendorDir.'/composer', 'files'); + } + + public function testMainPackageDevAutoloadingDisabledByDefault() + { + $package = new Package('a', '1.0', '1.0'); + $package->setAutoload(array( + 'psr-0' => array( + 'Main' => 'src/', + ), + )); + $package->setDevAutoload(array( + 'files' => array('devfiles/foo.php'), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue(array())); + + $this->fs->ensureDirectoryExists($this->workingDir.'/composer'); + $this->fs->ensureDirectoryExists($this->workingDir.'/src/Main'); + file_put_contents($this->workingDir.'/src/Main/ClassMain.php', 'fs->ensureDirectoryExists($this->workingDir.'/devfiles'); + file_put_contents($this->workingDir.'/devfiles/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + + // check standard autoload + $this->assertAutoloadFiles('main4', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('classmap7', $this->vendorDir.'/composer', 'classmap'); + + // make sure dev autoload is disabled when dev mode is set to false + $this->assertFalse(is_file($this->vendorDir.'/composer/autoload_files.php')); + } + public function testVendorDirSameAsWorkingDir() { $this->vendorDir = $this->workingDir; diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php new file mode 100644 index 000000000..5768726d1 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap7.php @@ -0,0 +1,10 @@ + $baseDir . '/src/Main/ClassMain.php', +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_files2.php b/tests/Composer/Test/Autoload/Fixtures/autoload_files2.php new file mode 100644 index 000000000..13cb9ecb3 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_files2.php @@ -0,0 +1,10 @@ + array($baseDir . '/src'), +); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c5e16d625..12caaffac 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,6 +13,8 @@ error_reporting(E_ALL); $loader = require __DIR__.'/../src/bootstrap.php'; + +// to be removed $loader->add('Composer\Test', __DIR__); require __DIR__.'/Composer/TestCase.php';