diff --git a/FxpAssetPlugin.php b/FxpAssetPlugin.php index a5d7038a..93e4cabd 100644 --- a/FxpAssetPlugin.php +++ b/FxpAssetPlugin.php @@ -23,6 +23,7 @@ use Fxp\Composer\AssetPlugin\Config\Config; use Fxp\Composer\AssetPlugin\Config\ConfigBuilder; use Fxp\Composer\AssetPlugin\Repository\AssetRepositoryManager; +use Fxp\Composer\AssetPlugin\Repository\ResolutionManager; use Fxp\Composer\AssetPlugin\Repository\VcsPackageFilter; use Fxp\Composer\AssetPlugin\Util\AssetPlugin; @@ -87,6 +88,7 @@ public function activate(Composer $composer, IOInterface $io) $this->io = $io; $this->packageFilter = new VcsPackageFilter($this->config, $composer->getPackage(), $composer->getInstallationManager(), $installedRepository); $this->assetRepositoryManager = new AssetRepositoryManager($io, $composer->getRepositoryManager(), $this->packageFilter); + $this->assetRepositoryManager->setResolutionManager(new ResolutionManager($this->config->getArray('resolutions'))); AssetPlugin::addRegistryRepositories($this->assetRepositoryManager, $this->packageFilter, $this->config); AssetPlugin::setVcsTypeRepositories($composer->getRepositoryManager()); diff --git a/Package/Loader/LazyAssetPackageLoader.php b/Package/Loader/LazyAssetPackageLoader.php index dfffbd52..04d961e6 100644 --- a/Package/Loader/LazyAssetPackageLoader.php +++ b/Package/Loader/LazyAssetPackageLoader.php @@ -264,7 +264,7 @@ protected function preProcess(VcsDriverInterface $driver, array $data, $identifi $data['source'] = $driver->getSource($identifier); } - return (array) $data; + return $this->assetRepositoryManager->solveResolutions((array) $data); } /** diff --git a/Repository/AbstractAssetVcsRepository.php b/Repository/AbstractAssetVcsRepository.php index 120d1b05..59494061 100644 --- a/Repository/AbstractAssetVcsRepository.php +++ b/Repository/AbstractAssetVcsRepository.php @@ -237,9 +237,9 @@ protected function preProcessAsset(array $data) // keep the name of the main identifier for all packages $data['name'] = $this->packageName ?: $data['name']; - $data = $this->assetType->getPackageConverter()->convert($data, $vcsRepos); + $data = (array) $this->assetType->getPackageConverter()->convert($data, $vcsRepos); - return (array) $data; + return $this->assetRepositoryManager->solveResolutions($data); } /** diff --git a/Repository/AssetRepositoryManager.php b/Repository/AssetRepositoryManager.php index cd07d99e..45234af0 100644 --- a/Repository/AssetRepositoryManager.php +++ b/Repository/AssetRepositoryManager.php @@ -43,6 +43,11 @@ class AssetRepositoryManager */ protected $pool; + /** + * @var ResolutionManager + */ + protected $resolutionManager; + /** * @var RepositoryInterface[] */ @@ -86,6 +91,30 @@ public function setPool(Pool $pool) return $this; } + /** + * Set the dependency resolution manager. + * + * @param ResolutionManager $resolutionManager The dependency resolution manager + */ + public function setResolutionManager(ResolutionManager $resolutionManager) + { + $this->resolutionManager = $resolutionManager; + } + + /** + * Solve the dependency resolutions. + * + * @param array $data + * + * @return array + */ + public function solveResolutions(array $data) + { + return null !== $this->resolutionManager + ? $this->resolutionManager->solveResolutions($data) + : $data; + } + /** * Adds asset vcs repositories. * diff --git a/Repository/NpmRepository.php b/Repository/NpmRepository.php index df89843b..70caf785 100644 --- a/Repository/NpmRepository.php +++ b/Repository/NpmRepository.php @@ -139,6 +139,7 @@ protected function createArrayRepositoryConfig(array $packageConfigs) foreach ($packageConfigs as $version => $config) { $config['version'] = $version; $config = $this->assetType->getPackageConverter()->convert($config); + $config = $this->assetRepositoryManager->solveResolutions($config); $packages[] = $loader->load($config); } diff --git a/Repository/ResolutionManager.php b/Repository/ResolutionManager.php new file mode 100644 index 00000000..287f4c08 --- /dev/null +++ b/Repository/ResolutionManager.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Fxp\Composer\AssetPlugin\Repository; + +/** + * Solve the conflicts of dependencies by the resolutions. + * + * @author François Pluchino + */ +class ResolutionManager +{ + /** + * @var array + */ + protected $resolutions; + + /** + * Constructor. + * + * @param array $resolutions The dependency resolutions + */ + public function __construct(array $resolutions = array()) + { + $this->resolutions = $resolutions; + } + + /** + * Solve the dependency resolutions. + * + * @param array $data The data of asset composer package + * + * @return array + */ + public function solveResolutions(array $data) + { + $data = $this->doSolveResolutions($data, 'require'); + $data = $this->doSolveResolutions($data, 'require-dev'); + + return $data; + } + + /** + * Solve the dependency resolutions. + * + * @param array $data The data of asset composer package + * @param string $section The dependency section in package + * + * @return array + */ + protected function doSolveResolutions(array $data, $section) + { + if (array_key_exists($section, $data) && is_array($data[$section])) { + foreach ($data[$section] as $dependency => &$range) { + foreach ($this->resolutions as $resolutionDependency => $resolutionRange) { + if ($dependency === $resolutionDependency) { + $range = $resolutionRange; + } + } + } + } + + return $data; + } +} diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 616aafc9..d738c5d3 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -511,6 +511,51 @@ You can further define this option for each package: With this configuration, all your github packages will use the native Git, except for the `bower-asset/example-asset1` package. +### Solve the conflicts of asset dependencies + +Bower include a [resolution section](https://jaketrent.com/post/bower-resolutions) to +solve the conflicts between 2 same dependencies but with different versions. + +As for NPM, it's possible to install several versions of the same dependency by different +dependencies, which is not the case for Bower and Composer. Only the installation of a +single version compatible for all dependencies is possible. + +The dependency resolution would force (replace) a version or range version directly in the +root Composer package. + +**Example:** +```json + "name": "foo/bar", + "require": { + "bower-asset/jquery": "^2.2.0" + } +``` +```json + "name": "bar/baz", + "require": { + "bower-asset/jquery": "2.0.*" + } +``` +```json + "name": "root/package", + "require": { + "foo/bar": "^1.0.0", + "bar/baz": "^1.0.0" + } + "config": { + "fxp-asset": { + "resolutions": { + "bower-asset/jquery": "^3.0.0" + } + } + } +``` + +Result, all asset packages with the `bower-asset/jquery` dependency will use the `^3.0.0` range version. + +> **Note:** +> Be careful when replacing the version, and check the compatibility before. + ### Define the config for all projects You can define each option (`config.fxp-asset.*`) in each project in the `composer.json` diff --git a/Tests/Package/Loader/LazyAssetPackageLoaderTest.php b/Tests/Package/Loader/LazyAssetPackageLoaderTest.php index fe773233..5415a850 100644 --- a/Tests/Package/Loader/LazyAssetPackageLoaderTest.php +++ b/Tests/Package/Loader/LazyAssetPackageLoaderTest.php @@ -74,6 +74,12 @@ protected function setUp() $this->assetRepositoryManager = $this->getMockBuilder(AssetRepositoryManager::class) ->disableOriginalConstructor()->getMock(); + $this->assetRepositoryManager->expects($this->any()) + ->method('solveResolutions') + ->willReturnCallback(function ($value) { + return $value; + }); + $this->lazyPackage ->expects($this->any()) ->method('getName') diff --git a/Tests/Repository/AssetRepositoryManagerTest.php b/Tests/Repository/AssetRepositoryManagerTest.php new file mode 100644 index 00000000..aa429b29 --- /dev/null +++ b/Tests/Repository/AssetRepositoryManagerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Fxp\Composer\AssetPlugin\Tests\Repository; + +use Composer\IO\IOInterface; +use Composer\Repository\RepositoryManager; +use Fxp\Composer\AssetPlugin\Repository\AssetRepositoryManager; +use Fxp\Composer\AssetPlugin\Repository\ResolutionManager; +use Fxp\Composer\AssetPlugin\Repository\VcsPackageFilter; + +/** + * Tests of Asset Repository Manager. + * + * @author François Pluchino + */ +class AssetRepositoryManagerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ResolutionManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $resolutionManager; + + /** + * @var AssetRepositoryManager + */ + protected $assertRepositoryManager; + + protected function setUp() + { + /* @var IOInterface|\PHPUnit_Framework_MockObject_MockObject $io */ + $io = $this->getMockBuilder(IOInterface::class)->getMock(); + /* @var RepositoryManager|\PHPUnit_Framework_MockObject_MockObject $rm */ + $rm = $this->getMockBuilder(RepositoryManager::class)->disableOriginalConstructor()->getMock(); + /* @var VcsPackageFilter|\PHPUnit_Framework_MockObject_MockObject $filter */ + $filter = $this->getMockBuilder(VcsPackageFilter::class)->disableOriginalConstructor()->getMock(); + + $this->resolutionManager = $this->getMockBuilder(ResolutionManager::class)->getMock(); + $this->assertRepositoryManager = new AssetRepositoryManager($io, $rm, $filter); + } + + public function getDataForSolveResolutions() + { + return array( + array(true), + array(false), + ); + } + + /** + * @dataProvider getDataForSolveResolutions + * + * @param bool $withResolutionManager + */ + public function testSolveResolutions($withResolutionManager) + { + $expected = array( + 'name' => 'foo/bar', + ); + + if ($withResolutionManager) { + $this->assertRepositoryManager->setResolutionManager($this->resolutionManager); + $this->resolutionManager->expects($this->once()) + ->method('solveResolutions') + ->with($expected) + ->willReturn($expected); + } else { + $this->resolutionManager->expects($this->never()) + ->method('solveResolutions'); + } + + $data = $this->assertRepositoryManager->solveResolutions($expected); + + $this->assertSame($expected, $data); + } +} diff --git a/Tests/Repository/AssetVcsRepositoryTest.php b/Tests/Repository/AssetVcsRepositoryTest.php index cfda4198..2d38b1cb 100644 --- a/Tests/Repository/AssetVcsRepositoryTest.php +++ b/Tests/Repository/AssetVcsRepositoryTest.php @@ -66,6 +66,12 @@ protected function setUp() $this->assetRepositoryManager = $this->getMockBuilder(AssetRepositoryManager::class) ->disableOriginalConstructor() ->getMock(); + + $this->assetRepositoryManager->expects($this->any()) + ->method('solveResolutions') + ->willReturnCallback(function ($value) { + return $value; + }); } protected function tearDown() diff --git a/Tests/Repository/ResolutionManagerTest.php b/Tests/Repository/ResolutionManagerTest.php new file mode 100644 index 00000000..a0643db8 --- /dev/null +++ b/Tests/Repository/ResolutionManagerTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Fxp\Composer\AssetPlugin\Tests\Repository; + +use Fxp\Composer\AssetPlugin\Repository\ResolutionManager; + +/** + * Tests of Resolution Manager. + * + * @author François Pluchino + */ +class ResolutionManagerTest extends \PHPUnit_Framework_TestCase +{ + public function testSolveResolutions() + { + $rm = new ResolutionManager(array( + 'foo/bar' => '^2.2.0', + 'bar/foo' => '^0.2.0', + )); + + $data = $rm->solveResolutions(array( + 'require' => array( + 'foo/bar' => '2.0.*', + 'foo/baz' => '~1.0', + ), + 'require-dev' => array( + 'bar/foo' => '^0.1.0', + 'test/dev' => '~1.0@dev', + ), + )); + + $expected = array( + 'require' => array( + 'foo/bar' => '^2.2.0', + 'foo/baz' => '~1.0', + ), + 'require-dev' => array( + 'bar/foo' => '^0.2.0', + 'test/dev' => '~1.0@dev', + ), + ); + + $this->assertSame($expected, $data); + } +}