blob: 251c8c9e36479c09a5b4eb9f2d47d10e7821cce5 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package_name.dart';
import 'pubspec.dart';
import 'source/hosted.dart';
import 'system_cache.dart';
/// Returns a new [Pubspec] without [original]'s dev_dependencies.
Pubspec stripDevDependencies(Pubspec original) {
ArgumentError.checkNotNull(original, 'original');
return Pubspec(
original.name,
version: original.version,
sdkConstraints: original.sdkConstraints,
dependencies: original.dependencies.values,
devDependencies: [], // explicitly give empty list, to prevent lazy parsing
dependencyOverrides: original.dependencyOverrides.values,
);
}
/// Returns a new [Pubspec] without [original]'s dependency_overrides.
Pubspec stripDependencyOverrides(Pubspec original) {
ArgumentError.checkNotNull(original, 'original');
return Pubspec(
original.name,
version: original.version,
sdkConstraints: original.sdkConstraints,
dependencies: original.dependencies.values,
devDependencies: original.devDependencies.values,
dependencyOverrides: [],
);
}
Future<Pubspec> constrainedToAtLeastNullSafetyPubspec(
Pubspec original, SystemCache cache) async {
/// Get the first version of [package] opting in to null-safety.
Future<VersionConstraint> constrainToFirstWithNullSafety(
PackageRange packageRange) async {
final ref = packageRange.toRef();
final available = await cache.source(ref.source).getVersions(ref);
if (available.isEmpty) {
return stripUpperBound(packageRange.constraint);
}
available.sort((x, y) => x.version.compareTo(y.version));
for (final p in available) {
final pubspec = await cache.source(ref.source).describe(p);
if (pubspec.languageVersion.supportsNullSafety) {
return VersionRange(min: p.version, includeMin: true);
}
}
return stripUpperBound(packageRange.constraint);
}
Future<List<PackageRange>> allConstrainedToAtLeastNullSafety(
Map<String, PackageRange> constrained,
) async {
final result = await Future.wait(constrained.keys.map((name) async {
final packageRange = constrained[name]!;
var unconstrainedRange = packageRange;
/// We only need to remove the upper bound if it is a hosted package.
if (packageRange.source is HostedSource) {
unconstrainedRange = PackageRange(
packageRange.name,
packageRange.source,
await constrainToFirstWithNullSafety(packageRange),
packageRange.description,
features: packageRange.features);
}
return unconstrainedRange;
}));
return result;
}
final constrainedLists = await Future.wait([
allConstrainedToAtLeastNullSafety(original.dependencies),
allConstrainedToAtLeastNullSafety(original.devDependencies),
]);
return Pubspec(
original.name,
version: original.version,
sdkConstraints: original.sdkConstraints,
dependencies: constrainedLists[0],
devDependencies: constrainedLists[1],
dependencyOverrides: original.dependencyOverrides.values,
);
}
/// Returns new pubspec with the same dependencies as [original] but with the
/// upper bounds of the constraints removed.
///
/// If [stripOnly] is provided, only the packages whose names are in
/// [stripOnly] will have their upper bounds removed. If [stripOnly] is
/// not specified or empty, then all packages will have their upper bounds
/// removed.
Pubspec stripVersionUpperBounds(Pubspec original,
{Iterable<String>? stripOnly}) {
ArgumentError.checkNotNull(original, 'original');
stripOnly ??= [];
List<PackageRange> _stripUpperBounds(
Map<String, PackageRange> constrained,
) {
final result = <PackageRange>[];
for (final name in constrained.keys) {
final packageRange = constrained[name]!;
var unconstrainedRange = packageRange;
/// We only need to remove the upper bound if it is a hosted package.
if (packageRange.source is HostedSource &&
(stripOnly!.isEmpty || stripOnly.contains(packageRange.name))) {
unconstrainedRange = PackageRange(
packageRange.name,
packageRange.source,
stripUpperBound(packageRange.constraint),
packageRange.description,
features: packageRange.features);
}
result.add(unconstrainedRange);
}
return result;
}
return Pubspec(
original.name,
version: original.version,
sdkConstraints: original.sdkConstraints,
dependencies: _stripUpperBounds(original.dependencies),
devDependencies: _stripUpperBounds(original.devDependencies),
dependencyOverrides: original.dependencyOverrides.values,
);
}
/// Removes the upper bound of [constraint]. If [constraint] is the
/// empty version constraint, [VersionConstraint.empty] will be returned.
@visibleForTesting
VersionConstraint stripUpperBound(VersionConstraint constraint) {
ArgumentError.checkNotNull(constraint, 'constraint');
/// A [VersionConstraint] has to either be a [VersionRange], [VersionUnion],
/// or the empty [VersionConstraint].
if (constraint is VersionRange) {
return VersionRange(min: constraint.min, includeMin: constraint.includeMin);
}
if (constraint is VersionUnion) {
if (constraint.ranges.isEmpty) return VersionConstraint.empty;
final firstRange = constraint.ranges.first;
return VersionRange(min: firstRange.min, includeMin: firstRange.includeMin);
}
assert(constraint == VersionConstraint.empty, 'unknown constraint type');
/// If it gets here, [constraint] is the empty version constraint, so we
/// just return an empty version constraint.
return VersionConstraint.empty;
}