pub upgrade foo, only unlocks foo from pubspec.lock (#2781)
* Fixed #2630 -- pub upgrade foo, just unlocks foo from `pubspec.lock`
* Fixed tests
* Update lib/src/solver.dart
Co-authored-by: Sigurd Meldgaard <sigurdm@google.com>
* Update lib/src/solver.dart
Co-authored-by: Sigurd Meldgaard <sigurdm@google.com>
* Added test for #2629
Co-authored-by: Sigurd Meldgaard <sigurdm@google.com>
diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart
index 667ff61..1a116b9 100644
--- a/lib/src/command/downgrade.dart
+++ b/lib/src/command/downgrade.dart
@@ -43,8 +43,11 @@
'The --packages-dir flag is no longer used and does nothing.'));
}
var dryRun = argResults['dry-run'];
- await entrypoint.acquireDependencies(SolveType.DOWNGRADE,
- useLatest: argResults.rest, dryRun: dryRun);
+ await entrypoint.acquireDependencies(
+ SolveType.DOWNGRADE,
+ unlock: argResults.rest,
+ dryRun: dryRun,
+ );
if (isOffline) {
log.warning('Warning: Downgrading when offline may not update you to '
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index b0b5f61..98248d1 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -374,7 +374,10 @@
/// Try to solve [pubspec] return [PackageId]s in the resolution or `[]`.
Future<List<PackageId>> _tryResolve(Pubspec pubspec, SystemCache cache) async {
final solveResult = await tryResolveVersions(
- SolveType.UPGRADE, cache, Package.inMemory(pubspec));
+ SolveType.UPGRADE,
+ cache,
+ Package.inMemory(pubspec),
+ );
if (solveResult == null) {
return [];
}
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 994cd7e..e51124f 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -75,10 +75,12 @@
}
Future<void> _runUpgrade() async {
- await entrypoint.acquireDependencies(SolveType.UPGRADE,
- useLatest: argResults.rest,
- dryRun: _dryRun,
- precompile: argResults['precompile']);
+ await entrypoint.acquireDependencies(
+ SolveType.UPGRADE,
+ unlock: argResults.rest,
+ dryRun: _dryRun,
+ precompile: argResults['precompile'],
+ );
_showOfflineWarning();
}
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index a7b3fa2..4da50ac 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -224,7 +224,7 @@
/// Updates [lockFile] and [packageRoot] accordingly.
Future<void> acquireDependencies(
SolveType type, {
- List<String> useLatest,
+ Iterable<String> unlock,
bool dryRun = false,
bool precompile = false,
}) async {
@@ -238,7 +238,7 @@
cache,
root,
lockFile: lockFile,
- useLatest: useLatest,
+ unlock: unlock,
),
);
diff --git a/lib/src/solver.dart b/lib/src/solver.dart
index e5ddf55..18b4174 100644
--- a/lib/src/solver.dart
+++ b/lib/src/solver.dart
@@ -21,20 +21,26 @@
/// that those dependencies place on each other and the requirements imposed by
/// [lockFile].
///
-/// If [useLatest] is given, then only the latest versions of the referenced
-/// packages will be used. This is for forcing an upgrade to one or more
-/// packages.
+/// If [unlock] is given, then only packages listed in [unlock] will be unlocked
+/// from [lockFile]. This is useful for a upgrading specific packages only.
///
-/// If [upgradeAll] is true, the contents of [lockFile] are ignored.
+/// If [unlock] is empty [SolveType.GET] interprets this as lock everything,
+/// while [SolveType.UPGRADE] and [SolveType.DOWNGRADE] interprets an empty
+/// [unlock] as unlock everything.
Future<SolveResult> resolveVersions(
- SolveType type, SystemCache cache, Package root,
- {LockFile lockFile, Iterable<String> useLatest}) {
+ SolveType type,
+ SystemCache cache,
+ Package root, {
+ LockFile lockFile,
+ Iterable<String> unlock,
+}) {
+ lockFile ??= LockFile.empty();
return VersionSolver(
type,
cache,
root,
- lockFile ?? LockFile.empty(),
- useLatest ?? const [],
+ lockFile,
+ unlock ?? [],
).solve();
}
@@ -46,17 +52,27 @@
/// Like [resolveVersions] except that this function returns `null` where a
/// similar call to [resolveVersions] would throw a [SolveFailure].
///
-/// If [useLatest] is given, then only the latest versions of the referenced
-/// packages will be used. This is for forcing an upgrade to one or more
-/// packages.
+/// If [unlock] is given, only packages listed in [unlock] will be unlocked
+/// from [lockFile]. This is useful for a upgrading specific packages only.
///
-/// If [upgradeAll] is true, the contents of [lockFile] are ignored.
+/// If [unlock] is empty [SolveType.GET] interprets this as lock everything,
+/// while [SolveType.UPGRADE] and [SolveType.DOWNGRADE] interprets an empty
+/// [unlock] as unlock everything.
Future<SolveResult> tryResolveVersions(
- SolveType type, SystemCache cache, Package root,
- {LockFile lockFile, Iterable<String> useLatest}) async {
+ SolveType type,
+ SystemCache cache,
+ Package root, {
+ LockFile lockFile,
+ Iterable<String> unlock,
+}) async {
try {
- return await resolveVersions(type, cache, root,
- lockFile: lockFile, useLatest: useLatest);
+ return await resolveVersions(
+ type,
+ cache,
+ root,
+ lockFile: lockFile,
+ unlock: unlock,
+ );
} on SolveFailure {
return null;
}
diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart
index fcc7bbe..67d4fe0 100644
--- a/lib/src/solver/version_solver.dart
+++ b/lib/src/solver/version_solver.dart
@@ -69,18 +69,13 @@
/// which other packages' constraints should be ignored.
final Set<String> _overriddenPackages;
- /// The set of packages for which the lockfile should be ignored and only the
- /// most recent versions should be used.
- final Set<String> _useLatest;
-
- /// The set of packages for which we've added an incompatibility that forces
- /// the latest version to be used.
- final _haveUsedLatest = <PackageRef>{};
+ /// The set of packages for which the lockfile should be ignored.
+ final Set<String> _unlock;
VersionSolver(this._type, this._systemCache, this._root, this._lockFile,
- Iterable<String> useLatest)
+ Iterable<String> unlock)
: _overriddenPackages = MapKeySet(_root.pubspec.dependencyOverrides),
- _useLatest = Set.from(useLatest);
+ _unlock = {...unlock};
/// Finds a set of dependencies that match the root package's constraints, or
/// throws an error if no such set is available.
@@ -324,22 +319,6 @@
// If we require a package from an unknown source, add an incompatibility
// that will force a conflict for that package.
for (var candidate in unsatisfied) {
- if (_useLatest.contains(candidate.name) &&
- candidate.source.hasMultipleVersions) {
- var ref = candidate.toRef();
- if (_haveUsedLatest.add(ref)) {
- // All versions of [ref] other than the latest are forbidden.
- var latestVersion = (await _packageLister(ref).latest).version;
- _addIncompatibility(Incompatibility([
- Term(
- ref.withConstraint(
- VersionConstraint.any.difference(latestVersion)),
- true),
- ], IncompatibilityCause.useLatest));
- return candidate.name;
- }
- }
-
if (candidate.source is! UnknownSource) continue;
_addIncompatibility(Incompatibility(
[Term(candidate.withConstraint(VersionConstraint.any), true)],
@@ -350,9 +329,6 @@
/// Prefer packages with as few remaining versions as possible, so that if a
/// conflict is necessary it's forced quickly.
var package = await minByAsync(unsatisfied, (package) async {
- // If we're forced to use the latest version of a package, it effectively
- // only has one version to choose from.
- if (_useLatest.contains(package.name)) return 1;
return await _packageLister(package).countVersions(package.constraint);
});
@@ -490,7 +466,12 @@
///
/// Returns `null` if it isn't in the lockfile (or has been unlocked).
PackageId _getLocked(String package) {
- if (_type == SolveType.GET) return _lockFile.packages[package];
+ if (_type == SolveType.GET) {
+ if (_unlock.contains(package)) {
+ return null;
+ }
+ return _lockFile.packages[package];
+ }
// When downgrading, we don't want to force the latest versions of
// non-hosted packages, since they don't support multiple versions and thus
@@ -500,7 +481,7 @@
if (locked != null && !locked.source.hasMultipleVersions) return locked;
}
- if (_useLatest.isEmpty || _useLatest.contains(package)) return null;
+ if (_unlock.isEmpty || _unlock.contains(package)) return null;
return _lockFile.packages[package];
}
diff --git a/test/downgrade/unlock_dependers_test.dart b/test/downgrade/unlock_dependers_test.dart
deleted file mode 100644
index 8f785a6..0000000
--- a/test/downgrade/unlock_dependers_test.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2012, 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 'package:test/test.dart';
-
-import '../descriptor.dart' as d;
-import '../test_pub.dart';
-
-void main() {
- test(
- "downgrades a locked package's dependers in order to get it to "
- 'min version', () async {
- await servePackages((builder) {
- builder.serve('foo', '2.0.0', deps: {'bar': '>1.0.0'});
- builder.serve('bar', '2.0.0');
- });
-
- await d.appDir({'foo': 'any', 'bar': 'any'}).create();
-
- await pubGet();
-
- await d.appPackagesFile({'foo': '2.0.0', 'bar': '2.0.0'}).validate();
-
- globalPackageServer.add((builder) {
- builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
- builder.serve('bar', '1.0.0');
- });
-
- await pubDowngrade(args: ['bar']);
-
- await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
- });
-}
diff --git a/test/downgrade/unlock_single_package_test.dart b/test/downgrade/unlock_single_package_test.dart
new file mode 100644
index 0000000..e381f37
--- /dev/null
+++ b/test/downgrade/unlock_single_package_test.dart
@@ -0,0 +1,59 @@
+// 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 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() {
+ test('can unlock a single package only in downgrade', () async {
+ await servePackages((builder) {
+ builder.serve('foo', '2.1.0', deps: {'bar': '>1.0.0'});
+ builder.serve('bar', '2.1.0');
+ });
+
+ await d.appDir({'foo': 'any', 'bar': 'any'}).create();
+
+ await pubGet();
+ await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.1.0'}).validate();
+
+ globalPackageServer.add((builder) {
+ builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
+ builder.serve('bar', '1.0.0');
+ });
+
+ await pubDowngrade(args: ['bar']);
+ await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.1.0'}).validate();
+
+ globalPackageServer.add((builder) {
+ builder.serve('foo', '2.0.0', deps: {'bar': 'any'});
+ builder.serve('bar', '2.0.0');
+ });
+
+ await pubDowngrade(args: ['bar']);
+ await d.appPackagesFile({'foo': '2.1.0', 'bar': '2.0.0'}).validate();
+
+ await pubDowngrade();
+ await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+ });
+
+ test('will not downgrade below constraint #2629', () async {
+ await servePackages((builder) {
+ builder.serve('foo', '1.0.0');
+ builder.serve('foo', '2.0.0');
+ builder.serve('foo', '2.1.0');
+ });
+
+ await d.appDir({'foo': '^2.0.0'}).create();
+
+ await pubGet();
+
+ await d.appPackagesFile({'foo': '2.1.0'}).validate();
+
+ await pubDowngrade(args: ['foo']);
+
+ await d.appPackagesFile({'foo': '2.0.0'}).validate();
+ });
+}
diff --git a/test/upgrade/hosted/unlock_dependers_test.dart b/test/upgrade/hosted/unlock_dependers_test.dart
deleted file mode 100644
index 2674505..0000000
--- a/test/upgrade/hosted/unlock_dependers_test.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2012, 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 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
- test(
- "upgrades a locked package's dependers in order to get it to max "
- 'version', () async {
- await servePackages((builder) {
- builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
- builder.serve('bar', '1.0.0');
- });
-
- await d.appDir({'foo': 'any', 'bar': 'any'}).create();
-
- await pubGet();
-
- await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
-
- globalPackageServer.add((builder) {
- builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
- builder.serve('bar', '2.0.0');
- });
-
- await pubUpgrade(args: ['bar']);
-
- await d.appPackagesFile({'foo': '2.0.0', 'bar': '2.0.0'}).validate();
- });
-}
diff --git a/test/upgrade/hosted/unlock_single_package_test.dart b/test/upgrade/hosted/unlock_single_package_test.dart
new file mode 100644
index 0000000..ed2b678
--- /dev/null
+++ b/test/upgrade/hosted/unlock_single_package_test.dart
@@ -0,0 +1,45 @@
+// 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 'package:test/test.dart';
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+void main() {
+ test('can unlock a single package only in upgrade', () async {
+ await servePackages((builder) {
+ builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
+ builder.serve('bar', '1.0.0');
+ });
+
+ await d.appDir({'foo': 'any', 'bar': 'any'}).create();
+
+ await pubGet();
+
+ await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+
+ globalPackageServer.add((builder) {
+ builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
+ builder.serve('bar', '2.0.0');
+ });
+
+ // This can't upgrade 'bar'
+ await pubUpgrade(args: ['bar']);
+
+ await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.0.0'}).validate();
+
+ // Introducing foo and bar 1.1.0, to show that only 'bar' will be upgraded
+ globalPackageServer.add((builder) {
+ builder.serve('foo', '1.1.0', deps: {'bar': '<2.0.0'});
+ builder.serve('bar', '1.1.0');
+ });
+
+ await pubUpgrade(args: ['bar']);
+ await d.appPackagesFile({'foo': '1.0.0', 'bar': '1.1.0'}).validate();
+
+ await pubUpgrade();
+ await d.appPackagesFile({'foo': '2.0.0', 'bar': '2.0.0'}).validate();
+ });
+}