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

Combining dependabot core version constraint (latest_allowable_version) with existing composer version constraint #10150

Merged
Show file tree
Hide file tree
Changes from 13 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
25 changes: 23 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,29 @@ public static function getLatestResolvableVersion(array $args): ?string
$io->loadConfiguration($config);
}

$package = $composer->getPackage();
$versionParser = new VersionParser();
$dependabotConstraint = '==' . $latestAllowableVersion;
$constraintString = $dependabotConstraint;
thavaahariharangit marked this conversation as resolved.
Show resolved Hide resolved
// combine new dependabot constraints with the existing composer constraints (if exists)
if (isset($package->getRequires()[$dependencyName])) {
// Main Composer Constraint
$composerConstraint = $package->getRequires()[$dependencyName]->getPrettyConstraint();
$constraintString = $dependabotConstraint . ' ' . $composerConstraint;
} elseif (isset($package->getDevRequires()[$dependencyName])) {
// Dev Composer Constraint
$composerConstraint = $package->getDevRequires()[$dependencyName]->getPrettyConstraint();
$constraintString = $dependabotConstraint . ' ' . $composerConstraint;
}

$constraint = $versionParser->parseConstraints($constraintString);
$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
70 changes: 60 additions & 10 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,8 +489,12 @@
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"}}')
allow(checker).to receive(:latest_version_from_registry)
.and_return(Gem::Version.new("2.4.1"))
end

# setting the latest allowable version to 2.4.1
thavaahariharangit marked this conversation as resolved.
Show resolved Hide resolved

it "is between 2.0.0 and 3.0.0" do
expect(latest_resolvable_version).to be < Gem::Version.new("3.0.0")
expect(latest_resolvable_version).to be > Gem::Version.new("2.0.0")
Expand All @@ -487,8 +515,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 +544,8 @@
context "when there is no lockfile" do
let(:project_name) { "version_conflict_without_lockfile" }

it "raises a resolvability error" do
expect { latest_resolvable_version }
.to raise_error(Dependabot::DependencyFileNotResolvable)
it "when no lockfile then version cannot be resolved" do
expect(latest_resolvable_version).to be_nil
thavaahariharangit marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
Expand Down Expand Up @@ -554,12 +581,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 +763,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 +790,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 +806,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
Loading