Skip to content
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
24 changes: 21 additions & 3 deletions src/Composer/InstalledPackageResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public function resolvePackageVersion(string $packageName): ?string
private function createInstalledPackages(array $packages): array
{
$packageConstraints = $this->resolvePackageConstraints();
$isLibrary = $this->isLibrary();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think library check is not needed, even on project, eg on "framework skeleton", phpunit range may exists to give user ability to use phpunit version based on specific php version.

$installedPackages = [];

foreach ($packages as $package) {
Expand All @@ -116,9 +117,15 @@ private function createInstalledPackages(array $packages): array

$constraint = $packageConstraints[$name] ?? null;
if (is_string($constraint)) {
// the "installed.json" can be outdated, e.g. after a branch switch;
// in such case the "composer.json" constraint has a priority
$version = $this->matchConstraintVersion($version, $constraint) ?? $version;
if ($isLibrary) {
// a library must stay compatible with the lowest version it declares,
// regardless of which one happens to be installed locally
$version = $this->resolveConstraintLowestVersion($constraint) ?? $version;
} else {
// the "installed.json" can be outdated, e.g. after a branch switch;
// in such case the "composer.json" constraint has a priority
$version = $this->matchConstraintVersion($version, $constraint) ?? $version;
}
}

$installedPackages[$name] = new InstalledPackage($name, $version);
Expand All @@ -127,6 +134,17 @@ private function createInstalledPackages(array $packages): array
return $installedPackages;
}

/**
* A library declares a compatibility range in its "composer.json"; the version-specific rules must target the
* lowest declared version, not the one that happens to be installed locally.
*/
private function isLibrary(): bool
{
$projectComposerJson = $this->loadProjectComposerJson();

return ($projectComposerJson['type'] ?? null) === 'library';
}

/**
* There is no vendor to read the installed versions from, so the constraints themselves are the only source
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"type": "library",
"require": {
"phpunit/phpunit": "^10.5 || ^11.0 || ^12.0",
"symfony/console": "^7.0"
},
"require-dev": {
"nette/utils": "^3.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"packages": [
{
"name": "phpunit/phpunit",
"version": "12.1.0",
"version_normalized": "12.1.0.0"
},
{
"name": "symfony/console",
"version": "v7.2.0",
"version_normalized": "7.2.0.0"
},
{
"name": "nette/utils",
"version": "v3.2.0",
"version_normalized": "3.2.0.0"
},
{
"name": "webmozart/assert",
"version": "1.11.0",
"version_normalized": "1.11.0.0"
}
]
}
19 changes: 19 additions & 0 deletions tests/Composer/InstalledPackageResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ public function testComposerJsonHasPriorityOverOutdatedInstalledJson(): void
$this->assertSame('1.11.0.0', $installedPackageResolver->resolvePackageVersion('webmozart/assert'));
}

public function testLibraryTargetsLowestDeclaredVersionEvenWhenInstalledSatisfies(): void
{
$installedPackageResolver = new InstalledPackageResolver(
__DIR__ . '/Fixture/InstalledPackageResolver/library_composer_json'
);

// installed 12.1.0 satisfies "^10.5 || ^11.0 || ^12.0", yet a library targets the lowest declared version
$this->assertSame('10.5.0.0', $installedPackageResolver->resolvePackageVersion('phpunit/phpunit'));

// installed 7.2.0 satisfies "^7.0", still lowered to the declared floor
$this->assertSame('7.0.0.0', $installedPackageResolver->resolvePackageVersion('symfony/console'));

// require-dev is respected as well
$this->assertSame('3.2.0.0', $installedPackageResolver->resolvePackageVersion('nette/utils'));

// not required in the "composer.json", the installed version stands
$this->assertSame('1.11.0.0', $installedPackageResolver->resolvePackageVersion('webmozart/assert'));
}

public function testStandaloneComposerJsonResolvesVersionsWithoutVendor(): void
{
$installedPackageResolver = new InstalledPackageResolver(
Expand Down
Loading