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
180 changes: 159 additions & 21 deletions lib/src/command/upgrade.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import '../package_name.dart';
import '../pubspec.dart';
import '../pubspec_utils.dart';
import '../solver.dart';
import '../solver/version_solver.dart';
import '../utils.dart';

/// Handles the `upgrade` pub command.
Expand All @@ -25,9 +26,16 @@ class UpgradeCommand extends PubCommand {
String get name => 'upgrade';
@override
String get description =>
"Upgrade the current package's dependencies to latest versions.";
"Upgrade the current package's dependencies to latest versions.\n"
'\n'
'Append `:latest` to a dependency to require the latest available '
'version.\n'
'\n'
'Append `:resolvable` to require the newest version resolvable with the '
'rest of\n'
'the dependency graph.';
@override
String get argumentsDescription => '[dependencies...]';
String get argumentsDescription => '[dependencies[:latest|:resolvable]...]';
@override
String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-upgrade';

Expand Down Expand Up @@ -111,26 +119,35 @@ class UpgradeCommand extends PubCommand {
late final Future<List<String>> _packagesToUpgrade =
_computePackagesToUpgrade();

late final List<_UpgradeTarget> _upgradeTargets =
argResults.rest.map(_parseUpgradeTarget).toList();

late final Future<List<ConstraintAndCause>?> _additionalConstraints =
_upgradeTargetConstraints();

late final Future<Map<String, PackageId>> _latestResolvablePackages =
_computeLatestResolvablePackages();

/// List of package names to upgrade, if empty then upgrade all packages.
///
/// This allows the user to specify list of names that they want the
/// upgrade command to affect.
Future<List<String>> _computePackagesToUpgrade() async {
if (argResults.flag('unlock-transitive')) {
final graph = await entrypoint.packageGraph;
return argResults.rest
return _upgradeTargets
.expand(
(package) => graph
(target) => graph
.transitiveDependencies(
package,
target.name,
followDevDependenciesFromPackage: true,
)
.map((p) => p.name),
)
.toSet()
.toList();
} else {
return argResults.rest;
return _upgradeTargets.map((target) => target.name).toList();
}
}

Expand All @@ -152,6 +169,18 @@ Consider using the Dart 2.19 sdk to migrate to null safety.''');
),
);
}
if (_upgradeTargets.any((target) => target.kind != null)) {
if (_upgradeMajorVersions) {
usageException(
'Cannot use `:latest` or `:resolvable` with `--major-versions`.',
);
}
if (_tighten) {
usageException(
'Cannot use `:latest` or `:resolvable` with `--tighten`.',
);
}
}

if (_upgradeMajorVersions) {
if (argResults.flag('example')) {
Expand Down Expand Up @@ -196,6 +225,7 @@ Consider using the Dart 2.19 sdk to migrate to null safety.''');
await e.acquireDependencies(
SolveType.upgrade,
unlock: await _packagesToUpgrade,
additionalConstraints: await _additionalConstraints,
dryRun: _dryRun,
precompile: _precompile,
summaryOnly: onlySummary,
Expand All @@ -204,6 +234,118 @@ Consider using the Dart 2.19 sdk to migrate to null safety.''');
_showOfflineWarning();
}

Future<List<ConstraintAndCause>?> _upgradeTargetConstraints() async {
final constraintFutures = <Future<ConstraintAndCause>>[];
for (final target in _upgradeTargets) {
final kind = target.kind;
if (kind == null) continue;

constraintFutures.add(
(() async {
final targetPackage = switch (kind) {
_UpgradeTargetKind.latest => await _latest(target.name),
_UpgradeTargetKind.resolvable => await _latestResolvable(
target.name,
),
};
return ConstraintAndCause(
targetPackage.toRange(),
'${targetPackage.name} ${targetPackage.version} was requested by '
'`$topLevelProgram pub upgrade ${target.argument}`.',
);
})(),
);
}
final constraints = await Future.wait(constraintFutures);
return constraints.isEmpty ? null : constraints;
}

Future<Map<String, PackageId>> _computeLatestResolvablePackages() async {
final solveResult = await log.spinner('Resolving dependencies', () async {
return await resolveVersions(
SolveType.upgrade,
cache,
entrypoint.workspaceRoot.transformWorkspace(
(package) => stripVersionBounds(package.pubspec),
),
);
}, condition: _shouldShowSpinner);
return {for (final package in solveResult.packages) package.name: package};
}

Future<PackageId> _latestResolvable(String package) async {
if (_packageRef(package) == null) {
dataError('Package `$package` is not in the current resolution.');
}
final latestResolvable = (await _latestResolvablePackages)[package];
if (latestResolvable == null) {
dataError(
'Package `$package` is not in the latest resolvable resolution.',
);
}
return latestResolvable;
}

Future<PackageId> _latest(String package) async {
final ref = _packageRef(package);
if (ref == null) {
dataError('Package `$package` is not in the current resolution.');
}
final current = entrypoint.lockFile.packages[package];
final latest = await cache.getLatest(ref, version: current?.version);
if (latest == null) {
dataError('Could not find package `$package`.');
}
return latest;
}

PackageRef? _packageRef(String package) {
final current = entrypoint.lockFile.packages[package];
if (current != null) return current.toRef();

for (final workspacePackage
in entrypoint.workspaceRoot.transitiveWorkspace) {
final dependency = workspacePackage.dependencies[package];
if (dependency != null) return dependency.toRef();
final devDependency = workspacePackage.devDependencies[package];
if (devDependency != null) return devDependency.toRef();
}
return null;
}

_UpgradeTarget _parseUpgradeTarget(String argument) {
final parts = argument.split(':');
if (parts.length > 2) {
usageException(
'Could not parse upgrade target `$argument`. Use `<package>`, '
'`<package>:latest`, or `<package>:resolvable`.',
);
}

final package = parts.first;
if (!packageNameRegExp.hasMatch(package)) {
usageException('Not a valid package name: "$package"');
}

if (parts.length == 1) {
return _UpgradeTarget(argument, package, null);
}

final suffix = parts.last;
final kind = switch (suffix) {
'latest' => _UpgradeTargetKind.latest,
'resolvable' => _UpgradeTargetKind.resolvable,
_ => null,
};
if (kind == null) {
usageException(
'Unknown upgrade target `$argument`. Use `<package>`, '
'`<package>:latest`, or `<package>:resolvable`.',
);
}
return _UpgradeTarget(argument, package, kind);
}

/// Return names of packages to be upgraded, and throws [UsageException] if
/// any package names not in the direct dependencies or dev_dependencies are
/// given.
Expand Down Expand Up @@ -240,21 +382,7 @@ be direct 'dependencies' or 'dev_dependencies', following packages are not:

Future<void> _runUpgradeMajorVersions() async {
final toUpgrade = await _directDependenciesToUpgrade();
// Solve [resolvablePubspec] in-memory and consolidate the resolved
// versions of the packages into a map for quick searching.
final resolvedPackages = <String, PackageId>{};
final solveResult = await log.spinner('Resolving dependencies', () async {
return await resolveVersions(
SolveType.upgrade,
cache,
entrypoint.workspaceRoot.transformWorkspace(
(package) => stripVersionBounds(package.pubspec),
),
);
}, condition: _shouldShowSpinner);
for (final resolvedPackage in solveResult.packages) {
resolvedPackages[resolvedPackage.name] = resolvedPackage;
}
final resolvedPackages = await _latestResolvablePackages;
final dependencyOverriddenDeps = <String>[];
// Changes to be made to `pubspec.yaml` of each package.
// Mapping from original to changed value.
Expand Down Expand Up @@ -377,3 +505,13 @@ be direct 'dependencies' or 'dev_dependencies', following packages are not:
}
}
}

enum _UpgradeTargetKind { latest, resolvable }

class _UpgradeTarget {
final String argument;
final String name;
final _UpgradeTargetKind? kind;

_UpgradeTarget(this.argument, this.name, this.kind);
}
7 changes: 7 additions & 0 deletions lib/src/entrypoint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import 'sdk/flutter.dart';
import 'solver.dart';
import 'solver/report.dart';
import 'solver/solve_suggestions.dart';
import 'solver/version_solver.dart';
import 'source/cached.dart';
import 'source/hosted.dart';
import 'source/root.dart';
Expand Down Expand Up @@ -556,6 +557,10 @@ See $workspacesDocUrl for more information.''',
/// The iterable [unlock] specifies the list of packages whose versions can be
/// changed even if they are locked in the pubspec.lock file.
///
/// The iterable [additionalConstraints] specifies extra constraints the
/// version solver must satisfy. When omitted, no extra constraints are
/// applied.
///
/// Shows a report of the changes made relative to the previous lockfile. If
/// this is an upgrade or downgrade, all transitive dependencies are shown in
/// the report. Otherwise, only dependencies that were changed are shown. If
Expand All @@ -575,6 +580,7 @@ See $workspacesDocUrl for more information.''',
Future<void> acquireDependencies(
SolveType type, {
Iterable<String> unlock = const [],
Iterable<ConstraintAndCause>? additionalConstraints,
bool dryRun = false,
bool precompile = false,
bool summaryOnly = false,
Expand Down Expand Up @@ -608,6 +614,7 @@ Try running `$topLevelProgram pub get` to create `$lockFilePath`.''');
workspaceRoot,
lockFile: lockFile,
unlock: unlock,
additionalConstraints: additionalConstraints,
);
});
} on SolveFailure catch (e) {
Expand Down
7 changes: 6 additions & 1 deletion test/testdata/goldens/help_test/pub upgrade --help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
$ pub upgrade --help
Upgrade the current package's dependencies to latest versions.

Usage: pub upgrade [dependencies...]
Append `:latest` to a dependency to require the latest available version.

Append `:resolvable` to require the newest version resolvable with the rest of
the dependency graph.

Usage: pub upgrade [dependencies[:latest|:resolvable]...]
-h, --help Print this usage information.
--[no-]offline Use cached packages instead of accessing the network.
-n, --dry-run Report what dependencies would change but don't change any.
Expand Down
Loading