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
9 changes: 8 additions & 1 deletion lib/src/solver/package_lister.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,16 @@ class PackageLister {
/// Returns the best version of this package that matches [constraint]
/// according to the solver's prioritization scheme, or `null` if no versions
/// match.
/// If [allowPrereleases] is false, this will only consider non-prerelease
/// versions unless there are no non-prerelease versions that match
/// [constraint].
///
/// Throws a [PackageNotFoundException] if this lister's package doesn't
/// exist.
Future<PackageId?> bestVersion(VersionConstraint constraint) async {
Future<PackageId?> bestVersion(
VersionConstraint constraint, {
bool allowPrereleases = true,
}) async {
final locked = _locked;
if (locked != null && constraint.allows(locked.version)) return locked;

Expand All @@ -192,6 +198,7 @@ class PackageLister {
if (isPastLimit(id.version)) break;

if (!constraint.allows(id.version)) continue;
if (!allowPrereleases && id.version.isPreRelease) continue;
if (!id.version.isPreRelease) {
return id;
}
Expand Down
37 changes: 36 additions & 1 deletion lib/src/solver/version_solver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,44 @@ class VersionSolver {
return null; // when unsatisfied.isEmpty
}

// Prereleases are allowed only if the dependency is transitive, or if
// the constraint explicitly allows prereleases.
bool shouldAllowPrereleases(String packageName) {
final workspaces = [_root, ..._root.workspaceChildren];
bool constraintContainsPrerelease(VersionConstraint? constraint) {
if (constraint is Version) {
return constraint.isPreRelease;
}
if (constraint is VersionRange) {
return (constraint.min != null && constraint.min!.isPreRelease) ||
(constraint.max != null && constraint.max!.isPreRelease) ||
constraint.isAny;
}
return false;
}

var isDirectOrDev = false;
for (final workspace in workspaces) {
final directDep = workspace.dependencies[packageName];
if (directDep != null &&
constraintContainsPrerelease(directDep.constraint)) {
return true;
}
final devDep = workspace.devDependencies[packageName];
if (devDep != null && constraintContainsPrerelease(devDep.constraint)) {
return true;
}
isDirectOrDev = isDirectOrDev || directDep != null || devDep != null;
}
return !isDirectOrDev;
}

PackageId? version;
try {
version = await _packageLister(package).bestVersion(package.constraint);
final allowPrereleases = shouldAllowPrereleases(package.name);
version = await _packageLister(
package,
).bestVersion(package.constraint, allowPrereleases: allowPrereleases);
} on PackageNotFoundException catch (error) {
_addIncompatibility(
Incompatibility([
Expand Down
14 changes: 14 additions & 0 deletions test/version_solver_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,20 @@ void prerelease() {
await d.appDir(dependencies: {'a': '^1.0.0'}).create();
await expectResolves(tries: 2);
});

// This is a regression test for #4659.
test('not upgrading to prerelease when constrained to stable', () async {
await servePackages()
..serve('a', '1.0.0', deps: {'c': '^1.0.0'})
..serve('b', '1.0.0', deps: {'c': '^1.0.0'})
..serve('c', '1.0.0')
..serve('a', '2.0.0', deps: {'c': '^2.0.0'})
..serve('b', '2.0.0-dev', deps: {'c': '^2.0.0'})
..serve('c', '2.0.0');

await d.appDir(dependencies: {'a': '^1.0.0', 'b': '^1.0.0'}).create();
await expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'c': '1.0.0'});
});
}

void override() {
Expand Down