Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade --tighten #3957

Merged
merged 4 commits into from
Sep 1, 2023
Merged
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
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