diff --git a/core/lib/Drupal/Component/VendorCleanup/Config.php b/core/lib/Drupal/Component/VendorCleanup/Config.php new file mode 100644 index 0000000000..b2e00ae3c7 --- /dev/null +++ b/core/lib/Drupal/Component/VendorCleanup/Config.php @@ -0,0 +1,196 @@ + ['tests', 'driver-testsuite'], + 'behat/mink-browserkit-driver' => ['tests'], + 'behat/mink-goutte-driver' => ['tests'], + 'behat/mink-selenium2-driver' => ['tests'], + 'brumann/polyfill-unserialize' => ['tests'], + 'composer/composer' => ['bin'], + 'drupal/coder' => [ + 'coder_sniffer/Drupal/Test', + 'coder_sniffer/DrupalPractice/Test', + ], + 'doctrine/cache' => ['tests'], + 'doctrine/collections' => ['tests'], + 'doctrine/common' => ['tests'], + 'doctrine/inflector' => ['tests'], + 'doctrine/instantiator' => ['tests'], + 'easyrdf/easyrdf' => ['scripts'], + 'egulias/email-validator' => ['documentation', 'tests'], + 'fabpot/goutte' => ['Goutte/Tests'], + 'guzzlehttp/promises' => ['tests'], + 'guzzlehttp/psr7' => ['tests'], + 'instaclick/php-webdriver' => ['doc', 'test'], + 'jcalderonzumba/gastonjs' => ['docs', 'examples', 'tests'], + 'jcalderonzumba/mink-phantomjs-driver' => ['tests'], + 'justinrainbow/json-schema' => ['demo'], + 'masterminds/html5' => ['bin', 'test'], + 'mikey179/vfsStream' => ['src/test'], + 'myclabs/deep-copy' => ['doc'], + 'paragonie/random_compat' => ['tests'], + 'pear/archive_tar' => ['docs', 'tests'], + 'pear/console_getopt' => ['tests'], + 'pear/pear-core-minimal' => ['tests'], + 'pear/pear_exception' => ['tests'], + 'phar-io/manifest' => ['examples', 'tests'], + 'phar-io/version' => ['tests'], + 'phpdocumentor/reflection-docblock' => ['tests'], + 'phpspec/prophecy' => ['fixtures', 'spec', 'tests'], + 'phpunit/php-code-coverage' => ['tests'], + 'phpunit/php-timer' => ['tests'], + 'phpunit/php-token-stream' => ['tests'], + 'phpunit/phpunit' => ['tests'], + 'phpunit/phpunit-mock-objects' => ['tests'], + 'sebastian/code-unit-reverse-lookup' => ['tests'], + 'sebastian/comparator' => ['tests'], + 'sebastian/diff' => ['tests'], + 'sebastian/environment' => ['tests'], + 'sebastian/exporter' => ['tests'], + 'sebastian/global-state' => ['tests'], + 'sebastian/object-enumerator' => ['tests'], + 'sebastian/object-reflector' => ['tests'], + 'sebastian/recursion-context' => ['tests'], + 'seld/jsonlint' => ['tests'], + 'squizlabs/php_codesniffer' => ['tests'], + 'stack/builder' => ['tests'], + 'symfony/browser-kit' => ['Tests'], + 'symfony/class-loader' => ['Tests'], + 'symfony/console' => ['Tests'], + 'symfony/css-selector' => ['Tests'], + 'symfony/debug' => ['Tests'], + 'symfony/dependency-injection' => ['Tests'], + 'symfony/dom-crawler' => ['Tests'], + 'symfony/filesystem' => ['Tests'], + 'symfony/finder' => ['Tests'], + 'symfony/event-dispatcher' => ['Tests'], + 'symfony/http-foundation' => ['Tests'], + 'symfony/http-kernel' => ['Tests'], + 'symfony/phpunit-bridge' => ['Tests'], + 'symfony/process' => ['Tests'], + 'symfony/psr-http-message-bridge' => ['Tests'], + 'symfony/routing' => ['Tests'], + 'symfony/serializer' => ['Tests'], + 'symfony/translation' => ['Tests'], + 'symfony/validator' => ['Tests', 'Resources'], + 'symfony/yaml' => ['Tests'], + 'symfony-cmf/routing' => ['Test', 'Tests'], + 'theseer/tokenizer' => ['tests'], + 'twig/twig' => ['doc', 'ext', 'test'], + 'zendframework/zend-escaper' => ['doc'], + 'zendframework/zend-feed' => ['doc'], + 'zendframework/zend-stdlib' => ['doc'], + ]; + + /** + * The root package. + * + * @var Composer\Package\RootPackageInterface + */ + protected $rootPackage; + + /** + * Configuration gleaned from the root package. + * + * @var array + */ + protected $configData = []; + + public function __construct(RootPackageInterface $root_package) { + $this->rootPackage = $root_package; + } + + /** + * Get the configured list of directories to remove from the root package. + * + * This is stored in composer.json extra:drupal-core-vendor-cleanup. + * + * @return array[] + * An array keyed by package name. Each array value is an array of paths, + * relative to the package. + */ + public function getAllCleanupPaths() { + if ($this->configData) { + return $this->configData; + } + // Get the root package config. + $package_config = $this->rootPackage->getExtra(); + if (isset($package_config['drupal-core-vendor-cleanup'])) { + $this->configData = $package_config['drupal-core-vendor-cleanup']; + } + // Ensure the values are arrays. + foreach ($this->configData as $package => $paths) { + if (!is_array($paths)) { + $this->configData[$package] = [$paths]; + } + } + // Merge root config with defaults. + foreach (static::$defaultConfig as $package => $paths) { + if (isset($this->configData[$package])) { + $this->configData[$package] = array_merge($this->configData[$package], $paths); + } + else { + $this->configData[$package] = $paths; + } + } + return $this->configData; + } + + /** + * Get a list of paths to remove for the given package. + * + * @param string $package + * The package name. + * + * @return string[] + * Array of paths to remove, relative to the package. + */ + public function getPathsForPackage($package) { + $paths = []; + $config = $this->getAllCleanupPaths(); + if (isset($config[$package])) { + $paths = $config[$package]; + } + else { + // Handle any mismatch in case between the package name and array key. For + // example, the array key 'mikey179/vfsStream' needs to be found when + // composer returns a package name of 'mikey179/vfsstream'. + foreach (array_keys($config) as $key) { + if (strtolower($key) == strtolower($package)) { + $paths = $config[$key]; + break; + } + } + } + return $paths; + } + + /** + * If there is no configuration in the root package, it is unconfigured. + * + * @return bool + * TRUE if the root package does not have a list of packages with + * directories to clean. + */ + public function isUnconfigured() { + return empty($this->getAllCleanupPaths()); + } + +} diff --git a/core/lib/Drupal/Component/VendorCleanup/LICENSE.txt b/core/lib/Drupal/Component/VendorCleanup/LICENSE.txt new file mode 100644 index 0000000000..94fb84639c --- /dev/null +++ b/core/lib/Drupal/Component/VendorCleanup/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/core/lib/Drupal/Component/VendorCleanup/README.txt b/core/lib/Drupal/Component/VendorCleanup/README.txt new file mode 100644 index 0000000000..8a6a97ea45 --- /dev/null +++ b/core/lib/Drupal/Component/VendorCleanup/README.txt @@ -0,0 +1,56 @@ +The Drupal Vendor Cleanup Composer Plugin +========================================= + +Thanks for using this Drupal component. + +You can participate in its development on Drupal.org, through our issue system: +https://www.drupal.org/project/issues/drupal + +You can get the full Drupal repo here: +https://www.drupal.org/project/drupal/git-instructions + +You can browse the full Drupal repo here: +http://cgit.drupalcode.org/drupal + +What does it do? +---------------- + +This Composer plugin removes extraneous directories from the project's vendor +directory. They're typically directories which might contain executable files, +such as test directories. + +This sort of processing is required for projects that have a vendor directory +inside the HTTP server docroot. This is a common layout for Drupal. + +By default, the plugin knows how to clean up packages for Drupal core, so you +can require drupal/core-vendor-cleanup in your project and the rest will happen +auto-magically. + +The plugin can also be configured to clean up additional packages using the +project's composer.json extra field. + +How do I set it up? +------------------- + +Require this Composer plugin into your project: + + composer require drupal/core-vendor-cleanup + +When you install or update, this plugin will look through each package and +remove directories it knows about. + +You can see the list of default package cleanups for this plugin in Config.php. +If you discover that this list needs updating, please file an issue about it: +https://www.drupal.org/project/issues/drupal + +In addition to the default list of packages, you can configure the plugin using +the root package's composer.json extra field, like this: + + "extra": { + "drupal-core-vendor-cleanup": { + "vendor/package": ["test", "documentation"] + } + } + +The above code will tell the plugin to remove the test/ and documentation/ +directories from the 'vendor/package' package when it is installed or updated. diff --git a/core/lib/Drupal/Component/VendorCleanup/TESTING.txt b/core/lib/Drupal/Component/VendorCleanup/TESTING.txt new file mode 100644 index 0000000000..7c4cbd7759 --- /dev/null +++ b/core/lib/Drupal/Component/VendorCleanup/TESTING.txt @@ -0,0 +1,18 @@ +HOW-TO: Test this Drupal component + +In order to test this component, you'll need to get the entire Drupal repo and +run the tests there. + +You'll find the tests under core/tests/Drupal/Tests/Component. + +You can get the full Drupal repo here: +https://www.drupal.org/project/drupal/git-instructions + +You can find more information about running PHPUnit tests with Drupal here: +https://www.drupal.org/node/2116263 + +Each component in the Drupal\Component namespace has its own annotated test +group. You can use this group to run only the tests for this component. Like +this: + +$ ./vendor/bin/phpunit -c core --group VendorCleanup diff --git a/core/lib/Drupal/Component/VendorCleanup/VendorCleanupPlugin.php b/core/lib/Drupal/Component/VendorCleanup/VendorCleanupPlugin.php new file mode 100644 index 0000000000..e8e3cfa1c8 --- /dev/null +++ b/core/lib/Drupal/Component/VendorCleanup/VendorCleanupPlugin.php @@ -0,0 +1,222 @@ +composer = $composer; + $this->io = $io; + + // Set up configuration. + $this->config = new Config($this->composer->getPackage()); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + ScriptEvents::POST_UPDATE_CMD => 'onPostCmd', + ScriptEvents::POST_INSTALL_CMD => 'onPostCmd', + PackageEvents::POST_PACKAGE_INSTALL => 'onPostPackageInstall', + PackageEvents::POST_PACKAGE_UPDATE => 'onPostPackageUpdate', + ]; + } + + /** + * POST_UPDATE_CMD and POST_INSTALL_CMD event handler. + * + * @param \Composer\Script\Event $event + * The Composer event. + */ + public function onPostCmd(Event $event) { + $this->cleanAllPackages($this->composer->getConfig()->get('vendor-dir')); + } + + /** + * POST_PACKAGE_INSTALL event handler. + * + * @param \Composer\Installer\PackageEvent $event + */ + public function onPostPackageInstall(PackageEvent $event) { + /** @var \Composer\Package\CompletePackage $package */ + $package = $event->getOperation()->getPackage(); + $package_name = $package->getName(); + $this->cleanPackage($this->composer->getConfig()->get('vendor-dir'), $package_name); + } + + /** + * POST_PACKAGE_UPDATE event handler. + * + * @param \Composer\Installer\PackageEvent $event + */ + public function onPostPackageUpdate(PackageEvent $event) { + /** @var \Composer\Package\CompletePackage $package */ + $package = $event->getOperation()->getTargetPackage(); + $package_name = $package->getName(); + $this->cleanPackage($this->composer->getConfig()->get('vendor-dir'), $package_name); + } + + /** + * Convenience method to get all installed packages from Composer. + * + * @return \Composer\Package\PackageInterface[] + * The list of installed packages. + */ + protected function getInstalledPackages() { + return $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(); + } + + /** + * Clean all configured packages. + * + * This applies in the context of a post-command event. + * + * @param string $vendor_dir + * Path to vendor directory + */ + public function cleanAllPackages($vendor_dir) { + // Get a list of all the packages available after the update or install + // command. + $installed_packages = []; + foreach ($this->getInstalledPackages() as $package) { + // Normalize package names to lower case. + $installed_packages[strtolower($package->getName())] = $package; + } + + // Get all the packages that we should clean up but haven't already. + $cleanup_packages = array_diff_key($this->config->getAllCleanupPaths(), $this->packagesAlreadyCleaned); + + // Get all the packages that are installed that we should clean up. + $packages_to_be_cleaned = array_intersect_key($cleanup_packages, $installed_packages); + + if (!$packages_to_be_cleaned) { + $this->io->write('Vendor directory already clean.'); + return; + } + $this->io->write('Cleaning vendor directory.'); + + foreach ($packages_to_be_cleaned as $package_name => $paths_for_package) { + $this->cleanPathsForPackage($vendor_dir, $package_name, $paths_for_package); + } + } + + /** + * Clean a single package. + * + * This applies in the context of a package post-install or post-update event. + * + * @param string $vendor_dir + * Path to vendor directory + * @param string $package_name + * Name of the package to clean + */ + public function cleanPackage($vendor_dir, $package_name) { + // Normalize package names to lower case. + $package_name = strtolower($package_name); + if (isset($this->packagesAlreadyCleaned[$package_name])) { + $this->io->write(sprintf(' %s already cleaned.', $package_name), TRUE, IOInterface::VERY_VERBOSE); + return; + } + + $paths_for_package = $this->config->getPathsForPackage($package_name); + if ($paths_for_package) { + $this->io->write(' Cleaning: ' . $package_name . ''); + $this->cleanPathsForPackage($vendor_dir, $package_name, $paths_for_package); + } + } + + /** + * Clean the installed directories for a named package. + * + * @param string $vendor_dir + * Path to vendor directory. + * @param string $package_name + * Name of package to sanitize. + * @param string $paths_for_package + * List of directories in $package_name to remove + */ + protected function cleanPathsForPackage($vendor_dir, $package_name, $paths_for_package) { + // Whatever happens here, this package counts as cleaned so that we don't + // process it more than once. + $this->packagesAlreadyCleaned[$package_name] = TRUE; + + $package_dir = $vendor_dir . '/' . $package_name; + if (is_dir($package_dir)) { + $this->io->write(sprintf(" Cleaning directories in %s", $package_name), TRUE, IOInterface::VERY_VERBOSE); + $fs = new Filesystem(); + foreach ($paths_for_package as $cleanup_item) { + $cleanup_path = $package_dir . '/' . $cleanup_item; + if (is_dir($cleanup_path)) { + if ($fs->removeDirectory($cleanup_path)) { + $this->io->write(sprintf(" Removing directory '%s'", $cleanup_item), TRUE, IOInterface::VERBOSE); + } + else { + // Always display a message if this fails as it means something + // has gone wrong. Therefore the message has to include the + // package name as the first informational message might not + // exist. + $this->io->write(sprintf(" Failure removing directory '%s' in package %s.", $cleanup_item, $package_name), TRUE, IOInterface::NORMAL); + } + } + else { + // If the package has changed or the --prefer-dist version does not + // include the directory. This is not an error. + $this->io->write(sprintf(" Directory '%s' does not exist.", $cleanup_path), TRUE, IOInterface::VERY_VERBOSE); + } + } + } + } + +} diff --git a/core/lib/Drupal/Component/VendorCleanup/composer.json b/core/lib/Drupal/Component/VendorCleanup/composer.json new file mode 100644 index 0000000000..0e1ce68a3e --- /dev/null +++ b/core/lib/Drupal/Component/VendorCleanup/composer.json @@ -0,0 +1,20 @@ +{ + "name": "drupal/core-vendor-cleanup", + "description": "Removes directories from the vendor directory.", + "keywords": ["drupal"], + "homepage": "https://www.drupal.org/project/drupal", + "license": "GPL-2.0-or-later", + "type": "composer-plugin", + "autoload": { + "psr-4": { + "Drupal\\Component\\VendorCleanup\\": "" + } + }, + "extra": { + "class": "Drupal\\Component\\VendorCleanup\\VendorCleanupPlugin" + }, + "require": { + "php": ">=7.0.8", + "composer-plugin-api": "^1.1" + } +} diff --git a/core/tests/Drupal/Tests/Component/VendorCleanup/ConfigTest.php b/core/tests/Drupal/Tests/Component/VendorCleanup/ConfigTest.php new file mode 100644 index 0000000000..ee00ed72cc --- /dev/null +++ b/core/tests/Drupal/Tests/Component/VendorCleanup/ConfigTest.php @@ -0,0 +1,86 @@ +getMockBuilder(Config::class) + ->setMethods(['getAllCleanupPaths']) + ->disableOriginalConstructor() + ->getMock(); + + $config->expects($this->once()) + ->method('getAllCleanupPaths') + ->willReturn(['PackAge' => ['path']]); + + $this->assertSame(['path'], $config->getPathsForPackage('pACKage')); + } + + /** + * @covers ::getAllCleanupPaths + */ + public function testNoRootMergeConfig() { + // Root package has no extra field. + $root = $this->getMockBuilder(RootPackageInterface::class) + ->setMethods(['getExtra']) + ->getMockForAbstractClass(); + $root->expects($this->once()) + ->method('getExtra') + ->willReturn([]); + + $config = new Config($root); + + $ref_default = new \ReflectionProperty($config, 'defaultConfig'); + $ref_default->setAccessible(TRUE); + + $ref_plugin_config = new \ReflectionMethod($config, 'getAllCleanupPaths'); + $ref_plugin_config->setAccessible(TRUE); + + $this->assertEquals( + $ref_default->getValue($config), $ref_plugin_config->invoke($config) + ); + } + + /** + * @covers ::getAllCleanupPaths + */ + public function testRootMergeConfig() { + // Root package has configuration in extra. + $root = $this->getMockBuilder(RootPackageInterface::class) + ->setMethods(['getExtra']) + ->getMockForAbstractClass(); + $root->expects($this->once()) + ->method('getExtra') + ->willReturn([ + 'drupal-core-vendor-cleanup' => [ + 'isa/string' => 'test_dir', + 'an/array' => ['test_dir', 'doc_dir'], + ], + ]); + + $config = new Config($root); + + $ref_plugin_config = new \ReflectionMethod($config, 'getAllCleanupPaths'); + $ref_plugin_config->setAccessible(TRUE); + + $plugin_config = $ref_plugin_config->invoke($config); + + $this->assertArraySubset([ + 'isa/string' => ['test_dir'], + 'an/array' => ['test_dir', 'doc_dir'], + ], $plugin_config); + } + +} diff --git a/core/tests/Drupal/Tests/Component/VendorCleanup/VendorCleanupPluginTest.php b/core/tests/Drupal/Tests/Component/VendorCleanup/VendorCleanupPluginTest.php new file mode 100644 index 0000000000..853818029c --- /dev/null +++ b/core/tests/Drupal/Tests/Component/VendorCleanup/VendorCleanupPluginTest.php @@ -0,0 +1,123 @@ + [ + 'package' => [ + 'tests' => [ + 'SomeTest.php' => 'getMockBuilder(Config::class) + ->setMethods(['getPathsForPackage']) + ->disableOriginalConstructor() + ->getMock(); + $config->expects($this->once()) + ->method('getPathsForPackage') + ->willReturn(['tests']); + + $plugin = new VendorCleanupPlugin(); + $ref_config = new \ReflectionProperty($plugin, 'config'); + $ref_config->setAccessible(TRUE); + $ref_config->setValue($plugin, $config); + + $io = $this->prophesize(IOInterface::class); + $ref_io = new \ReflectionProperty($plugin, 'io'); + $ref_io->setAccessible(TRUE); + $ref_io->setValue($plugin, $io->reveal()); + + $this->assertFileExists(vfsStream::url('vendor/drupal/package/tests/SomeTest.php')); + + $plugin->cleanPackage(vfsStream::url('vendor'), 'drupal/package'); + + $this->assertFileNotExists(vfsStream::url('vendor/drupal/package/tests')); + } + + /** + * @covers ::cleanPathsForPackage + */ + public function testCleanPathsForPackage() { + $plugin = new VendorCleanupPlugin(); + + $io = $this->prophesize(IOInterface::class); + $ref_io = new \ReflectionProperty($plugin, 'io'); + $ref_io->setAccessible(TRUE); + $ref_io->setValue($plugin, $io->reveal()); + + $this->assertFileExists(vfsStream::url('vendor/drupal/package/tests/SomeTest.php')); + + $ref_clean = new \ReflectionMethod($plugin, 'cleanPathsForPackage'); + $ref_clean->setAccessible(TRUE); + $ref_clean->invokeArgs($plugin, [vfsStream::url('vendor'), 'drupal/package', ['tests']]); + + $this->assertFileNotExists(vfsStream::url('vendor/drupal/package/tests')); + } + + /** + * @covers ::cleanAllPackages + */ + public function testCleanAllPackages() { + $config = $this->getMockBuilder(Config::class) + ->setMethods(['getAllCleanupPaths']) + ->disableOriginalConstructor() + ->getMock(); + $config->expects($this->once()) + ->method('getAllCleanupPaths') + ->willReturn(['drupal/package' => ['tests']]); + + $package = $this->getMockBuilder(PackageInterface::class) + ->setMethods(['getName']) + ->getMockForAbstractClass(); + $package->expects($this->any()) + ->method('getName') + ->willReturn('drupal/package'); + + $plugin = $this->getMockBuilder(VendorCleanupPlugin::class) + ->setMethods(['getInstalledPackages']) + ->getMock(); + $plugin->expects($this->once()) + ->method('getInstalledPackages') + ->willReturn([$package]); + + $io = $this->prophesize(IOInterface::class); + $ref_io = new \ReflectionProperty($plugin, 'io'); + $ref_io->setAccessible(TRUE); + $ref_io->setValue($plugin, $io->reveal()); + + $ref_config = new \ReflectionProperty($plugin, 'config'); + $ref_config->setAccessible(TRUE); + $ref_config->setValue($plugin, $config); + + $this->assertFileExists(vfsStream::url('vendor/drupal/package/tests/SomeTest.php')); + + $plugin->cleanAllPackages(vfsStream::url('vendor')); + + $this->assertFileNotExists(vfsStream::url('vendor/drupal/package/tests')); + } + +} diff --git a/core/tests/Drupal/Tests/ComposerIntegrationTest.php b/core/tests/Drupal/Tests/ComposerIntegrationTest.php index 4bed30bcc9..1050641a94 100644 --- a/core/tests/Drupal/Tests/ComposerIntegrationTest.php +++ b/core/tests/Drupal/Tests/ComposerIntegrationTest.php @@ -63,6 +63,7 @@ protected function getPaths() { $this->root . '/core/lib/Drupal/Component/Transliteration', $this->root . '/core/lib/Drupal/Component/Utility', $this->root . '/core/lib/Drupal/Component/Uuid', + $this->root . '/core/lib/Drupal/Component/VendorCleanup', $this->root . '/core/lib/Drupal/Component/Version', ]; }