annotate core/lib/Drupal/Core/Composer/Composer.php @ 17:129ea1e6d783

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