Skip to content

Commit

Permalink
upgrade --tighten (#3957)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigurdm authored Sep 1, 2023
1 parent a2ff15a commit 1ff7ed9
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 26 deletions.
156 changes: 130 additions & 26 deletions lib/src/command/upgrade.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml_edit/yaml_edit.dart';
Expand All @@ -18,6 +19,7 @@ import '../package.dart';
import '../package_name.dart';
import '../pubspec.dart';
import '../pubspec_utils.dart';
import '../sdk.dart';
import '../solver.dart';
import '../source/hosted.dart';
import '../utils.dart';
Expand Down Expand Up @@ -65,6 +67,13 @@ class UpgradeCommand extends PubCommand {

argParser.addFlag('packages-dir', hide: true);

argParser.addFlag(
'tighten',
help:
'Updates lower bounds in pubspec.yaml to match the resolved version.',
negatable: false,
);

argParser.addFlag(
'major-versions',
help: 'Upgrades packages to their latest resolvable versions, '
Expand Down Expand Up @@ -92,8 +101,16 @@ class UpgradeCommand extends PubCommand {

bool get _dryRun => argResults.flag('dry-run');

bool get _tighten => argResults.flag('tighten');

bool get _precompile => argResults.flag('precompile');

/// 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.
List<String> get _packagesToUpgrade => argResults.rest;

bool get _upgradeNullSafety =>
argResults.flag('nullsafety') || argResults.flag('null-safety');

Expand Down Expand Up @@ -122,6 +139,20 @@ Consider using the Dart 2.19 sdk to migrate to null safety.''');
await _runUpgradeMajorVersions();
} else {
await _runUpgrade(entrypoint);
if (_tighten) {
final changes = tighten(
entrypoint.root.pubspec,
entrypoint.lockFile.packages.values.toList(),
);
if (!_dryRun) {
final newPubspecText = _updatePubspec(changes);

if (changes.isNotEmpty) {
writeTextFile(entrypoint.pubspecPath, newPubspecText);
}
}
_outputChangeSummary(changes);
}
}
if (argResults.flag('example') && entrypoint.example != null) {
// Reload the entrypoint to ensure we pick up potential changes that has
Expand All @@ -134,15 +165,71 @@ Consider using the Dart 2.19 sdk to migrate to null safety.''');
Future<void> _runUpgrade(Entrypoint e, {bool onlySummary = false}) async {
await e.acquireDependencies(
SolveType.upgrade,
unlock: argResults.rest,
unlock: _packagesToUpgrade,
dryRun: _dryRun,
precompile: _precompile,
summaryOnly: onlySummary,
analytics: analytics,
);

_showOfflineWarning();
}

/// Returns a list of changes to constraints in [pubspec] updated them to
/// have their lower bound match the version in [packages].
///
/// The return value is a mapping from the original package range to the updated.
///
/// If packages to update where given in [_packagesToUpgrade], only those are
/// tightened. Otherwise all packages are tightened.
///
/// If a dependency has already been updated in [existingChanges], the update
/// will apply on top of that change (eg. preserving the new upper bound).
Map<PackageRange, PackageRange> tighten(
Pubspec pubspec,
List<PackageId> packages, {
Map<PackageRange, PackageRange> existingChanges = const {},
}) {
final result = {...existingChanges};
if (argResults.flag('example') && entrypoint.example != null) {
log.warning(
'Running `upgrade --tighten` only in `${entrypoint.rootDir}`. Run `$topLevelProgram pub upgrade --tighten --directory example/` separately.',
);
}
final toTighten = _packagesToUpgrade.isEmpty
? [
...pubspec.dependencies.values,
...pubspec.devDependencies.values,
]
: [
for (final name in _packagesToUpgrade)
pubspec.dependencies[name] ?? pubspec.devDependencies[name],
].whereNotNull();
for (final range in toTighten) {
final constraint = (result[range] ?? range).constraint;
final resolvedVersion =
packages.firstWhere((p) => p.name == range.name).version;
if (range.source is HostedSource && constraint.isAny) {
result[range] = range
.toRef()
.withConstraint(VersionConstraint.compatibleWith(resolvedVersion));
} else if (constraint is VersionRange) {
final min = constraint.min;
if (min != null && min < resolvedVersion) {
result[range] = range.toRef().withConstraint(
VersionRange(
min: resolvedVersion,
max: constraint.max,
includeMin: true,
includeMax: constraint.includeMax,
).asCompatibleWithIfPossible(),
);
}
}
}
return result;
}

/// 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 All @@ -154,7 +241,8 @@ Consider using the Dart 2.19 sdk to migrate to null safety.''');
...entrypoint.root.pubspec.dependencies.keys,
...entrypoint.root.pubspec.devDependencies.keys,
];
final toUpgrade = argResults.rest.isEmpty ? directDeps : argResults.rest;
final toUpgrade =
_packagesToUpgrade.isEmpty ? directDeps : _packagesToUpgrade;

// Check that all package names in upgradeOnly are direct-dependencies
final notInDeps = toUpgrade.where((n) => !directDeps.contains(n));
Expand Down Expand Up @@ -203,7 +291,7 @@ be direct 'dependencies' or 'dev_dependencies', following packages are not:

// Changes to be made to `pubspec.yaml`.
// Mapping from original to changed value.
final changes = <PackageRange, PackageRange>{};
var changes = <PackageRange, PackageRange>{};
final declaredHostedDependencies = [
...entrypoint.root.pubspec.dependencies.values,
...entrypoint.root.pubspec.devDependencies.values,
Expand Down Expand Up @@ -233,41 +321,41 @@ be direct 'dependencies' or 'dev_dependencies', following packages are not:
),
);
}
final newPubspecText = _updatePubspec(changes);
var newPubspecText = _updatePubspec(changes);
if (_tighten) {
// Do another solve with the updated constraints to obtain the correct
// versions to tighten to. This should be fast (everything is cached, and
// no backtracking needed) so we don't show a spinner.

final solveResult = await resolveVersions(
SolveType.upgrade,
cache,
Package.inMemory(_updatedPubspec(newPubspecText, entrypoint)),
);
changes = tighten(
entrypoint.root.pubspec,
solveResult.packages,
existingChanges: changes,
);
newPubspecText = _updatePubspec(changes);
}

// When doing '--majorVersions' for specific packages we try to update other
// packages as little as possible to make a focused change (SolveType.get).
//
// But without a specific package we want to get as many non-major updates
// as possible (SolveType.upgrade).
final solveType =
argResults.rest.isEmpty ? SolveType.upgrade : SolveType.get;
_packagesToUpgrade.isEmpty ? SolveType.upgrade : SolveType.get;

if (!_dryRun) {
if (changes.isNotEmpty) {
writeTextFile(entrypoint.pubspecPath, newPubspecText);
}
}

String? overridesFileContents;
final overridesPath =
p.join(entrypoint.rootDir, Pubspec.pubspecOverridesFilename);
try {
overridesFileContents = readTextFile(overridesPath);
} on IOException {
overridesFileContents = null;
}

await entrypoint
.withPubspec(
Pubspec.parse(
newPubspecText,
cache.sources,
location: Uri.parse(entrypoint.pubspecPath),
overridesFileContents: overridesFileContents,
overridesLocation: Uri.file(overridesPath),
),
)
.withPubspec(_updatedPubspec(newPubspecText, entrypoint))
.acquireDependencies(
solveType,
dryRun: _dryRun,
Expand All @@ -291,6 +379,24 @@ be direct 'dependencies' or 'dev_dependencies', following packages are not:
_showOfflineWarning();
}

Pubspec _updatedPubspec(String contents, Entrypoint entrypoint) {
String? overridesFileContents;
final overridesPath =
p.join(entrypoint.rootDir, Pubspec.pubspecOverridesFilename);
try {
overridesFileContents = readTextFile(overridesPath);
} on IOException {
overridesFileContents = null;
}
return Pubspec.parse(
contents,
cache.sources,
location: Uri.parse(entrypoint.pubspecPath),
overridesFileContents: overridesFileContents,
overridesLocation: Uri.file(overridesPath),
);
}

/// Updates `pubspec.yaml` with given [changes].
String _updatePubspec(
Map<PackageRange, PackageRange> changes,
Expand All @@ -311,9 +417,7 @@ be direct 'dependencies' or 'dev_dependencies', following packages are not:
}

/// Outputs a summary of changes made to `pubspec.yaml`.
void _outputChangeSummary(
Map<PackageRange, PackageRange> changes,
) {
void _outputChangeSummary(Map<PackageRange, PackageRange> changes) {
ArgumentError.checkNotNull(changes, 'changes');

if (changes.isEmpty) {
Expand Down
1 change: 1 addition & 0 deletions test/testdata/goldens/help_test/pub upgrade --help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Usage: pub upgrade [dependencies...]
--[no-]offline Use cached packages instead of accessing the network.
-n, --dry-run Report what dependencies would change but don't change any.
--[no-]precompile Precompile executables in immediate dependencies.
--tighten Updates lower bounds in pubspec.yaml to match the resolved version.
--major-versions Upgrades packages to their latest resolvable versions, and updates pubspec.yaml.
-C, --directory=<dir> Run this in the directory <dir>.

Expand Down
Loading

0 comments on commit 1ff7ed9

Please sign in to comment.