Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow merging "scripts" in a deep way #177

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Usage
"merge-dev": true,
"merge-extra": false,
"merge-extra-deep": false,
"merge-scripts": false
"merge-scripts": false,
"merge-scripts-deep": false
}
}
}
Expand Down Expand Up @@ -177,7 +178,10 @@ scripts section is to accept the first version of any key found (e.g. a key in
the master config wins over the version found in any imported config). If
`replace` mode is active ([see above](#replace)) then this behavior changes
and the last key found will win (e.g. the key in the master config is replaced
by the key in the imported config).
by the key in the imported config). If `"merge-scripts-deep": true` is
specified then, the sections are merged similar to array_merge_recursive() -
however duplicate string array keys are replaced instead of merged, while
numeric array keys are merged as usual.

Note: [custom commands][] added by merged configuration will work when invoked
as `composer run-script my-cool-command` but will not be available using the
Expand Down
4 changes: 2 additions & 2 deletions src/Merge/ExtraPackage.php
Original file line number Diff line number Diff line change
Expand Up @@ -469,11 +469,11 @@ public function mergeScripts(RootPackageInterface $root, PluginState $state)

if ($state->replaceDuplicateLinks()) {
$unwrapped->setScripts(
array_merge($rootScripts, $scripts)
self::mergeExtraArray($state->shouldMergeScriptsDeep(), $rootScripts, $scripts)
);
} else {
$unwrapped->setScripts(
array_merge($scripts, $rootScripts)
self::mergeExtraArray($state->shouldMergeScriptsDeep(), $scripts, $rootScripts)
);
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/Merge/PluginState.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ class PluginState
*/
protected $mergeScripts = false;

/**
* Whether to merge the scripts section in a deep / recursive way.
*
* By default the scripts section is merged with array_merge() and duplicate
* keys are ignored. When enabled this allows to merge the arrays recursively
* using the following rule: Integer keys are merged, while array values are
* replaced where the later values overwrite the former.
*
* When 'replace' mode is activated the order of array merges is exchanged.
*
* @var bool $mergeScriptsDeep
*/
protected $mergeScriptsDeep;

/**
* @var bool $firstInstall
*/
Expand Down Expand Up @@ -149,6 +163,7 @@ public function loadSettings()
'merge-extra' => false,
'merge-extra-deep' => false,
'merge-scripts' => false,
'merge-scripts-deep' => false,
),
isset($extra['merge-plugin']) ? $extra['merge-plugin'] : array()
);
Expand All @@ -164,6 +179,7 @@ public function loadSettings()
$this->mergeExtra = (bool)$config['merge-extra'];
$this->mergeExtraDeep = (bool)$config['merge-extra-deep'];
$this->mergeScripts = (bool)$config['merge-scripts'];
$this->mergeScriptsDeep = (bool)$config['merge-scripts-deep'];
}

/**
Expand Down Expand Up @@ -413,5 +429,22 @@ public function shouldMergeScripts()
{
return $this->mergeScripts;
}

/**
* Should the scripts section be merged deep / recursively?
*
* By default the scripts section is merged with array_merge() and duplicate
* keys are ignored. When enabled this allows to merge the arrays recursively
* using the following rule: Integer keys are merged, while array values are
* replaced where the later values overwrite the former.
*
* When 'replace' mode is activated the order of array merges is exchanged.
*
* @return bool
*/
public function shouldMergeScriptsDeep()
{
return $this->mergeScriptsDeep;
}
}
// vim:sw=4:ts=4:sts=4:et:
55 changes: 53 additions & 2 deletions tests/phpunit/MergePluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace Wikimedia\Composer;

use Prophecy\Prophecy\ObjectProphecy;
use Wikimedia\Composer\Merge\ExtraPackage;
use Wikimedia\Composer\Merge\PluginState;

Expand Down Expand Up @@ -859,7 +860,7 @@ public function provideDeepMerge()
}

/**
* Given a root package with an scripts section
* Given a root package with a scripts section
* and a composer.local.json with an extra section with no conflicting keys
* When the plugin is run
* Then the root package scripts section should be extended with content from the local config.
Expand Down Expand Up @@ -897,7 +898,7 @@ function ($args) use ($that) {
}

/**
* Given a root package with an scripts section
* Given a root package with a scripts section
* and a composer.local.json with an extra section with a conflicting key
* When the plugin is run
* Then the version in the root package should win.
Expand Down Expand Up @@ -970,6 +971,56 @@ function ($args) use ($that) {
$this->assertEquals(0, count($extraInstalls));
}

/**
* Given a root package with a scripts section
* and a composer.local.json with a scripts section with conflicting keys that are arrays
* and the 'merge-scripts-deep' option being activated
* When the plugin is run
* Then the root package scripts section should be extended with content from the base config
* and deep keys should be merged together, but root config wins on key conflicts.
*
* @dataProvider provideDeepMerge
*/
public function testMergeScriptsDeep($suffix, $replace)
{
$that = $this;
$dir = $this->fixtureDir(__FUNCTION__ . $suffix);

/* @var ObjectProphecy|RootPackage $root */
$root = $this->rootFromJson("{$dir}/composer.json");

$root->setScripts(Argument::type('array'))->will(
function ($args) use ($that, $replace) {
$scripts = $args[0];
$that->assertEquals(2, count($scripts));
$that->assertArrayHasKey('example-script', $scripts);
if ($replace) {
$that->assertEquals(array(
"echo 'hello world'",
"echo 'hello again world'",
), $scripts['example-script']);
} else {
$that->assertEquals(array(
"echo 'hello again world'",
"echo 'hello world'",
), $scripts['example-script']);
}
$that->assertArrayHasKey('example-script2', $scripts);
$that->assertEquals(array("echo 'hello world'"), $scripts['example-script2']);
}
)->shouldBeCalled();

$root->getRepositories()->shouldNotBeCalled();
$root->getConflicts()->shouldNotBeCalled();
$root->getReplaces()->shouldNotBeCalled();
$root->getProvides()->shouldNotBeCalled();
$root->getSuggests()->shouldNotBeCalled();

$extraInstalls = $this->triggerPlugin($root->reveal(), $dir);

$this->assertEquals(0, count($extraInstalls));
}

/**
* @dataProvider provideOnPostPackageInstall
* @param string $package Package installed
Expand Down
14 changes: 14 additions & 0 deletions tests/phpunit/fixtures/testMergeScriptsDeep/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"scripts": {
"example-script": [
"echo 'hello world'"
]
},
"extra": {
"merge-plugin": {
"merge-scripts": true,
"merge-scripts-deep": true,
"include": "composer.local.json"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"scripts": {
"example-script": [
"echo 'hello again world'"
],
"example-script2": "echo 'hello world'"
}
}
15 changes: 15 additions & 0 deletions tests/phpunit/fixtures/testMergeScriptsDeepReplace/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"scripts": {
"example-script": [
"echo 'hello world'"
]
},
"extra": {
"merge-plugin": {
"merge-scripts": true,
"merge-scripts-deep": true,
"replace": true,
"include": "composer.local.json"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"scripts": {
"example-script": [
"echo 'hello again world'"
],
"example-script2": "echo 'hello world'"
}
}