Unverified Commit 39ec77f4 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3103090 by greg.1.anderson, alexpott, jungle, ravi.shankar, dww,...

Issue #3103090 by greg.1.anderson, alexpott, jungle, ravi.shankar, dww, Mile23, larowlan: Avoid re-scaffolding unchanged files (and printing scaffold file information over and over)
parent 0146867b
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -154,11 +154,17 @@ public function scaffold() {
    $scaffold_options = $this->manageOptions->getOptions();

    // Create a collection of scaffolded files to process. This determines which
    // take priority and which are conjoined.
    // take priority and which are combined.
    $scaffold_files = new ScaffoldFileCollection($file_mappings, $location_replacements);

    // Get the scaffold files whose contents on disk match what we are about to
    // write. We can remove these from consideration, as rewriting would be a
    // no-op.
    $unchanged = $scaffold_files->checkUnchanged();
    $scaffold_files->filterFiles($unchanged);

    // Process the list of scaffolded files.
    $scaffold_results = ScaffoldFileCollection::process($scaffold_files, $this->io, $scaffold_options);
    $scaffold_results = $scaffold_files->process($this->io, $scaffold_options);

    // Generate an autoload file in the document root that includes the
    // autoload.php file in the vendor directory, wherever that is. Drupal
@@ -195,7 +201,7 @@ protected function getVendorPath() {
   *   A multidimensional array of file mappings, as returned by
   *   self::getAllowedPackages().
   *
   * @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[]
   * @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[][]
   *   An array of destination paths => scaffold operation objects.
   */
  protected function getFileMappingsFromPackages(array $allowed_packages) {
+27 −2
Original line number Diff line number Diff line
@@ -11,17 +11,42 @@
 */
abstract class AbstractOperation implements OperationInterface {

  /**
   * Cached contents of scaffold file to be written to disk.
   *
   * @var string
   */
  protected $contents;

  /**
   * {@inheritdoc}
   */
  final public function contents() {
    if (!isset($this->contents)) {
      $this->contents = $this->generateContents();
    }
    return $this->contents;
  }

  /**
   * Load the scaffold contents or otherwise generate what is needed.
   *
   * @return string
   *   The contents of the scaffold file.
   */
  abstract protected function generateContents();

  /**
   * {@inheritdoc}
   */
  public function combineWithConjunctionTarget(OperationInterface $conjunction_target) {
  public function scaffoldOverExistingTarget(OperationInterface $existing_target) {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function missingConjunctionTarget(ScaffoldFilePath $destination) {
  public function scaffoldAtNewLocation(ScaffoldFilePath $destination) {
    return $this;
  }

+46 −28
Original line number Diff line number Diff line
@@ -49,6 +49,13 @@ class AppendOp extends AbstractOperation {
   */
  protected $forceAppend;

  /**
   * The contents from the file that we are prepending / appending to.
   *
   * @var string
   */
  protected $originalContents;

  /**
   * Constructs an AppendOp.
   *
@@ -69,16 +76,36 @@ public function __construct(ScaffoldFilePath $prepend_path = NULL, ScaffoldFileP
    $this->managed = TRUE;
  }

  /**
   * {@inheritdoc}
   */
  protected function generateContents() {
    // Fetch the prepend contents, if provided.
    $prepend_contents = '';
    if (!empty($this->prepend)) {
      $prepend_contents = file_get_contents($this->prepend->fullPath()) . "\n";
    }
    // Fetch the append contents, if provided.
    $append_contents = '';
    if (!empty($this->append)) {
      $append_contents = "\n" . file_get_contents($this->append->fullPath());
    }

    // Get the original contents, or the default data if the original is empty.
    $original_contents = $this->originalContents;
    if (empty($original_contents) && !empty($this->default)) {
      $original_contents = file_get_contents($this->default->fullPath());
    }

    // Attach it all together.
    return $prepend_contents . $original_contents . $append_contents;
  }

  /**
   * {@inheritdoc}
   */
  public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) {
    $destination_path = $destination->fullPath();
    // This is just a sanity check; the OperationFactory has in theory already
    // accounted for this, and will return a SkipOp with a warning message.
    if (!file_exists($destination_path) && empty($this->default)) {
      throw new \RuntimeException($destination->getInterpolator()->interpolate("Cannot append/prepend because no prior package provided a scaffold file at [dest-rel-path]."));
    }
    $interpolator = $destination->getInterpolator();

    // Be extra-noisy of creating a new file or appending to a non-scaffold
@@ -93,33 +120,20 @@ public function process(ScaffoldFilePath $destination, IOInterface $io, Scaffold
      $io->write($interpolator->interpolate($message));
    }

    // Fetch the prepend contents, if provided.
    $prepend_contents = '';
    // Notify that we are prepending, if there is prepend data.
    if (!empty($this->prepend)) {
      $this->prepend->addInterpolationData($interpolator, 'prepend');
      $prepend_contents = file_get_contents($this->prepend->fullPath()) . "\n";
      $io->write($interpolator->interpolate("  - Prepend to <info>[dest-rel-path]</info> from <info>[prepend-rel-path]</info>"));
    }
    // Fetch the append contents, if provided.
    // Notify that we are appending, if there is append data.
    $append_contents = '';
    if (!empty($this->append)) {
      $this->append->addInterpolationData($interpolator, 'append');
      $append_contents = "\n" . file_get_contents($this->append->fullPath());
      $io->write($interpolator->interpolate("  - Append to <info>[dest-rel-path]</info> from <info>[append-rel-path]</info>"));
    }
    // We typically should always have content if we get here; the
    // OperationFactory should create a SkipOp instead of an AppendOp if there
    // is no append / prepend content. The edge case is if there is content
    // that is all 'trim'ed away. Then we get a message that we are appending,
    // although nothing will in fact actually happen.
    if (!empty(trim($prepend_contents)) || !empty(trim($append_contents))) {
      // None of our asset files are very large, so we will load each one into
      // memory for processing.
      $original_contents = file_get_contents(file_exists($destination_path) ? $destination_path : $this->default->fullPath());
      // Write the appended and prepended contents back to the file.
      $altered_contents = $prepend_contents . $original_contents . $append_contents;
      file_put_contents($destination_path, $altered_contents);
    }

    // Write the resulting data
    file_put_contents($destination_path, $this->contents());

    // Return a ScaffoldResult with knowledge of whether this file is managed.
    return new ScaffoldResult($destination, $this->managed);
@@ -128,16 +142,17 @@ public function process(ScaffoldFilePath $destination, IOInterface $io, Scaffold
  /**
   * {@inheritdoc}
   */
  public function combineWithConjunctionTarget(OperationInterface $conjunction_target) {
    return new ConjunctionOp($conjunction_target, $this);
  public function scaffoldOverExistingTarget(OperationInterface $existing_target) {
    $this->originalContents = $existing_target->contents();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function missingConjunctionTarget(ScaffoldFilePath $destination) {
    // If there is no conjunction target (the destination is not scaffolded),
    // then any append we do will be to an unmanaged file.
  public function scaffoldAtNewLocation(ScaffoldFilePath $destination) {
    // If there is no existing scaffold file at the target location, then any
    // append we do will be to an unmanaged file.
    $this->managed = FALSE;

    // Default: do not allow an append over a file that was not scaffolded.
@@ -164,6 +179,9 @@ public function missingConjunctionTarget(ScaffoldFilePath $destination) {
      return new SkipOp($message);
    }

    // Cache the original data to use during append.
    $this->originalContents = $existingData;

    return $this;
  }

+0 −53
Original line number Diff line number Diff line
<?php

namespace Drupal\Composer\Plugin\Scaffold\Operations;

use Composer\IO\IOInterface;
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
use Drupal\Composer\Plugin\Scaffold\ScaffoldOptions;

/**
 * Joins two operations on the same file into a single operation.
 *
 * @internal
 */
class ConjunctionOp extends AbstractOperation {

  /**
   * The first operation.
   *
   * @var \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
   */
  protected $firstOperation;

  /**
   * The second operation.
   *
   * @var \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
   */
  protected $secondOperation;

  /**
   * ConjunctionOp constructor.
   *
   * @param \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface $first_operation
   * @param \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface $second_operation
   */
  public function __construct(OperationInterface $first_operation, OperationInterface $second_operation) {
    $this->firstOperation = $first_operation;
    $this->secondOperation = $second_operation;
  }

  /**
   * {@inheritdoc}
   */
  public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) {
    $destination_path = $destination->fullPath();
    // First, scaffold the original file. Disable symlinking, because we
    // need a copy of the file if we're going to append / prepend to it.
    @unlink($destination_path);
    $this->firstOperation->process($destination, $io, $options->overrideSymlink(FALSE));
    return $this->secondOperation->process($destination, $io, $options);
  }

}
+15 −4
Original line number Diff line number Diff line
@@ -13,6 +13,14 @@
 */
interface OperationInterface {

  /**
   * Returns the exact data that will be written to the scaffold files.
   *
   * @return string
   *   Data to be written to the scaffold location.
   */
  public function contents();

  /**
   * Process this scaffold operation.
   *
@@ -29,18 +37,18 @@ interface OperationInterface {
  public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options);

  /**
   * Determines what to do if operation is used with a previous operation.
   * Determines what to do if operation is used at same path as a previous op.
   *
   * Default behavior is to scaffold this operation at the specified
   * destination, ignoring whatever was there before.
   *
   * @param OperationInterface $conjunction_target
   * @param OperationInterface $existing_target
   *   Existing file at the destination path that we should combine with.
   *
   * @return OperationInterface
   *   The op to use at this destination.
   */
  public function combineWithConjunctionTarget(OperationInterface $conjunction_target);
  public function scaffoldOverExistingTarget(OperationInterface $existing_target);

  /**
   * Determines what to do if operation is used without a previous operation.
@@ -50,9 +58,12 @@ public function combineWithConjunctionTarget(OperationInterface $conjunction_tar
   * and therefore do not need to do anything special when there is no existing
   * file.
   *
   * @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
   *   Scaffold file's destination path.
   *
   * @return OperationInterface
   *   The op to use at this destination.
   */
  public function missingConjunctionTarget(ScaffoldFilePath $destination);
  public function scaffoldAtNewLocation(ScaffoldFilePath $destination);

}
Loading