Commit 3527513c authored by catch's avatar catch
Browse files

Issue #3076684 by Spokje, andypost, longwave, paulocs, Mile23, KapilV,...

Issue #3076684 by Spokje, andypost, longwave, paulocs, Mile23, KapilV, greg.1.anderson: Remove deprecated vendor cleanup scripts

(cherry picked from commit c042f344)
parent 78763eb5
Loading
Loading
Loading
Loading
+0 −227
Original line number Diff line number Diff line
@@ -2,12 +2,8 @@

namespace Drupal\Core\Composer;

use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\Installer\PackageEvent;
use Composer\Script\Event;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\ProcessExecutor;
use Drupal\Component\FileSecurity\FileSecurity;

/**
 * Provides static functions for composer script events.
@@ -16,66 +12,6 @@
 */
class Composer {

  protected static $packageToCleanup = [
    'behat/mink' => ['tests'],
    'behat/mink-browserkit-driver' => ['tests'],
    'behat/mink-selenium2-driver' => ['tests'],
    'composer/composer' => ['bin'],
    'drupal/coder' => ['coder_sniffer/Drupal/Test', 'coder_sniffer/DrupalPractice/Test'],
    'doctrine/instantiator' => ['tests'],
    'easyrdf/easyrdf' => ['scripts'],
    'egulias/email-validator' => ['documentation', 'tests'],
    'guzzlehttp/promises' => ['tests'],
    'guzzlehttp/psr7' => ['tests'],
    'instaclick/php-webdriver' => ['doc', 'test'],
    'justinrainbow/json-schema' => ['demo'],
    'masterminds/html5' => ['bin', 'test'],
    'mikey179/vfsStream' => ['src/test'],
    'myclabs/deep-copy' => ['doc'],
    '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/phpunit' => ['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'],
    'symfony/browser-kit' => ['Tests'],
    'symfony/console' => ['Tests'],
    'symfony/css-selector' => ['Tests'],
    'symfony/dependency-injection' => ['Tests'],
    'symfony/dom-crawler' => ['Tests'],
    'symfony/filesystem' => ['Tests'],
    'symfony/finder' => ['Tests'],
    'symfony/error-handler' => ['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/validator' => ['Tests', 'Resources'],
    'symfony/yaml' => ['Tests'],
    'theseer/tokenizer' => ['tests'],
    'twig/twig' => ['doc', 'ext', 'test', 'tests'],
  ];

  /**
   * Add vendor classes to Composer's static classmap.
   *
@@ -149,169 +85,6 @@ public static function preAutoloadDump(Event $event) {
    $package->setAutoload($autoload);
  }

  /**
   * Ensures that .htaccess and web.config files are present in Composer root.
   *
   * @param \Composer\Script\Event $event
   *   The event.
   *
   * @deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Any
   * "scripts" section mentioning this in composer.json can be removed and
   * replaced with the drupal/core-vendor-hardening Composer plugin, as needed.
   *
   * @see https://www.drupal.org/node/3260624
   */
  public static function ensureHtaccess(Event $event) {
    trigger_error('Calling ' . __METHOD__ . ' from composer.json is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Any "scripts" section mentioning this in composer.json can be removed and replaced with the drupal/core-vendor-hardening Composer plugin, as needed. See https://www.drupal.org/node/3260624', E_USER_DEPRECATED);

    // The current working directory for composer scripts is where you run
    // composer from.
    $vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');

    // Prevent access to vendor directory on Apache servers.
    FileSecurity::writeHtaccess($vendor_dir);

    // Prevent access to vendor directory on IIS servers.
    FileSecurity::writeWebConfig($vendor_dir);
  }

  /**
   * Remove possibly problematic test files from vendored projects.
   *
   * @param \Composer\Installer\PackageEvent $event
   *   A PackageEvent object to get the configured composer vendor directories
   *   from.
   *
   * @deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Any
   * "scripts" section mentioning this in composer.json can be removed and
   * replaced with the drupal/core-vendor-hardening Composer plugin, as needed.
   *
   * @see https://www.drupal.org/node/3260624
   */
  public static function vendorTestCodeCleanup(PackageEvent $event) {
    trigger_error('Calling ' . __METHOD__ . ' from composer.json is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Any "scripts" section mentioning this in composer.json can be removed and replaced with the drupal/core-vendor-hardening Composer plugin, as needed. See https://www.drupal.org/node/3260624', E_USER_DEPRECATED);

    $vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');
    $io = $event->getIO();
    $op = $event->getOperation();
    if ($op instanceof UpdateOperation) {
      $package = $op->getTargetPackage();
    }
    else {
      $package = $op->getPackage();
    }
    $package_key = static::findPackageKey($package->getName());
    $message = sprintf("    Processing <comment>%s</comment>", $package->getPrettyName());
    if ($io->isVeryVerbose()) {
      $io->write($message);
    }
    if ($package_key) {
      foreach (static::$packageToCleanup[$package_key] as $path) {
        $dir_to_remove = $vendor_dir . '/' . $package_key . '/' . $path;
        $print_message = $io->isVeryVerbose();
        if (is_dir($dir_to_remove)) {
          if (static::deleteRecursive($dir_to_remove)) {
            $message = sprintf("      <info>Removing directory '%s'</info>", $path);
          }
          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.
            $print_message = TRUE;
            $message = sprintf("      <error>Failure removing directory '%s'</error> in package <comment>%s</comment>.", $path, $package->getPrettyName());
          }
        }
        else {
          // If the package has changed or the --prefer-dist version does not
          // include the directory this is not an error.
          $message = sprintf("      Directory '%s' does not exist", $path);
        }
        if ($print_message) {
          $io->write($message);
        }
      }

      if ($io->isVeryVerbose()) {
        // Add a new line to separate this output from the next package.
        $io->write("");
      }
    }
  }

  /**
   * Find the array key for a given package name with a case-insensitive search.
   *
   * @param string $package_name
   *   The package name from composer. This is always already lower case.
   *
   * @return string|null
   *   The string key, or NULL if none was found.
   *
   * @internal
   */
  protected static function findPackageKey($package_name) {
    $package_key = NULL;
    // In most cases the package name is already used as the array key.
    if (isset(static::$packageToCleanup[$package_name])) {
      $package_key = $package_name;
    }
    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 (static::$packageToCleanup as $key => $dirs) {
        if (strtolower($key) === $package_name) {
          $package_key = $key;
          break;
        }
      }
    }
    return $package_key;
  }

  /**
   * Removes Composer's timeout so that scripts can run indefinitely.
   *
   * @deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. There is no
   *   replacement.
   *
   * @see https://www.drupal.org/node/3260624
   */
  public static function removeTimeout() {
    trigger_error('Calling ' . __METHOD__ . ' from composer.json is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. There is no replacement. See https://www.drupal.org/node/3260624', E_USER_DEPRECATED);

    ProcessExecutor::setTimeout(0);
  }

  /**
   * Helper method to remove directories and the files they contain.
   *
   * @param string $path
   *   The directory or file to remove. It must exist.
   *
   * @return bool
   *   TRUE on success or FALSE on failure.
   *
   * @internal
   */
  protected static function deleteRecursive($path) {
    if (is_file($path) || is_link($path)) {
      return unlink($path);
    }
    $success = TRUE;
    $dir = dir($path);
    while (($entry = $dir->read()) !== FALSE) {
      if ($entry == '.' || $entry == '..') {
        continue;
      }
      $entry_path = $path . '/' . $entry;
      $success = static::deleteRecursive($entry_path) && $success;
    }
    $dir->close();

    return rmdir($path) && $success;
  }

  /**
   * Fires the drupal-phpunit-upgrade script event if necessary.
   *
+0 −135
Original line number Diff line number Diff line
<?php

namespace Drupal\BuildTests\Composer;

use Composer\Json\JsonFile;
use Drupal\BuildTests\Framework\BuildTestBase;
use Drupal\Composer\Composer;

/**
 * @group Composer
 * @group legacy
 * @requires externalCommand composer
 * @coversDefaultClass \Drupal\Core\Composer\Composer
 */
class LegacyScriptsTest extends BuildTestBase {

  /**
   * @covers ::vendorTestCodeCleanup
   */
  public function testVendorTestCodeCleanup() {
    $package_dir = 'composer/Template/RecommendedProject';

    // Create a "Composer"-type repository containing one entry for every
    // package in the vendor directory.
    $vendor_packages_path = $this->getWorkspaceDirectory() . '/vendor_packages/packages.json';
    $this->makeVendorPackage($vendor_packages_path);

    // Make a copy of the code to alter in the workspace directory.
    $this->copyCodebase();

    // Remove the packages.drupal.org entry (and any other custom repository)
    // from the site under test's repositories section. There is no way to do
    // this via `composer config --unset`, so we read and rewrite composer.json.
    $composer_json_path = $this->getWorkspaceDirectory() . "/$package_dir/composer.json";
    $composer_json = json_decode(file_get_contents($composer_json_path), TRUE);
    unset($composer_json['repositories']);
    $json = json_encode($composer_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    file_put_contents($composer_json_path, $json);

    // Set up the template to use our path repos. Inclusion of metapackages is
    // reported differently, so we load up a separate set for them.
    $metapackage_path_repos = $this->getPathReposForType($this->getWorkspaceDirectory(), 'Metapackage');
    $this->assertArrayHasKey('drupal/core-recommended', $metapackage_path_repos);
    $path_repos = array_merge($metapackage_path_repos, $this->getPathReposForType($this->getWorkspaceDirectory(), 'Plugin'));
    // Always add drupal/core as a path repo.
    $path_repos['drupal/core'] = $this->getWorkspaceDirectory() . '/core';
    foreach ($path_repos as $name => $path) {
      $this->executeCommand("composer config --no-interaction repositories.$name path $path", $package_dir);
      $this->assertCommandSuccessful();
    }

    // Add our vendor package repository to our site under test's repositories
    // section. Call it "local" (although the name does not matter).
    $this->executeCommand("composer config --no-interaction repositories.local composer file://" . $vendor_packages_path, $package_dir);
    $this->assertCommandSuccessful();

    // Add the vendorTestCodeCleanup script as a post-install command.
    $this->executeCommand('composer config scripts.post-package-install "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup"');
    $this->assertCommandSuccessful();

    // Attempt to install packages which will trigger the script.
    $this->executeCommand('composer install');
    $this->assertCommandSuccessful();
  }

  /**
   * Get Composer items that we want to be path repos, from within a directory.
   *
   * @param string $workspace_directory
   *   The full path to the workspace directory.
   * @param string $subdir
   *   The subdirectory to search under composer/.
   *
   * @return string[]
   *   Array of paths, indexed by package name.
   */
  public function getPathReposForType($workspace_directory, $subdir) {
    // Find the Composer items that we want to be path repos.
    /** @var \SplFileInfo[] $path_repos */
    $path_repos = Composer::composerSubprojectPaths($workspace_directory, $subdir);

    $data = [];
    foreach ($path_repos as $path_repo) {
      $json_file = new JsonFile($path_repo->getPathname());
      $json = $json_file->read();
      $data[$json['name']] = $path_repo->getPath();
    }
    return $data;
  }

  /**
   * Creates a test package that points to all the projects in vendor.
   *
   * @param string $repository_path
   *   The path where to create the test package.
   */
  protected function makeVendorPackage($repository_path) {
    $root = $this->getDrupalRoot();
    $process = $this->executeCommand("composer --working-dir=$root info --format=json");
    $this->assertCommandSuccessful();
    $installed = json_decode($process->getOutput(), TRUE);

    // Build out package definitions for everything installed in
    // the vendor directory.
    $packages = [];
    foreach ($installed['installed'] as $project) {
      $name = $project['name'];
      $version = $project['version'];
      $path = "vendor/$name";
      $full_path = "$root/$path";
      // We are building a set of path repositories to projects in the vendor
      // directory, so we will skip any project that does not exist in vendor.
      // Also skip the projects that are symlinked in vendor. These are in our
      // metapackage. They will be represented as path repositories in the test
      // project's composer.json.
      if (is_dir($full_path) && !is_link($full_path)) {
        $packages['packages'][$name] = [
          $version => [
            "name" => $name,
            "dist" => [
              "type" => "path",
              "url" => $full_path,
            ],
            "version" => $version,
          ],
        ];
      }
    }

    $json = json_encode($packages, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    mkdir(dirname($repository_path));
    file_put_contents($repository_path, $json);
  }

}
+2 −17
Original line number Diff line number Diff line
@@ -3,7 +3,6 @@
namespace Drupal\Tests;

use Drupal\Composer\Plugin\VendorHardening\Config;
use Drupal\Core\Composer\Composer;
use Drupal\Tests\Composer\ComposerIntegrationTrait;
use Symfony\Component\Yaml\Yaml;

@@ -255,17 +254,15 @@ protected static function getContentHash($composerFileContents)

  /**
   * Tests the vendor cleanup utilities do not have obsolete packages listed.
   *
   * @dataProvider providerTestVendorCleanup
   */
  public function testVendorCleanup($class, $property) {
  public function testVendorCleanup(): void {
    $lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
    $packages = [];
    foreach (array_merge($lock['packages'], $lock['packages-dev']) as $package) {
      $packages[] = $package['name'];
    }

    $reflection = new \ReflectionProperty($class, $property);
    $reflection = new \ReflectionProperty(Config::class, 'defaultConfig');
    $reflection->setAccessible(TRUE);
    $config = $reflection->getValue();
    foreach (array_keys($config) as $package) {
@@ -273,16 +270,4 @@ public function testVendorCleanup($class, $property) {
    }
  }

  /**
   * Data provider for the vendor cleanup utility classes.
   *
   * @return array[]
   */
  public function providerTestVendorCleanup() {
    return [
      [Composer::class, 'packageToCleanup'],
      [Config::class, 'defaultConfig'],
    ];
  }

}
+0 −78
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\Core\Composer;

use Composer\Config;
use Composer\Composer as ComposerClass;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\Installer\PackageEvent;
use Composer\IO\IOInterface;
use Composer\Package\Package;
use Composer\Script\Event;
use Drupal\Core\Composer\Composer;
use Drupal\Tests\UnitTestCase;

/**
 * Tests the deprecations in the Drupal\Core\Composer\Composer class.
 *
 * @group Composer
 * @coversDefaultClass \Drupal\Core\Composer\Composer
 */
class ComposerDeprecationTest extends UnitTestCase {

  /**
   * @covers ::ensureHtaccess
   * @group legacy
   */
  public function testEnsureHtaccess() {
    $event = $this->prophesize(Event::class);

    $composer = $this->prophesize(ComposerClass::class);
    $event->getComposer()->willReturn($composer->reveal());

    $config = $this->prophesize(Config::class);
    $composer->getConfig()->willReturn($config->reveal());

    $this->expectDeprecation('Unsilenced deprecation: Calling Drupal\Core\Composer\Composer::ensureHtaccess from composer.json is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Any "scripts" section mentioning this in composer.json can be removed and replaced with the drupal/core-vendor-hardening Composer plugin, as needed. See https://www.drupal.org/node/3260624');
    Composer::ensureHtaccess($event->reveal());
  }

  /**
   * @covers ::vendorTestCodeCleanup
   * @group legacy
   */
  public function testVendorTestCodeCleanup() {
    $event = $this->prophesize(PackageEvent::class);

    $composer = $this->prophesize(ComposerClass::class);
    $event->getComposer()->willReturn($composer->reveal());

    $config = $this->prophesize(Config::class);
    $composer->getConfig()->willReturn($config->reveal());

    $operation = $this->prophesize(UpdateOperation::class);
    $event->getOperation()->willReturn($operation->reveal());

    $package = $this->prophesize(Package::class);
    $operation->getTargetPackage()->willReturn($package->reveal());

    $package->getName()->willReturn('foo');
    $package->getPrettyName()->willReturn('foo');

    $io = $this->prophesize(IOInterface::class);
    $event->getIO()->willReturn($io->reveal());

    $this->expectDeprecation('Unsilenced deprecation: Calling Drupal\Core\Composer\Composer::vendorTestCodeCleanup from composer.json is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Any "scripts" section mentioning this in composer.json can be removed and replaced with the drupal/core-vendor-hardening Composer plugin, as needed. See https://www.drupal.org/node/3260624');
    Composer::vendorTestCodeCleanup($event->reveal());
  }

  /**
   * @covers ::removeTimeout
   * @group legacy
   */
  public function testRemoveTimeout() {
    $this->expectDeprecation('Unsilenced deprecation: Calling Drupal\Core\Composer\Composer::removeTimeout from composer.json is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. There is no replacement. See https://www.drupal.org/node/3260624');
    Composer::removeTimeout();
  }

}