checkCrossUpgradeConflicts method
Future<List<CascadingImpact> >
checkCrossUpgradeConflicts(
- List<
Map< upgrades,String, String> > - PubspecData currentPubspec
Check if multiple simultaneous upgrades have conflicts with each other.
For every pair of planned upgrades, this fetches each target version's dependency map and verifies that they are mutually satisfiable. It also checks each upgrade's requirements against the current pubspec.
upgrades is a list of maps, each containing packageName and
targetVersion keys.
Implementation
Future<List<CascadingImpact>> checkCrossUpgradeConflicts(
List<Map<String, String>> upgrades,
PubspecData currentPubspec,
) async {
if (upgrades.length < 2) {
// A single upgrade cannot conflict with itself; delegate to [resolve].
if (upgrades.length == 1) {
return resolve(
packageName: upgrades.first['packageName']!,
targetVersion: upgrades.first['targetVersion']!,
currentPubspec: currentPubspec,
);
}
return [];
}
final impacts = <CascadingImpact>[];
// Fetch all target pubspecs in parallel.
final pubspecFutures = <String, Future<Map<String, dynamic>?>>{};
for (final upgrade in upgrades) {
final name = upgrade['packageName']!;
final version = upgrade['targetVersion']!;
final key = '$name@$version';
pubspecFutures[key] = _pubApi.getVersionPubspec(name, version);
}
// Await all results, tolerating individual failures.
final pubspecs = <String, Map<String, dynamic>>{};
for (final entry in pubspecFutures.entries) {
try {
final result = await entry.value;
if (result != null) {
pubspecs[entry.key] = result;
} else {
Logger.warn(
'CascadeResolver: null pubspec for ${entry.key}',
);
}
} catch (e) {
Logger.warn(
'CascadeResolver: failed to fetch pubspec for ${entry.key}: $e',
);
}
}
// Build a map from each upgrade to its dependency constraints.
final depConstraints = <String, Map<String, String>>{};
for (final entry in pubspecs.entries) {
final deps =
entry.value['dependencies'] as Map<String, dynamic>? ?? {};
final constraints = <String, String>{};
for (final dep in deps.entries) {
final constraint = _extractConstraint(dep.value);
if (constraint != null) {
constraints[dep.key] = constraint;
}
}
depConstraints[entry.key] = constraints;
}
// 1. Check each upgrade against the current pubspec.
for (final upgrade in upgrades) {
final key =
'${upgrade['packageName']}@${upgrade['targetVersion']}';
final constraints = depConstraints[key];
if (constraints == null) continue;
for (final dep in constraints.entries) {
final currentDep = currentPubspec.dependencies[dep.key] ??
currentPubspec.devDependencies[dep.key];
if (currentDep == null || currentDep.resolvedVersion == null) continue;
if (!_versionResolver.satisfies(
currentDep.resolvedVersion!,
dep.value,
)) {
impacts.add(CascadingImpact(
dependencyName: dep.key,
requiredBy: key,
currentConstraint: currentDep.resolvedVersion!,
conflictReason:
'Requires ${dep.key} ${dep.value} but current version '
'is ${currentDep.resolvedVersion}',
));
}
}
}
// 2. Cross-check every pair of upgrades for mutual conflicts.
final keys = depConstraints.keys.toList();
for (var i = 0; i < keys.length; i++) {
for (var j = i + 1; j < keys.length; j++) {
final constraintsA = depConstraints[keys[i]]!;
final constraintsB = depConstraints[keys[j]]!;
_checkPairwiseConflicts(
upgradeA: keys[i],
constraintsA: constraintsA,
upgradeB: keys[j],
constraintsB: constraintsB,
impacts: impacts,
);
}
}
if (impacts.isNotEmpty) {
Logger.info(
'CascadeResolver: found ${impacts.length} cross-upgrade conflict(s)',
);
}
return impacts;
}