Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
11
vendor/cweagans/composer-patches/.editorconfig
vendored
Normal file
11
vendor/cweagans/composer-patches/.editorconfig
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# This is the top-most .editorconfig file; do not search in parent directories.
|
||||
root = true
|
||||
|
||||
# All files.
|
||||
[*]
|
||||
end_of_line = LF
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
1
vendor/cweagans/composer-patches/.gitignore
vendored
Normal file
1
vendor/cweagans/composer-patches/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
vendor/
|
9
vendor/cweagans/composer-patches/LICENSE.md
vendored
Normal file
9
vendor/cweagans/composer-patches/LICENSE.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
Copyright 2013 Cameron Eagans
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
161
vendor/cweagans/composer-patches/README.md
vendored
Normal file
161
vendor/cweagans/composer-patches/README.md
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
# composer-patches
|
||||
|
||||
Simple patches plugin for Composer. Applies a patch from a local or remote file to any package required with composer.
|
||||
|
||||
Note that the 1.x versions of Composer Patches are supported on a best-effort
|
||||
basis due to the imminent release of 2.0.0. You may still be interested in
|
||||
using 1.x if you need Composer to cooperate with earlier PHP versions. No new
|
||||
features will be added to 1.x releases, but any security or bug fixes will
|
||||
still be accepted.
|
||||
|
||||
## Usage
|
||||
|
||||
Example composer.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"cweagans/composer-patches": "~1.0",
|
||||
"drupal/drupal": "~8.2"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "source"
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"drupal/drupal": {
|
||||
"Add startup configuration for PHP server": "https://www.drupal.org/files/issues/add_a_startup-1543858-30.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Using an external patch file
|
||||
|
||||
Instead of a patches key in your root composer.json, use a patches-file key.
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"cweagans/composer-patches": "~1.0",
|
||||
"drupal/drupal": "~8.2"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "source"
|
||||
},
|
||||
"extra": {
|
||||
"patches-file": "local/path/to/your/composer.patches.json"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Then your `composer.patches.json` should look like this:
|
||||
|
||||
```
|
||||
{
|
||||
"patches": {
|
||||
"vendor/project": {
|
||||
"Patch title": "http://example.com/url/to/patch.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Allowing patches to be applied from dependencies
|
||||
|
||||
If you want your project to accept patches from dependencies, you must have the following in your composer file:
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"cweagans/composer-patches": "^1.5.0"
|
||||
},
|
||||
"extra": {
|
||||
"enable-patching": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ignoring patches
|
||||
|
||||
There may be situations in which you want to ignore a patch supplied by a dependency. For example:
|
||||
|
||||
- You use a different more recent version of a dependency, and now a patch isn't applying.
|
||||
- You have a more up to date patch than the dependency, and want to use yours instead of theirs.
|
||||
- A dependency's patch adds a feature to a project that you don't need.
|
||||
- Your patches conflict with a dependency's patches.
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"cweagans/composer-patches": "~1.0",
|
||||
"drupal/drupal": "~8.2",
|
||||
"drupal/lightning": "~8.1"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "source"
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"drupal/drupal": {
|
||||
"Add startup configuration for PHP server": "https://www.drupal.org/files/issues/add_a_startup-1543858-30.patch"
|
||||
}
|
||||
},
|
||||
"patches-ignore": {
|
||||
"drupal/lightning": {
|
||||
"drupal/panelizer": {
|
||||
"This patch has known conflicts with our Quick Edit integration": "https://www.drupal.org/files/issues/2664682-49.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using patches from HTTP URLs
|
||||
|
||||
Composer [blocks](https://getcomposer.org/doc/06-config.md#secure-http) you from downloading anything from HTTP URLs, you can disable this for your project by adding a `secure-http` setting in the config section of your `composer.json`. Note that the `config` section should be under the root of your `composer.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"secure-http": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, it's always advised to setup HTTPS to prevent MITM code injection.
|
||||
|
||||
## Patches containing modifications to composer.json files
|
||||
|
||||
Because patching occurs _after_ Composer calculates dependencies and installs packages, changes to an underlying dependency's `composer.json` file introduced in a patch will have _no effect_ on installed packages.
|
||||
|
||||
If you need to modify a dependency's `composer.json` or its underlying dependencies, you cannot use this plugin. Instead, you must do one of the following:
|
||||
- Work to get the underlying issue resolved in the upstream package.
|
||||
- Fork the package and [specify your fork as the package repository](https://getcomposer.org/doc/05-repositories.md#vcs) in your root `composer.json`
|
||||
- Specify compatible package version requirements in your root `composer.json`
|
||||
|
||||
## Error handling
|
||||
|
||||
If a patch cannot be applied (hunk failed, different line endings, etc.) a message will be shown and the patch will be skipped.
|
||||
|
||||
To enforce throwing an error and stopping package installation/update immediately, you have two available options:
|
||||
|
||||
1. Add `"composer-exit-on-patch-failure": true` option to the `extra` section of your composer.json file.
|
||||
1. Export `COMPOSER_EXIT_ON_PATCH_FAILURE=1`
|
||||
|
||||
By default, failed patches are skipped.
|
||||
|
||||
## Difference between this and netresearch/composer-patches-plugin
|
||||
|
||||
- This plugin is much more simple to use and maintain
|
||||
- This plugin doesn't require you to specify which package version you're patching
|
||||
- This plugin is easy to use with Drupal modules (which don't use semantic versioning).
|
||||
- This plugin will gather patches from all dependencies and apply them as if they were in the root composer.json
|
||||
|
||||
## Credits
|
||||
|
||||
A ton of this code is adapted or taken straight from https://github.com/jpstacey/composer-patcher, which is abandoned in favor of https://github.com/netresearch/composer-patches-plugin, which is (IMHO) overly complex and difficult to use.
|
30
vendor/cweagans/composer-patches/composer.json
vendored
Normal file
30
vendor/cweagans/composer-patches/composer.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "cweagans/composer-patches",
|
||||
"description": "Provides a way to patch Composer packages.",
|
||||
"minimum-stability": "dev",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "cweagans\\Composer\\Patches"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Cameron Eagans",
|
||||
"email": "me@cweagans.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"composer-plugin-api": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "~1.0",
|
||||
"phpunit/phpunit": "~4.6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {"cweagans\\Composer\\": "src"}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {"cweagans\\Composer\\Tests\\": "tests"}
|
||||
}
|
||||
}
|
1564
vendor/cweagans/composer-patches/composer.lock
generated
vendored
Normal file
1564
vendor/cweagans/composer-patches/composer.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
18
vendor/cweagans/composer-patches/phpunit.xml.dist
vendored
Normal file
18
vendor/cweagans/composer-patches/phpunit.xml.dist
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!--?xml version="1.0" encoding="UTF-8"?-->
|
||||
|
||||
<phpunit colors="true" bootstrap="vendor/autoload.php">
|
||||
<testsuites>
|
||||
<testsuite name="composer-patches">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<!-- Filter for coverage reports. -->
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>src/</directory>
|
||||
</whitelist>
|
||||
<blacklist>
|
||||
<directory>vendor/</directory>
|
||||
</blacklist>
|
||||
</filter>
|
||||
</phpunit>
|
70
vendor/cweagans/composer-patches/src/PatchEvent.php
vendored
Normal file
70
vendor/cweagans/composer-patches/src/PatchEvent.php
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Dispatch events when patches are applied.
|
||||
*/
|
||||
|
||||
namespace cweagans\Composer;
|
||||
|
||||
use Composer\EventDispatcher\Event;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
class PatchEvent extends Event {
|
||||
|
||||
/**
|
||||
* @var PackageInterface $package
|
||||
*/
|
||||
protected $package;
|
||||
/**
|
||||
* @var string $url
|
||||
*/
|
||||
protected $url;
|
||||
/**
|
||||
* @var string $description
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* Constructs a PatchEvent object.
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param PackageInterface $package
|
||||
* @param string $url
|
||||
* @param string $description
|
||||
*/
|
||||
public function __construct($eventName, PackageInterface $package, $url, $description) {
|
||||
parent::__construct($eventName);
|
||||
$this->package = $package;
|
||||
$this->url = $url;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package that is patched.
|
||||
*
|
||||
* @return PackageInterface
|
||||
*/
|
||||
public function getPackage() {
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url of the patch.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl() {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the patch.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
}
|
30
vendor/cweagans/composer-patches/src/PatchEvents.php
vendored
Normal file
30
vendor/cweagans/composer-patches/src/PatchEvents.php
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Dispatch events when patches are applied.
|
||||
*/
|
||||
|
||||
namespace cweagans\Composer;
|
||||
|
||||
class PatchEvents {
|
||||
|
||||
/**
|
||||
* The PRE_PATCH_APPLY event occurs before a patch is applied.
|
||||
*
|
||||
* The event listener method receives a cweagans\Composer\PatchEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRE_PATCH_APPLY = 'pre-patch-apply';
|
||||
|
||||
/**
|
||||
* The POST_PATCH_APPLY event occurs after a patch is applied.
|
||||
*
|
||||
* The event listener method receives a cweagans\Composer\PatchEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const POST_PATCH_APPLY = 'post-patch-apply';
|
||||
|
||||
}
|
550
vendor/cweagans/composer-patches/src/Patches.php
vendored
Normal file
550
vendor/cweagans/composer-patches/src/Patches.php
vendored
Normal file
|
@ -0,0 +1,550 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides a way to patch Composer packages after installation.
|
||||
*/
|
||||
|
||||
namespace cweagans\Composer;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Installer\PackageEvents;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class Patches implements PluginInterface, EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* @var Composer $composer
|
||||
*/
|
||||
protected $composer;
|
||||
/**
|
||||
* @var IOInterface $io
|
||||
*/
|
||||
protected $io;
|
||||
/**
|
||||
* @var EventDispatcher $eventDispatcher
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
/**
|
||||
* @var ProcessExecutor $executor
|
||||
*/
|
||||
protected $executor;
|
||||
/**
|
||||
* @var array $patches
|
||||
*/
|
||||
protected $patches;
|
||||
|
||||
/**
|
||||
* Apply plugin modifications to composer
|
||||
*
|
||||
* @param Composer $composer
|
||||
* @param IOInterface $io
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io) {
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->eventDispatcher = $composer->getEventDispatcher();
|
||||
$this->executor = new ProcessExecutor($this->io);
|
||||
$this->patches = array();
|
||||
$this->installedPatches = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of event names this subscriber wants to listen to.
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
ScriptEvents::PRE_INSTALL_CMD => array('checkPatches'),
|
||||
ScriptEvents::PRE_UPDATE_CMD => array('checkPatches'),
|
||||
PackageEvents::PRE_PACKAGE_INSTALL => array('gatherPatches'),
|
||||
PackageEvents::PRE_PACKAGE_UPDATE => array('gatherPatches'),
|
||||
// The following is a higher weight for compatibility with
|
||||
// https://github.com/AydinHassan/magento-core-composer-installer and more generally for compatibility with
|
||||
// every Composer plugin which deploys downloaded packages to other locations.
|
||||
// In such cases you want that those plugins deploy patched files so they have to run after
|
||||
// the "composer-patches" plugin.
|
||||
// @see: https://github.com/cweagans/composer-patches/pull/153
|
||||
PackageEvents::POST_PACKAGE_INSTALL => array('postInstall', 10),
|
||||
PackageEvents::POST_PACKAGE_UPDATE => array('postInstall', 10),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Before running composer install,
|
||||
* @param Event $event
|
||||
*/
|
||||
public function checkPatches(Event $event) {
|
||||
if (!$this->isPatchingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$repositoryManager = $this->composer->getRepositoryManager();
|
||||
$localRepository = $repositoryManager->getLocalRepository();
|
||||
$installationManager = $this->composer->getInstallationManager();
|
||||
$packages = $localRepository->getPackages();
|
||||
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
$patches_ignore = isset($extra['patches-ignore']) ? $extra['patches-ignore'] : array();
|
||||
|
||||
$tmp_patches = $this->grabPatches();
|
||||
foreach ($packages as $package) {
|
||||
$extra = $package->getExtra();
|
||||
if (isset($extra['patches'])) {
|
||||
if (isset($patches_ignore[$package->getName()])) {
|
||||
foreach ($patches_ignore[$package->getName()] as $package_name => $patches) {
|
||||
if (isset($extra['patches'][$package_name])) {
|
||||
$extra['patches'][$package_name] = array_diff($extra['patches'][$package_name], $patches);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->installedPatches[$package->getName()] = $extra['patches'];
|
||||
}
|
||||
$patches = isset($extra['patches']) ? $extra['patches'] : array();
|
||||
$tmp_patches = array_merge_recursive($tmp_patches, $patches);
|
||||
}
|
||||
|
||||
if ($tmp_patches == FALSE) {
|
||||
$this->io->write('<info>No patches supplied.</info>');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove packages for which the patch set has changed.
|
||||
foreach ($packages as $package) {
|
||||
if (!($package instanceof AliasPackage)) {
|
||||
$package_name = $package->getName();
|
||||
$extra = $package->getExtra();
|
||||
$has_patches = isset($tmp_patches[$package_name]);
|
||||
$has_applied_patches = isset($extra['patches_applied']);
|
||||
if (($has_patches && !$has_applied_patches)
|
||||
|| (!$has_patches && $has_applied_patches)
|
||||
|| ($has_patches && $has_applied_patches && $tmp_patches[$package_name] !== $extra['patches_applied'])) {
|
||||
$uninstallOperation = new UninstallOperation($package, 'Removing package so it can be re-installed and re-patched.');
|
||||
$this->io->write('<info>Removing package ' . $package_name . ' so that it can be re-installed and re-patched.</info>');
|
||||
$installationManager->uninstall($localRepository, $uninstallOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the Locker isn't available, then we don't need to do this.
|
||||
// It's the first time packages have been installed.
|
||||
catch (\LogicException $e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather patches from dependencies and store them for later use.
|
||||
*
|
||||
* @param PackageEvent $event
|
||||
*/
|
||||
public function gatherPatches(PackageEvent $event) {
|
||||
// If we've already done this, then don't do it again.
|
||||
if (isset($this->patches['_patchesGathered'])) {
|
||||
$this->io->write('<info>Patches already gathered. Skipping</info>', TRUE, IOInterface::VERBOSE);
|
||||
return;
|
||||
}
|
||||
// If patching has been disabled, bail out here.
|
||||
elseif (!$this->isPatchingEnabled()) {
|
||||
$this->io->write('<info>Patching is disabled. Skipping.</info>', TRUE, IOInterface::VERBOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->patches = $this->grabPatches();
|
||||
if (empty($this->patches)) {
|
||||
$this->io->write('<info>No patches supplied.</info>');
|
||||
}
|
||||
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
$patches_ignore = isset($extra['patches-ignore']) ? $extra['patches-ignore'] : array();
|
||||
|
||||
// Now add all the patches from dependencies that will be installed.
|
||||
$operations = $event->getOperations();
|
||||
$this->io->write('<info>Gathering patches for dependencies. This might take a minute.</info>');
|
||||
foreach ($operations as $operation) {
|
||||
if ($operation->getJobType() == 'install' || $operation->getJobType() == 'update') {
|
||||
$package = $this->getPackageFromOperation($operation);
|
||||
$extra = $package->getExtra();
|
||||
if (isset($extra['patches'])) {
|
||||
if (isset($patches_ignore[$package->getName()])) {
|
||||
foreach ($patches_ignore[$package->getName()] as $package_name => $patches) {
|
||||
if (isset($extra['patches'][$package_name])) {
|
||||
$extra['patches'][$package_name] = array_diff($extra['patches'][$package_name], $patches);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->patches = $this->arrayMergeRecursiveDistinct($this->patches, $extra['patches']);
|
||||
}
|
||||
// Unset installed patches for this package
|
||||
if(isset($this->installedPatches[$package->getName()])) {
|
||||
unset($this->installedPatches[$package->getName()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge installed patches from dependencies that did not receive an update.
|
||||
foreach ($this->installedPatches as $patches) {
|
||||
$this->patches = array_merge_recursive($this->patches, $patches);
|
||||
}
|
||||
|
||||
// If we're in verbose mode, list the projects we're going to patch.
|
||||
if ($this->io->isVerbose()) {
|
||||
foreach ($this->patches as $package => $patches) {
|
||||
$number = count($patches);
|
||||
$this->io->write('<info>Found ' . $number . ' patches for ' . $package . '.</info>');
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we don't gather patches again. Extra keys in $this->patches
|
||||
// won't hurt anything, so we'll just stash it there.
|
||||
$this->patches['_patchesGathered'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the patches from root composer or external file
|
||||
* @return Patches
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function grabPatches() {
|
||||
// First, try to get the patches from the root composer.json.
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
if (isset($extra['patches'])) {
|
||||
$this->io->write('<info>Gathering patches for root package.</info>');
|
||||
$patches = $extra['patches'];
|
||||
return $patches;
|
||||
}
|
||||
// If it's not specified there, look for a patches-file definition.
|
||||
elseif (isset($extra['patches-file'])) {
|
||||
$this->io->write('<info>Gathering patches from patch file.</info>');
|
||||
$patches = file_get_contents($extra['patches-file']);
|
||||
$patches = json_decode($patches, TRUE);
|
||||
$error = json_last_error();
|
||||
if ($error != 0) {
|
||||
switch ($error) {
|
||||
case JSON_ERROR_DEPTH:
|
||||
$msg = ' - Maximum stack depth exceeded';
|
||||
break;
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
$msg = ' - Underflow or the modes mismatch';
|
||||
break;
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
$msg = ' - Unexpected control character found';
|
||||
break;
|
||||
case JSON_ERROR_SYNTAX:
|
||||
$msg = ' - Syntax error, malformed JSON';
|
||||
break;
|
||||
case JSON_ERROR_UTF8:
|
||||
$msg = ' - Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
break;
|
||||
default:
|
||||
$msg = ' - Unknown error';
|
||||
break;
|
||||
}
|
||||
throw new \Exception('There was an error in the supplied patches file:' . $msg);
|
||||
}
|
||||
if (isset($patches['patches'])) {
|
||||
$patches = $patches['patches'];
|
||||
return $patches;
|
||||
}
|
||||
elseif(!$patches) {
|
||||
throw new \Exception('There was an error in the supplied patch file');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PackageEvent $event
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function postInstall(PackageEvent $event) {
|
||||
|
||||
// Check if we should exit in failure.
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
$exitOnFailure = getenv('COMPOSER_EXIT_ON_PATCH_FAILURE') || !empty($extra['composer-exit-on-patch-failure']);
|
||||
|
||||
// Get the package object for the current operation.
|
||||
$operation = $event->getOperation();
|
||||
/** @var PackageInterface $package */
|
||||
$package = $this->getPackageFromOperation($operation);
|
||||
$package_name = $package->getName();
|
||||
|
||||
if (!isset($this->patches[$package_name])) {
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->io->write('<info>No patches found for ' . $package_name . '.</info>');
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->io->write(' - Applying patches for <info>' . $package_name . '</info>');
|
||||
|
||||
// Get the install path from the package object.
|
||||
$manager = $event->getComposer()->getInstallationManager();
|
||||
$install_path = $manager->getInstaller($package->getType())->getInstallPath($package);
|
||||
|
||||
// Set up a downloader.
|
||||
$downloader = new RemoteFilesystem($this->io, $this->composer->getConfig());
|
||||
|
||||
// Track applied patches in the package info in installed.json
|
||||
$localRepository = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
$localPackage = $localRepository->findPackage($package_name, $package->getVersion());
|
||||
$extra = $localPackage->getExtra();
|
||||
$extra['patches_applied'] = array();
|
||||
|
||||
foreach ($this->patches[$package_name] as $description => $url) {
|
||||
$this->io->write(' <info>' . $url . '</info> (<comment>' . $description. '</comment>)');
|
||||
try {
|
||||
$this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::PRE_PATCH_APPLY, $package, $url, $description));
|
||||
$this->getAndApplyPatch($downloader, $install_path, $url, $package);
|
||||
$this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::POST_PATCH_APPLY, $package, $url, $description));
|
||||
$extra['patches_applied'][$description] = $url;
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->io->write(' <error>Could not apply patch! Skipping. The error was: ' . $e->getMessage() . '</error>');
|
||||
if ($exitOnFailure) {
|
||||
throw new \Exception("Cannot apply patch $description ($url)!");
|
||||
}
|
||||
}
|
||||
}
|
||||
$localPackage->setExtra($extra);
|
||||
|
||||
$this->io->write('');
|
||||
$this->writePatchReport($this->patches[$package_name], $install_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Package object from an OperationInterface object.
|
||||
*
|
||||
* @param OperationInterface $operation
|
||||
* @return PackageInterface
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getPackageFromOperation(OperationInterface $operation) {
|
||||
if ($operation instanceof InstallOperation) {
|
||||
$package = $operation->getPackage();
|
||||
}
|
||||
elseif ($operation instanceof UpdateOperation) {
|
||||
$package = $operation->getTargetPackage();
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Unknown operation: ' . get_class($operation));
|
||||
}
|
||||
|
||||
return $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a patch on code in the specified directory.
|
||||
*
|
||||
* @param RemoteFilesystem $downloader
|
||||
* @param $install_path
|
||||
* @param $patch_url
|
||||
* @param PackageInterface $package
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getAndApplyPatch(RemoteFilesystem $downloader, $install_path, $patch_url, PackageInterface $package) {
|
||||
|
||||
// Local patch file.
|
||||
if (file_exists($patch_url)) {
|
||||
$filename = realpath($patch_url);
|
||||
}
|
||||
else {
|
||||
// Generate random (but not cryptographically so) filename.
|
||||
$filename = uniqid(sys_get_temp_dir().'/') . ".patch";
|
||||
|
||||
// Download file from remote filesystem to this location.
|
||||
$hostname = parse_url($patch_url, PHP_URL_HOST);
|
||||
$downloader->copy($hostname, $patch_url, $filename, FALSE);
|
||||
}
|
||||
|
||||
// The order here is intentional. p1 is most likely to apply with git apply.
|
||||
// p0 is next likely. p2 is extremely unlikely, but for some special cases,
|
||||
// it might be useful. p4 is useful for Magento 2 patches
|
||||
$patch_levels = array('-p1', '-p0', '-p2', '-p4');
|
||||
|
||||
// Check for specified patch level for this package.
|
||||
if (!empty($this->composer->getPackage()->getExtra()['patchLevel'][$package->getName()])){
|
||||
$patch_levels = array($this->composer->getPackage()->getExtra()['patchLevel'][$package->getName()]);
|
||||
}
|
||||
// Attempt to apply with git apply.
|
||||
$patched = $this->applyPatchWithGit($install_path, $patch_levels, $filename);
|
||||
|
||||
// In some rare cases, git will fail to apply a patch, fallback to using
|
||||
// the 'patch' command.
|
||||
if (!$patched) {
|
||||
foreach ($patch_levels as $patch_level) {
|
||||
// --no-backup-if-mismatch here is a hack that fixes some
|
||||
// differences between how patch works on windows and unix.
|
||||
if ($patched = $this->executeCommand("patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $install_path, $filename)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the temporary patch file.
|
||||
if (isset($hostname)) {
|
||||
unlink($filename);
|
||||
}
|
||||
// If the patch *still* isn't applied, then give up and throw an Exception.
|
||||
// Otherwise, let the user know it worked.
|
||||
if (!$patched) {
|
||||
throw new \Exception("Cannot apply patch $patch_url");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the root package enables patching.
|
||||
*
|
||||
* @return bool
|
||||
* Whether patching is enabled. Defaults to TRUE.
|
||||
*/
|
||||
protected function isPatchingEnabled() {
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
|
||||
if (empty($extra['patches']) && empty($extra['patches-ignore']) && !isset($extra['patches-file'])) {
|
||||
// The root package has no patches of its own, so only allow patching if
|
||||
// it has specifically opted in.
|
||||
return isset($extra['enable-patching']) ? $extra['enable-patching'] : FALSE;
|
||||
}
|
||||
else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a patch report to the target directory.
|
||||
*
|
||||
* @param array $patches
|
||||
* @param string $directory
|
||||
*/
|
||||
protected function writePatchReport($patches, $directory) {
|
||||
$output = "This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)\n";
|
||||
$output .= "Patches applied to this directory:\n\n";
|
||||
foreach ($patches as $description => $url) {
|
||||
$output .= $description . "\n";
|
||||
$output .= 'Source: ' . $url . "\n\n\n";
|
||||
}
|
||||
file_put_contents($directory . "/PATCHES.txt", $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a shell command with escaping.
|
||||
*
|
||||
* @param string $cmd
|
||||
* @return bool
|
||||
*/
|
||||
protected function executeCommand($cmd) {
|
||||
// Shell-escape all arguments except the command.
|
||||
$args = func_get_args();
|
||||
foreach ($args as $index => $arg) {
|
||||
if ($index !== 0) {
|
||||
$args[$index] = escapeshellarg($arg);
|
||||
}
|
||||
}
|
||||
|
||||
// And replace the arguments.
|
||||
$command = call_user_func_array('sprintf', $args);
|
||||
$output = '';
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->io->write('<comment>' . $command . '</comment>');
|
||||
$io = $this->io;
|
||||
$output = function ($type, $data) use ($io) {
|
||||
if ($type == Process::ERR) {
|
||||
$io->write('<error>' . $data . '</error>');
|
||||
}
|
||||
else {
|
||||
$io->write('<comment>' . $data . '</comment>');
|
||||
}
|
||||
};
|
||||
}
|
||||
return ($this->executor->execute($command, $output) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively merge arrays without changing data types of values.
|
||||
*
|
||||
* Does not change the data types of the values in the arrays. Matching keys'
|
||||
* values in the second array overwrite those in the first array, as is the
|
||||
* case with array_merge.
|
||||
*
|
||||
* @param array $array1
|
||||
* The first array.
|
||||
* @param array $array2
|
||||
* The second array.
|
||||
* @return array
|
||||
* The merged array.
|
||||
*
|
||||
* @see http://php.net/manual/en/function.array-merge-recursive.php#92195
|
||||
*/
|
||||
protected function arrayMergeRecursiveDistinct(array $array1, array $array2) {
|
||||
$merged = $array1;
|
||||
|
||||
foreach ($array2 as $key => &$value) {
|
||||
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
|
||||
$merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
|
||||
}
|
||||
else {
|
||||
$merged[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to apply a patch with git apply.
|
||||
*
|
||||
* @param $install_path
|
||||
* @param $patch_levels
|
||||
* @param $filename
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if patch was applied, FALSE otherwise.
|
||||
*/
|
||||
protected function applyPatchWithGit($install_path, $patch_levels, $filename) {
|
||||
// Do not use git apply unless the install path is itself a git repo
|
||||
// @see https://stackoverflow.com/a/27283285
|
||||
if (!is_dir($install_path . '/.git')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$patched = FALSE;
|
||||
foreach ($patch_levels as $patch_level) {
|
||||
if ($this->io->isVerbose()) {
|
||||
$comment = 'Testing ability to patch with git apply.';
|
||||
$comment .= ' This command may produce errors that can be safely ignored.';
|
||||
$this->io->write('<comment>' . $comment . '</comment>');
|
||||
}
|
||||
$checked = $this->executeCommand('git -C %s apply --check -v %s %s', $install_path, $patch_level, $filename);
|
||||
$output = $this->executor->getErrorOutput();
|
||||
if (substr($output, 0, 7) == 'Skipped') {
|
||||
// Git will indicate success but silently skip patches in some scenarios.
|
||||
//
|
||||
// @see https://github.com/cweagans/composer-patches/pull/165
|
||||
$checked = FALSE;
|
||||
}
|
||||
if ($checked) {
|
||||
// Apply the first successful style.
|
||||
$patched = $this->executeCommand('git -C %s apply %s %s', $install_path, $patch_level, $filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $patched;
|
||||
}
|
||||
|
||||
}
|
39
vendor/cweagans/composer-patches/tests/PatchEventTest.php
vendored
Normal file
39
vendor/cweagans/composer-patches/tests/PatchEventTest.php
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Tests event dispatching.
|
||||
*/
|
||||
|
||||
namespace cweagans\Composer\Tests;
|
||||
|
||||
use cweagans\Composer\PatchEvent;
|
||||
use cweagans\Composer\PatchEvents;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
class PatchEventTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* Tests all the getters.
|
||||
*
|
||||
* @dataProvider patchEventDataProvider
|
||||
*/
|
||||
public function testGetters($event_name, PackageInterface $package, $url, $description) {
|
||||
$patch_event = new PatchEvent($event_name, $package, $url, $description);
|
||||
$this->assertEquals($event_name, $patch_event->getName());
|
||||
$this->assertEquals($package, $patch_event->getPackage());
|
||||
$this->assertEquals($url, $patch_event->getUrl());
|
||||
$this->assertEquals($description, $patch_event->getDescription());
|
||||
}
|
||||
|
||||
public function patchEventDataProvider() {
|
||||
$prophecy = $this->prophesize('Composer\Package\PackageInterface');
|
||||
$package = $prophecy->reveal();
|
||||
|
||||
return array(
|
||||
array(PatchEvents::PRE_PATCH_APPLY, $package, 'https://www.drupal.org', 'A test patch'),
|
||||
array(PatchEvents::POST_PATCH_APPLY, $package, 'https://www.drupal.org', 'A test patch'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue