Skip to content

Commit

Permalink
Combining dependabot core version constraint (latest_allowable_versio…
Browse files Browse the repository at this point in the history
…n) with existing composer version constraint (#10150)

* Converting absolute versions to constraints to check updates.

* Setting the latest allowable version as contraint.

* Setting the latest allowable version as contraint.

* Setting the latest allowable version as contraint.

* Converting absolute versions to constraints to check updates.

* Setting the latest allowable version as contraint.

* Setting the latest allowable version as contraint.

* Setting the latest allowable version as contraint.

* Combine existing constraint with latest allowable version constraint.

* After smoke test.

* Code cleanup.

* Unit test fixes.

* Updated as per review comments and handling dev requires separately.

---------

Co-authored-by: “Thavachelvam <“thavaahariharangit@git.com”>
  • Loading branch information
thavaahariharangit and “Thavachelvam authored Jul 9, 2024
1 parent 2774340 commit ab3d8c1
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 13 deletions.
32 changes: 30 additions & 2 deletions composer/helpers/v2/src/UpdateChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Installer;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;

final class UpdateChecker
{
public static function getLatestResolvableVersion(array $args): ?string
{
[$workingDirectory, $dependencyName, $gitCredentials, $registryCredentials] = $args;
[$workingDirectory, $dependencyName, $gitCredentials, $registryCredentials, $latestAllowableVersion] = $args;

$httpBasicCredentials = [];

Expand Down Expand Up @@ -48,10 +50,36 @@ public static function getLatestResolvableVersion(array $args): ?string
$io->loadConfiguration($config);
}

$package = $composer->getPackage();
$versionParser = new VersionParser();
// constraint from dependabot
$dependabotConstraint = '==' . $latestAllowableVersion;
// combine new dependabot constraints with the existing composer constraints (if exists)
if (isset($package->getRequires()[$dependencyName])) {
// Root Composer Constraint
$composerConstraint = $package->getRequires()[$dependencyName]->getPrettyConstraint();
$combinedConstraint = $dependabotConstraint . ' ' . $composerConstraint;
$constraint = $versionParser->parseConstraints($combinedConstraint);
$link = new Link($package->getName(), $dependencyName, $constraint);
$package->setRequires([$dependencyName => $link]);
} elseif (isset($package->getDevRequires()[$dependencyName])) {
// Dev Composer Constraint
$composerConstraint = $package->getDevRequires()[$dependencyName]->getPrettyConstraint();
$combinedConstraint = $dependabotConstraint . ' ' . $composerConstraint;
$constraint = $versionParser->parseConstraints($combinedConstraint);
$link = new Link($package->getName(), $dependencyName, $constraint);
$package->setDevRequires([$dependencyName => $link]);
} else {
// No Composer Constraint
$constraint = $versionParser->parseConstraints($dependabotConstraint);
$link = new Link($package->getName(), $dependencyName, $constraint);
$package->setRequires([$dependencyName => $link]);
}

$install = new Installer(
$io,
$config,
$composer->getPackage(), // @phpstan-ignore-line
$package, // @phpstan-ignore-line
$composer->getDownloadManager(),
$composer->getRepositoryManager(),
$composer->getLocker(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ def run_update_checker
Dir.pwd,
dependency.name.downcase,
git_credentials,
registry_credentials
registry_credentials,
@latest_allowable_version.to_s
]
)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
context "with a library using a >= PHP constraint" do
let(:project_name) { "php_specified_in_library" }
let(:dependency_name) { "phpdocumentor/reflection-docblock" }
let(:latest_allowable_version) { Gem::Version.new("3.3.2") }
let(:dependency_version) { "2.0.4" }
let(:string_req) { "2.0.4" }

Expand All @@ -66,13 +67,15 @@
context "with an application using a >= PHP constraint" do
let(:project_name) { "php_specified_without_lockfile" }
let(:dependency_name) { "phpdocumentor/reflection-docblock" }
let(:latest_allowable_version) { Gem::Version.new("3.3.2") }
let(:dependency_version) { "2.0.4" }
let(:string_req) { "2.0.4" }

it { is_expected.to eq(Dependabot::Composer::Version.new("3.3.2")) }

context "when the minimum version is invalid" do
let(:dependency_version) { "4.2.0" }
let(:latest_allowable_version) { Gem::Version.new("4.3.1") }
let(:string_req) { "4.2.0" }

it { is_expected.to be >= Dependabot::Composer::Version.new("4.3.1") }
Expand All @@ -83,6 +86,7 @@
context "when the minimum version is invalid" do
let(:project_name) { "php_specified_min_invalid_without_lockfile" }
let(:dependency_name) { "phpdocumentor/reflection-docblock" }
let(:latest_allowable_version) { Gem::Version.new("3.2.2") }
let(:dependency_version) { "2.0.4" }
let(:string_req) { "2.0.4" }

Expand All @@ -103,11 +107,16 @@
context "with a dependency that's provided by another dep" do
let(:project_name) { "provided_dependency" }
let(:string_req) { "^1.0" }
let(:latest_allowable_version) { Gem::Version.new("6.0.0") }
let(:dependency_name) { "php-http/client-implementation" }
let(:dependency_version) { nil }

it { is_expected.to eq(Dependabot::Composer::Version.new("1.0")) }
# Root composer.json requires php-http/client-implementation ==6.0.0 ^1.0, it could not be found in any version,
it "raises a Dependabot::DependencyFileNotResolvable error" do
expect { resolver.latest_resolvable_version }
.to raise_error(Dependabot::DependencyFileNotResolvable) do |error|
expect(error.message).to include("Your requirements could not be resolved to an installable set of packages.")
end
end
end

context "with a dependency that uses a stability flag" do
Expand Down
76 changes: 68 additions & 8 deletions composer/spec/dependabot/composer/update_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@
describe "#latest_resolvable_version" do
subject(:latest_resolvable_version) { checker.latest_resolvable_version }

# setting the latest allowable version to 1.22.0
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("1.22.0"))
end

it "returns a non-normalized version, following semver" do
expect(latest_resolvable_version.segments.count).to eq(3)
end
Expand All @@ -209,7 +215,7 @@
context "when the user is ignoring the latest version" do
let(:ignored_versions) { [">= 1.22.0.a, < 4.0"] }

it { is_expected.to eq(Gem::Version.new("1.21.0")) }
it { is_expected.to eq(Gem::Version.new("1.22.0")) }
end

context "without a lockfile" do
Expand All @@ -228,6 +234,12 @@
}]
end

# setting the latest allowable version to 4.3.0
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("4.3.0"))
end

it { is_expected.to be >= Gem::Version.new("4.3.0") }
end

Expand All @@ -244,7 +256,13 @@
}]
end

it { is_expected.to be >= Gem::Version.new("5.2.45") }
# setting the latest allowable version to 1.22.0
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("5.4.36"))
end

it { is_expected.to be >= Gem::Version.new("5.4.36") }

context "when as a platform requirement" do
let(:project_name) { "old_php_platform" }
Expand Down Expand Up @@ -281,6 +299,12 @@
}]
end

# setting the latest allowable version to 5.2.45
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("5.2.45"))
end

it { is_expected.to be >= Gem::Version.new("5.2.45") }
end
end
Expand Down Expand Up @@ -465,6 +489,9 @@
v1_metadata_url = "https://repo.packagist.org/p/#{dependency_name.downcase}.json"
# v1 url doesn't always return 404 for missing packages
stub_request(:get, v1_metadata_url).to_return(status: 200, body: '{"error":{"code":404,"message":"Not Found"}}')
# setting the latest allowable version to 2.4.1
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("2.4.1"))
end

it "is between 2.0.0 and 3.0.0" do
Expand All @@ -487,8 +514,8 @@
end
let(:ignored_versions) { [">= 2.8.0"] }

it "is the highest resolvable version" do
expect(latest_resolvable_version).to eq(Gem::Version.new("2.1.7"))
it "latest version 1.22.0 cannot be resolved." do
expect(latest_resolvable_version).to be_nil
end

context "when the blocking dependency is a git dependency" do
Expand Down Expand Up @@ -516,9 +543,19 @@
context "when there is no lockfile" do
let(:project_name) { "version_conflict_without_lockfile" }

# setting the latest allowable version to 2.1.7
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("2.1.7"))
end

# Root composer.json requires monolog/monolog ==2.1.7 1.22.0
it "raises a resolvability error" do
expect { latest_resolvable_version }
.to raise_error(Dependabot::DependencyFileNotResolvable)
.to raise_error(Dependabot::DependencyFileNotResolvable) do |error|
expect(error.message)
.to include("Your requirements could not be resolved to an installable set of packages.")
end
end
end
end
Expand Down Expand Up @@ -554,12 +591,26 @@
context "when there is no lockfile" do
let(:project_name) { "version_conflict_on_update_without_lockfile" }

it { is_expected.to be_nil }
# Root composer.json requires longman/telegram-bot ==1.22.0 *
it "raises a resolvability error" do
expect { latest_resolvable_version }
.to raise_error(Dependabot::DependencyFileNotResolvable) do |error|
expect(error.message)
.to include("Your requirements could not be resolved to an installable set of packages.")
end
end

context "when the conflict comes from a loose PHP version" do
let(:project_name) { "version_conflict_library" }

it { is_expected.to be_nil }
# Root composer.json requires longman/telegram-bot ==1.22.0 *
it "raises a resolvability error" do
expect { latest_resolvable_version }
.to raise_error(Dependabot::DependencyFileNotResolvable) do |error|
expect(error.message)
.to include("Your requirements could not be resolved to an installable set of packages.")
end
end
end
end
end
Expand Down Expand Up @@ -722,7 +773,10 @@
}]
end

# setting the latest allowable version to 3.0.2
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("3.0.2"))
stub_request(:get, "https://wpackagist.org/packages.json")
.to_return(
status: 200,
Expand All @@ -746,7 +800,7 @@
}]
end

it { is_expected.to be >= Gem::Version.new("5.2.30") }
it { is_expected.to be_nil }
end

context "when a sub-dependency would block the update" do
Expand All @@ -762,6 +816,12 @@
}]
end

# setting the latest allowable version to 5.6.23
before do
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("5.6.23"))
end

# 5.5.0 series and up require an update to illuminate/contracts
it { is_expected.to be >= Gem::Version.new("5.6.23") }
end
Expand Down

0 comments on commit ab3d8c1

Please sign in to comment.