Skip to content

Commit ac38366

Browse files
authored
Merge pull request #547 from cweagans/dependency-patches
Dependency patch resolution
2 parents 4ce0ade + 5d52614 commit ac38366

File tree

23 files changed

+271
-36
lines changed

23 files changed

+271
-36
lines changed

docs/api/patches-lock-json.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ If the [`COMPOSER`]({{< relref "../usage/configuration.md#composer" >}}) environ
1919
```
2020
* Each patch definition will look like the [expanded format]({{< relref "../usage/defining-patches.md#expanded-format" >}}) that users can put into their `composer.json` or external patches file.
2121
* No _removals_ or _changes_ will be made to the patch definition object. _Additional_ keys may be created, so any JSON parsing you're doing should be tolerant of new keys.
22-
* The `extra` object in each patch definition may contain a number of attributes set by other projects or by the user and should be treated as free-form input.
22+
* The `extra` object in each patch definition may contain a number of attributes set by other projects or by the user and should be treated as free-form input. Currently, Composer Patches uses this attribute to store information about where a patch was defined (in the `provenance` key).

docs/usage/configuration.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ Technically, this value can be a path to a file that is nested in a deeper direc
4949

5050
---
5151

52+
### `ignore-dependency-patches`
53+
54+
```json
55+
{
56+
[...],
57+
"extra": {
58+
"composer-patches": {
59+
"ignore-dependency-patches": [
60+
"some/package",
61+
]
62+
}
63+
}
64+
}
65+
```
66+
67+
**Default value**: empty
68+
69+
`ignore-dependency-patches` allows you to ignore patches defined by the listed dependencies. For instance, if your project requires `drupal/core` and `some/package`, and `some/package` defines a patch for `drupal/core`, listing `some/package` in `ignore-dependency-patches` would cause that patch to be ignored. This does _not_ affect the _target_ of those patches. For instance, listing `drupal/core` here would not cause patches _to_ `drupal/core` to be ignored.
70+
71+
---
72+
5273
### `default-patch-depth`
5374

5475
```json
@@ -81,7 +102,8 @@ You probably don't need to change this value. Instead, consider setting a packag
81102
"composer-patches": {
82103
"disable-resolvers": [
83104
"\\cweagans\\Composer\\Resolver\\RootComposer",
84-
"\\cweagans\\Composer\\Resolver\\PatchesFile"
105+
"\\cweagans\\Composer\\Resolver\\PatchesFile",
106+
"\\cweagans\\Composer\\Resolver\\Dependencies"
85107
]
86108
}
87109
}

docs/usage/defining-patches.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ If you're defining patches in `patches.json` (or some other separate patches fil
130130
}
131131
```
132132

133+
### Dependencies
134+
135+
Packages required by your project can define patches as well. They can do so by defining patches in their root `composer.json` file. Defining patches in a separate `patches.json` in a dependency is currently unsupported.
136+
137+
{{< callout title="Patch paths are always relative to the root of your project" >}}
138+
Patches defined by a dependency should always use a publicly accessible URL, rather than a local file path. Composer Patches _will not_ attempt to modify file paths so that the patch file can be found within the installed location of a dependency.
139+
{{< /callout >}}
140+
141+
133142
## Duplicate patches
134143

135144
If the same patch is defined in multiple places, the first one added to the patch collection "wins". Subsequent definitions of the same patch will be ignored without emitting an error. The criteria used for determining whether two patches are the same are:

src/Capability/Resolver/CoreResolverProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public function getResolvers(): array
1616
return [
1717
new RootComposer($this->composer, $this->io, $this->plugin),
1818
new PatchesFile($this->composer, $this->io, $this->plugin),
19+
new Dependencies($this->composer, $this->io, $this->plugin),
1920
];
2021
}
2122
}

src/Downloader.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ public function downloadPatch(Patch $patch)
5252
}
5353

5454
foreach ($this->getDownloaders() as $downloader) {
55-
if (in_array(get_class($downloader), $this->disabledDownloaders, true)) {
55+
$class = "\\" . get_class($downloader);
56+
if (in_array($class, $this->disabledDownloaders, true)) {
5657
$this->io->write(
57-
'<info> - Skipping downloader ' . get_class($downloader) . '</info>',
58+
'<info> - Skipping downloader ' . $class . '</info>',
5859
true,
5960
IOInterface::VERBOSE
6061
);

src/Patch.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Patch implements JsonSerializable
5050
public ?string $localPath;
5151

5252
/**
53-
* This is unused in the main plugin, but can be used as a place for other plugins to store data about a patch.
53+
* Can be used as a place for other plugins to store data about a patch.
5454
*
5555
* This should be treated as an associative array and should contain only scalar values.
5656
*

src/Patcher.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ public function __construct(Composer $composer, IOInterface $io, array $disabled
4141
public function applyPatch(Patch $patch, string $path): bool
4242
{
4343
foreach ($this->getPatchers() as $patcher) {
44-
if (in_array(get_class($patcher), $this->disabledPatchers, true)) {
44+
$class = "\\" . get_class($patcher);
45+
if (in_array($class, $this->disabledPatchers, true)) {
4546
$this->io->write(
46-
'<info> - Skipping patcher ' . get_class($patcher) . '</info>',
47+
'<info> - Skipping patcher ' . $class . '</info>',
4748
true,
4849
IOInterface::VERBOSE
4950
);

src/Plugin/Patches.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ public function activate(Composer $composer, IOInterface $io): void
145145
'patches-file' => [
146146
'type' => 'string',
147147
'default' => 'patches.json',
148-
]
148+
],
149+
"ignore-dependency-patches" => [
150+
'type' => 'list',
151+
'default' => [],
152+
],
149153
];
150154
$this->configure($this->composer->getPackage()->getExtra(), 'composer-patches');
151155
}

src/Resolver.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ public function loadFromResolvers(): PatchCollection
4545
// Let each resolver discover patches and add them to the PatchCollection.
4646
/** @var ResolverInterface $resolver */
4747
foreach ($this->getPatchResolvers() as $resolver) {
48-
if (in_array(get_class($resolver), $this->disabledResolvers, true)) {
48+
$class = "\\" . get_class($resolver);
49+
50+
if (in_array($class, $this->disabledResolvers, true)) {
4951
$this->io->write(
50-
'<info> - Skipping resolver ' . get_class($resolver) . '</info>',
52+
'<info> - Skipping resolver ' . $class . '</info>',
5153
true,
5254
IOInterface::VERBOSE
5355
);

src/Resolver/Dependencies.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Contains \cweagans\Composer\Resolvers\Dependencies.
6+
*/
7+
8+
namespace cweagans\Composer\Resolver;
9+
10+
use cweagans\Composer\Patch;
11+
use cweagans\Composer\PatchCollection;
12+
13+
class Dependencies extends ResolverBase
14+
{
15+
/**
16+
* {@inheritDoc}
17+
*/
18+
public function resolve(PatchCollection $collection): void
19+
{
20+
$locker = $this->composer->getLocker();
21+
if (!$locker->isLocked()) {
22+
$this->io->write(' - <info>Composer lock file does not exist.</info>');
23+
$this->io->write(' - <info>Patches defined in dependencies will not be resolved.</info>');
24+
return;
25+
}
26+
27+
$this->io->write(' - <info>Resolving patches from dependencies.</info>');
28+
29+
$ignored_dependencies = $this->plugin->getConfig('ignore-dependency-patches');
30+
31+
$lockdata = $locker->getLockData();
32+
foreach ($lockdata['packages'] as $p) {
33+
// If we're supposed to skip gathering patches from a dependency, do that.
34+
if (in_array($p['name'], $ignored_dependencies)) {
35+
continue;
36+
}
37+
38+
// Find patches in the composer.json for dependencies.
39+
if (!isset($p['extra']) || !isset($p['extra']['patches'])) {
40+
continue;
41+
}
42+
foreach ($this->findPatchesInJson($p['extra']['patches']) as $package => $patches) {
43+
foreach ($patches as $patch) {
44+
$patch->extra['provenance'] = "dependency:" . $package;
45+
46+
/** @var Patch $patch */
47+
$collection->addPatch($patch);
48+
}
49+
}
50+
51+
// TODO: Also find patches in a configured patches.json for the dependency.
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)