annotate core/lib/Drupal/Core/Composer/Composer.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Composer;
Chris@0 4
Chris@0 5 use Drupal\Component\PhpStorage\FileStorage;
Chris@0 6 use Composer\Script\Event;
Chris@0 7 use Composer\Installer\PackageEvent;
Chris@0 8 use Composer\Semver\Constraint\Constraint;
Chris@17 9 use Composer\Util\ProcessExecutor;
Chris@0 10
Chris@0 11 /**
Chris@0 12 * Provides static functions for composer script events.
Chris@0 13 *
Chris@0 14 * @see https://getcomposer.org/doc/articles/scripts.md
Chris@0 15 */
Chris@0 16 class Composer {
Chris@0 17
Chris@0 18 protected static $packageToCleanup = [
Chris@0 19 'behat/mink' => ['tests', 'driver-testsuite'],
Chris@0 20 'behat/mink-browserkit-driver' => ['tests'],
Chris@0 21 'behat/mink-goutte-driver' => ['tests'],
Chris@18 22 'brumann/polyfill-unserialize' => ['tests'],
Chris@0 23 'drupal/coder' => ['coder_sniffer/Drupal/Test', 'coder_sniffer/DrupalPractice/Test'],
Chris@0 24 'doctrine/cache' => ['tests'],
Chris@0 25 'doctrine/collections' => ['tests'],
Chris@0 26 'doctrine/common' => ['tests'],
Chris@0 27 'doctrine/inflector' => ['tests'],
Chris@0 28 'doctrine/instantiator' => ['tests'],
Chris@0 29 'egulias/email-validator' => ['documentation', 'tests'],
Chris@0 30 'fabpot/goutte' => ['Goutte/Tests'],
Chris@0 31 'guzzlehttp/promises' => ['tests'],
Chris@0 32 'guzzlehttp/psr7' => ['tests'],
Chris@0 33 'jcalderonzumba/gastonjs' => ['docs', 'examples', 'tests'],
Chris@0 34 'jcalderonzumba/mink-phantomjs-driver' => ['tests'],
Chris@0 35 'masterminds/html5' => ['test'],
Chris@0 36 'mikey179/vfsStream' => ['src/test'],
Chris@0 37 'paragonie/random_compat' => ['tests'],
Chris@18 38 'pear/archive_tar' => ['tests'],
Chris@18 39 'pear/console_getopt' => ['tests'],
Chris@18 40 'pear/pear-core-minimal' => ['tests'],
Chris@18 41 'pear/pear_exception' => ['tests'],
Chris@0 42 'phpdocumentor/reflection-docblock' => ['tests'],
Chris@0 43 'phpunit/php-code-coverage' => ['tests'],
Chris@0 44 'phpunit/php-timer' => ['tests'],
Chris@0 45 'phpunit/php-token-stream' => ['tests'],
Chris@0 46 'phpunit/phpunit' => ['tests'],
Chris@0 47 'phpunit/php-mock-objects' => ['tests'],
Chris@0 48 'sebastian/comparator' => ['tests'],
Chris@0 49 'sebastian/diff' => ['tests'],
Chris@0 50 'sebastian/environment' => ['tests'],
Chris@0 51 'sebastian/exporter' => ['tests'],
Chris@0 52 'sebastian/global-state' => ['tests'],
Chris@0 53 'sebastian/recursion-context' => ['tests'],
Chris@0 54 'stack/builder' => ['tests'],
Chris@0 55 'symfony/browser-kit' => ['Tests'],
Chris@0 56 'symfony/class-loader' => ['Tests'],
Chris@0 57 'symfony/console' => ['Tests'],
Chris@0 58 'symfony/css-selector' => ['Tests'],
Chris@0 59 'symfony/debug' => ['Tests'],
Chris@0 60 'symfony/dependency-injection' => ['Tests'],
Chris@0 61 'symfony/dom-crawler' => ['Tests'],
Chris@0 62 // @see \Drupal\Tests\Component\EventDispatcher\ContainerAwareEventDispatcherTest
Chris@0 63 // 'symfony/event-dispatcher' => ['Tests'],
Chris@0 64 'symfony/http-foundation' => ['Tests'],
Chris@0 65 'symfony/http-kernel' => ['Tests'],
Chris@0 66 'symfony/process' => ['Tests'],
Chris@0 67 'symfony/psr-http-message-bridge' => ['Tests'],
Chris@0 68 'symfony/routing' => ['Tests'],
Chris@0 69 'symfony/serializer' => ['Tests'],
Chris@0 70 'symfony/translation' => ['Tests'],
Chris@0 71 'symfony/validator' => ['Tests', 'Resources'],
Chris@0 72 'symfony/yaml' => ['Tests'],
Chris@0 73 'symfony-cmf/routing' => ['Test', 'Tests'],
Chris@0 74 'twig/twig' => ['doc', 'ext', 'test'],
Chris@0 75 ];
Chris@0 76
Chris@0 77 /**
Chris@0 78 * Add vendor classes to Composer's static classmap.
Chris@0 79 */
Chris@0 80 public static function preAutoloadDump(Event $event) {
Chris@0 81 // Get the configured vendor directory.
Chris@0 82 $vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');
Chris@0 83
Chris@0 84 // We need the root package so we can add our classmaps to its loader.
Chris@0 85 $package = $event->getComposer()->getPackage();
Chris@0 86 // We need the local repository so that we can query and see if it's likely
Chris@0 87 // that our files are present there.
Chris@0 88 $repository = $event->getComposer()->getRepositoryManager()->getLocalRepository();
Chris@0 89 // This is, essentially, a null constraint. We only care whether the package
Chris@0 90 // is present in the vendor directory yet, but findPackage() requires it.
Chris@0 91 $constraint = new Constraint('>', '');
Chris@0 92 // It's possible that there is no classmap specified in a custom project
Chris@0 93 // composer.json file. We need one so we can optimize lookup for some of our
Chris@0 94 // dependencies.
Chris@0 95 $autoload = $package->getAutoload();
Chris@0 96 if (!isset($autoload['classmap'])) {
Chris@0 97 $autoload['classmap'] = [];
Chris@0 98 }
Chris@0 99 // Check for our packages, and then optimize them if they're present.
Chris@0 100 if ($repository->findPackage('symfony/http-foundation', $constraint)) {
Chris@0 101 $autoload['classmap'] = array_merge($autoload['classmap'], [
Chris@0 102 $vendor_dir . '/symfony/http-foundation/Request.php',
Chris@0 103 $vendor_dir . '/symfony/http-foundation/ParameterBag.php',
Chris@0 104 $vendor_dir . '/symfony/http-foundation/FileBag.php',
Chris@0 105 $vendor_dir . '/symfony/http-foundation/ServerBag.php',
Chris@0 106 $vendor_dir . '/symfony/http-foundation/HeaderBag.php',
Chris@0 107 ]);
Chris@0 108 }
Chris@0 109 if ($repository->findPackage('symfony/http-kernel', $constraint)) {
Chris@0 110 $autoload['classmap'] = array_merge($autoload['classmap'], [
Chris@0 111 $vendor_dir . '/symfony/http-kernel/HttpKernel.php',
Chris@0 112 $vendor_dir . '/symfony/http-kernel/HttpKernelInterface.php',
Chris@0 113 $vendor_dir . '/symfony/http-kernel/TerminableInterface.php',
Chris@0 114 ]);
Chris@0 115 }
Chris@0 116 $package->setAutoload($autoload);
Chris@0 117 }
Chris@0 118
Chris@0 119 /**
Chris@0 120 * Ensures that .htaccess and web.config files are present in Composer root.
Chris@0 121 *
Chris@0 122 * @param \Composer\Script\Event $event
Chris@0 123 */
Chris@0 124 public static function ensureHtaccess(Event $event) {
Chris@0 125
Chris@0 126 // The current working directory for composer scripts is where you run
Chris@0 127 // composer from.
Chris@0 128 $vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');
Chris@0 129
Chris@0 130 // Prevent access to vendor directory on Apache servers.
Chris@0 131 $htaccess_file = $vendor_dir . '/.htaccess';
Chris@0 132 if (!file_exists($htaccess_file)) {
Chris@0 133 file_put_contents($htaccess_file, FileStorage::htaccessLines(TRUE) . "\n");
Chris@0 134 }
Chris@0 135
Chris@0 136 // Prevent access to vendor directory on IIS servers.
Chris@0 137 $webconfig_file = $vendor_dir . '/web.config';
Chris@0 138 if (!file_exists($webconfig_file)) {
Chris@0 139 $lines = <<<EOT
Chris@0 140 <configuration>
Chris@0 141 <system.webServer>
Chris@0 142 <authorization>
Chris@0 143 <deny users="*">
Chris@0 144 </authorization>
Chris@0 145 </system.webServer>
Chris@0 146 </configuration>
Chris@0 147 EOT;
Chris@0 148 file_put_contents($webconfig_file, $lines . "\n");
Chris@0 149 }
Chris@0 150 }
Chris@0 151
Chris@0 152 /**
Chris@14 153 * Fires the drupal-phpunit-upgrade script event if necessary.
Chris@14 154 *
Chris@14 155 * @param \Composer\Script\Event $event
Chris@14 156 */
Chris@14 157 public static function upgradePHPUnit(Event $event) {
Chris@14 158 $repository = $event->getComposer()->getRepositoryManager()->getLocalRepository();
Chris@14 159 // This is, essentially, a null constraint. We only care whether the package
Chris@14 160 // is present in the vendor directory yet, but findPackage() requires it.
Chris@14 161 $constraint = new Constraint('>', '');
Chris@14 162 $phpunit_package = $repository->findPackage('phpunit/phpunit', $constraint);
Chris@14 163 if (!$phpunit_package) {
Chris@14 164 // There is nothing to do. The user is probably installing using the
Chris@14 165 // --no-dev flag.
Chris@14 166 return;
Chris@14 167 }
Chris@14 168
Chris@17 169 // If the PHP version is 7.0 or above and PHPUnit is less than version 6
Chris@14 170 // call the drupal-phpunit-upgrade script to upgrade PHPUnit.
Chris@14 171 if (!static::upgradePHPUnitCheck($phpunit_package->getVersion())) {
Chris@14 172 $event->getComposer()
Chris@14 173 ->getEventDispatcher()
Chris@14 174 ->dispatchScript('drupal-phpunit-upgrade');
Chris@14 175 }
Chris@14 176 }
Chris@14 177
Chris@14 178 /**
Chris@14 179 * Determines if PHPUnit needs to be upgraded.
Chris@14 180 *
Chris@14 181 * This method is located in this file because it is possible that it is
Chris@14 182 * called before the autoloader is available.
Chris@14 183 *
Chris@14 184 * @param string $phpunit_version
Chris@14 185 * The PHPUnit version string.
Chris@14 186 *
Chris@14 187 * @return bool
Chris@14 188 * TRUE if the PHPUnit needs to be upgraded, FALSE if not.
Chris@14 189 */
Chris@14 190 public static function upgradePHPUnitCheck($phpunit_version) {
Chris@17 191 return !(version_compare(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, '7.0') >= 0 && version_compare($phpunit_version, '6.1') < 0);
Chris@14 192 }
Chris@14 193
Chris@14 194 /**
Chris@0 195 * Remove possibly problematic test files from vendored projects.
Chris@0 196 *
Chris@0 197 * @param \Composer\Installer\PackageEvent $event
Chris@0 198 * A PackageEvent object to get the configured composer vendor directories
Chris@0 199 * from.
Chris@0 200 */
Chris@0 201 public static function vendorTestCodeCleanup(PackageEvent $event) {
Chris@0 202 $vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');
Chris@0 203 $io = $event->getIO();
Chris@0 204 $op = $event->getOperation();
Chris@0 205 if ($op->getJobType() == 'update') {
Chris@0 206 $package = $op->getTargetPackage();
Chris@0 207 }
Chris@0 208 else {
Chris@0 209 $package = $op->getPackage();
Chris@0 210 }
Chris@0 211 $package_key = static::findPackageKey($package->getName());
Chris@0 212 $message = sprintf(" Processing <comment>%s</comment>", $package->getPrettyName());
Chris@0 213 if ($io->isVeryVerbose()) {
Chris@0 214 $io->write($message);
Chris@0 215 }
Chris@0 216 if ($package_key) {
Chris@0 217 foreach (static::$packageToCleanup[$package_key] as $path) {
Chris@0 218 $dir_to_remove = $vendor_dir . '/' . $package_key . '/' . $path;
Chris@0 219 $print_message = $io->isVeryVerbose();
Chris@0 220 if (is_dir($dir_to_remove)) {
Chris@0 221 if (static::deleteRecursive($dir_to_remove)) {
Chris@0 222 $message = sprintf(" <info>Removing directory '%s'</info>", $path);
Chris@0 223 }
Chris@0 224 else {
Chris@0 225 // Always display a message if this fails as it means something has
Chris@0 226 // gone wrong. Therefore the message has to include the package name
Chris@0 227 // as the first informational message might not exist.
Chris@0 228 $print_message = TRUE;
Chris@0 229 $message = sprintf(" <error>Failure removing directory '%s'</error> in package <comment>%s</comment>.", $path, $package->getPrettyName());
Chris@0 230 }
Chris@0 231 }
Chris@0 232 else {
Chris@0 233 // If the package has changed or the --prefer-dist version does not
Chris@0 234 // include the directory this is not an error.
Chris@0 235 $message = sprintf(" Directory '%s' does not exist", $path);
Chris@0 236 }
Chris@0 237 if ($print_message) {
Chris@0 238 $io->write($message);
Chris@0 239 }
Chris@0 240 }
Chris@0 241
Chris@0 242 if ($io->isVeryVerbose()) {
Chris@0 243 // Add a new line to separate this output from the next package.
Chris@0 244 $io->write("");
Chris@0 245 }
Chris@0 246 }
Chris@0 247 }
Chris@0 248
Chris@0 249 /**
Chris@0 250 * Find the array key for a given package name with a case-insensitive search.
Chris@0 251 *
Chris@0 252 * @param string $package_name
Chris@0 253 * The package name from composer. This is always already lower case.
Chris@0 254 *
Chris@0 255 * @return string|null
Chris@0 256 * The string key, or NULL if none was found.
Chris@0 257 */
Chris@0 258 protected static function findPackageKey($package_name) {
Chris@0 259 $package_key = NULL;
Chris@0 260 // In most cases the package name is already used as the array key.
Chris@0 261 if (isset(static::$packageToCleanup[$package_name])) {
Chris@0 262 $package_key = $package_name;
Chris@0 263 }
Chris@0 264 else {
Chris@0 265 // Handle any mismatch in case between the package name and array key.
Chris@0 266 // For example, the array key 'mikey179/vfsStream' needs to be found
Chris@0 267 // when composer returns a package name of 'mikey179/vfsstream'.
Chris@0 268 foreach (static::$packageToCleanup as $key => $dirs) {
Chris@0 269 if (strtolower($key) === $package_name) {
Chris@0 270 $package_key = $key;
Chris@0 271 break;
Chris@0 272 }
Chris@0 273 }
Chris@0 274 }
Chris@0 275 return $package_key;
Chris@0 276 }
Chris@0 277
Chris@0 278 /**
Chris@17 279 * Removes Composer's timeout so that scripts can run indefinitely.
Chris@17 280 */
Chris@17 281 public static function removeTimeout() {
Chris@17 282 ProcessExecutor::setTimeout(0);
Chris@17 283 }
Chris@17 284
Chris@17 285 /**
Chris@0 286 * Helper method to remove directories and the files they contain.
Chris@0 287 *
Chris@0 288 * @param string $path
Chris@0 289 * The directory or file to remove. It must exist.
Chris@0 290 *
Chris@0 291 * @return bool
Chris@0 292 * TRUE on success or FALSE on failure.
Chris@0 293 */
Chris@0 294 protected static function deleteRecursive($path) {
Chris@0 295 if (is_file($path) || is_link($path)) {
Chris@0 296 return unlink($path);
Chris@0 297 }
Chris@0 298 $success = TRUE;
Chris@0 299 $dir = dir($path);
Chris@0 300 while (($entry = $dir->read()) !== FALSE) {
Chris@0 301 if ($entry == '.' || $entry == '..') {
Chris@0 302 continue;
Chris@0 303 }
Chris@0 304 $entry_path = $path . '/' . $entry;
Chris@0 305 $success = static::deleteRecursive($entry_path) && $success;
Chris@0 306 }
Chris@0 307 $dir->close();
Chris@0 308
Chris@0 309 return rmdir($path) && $success;
Chris@0 310 }
Chris@0 311
Chris@0 312 }