diff --git a/lib/src/command.dart b/lib/src/command.dart index 1f5ac2a872f2c57708853ac44aa59247f373094a..c37dc1cad361f2db69714010dec4d526c057a7f0 100644 --- a/lib/src/command.dart +++ b/lib/src/command.dart @@ -13,6 +13,7 @@ import 'package:path/path.dart' as path; import 'command/build.dart'; import 'command/cache.dart'; import 'command/deps.dart'; +import 'command/downgrade.dart'; import 'command/get.dart'; import 'command/global.dart'; import 'command/help.dart'; @@ -261,6 +262,7 @@ _initCommands() { 'build': new BuildCommand(), 'cache': new CacheCommand(), 'deps': new DepsCommand(), + 'downgrade': new DowngradeCommand(), 'global': new GlobalCommand(), 'get': new GetCommand(), 'help': new HelpCommand(), diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart new file mode 100644 index 0000000000000000000000000000000000000000..94e8fc6c056d3442c6937f47857e9d648ae559a8 --- /dev/null +++ b/lib/src/command/downgrade.dart @@ -0,0 +1,41 @@ +// 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. + +library pub.command.downgrade; + +import 'dart:async'; + +import '../command.dart'; +import '../log.dart' as log; +import '../solver/version_solver.dart'; + +/// Handles the `downgrade` pub command. +class DowngradeCommand extends PubCommand { + String get description => + "Downgrade the current package's dependencies to oldest versions.\n\n" + "This doesn't modify the lockfile, so it can be reset with \"pub get\"."; + String get usage => "pub downgrade [dependencies...]"; + bool get takesArguments => true; + + bool get isOffline => commandOptions['offline']; + + DowngradeCommand() { + commandParser.addFlag('offline', + help: 'Use cached packages instead of accessing the network.'); + + commandParser.addFlag('dry-run', abbr: 'n', negatable: false, + help: "Report what dependencies would change but don't change any."); + } + + Future onRun() { + var dryRun = commandOptions['dry-run']; + return entrypoint.acquireDependencies(SolveType.DOWNGRADE, + useLatest: commandOptions.rest, dryRun: dryRun).then((_) { + if (isOffline) { + log.warning("Warning: Downgrading when offline may not update you to " + "the oldest versions of your dependencies."); + } + }); + } +} diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart index 3c066fdbabae3c7ebde91eac83233c9c7ff45add..7394e0c76ae1312e8c9037d4f175af677c3e4be3 100644 --- a/lib/src/command/get.dart +++ b/lib/src/command/get.dart @@ -7,6 +7,7 @@ library pub.command.get; import 'dart:async'; import '../command.dart'; +import '../solver/version_solver.dart'; /// Handles the `get` pub command. class GetCommand extends PubCommand { @@ -25,6 +26,7 @@ class GetCommand extends PubCommand { } Future onRun() { - return entrypoint.acquireDependencies(dryRun: commandOptions['dry-run']); + return entrypoint.acquireDependencies(SolveType.GET, + dryRun: commandOptions['dry-run']); } } diff --git a/lib/src/command/global_deactivate.dart b/lib/src/command/global_deactivate.dart index 047067aae30bbad80fc61125b39a332496d84da4..6d03f55519f436c42dcfa3a3d9e86280045a6abf 100644 --- a/lib/src/command/global_deactivate.dart +++ b/lib/src/command/global_deactivate.dart @@ -8,7 +8,6 @@ import 'dart:async'; import '../command.dart'; import '../utils.dart'; -import '../version.dart'; /// Handles the `global deactivate` pub command. class GlobalDeactivateCommand extends PubCommand { @@ -31,5 +30,6 @@ class GlobalDeactivateCommand extends PubCommand { } globals.deactivate(commandOptions.rest.first); + return null; } } diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart index e8b6ad0bbba9a579a8a4544ed25cd73e6bc11468..c42c33b385b4df610dad2dd9fd01999158681274 100644 --- a/lib/src/command/upgrade.dart +++ b/lib/src/command/upgrade.dart @@ -8,6 +8,7 @@ import 'dart:async'; import '../command.dart'; import '../log.dart' as log; +import '../solver/version_solver.dart'; /// Handles the `upgrade` pub command. class UpgradeCommand extends PubCommand { @@ -30,8 +31,8 @@ class UpgradeCommand extends PubCommand { Future onRun() { var dryRun = commandOptions['dry-run']; - return entrypoint.acquireDependencies(useLatest: commandOptions.rest, - isUpgrade: true, dryRun: dryRun).then((_) { + return entrypoint.acquireDependencies(SolveType.UPGRADE, + useLatest: commandOptions.rest, dryRun: dryRun).then((_) { if (isOffline) { log.warning("Warning: Upgrading when offline may not update you to the " "latest versions of your dependencies."); diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index c866d60eab116e4c1180de96b1750a7bb27c732b..67e2ebbacec8348f8c780c7319cc10aa28a9fa2a 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -79,6 +79,8 @@ class Entrypoint { /// Gets all dependencies of the [root] package. /// + /// Performs version resolution according to [SolveType]. + /// /// [useLatest], if provided, defines a list of packages that will be /// unlocked and forced to their latest versions. If [upgradeAll] is /// true, the previous lockfile is ignored and all packages are re-resolved @@ -86,21 +88,21 @@ class Entrypoint { /// previously locked packages. /// /// Shows a report of the changes made relative to the previous lockfile. If - /// [isUpgrade] is `true`, all transitive dependencies are shown in the - /// report. Otherwise, only dependencies that were changed are shown. If + /// this is an upgrade or downgrade, all transitive dependencies are shown in + /// the report. Otherwise, only dependencies that were changed are shown. If /// [dryRun] is `true`, no physical changes are made. - Future acquireDependencies({List<String> useLatest, bool isUpgrade: false, + Future acquireDependencies(SolveType type, {List<String> useLatest, bool dryRun: false}) { return syncFuture(() { - return resolveVersions(cache.sources, root, lockFile: lockFile, - useLatest: useLatest, upgradeAll: isUpgrade && useLatest.isEmpty); + return resolveVersions(type, cache.sources, root, lockFile: lockFile, + useLatest: useLatest); }).then((result) { if (!result.succeeded) throw result.error; - result.showReport(isUpgrade: isUpgrade); + result.showReport(type); if (dryRun) { - result.summarizeChanges(isUpgrade: isUpgrade, dryRun: dryRun); + result.summarizeChanges(type, dryRun: dryRun); return null; } @@ -110,7 +112,7 @@ class Entrypoint { _saveLockFile(ids); _linkSelf(); _linkSecondaryPackageDirs(); - result.summarizeChanges(isUpgrade: isUpgrade, dryRun: dryRun); + result.summarizeChanges(type, dryRun: dryRun); }); }); } @@ -211,7 +213,7 @@ class Entrypoint { }); }).then((upToDate) { if (upToDate) return null; - return acquireDependencies(); + return acquireDependencies(SolveType.GET); }); } diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index 37981460b5746fce2b96e7babb6e0e9470427341..b9bb54881fc29e904daf9b3afdc69add61c7c97e 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart @@ -5,7 +5,6 @@ library pub.global_packages; import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; @@ -78,10 +77,11 @@ class GlobalPackages { }).then((p) { package = p; // Resolve it and download its dependencies. - return resolveVersions(cache.sources, package, lockFile: lockFile); + return resolveVersions(SolveType.GET, cache.sources, package, + lockFile: lockFile); }).then((result) { if (!result.succeeded) throw result.error; - result.showReport(); + result.showReport(SolveType.GET); // Make sure all of the dependencies are locally installed. return Future.wait(result.packages.map((id) { diff --git a/lib/src/solver/backtracking_solver.dart b/lib/src/solver/backtracking_solver.dart index 4c6033369bacd5a8e80cb974ac49e79548dede22..d8b57614b5ca2325509445d049feb558c2e2295b 100644 --- a/lib/src/solver/backtracking_solver.dart +++ b/lib/src/solver/backtracking_solver.dart @@ -59,6 +59,7 @@ import 'version_solver.dart'; /// versions for speculative package selections. Backtracks and advances to the /// next potential solution in the case of a failure. class BacktrackingSolver { + final SolveType type; final SourceRegistry sources; final Package root; @@ -73,9 +74,6 @@ class BacktrackingSolver { /// packages. final _forceLatest = new Set<String>(); - /// If this is set, the contents of [lockFile] are ignored while solving. - final bool _upgradeAll; - /// The set of packages whose dependecy is being overridden by the root /// package, keyed by the name of the package. /// @@ -109,11 +107,11 @@ class BacktrackingSolver { int get attemptedSolutions => _attemptedSolutions; var _attemptedSolutions = 1; - BacktrackingSolver(SourceRegistry sources, this.root, this.lockFile, - List<String> useLatest, {bool upgradeAll: false}) - : sources = sources, - cache = new PubspecCache(sources), - _upgradeAll = upgradeAll { + BacktrackingSolver(SolveType type, SourceRegistry sources, this.root, + this.lockFile, List<String> useLatest) + : type = type, + sources = sources, + cache = new PubspecCache(type, sources) { for (var package in useLatest) { _forceLatest.add(package); } @@ -218,9 +216,19 @@ class BacktrackingSolver { /// /// Returns `null` if it isn't in the lockfile (or has been unlocked). PackageId getLocked(String package) { - if (_upgradeAll) return null; - if (_forceLatest.contains(package)) return null; + if (type == SolveType.GET) 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 + // can't be downgraded. + if (type == SolveType.DOWNGRADE) { + var locked = lockFile.packages[package]; + if (locked != null && !sources[locked.source].hasMultipleVersions) { + return locked; + } + } + if (_forceLatest.isEmpty || _forceLatest.contains(package)) return null; return lockFile.packages[package]; } diff --git a/lib/src/solver/solve_report.dart b/lib/src/solver/solve_report.dart index 4012bdcd74e8d960c4a11853a68864be015938ab..7fed60a7ea60453232abd95ae06997f136837b53 100644 --- a/lib/src/solver/solve_report.dart +++ b/lib/src/solver/solve_report.dart @@ -18,9 +18,7 @@ import 'version_solver.dart'; /// /// It's a report builder. class SolveReport { - /// Whether all dependencies should be reported, or just ones that changed. - final bool _showAll; - + final SolveType _type; final SourceRegistry _sources; final Package _root; final LockFile _previousLockFile; @@ -31,9 +29,8 @@ class SolveReport { final _output = new StringBuffer(); - SolveReport(this._sources, this._root, this._previousLockFile, - this._result, {bool showAll: false}) - : _showAll = showAll { + SolveReport(this._type, this._sources, this._root, this._previousLockFile, + this._result) { // Fill the map so we can use it later. for (var id in _result.packages) { _dependencies[id.name] = id; @@ -80,10 +77,10 @@ class SolveReport { } } else { if (numChanged == 0) { - if (_showAll) { - log.message("No dependencies changed."); - } else { + if (_type == SolveType.GET) { log.message("Got dependencies!"); + } else { + log.message("No dependencies changed."); } } else if (numChanged == 1) { log.message("Changed $numChanged dependency!"); @@ -137,8 +134,8 @@ class SolveReport { /// Reports the results of the upgrade on the package named [name]. /// /// If [alwaysShow] is true, the package is reported even if it didn't change, - /// regardless of [_showAll]. If [highlightOverride] is true (or absent), - /// writes "(override)" next to overridden packages. + /// regardless of [_type]. If [highlightOverride] is true (or absent), writes + /// "(override)" next to overridden packages. void _reportPackage(String name, {bool alwaysShow: false, bool highlightOverride: true}) { var newId = _dependencies[name]; @@ -186,7 +183,9 @@ class SolveReport { icon = " "; } - if (!(alwaysShow || changed || addedOrRemoved || _showAll)) return; + if (_type == SolveType.GET && !(alwaysShow || changed || addedOrRemoved)) { + return; + } _output.write(icon); _output.write(log.bold(id.name)); @@ -207,8 +206,9 @@ class SolveReport { // See if there are any newer versions of the package that we were // unable to upgrade to. - if (newId != null) { + if (newId != null && _type != SolveType.DOWNGRADE) { var versions = _result.availableVersions[newId.name]; + var newerStable = false; var newerUnstable = false; diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart index 9e0939c956e49dbd376f86ee391557c257370c5a..ec276ea22d0911d0c8185405897348840e056534 100644 --- a/lib/src/solver/version_solver.dart +++ b/lib/src/solver/version_solver.dart @@ -30,14 +30,14 @@ import 'solve_report.dart'; /// packages. /// /// If [upgradeAll] is true, the contents of [lockFile] are ignored. -Future<SolveResult> resolveVersions(SourceRegistry sources, Package root, - {LockFile lockFile, List<String> useLatest, bool upgradeAll: false}) { +Future<SolveResult> resolveVersions(SolveType type, SourceRegistry sources, + Package root, {LockFile lockFile, List<String> useLatest}) { if (lockFile == null) lockFile = new LockFile.empty(); if (useLatest == null) useLatest = []; return log.progress('Resolving dependencies', () { - return new BacktrackingSolver(sources, root, lockFile, useLatest, - upgradeAll: upgradeAll).solve(); + return new BacktrackingSolver(type, sources, root, lockFile, useLatest) + .solve(); }); } @@ -87,21 +87,18 @@ class SolveResult { /// Displays a report of what changes were made to the lockfile. /// - /// If [isUpgrade] is true, a "pub upgrade" was run, otherwise it was another - /// command. - void showReport({bool isUpgrade: false}) { - new SolveReport(_sources, _root, _previousLockFile, this, - showAll: isUpgrade).show(); + /// [type] is the type of version resolution that was run. + void showReport(SolveType type) { + new SolveReport(type, _sources, _root, _previousLockFile, this).show(); } /// Displays a one-line message summarizing what changes were made (or would /// be made) to the lockfile. /// - /// If [isUpgrade] is true, a "pub upgrade" was run, otherwise it was another - /// command. - void summarizeChanges({bool isUpgrade: false, bool dryRun: false}) { - new SolveReport(_sources, _root, _previousLockFile, this, - showAll: isUpgrade).summarize(dryRun: dryRun); + /// [type] is the type of version resolution that was run. + void summarizeChanges(SolveType type, {bool dryRun: false}) { + new SolveReport(type, _sources, _root, _previousLockFile, this) + .summarize(dryRun: dryRun); } String toString() { @@ -130,6 +127,9 @@ class PubspecCache { /// The already-requested cached pubspecs. final _pubspecs = new Map<PackageId, Pubspec>(); + /// The type of version resolution that was run. + final SolveType _type; + /// The number of times a version list was requested and it wasn't cached and /// had to be requested from the source. int _versionCacheMisses = 0; @@ -146,7 +146,7 @@ class PubspecCache { /// returned. int _pubspecCacheHits = 0; - PubspecCache(this._sources); + PubspecCache(this._type, this._sources); /// Caches [pubspec] as the [Pubspec] for the package identified by [id]. void cache(PackageId id, Pubspec pubspec) { @@ -205,7 +205,8 @@ class PubspecCache { return source.getVersions(package.name, package.description) .then((versions) { // Sort by priority so we try preferred versions first. - versions.sort(Version.prioritize); + versions.sort(_type == SolveType.DOWNGRADE ? Version.antiPrioritize : + Version.prioritize); var ids = versions.reversed.map( (version) => package.atVersion(version)).toList(); @@ -286,6 +287,27 @@ class Dependency { String toString() => '$depender $dependerVersion -> $dep'; } +/// An enum for types of version resolution. +class SolveType { + /// As few changes to the lockfile as possible to be consistent with the + /// pubspec. + static const GET = const SolveType._("get"); + + /// Upgrade all packages or specific packages to the highest versions + /// possible, regardless of the lockfile. + static const UPGRADE = const SolveType._("upgrade"); + + /// Downgrade all packages or specific packages to the lowest versions + /// possible, regardless of the lockfile. + static const DOWNGRADE = const SolveType._("downgrade"); + + final String _name; + + const SolveType._(this._name); + + String toString() => _name; +} + /// Base class for all failures that can occur while trying to resolve versions. abstract class SolveFailure implements ApplicationException { /// The name of the package whose version could not be solved. diff --git a/lib/src/source.dart b/lib/src/source.dart index 0235792e09bae5f34aae4f07eccea0e4bd17199d..4152384e1589d4fa67999c1ce7c4191afb9f374e 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -27,6 +27,12 @@ abstract class Source { /// all sources. String get name; + /// Whether this source can choose between multiple versions of the same + /// package during version solving. + /// + /// Defaults to `false`. + final bool hasMultipleVersions = false; + /// Whether or not this source is the default source. bool get isDefault => systemCache.sources.defaultSource == this; diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart index ec21d2080899105b026cca63c29459651116a558..5dcba33efcffd135d2d7b46745fee0a8ff704039 100644 --- a/lib/src/source/hosted.dart +++ b/lib/src/source/hosted.dart @@ -25,6 +25,7 @@ import 'cached.dart'; /// the same API as pub.dartlang.org. class HostedSource extends CachedSource { final name = "hosted"; + final hasMultipleVersions = true; /// Gets the default URL for the package server for hosted dependencies. static String get defaultUrl { diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 3c2e1aa31f581d2b024527bd525a7032f1b23947..4e1a369bfd1335643b152271cc3aa22363abcf8a 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -368,6 +368,15 @@ maxAll(Iterable iter, [int compare(element1, element2)]) { compare(element, max) > 0 ? element : max); } +/// Returns the minimum value in [iter] by [compare]. +/// +/// [compare] defaults to [Comparable.compare]. +minAll(Iterable iter, [int compare(element1, element2)]) { + if (compare == null) compare = Comparable.compare; + return iter.reduce((max, element) => + compare(element, max) < 0 ? element : max); +} + /// Replace each instance of [matcher] in [source] with the return value of /// [fn]. String replace(String source, Pattern matcher, String fn(Match)) { diff --git a/lib/src/version.dart b/lib/src/version.dart index 7e33fe2de8e0909e22e88bcdf70c7ad9b4444888..50cfb0c57123c22b22bbf0f65164932f6e1d86f9 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -54,6 +54,18 @@ class Version implements Comparable<Version>, VersionConstraint { return a.compareTo(b); } + /// Like [proiritize], but lower version numbers are considered greater than + /// higher version numbers. + /// + /// This still considers prerelease versions to be lower than non-prerelease + /// versions. + static int antiPrioritize(Version a, Version b) { + if (a.isPreRelease && !b.isPreRelease) return -1; + if (!a.isPreRelease && b.isPreRelease) return 1; + + return b.compareTo(a); + } + /// The major version number: "1" in "1.2.3". final int major; diff --git a/test/downgrade/does_not_show_other_versions_test.dart b/test/downgrade/does_not_show_other_versions_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..e62bff04a455ff56038eda0b140c6b94af2ffa8f --- /dev/null +++ b/test/downgrade/does_not_show_other_versions_test.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2013, 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. + +library pub_tests; + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +main() { + initConfig(); + integration("does not show how many other versions are available", () { + servePackages([ + packageMap("downgraded", "1.0.0"), + packageMap("downgraded", "2.0.0"), + packageMap("downgraded", "3.0.0-dev") + ]); + + d.appDir({ + "downgraded": "3.0.0-dev" + }).create(); + + pubGet(); + + // Loosen the constraints. + d.appDir({ + "downgraded": ">=2.0.0" + }).create(); + + pubDowngrade(output: contains("downgraded 2.0.0 (was 3.0.0-dev)")); + }); +} diff --git a/test/downgrade/doesnt_change_git_dependencies_test.dart b/test/downgrade/doesnt_change_git_dependencies_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..b661c7dc4c6bef28939fcccf9c4f2a19fbcfa270 --- /dev/null +++ b/test/downgrade/doesnt_change_git_dependencies_test.dart @@ -0,0 +1,47 @@ +// Copyright (c) 2014, 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:path/path.dart' as path; +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../lib/src/io.dart'; +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +main() { + initConfig(); + integration("doesn't change git dependencies", () { + ensureGit(); + + d.git('foo.git', [ + d.libDir('foo'), + d.libPubspec('foo', '1.0.0') + ]).create(); + + d.appDir({ + "foo": {"git": "../foo.git"} + }).create(); + + pubGet(); + + d.dir(packagesPath, [ + d.dir('foo', [ + d.file('foo.dart', 'main() => "foo";') + ]) + ]).validate(); + + d.git('foo.git', [ + d.libDir('foo', 'foo 2'), + d.libPubspec('foo', '1.0.0') + ]).commit(); + + pubDowngrade(); + + d.dir(packagesPath, [ + d.dir('foo', [ + d.file('foo.dart', 'main() => "foo";') + ]) + ]).validate(); + }); +} diff --git a/test/downgrade/dry_run_does_not_apply_changes_test.dart b/test/downgrade/dry_run_does_not_apply_changes_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..4bae76351e163fe99c9e88488edd27c15f90d7e7 --- /dev/null +++ b/test/downgrade/dry_run_does_not_apply_changes_test.dart @@ -0,0 +1,50 @@ +// Copyright (c) 2014, 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:path/path.dart' as path; +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../lib/src/io.dart'; +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +main() { + initConfig(); + integration("--dry-run shows report but does not apply changes", () { + servePackages([ + packageMap("foo", "1.0.0"), + packageMap("foo", "2.0.0"), + ]); + + // Create the first lockfile. + d.appDir({ + "foo": "2.0.0" + }).create(); + + pubGet(); + + // Change the pubspec. + d.appDir({ + "foo": "any" + }).create(); + + // Also delete the "packages" directory. + schedule(() { + deleteEntry(path.join(sandboxDir, appPath, "packages")); + }); + + // Do the dry run. + pubDowngrade(args: ["--dry-run"], output: allOf([ + contains("< foo 1.0.0"), + contains("Would change 1 dependency.") + ])); + + d.dir(appPath, [ + // The lockfile should be unmodified. + d.matcherFile("pubspec.lock", contains("2.0.0")), + // The "packages" directory should not have been regenerated. + d.nothing("packages") + ]).validate(); + }); +} diff --git a/test/downgrade/unlock_dependers_test.dart b/test/downgrade/unlock_dependers_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..93a376c0416817c2c228b05419f203101128273e --- /dev/null +++ b/test/downgrade/unlock_dependers_test.dart @@ -0,0 +1,40 @@ +// 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. + +library pub_tests; + +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +main() { + initConfig(); + integration("downgrades a locked package's dependers in order to get it to " + "min version", () { + servePackages([ + packageMap("foo", "2.0.0", {"bar": ">1.0.0"}), + packageMap("bar", "2.0.0") + ]); + + d.appDir({"foo": "any", "bar": "any"}).create(); + + pubGet(); + + d.packagesDir({ + "foo": "2.0.0", + "bar": "2.0.0" + }).validate(); + + servePackages([ + packageMap("foo", "1.0.0", {"bar": "any"}), + packageMap("bar", "1.0.0") + ]); + + pubDowngrade(args: ['bar']); + + d.packagesDir({ + "foo": "1.0.0", + "bar": "1.0.0" + }).validate(); + }); +} diff --git a/test/downgrade/unlock_if_necessary_test.dart b/test/downgrade/unlock_if_necessary_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..48c7bd86fe8176cbf83ff20ee7aa29fe029eada5 --- /dev/null +++ b/test/downgrade/unlock_if_necessary_test.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2014, 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. + +library pub_tests; + +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +main() { + initConfig(); + integration("downgrades one locked hosted package's dependencies if it's " + "necessary", () { + servePackages([ + packageMap("foo", "2.0.0", {"foo_dep": "any"}), + packageMap("foo_dep", "2.0.0") + ]); + + d.appDir({"foo": "any"}).create(); + + pubGet(); + + d.packagesDir({ + "foo": "2.0.0", + "foo_dep": "2.0.0" + }).validate(); + + servePackages([ + packageMap("foo", "1.0.0", {"foo_dep": "<2.0.0"}), + packageMap("foo_dep", "1.0.0") + ]); + + pubDowngrade(args: ['foo']); + + d.packagesDir({ + "foo": "1.0.0", + "foo_dep": "1.0.0" + }).validate(); + }); +} diff --git a/test/pub_test.dart b/test/pub_test.dart index ae6ad21e97ee46d3de765d7b4c8624475232f99c..525cd0fd0ecea58aac8262176e5805bbb286e642 100644 --- a/test/pub_test.dart +++ b/test/pub_test.dart @@ -28,18 +28,19 @@ final USAGE_STRING = """ -v, --verbose Shortcut for "--verbosity=all". Available commands: - build Apply transformers to build a package. - cache Work with the system cache. - deps Print package dependencies. - get Get the current package's dependencies. - global Work with global packages. - help Display help information for Pub. - publish Publish the current package to pub.dartlang.org. - run Run an executable from a package. - serve Run a local web development server. - upgrade Upgrade the current package's dependencies to latest versions. - uploader Manage uploaders for a package on pub.dartlang.org. - version Print pub version. + build Apply transformers to build a package. + cache Work with the system cache. + deps Print package dependencies. + downgrade Downgrade the current package's dependencies to oldest versions. + get Get the current package's dependencies. + global Work with global packages. + help Display help information for Pub. + publish Publish the current package to pub.dartlang.org. + run Run an executable from a package. + serve Run a local web development server. + upgrade Upgrade the current package's dependencies to latest versions. + uploader Manage uploaders for a package on pub.dartlang.org. + version Print pub version. Run "pub help [command]" for more information about a command. See http://dartlang.org/tools/pub for detailed documentation. @@ -127,18 +128,19 @@ main() { Could not find a command named "quylthulg". Available commands: - build Apply transformers to build a package. - cache Work with the system cache. - deps Print package dependencies. - get Get the current package's dependencies. - global Work with global packages. - help Display help information for Pub. - publish Publish the current package to pub.dartlang.org. - run Run an executable from a package. - serve Run a local web development server. - upgrade Upgrade the current package's dependencies to latest versions. - uploader Manage uploaders for a package on pub.dartlang.org. - version Print pub version. + build Apply transformers to build a package. + cache Work with the system cache. + deps Print package dependencies. + downgrade Downgrade the current package's dependencies to oldest versions. + get Get the current package's dependencies. + global Work with global packages. + help Display help information for Pub. + publish Publish the current package to pub.dartlang.org. + run Run an executable from a package. + serve Run a local web development server. + upgrade Upgrade the current package's dependencies to latest versions. + uploader Manage uploaders for a package on pub.dartlang.org. + version Print pub version. ''', exitCode: exit_codes.USAGE); }); @@ -297,18 +299,19 @@ main() { Could not find a command named "quylthulg". Available commands: - build Apply transformers to build a package. - cache Work with the system cache. - deps Print package dependencies. - get Get the current package's dependencies. - global Work with global packages. - help Display help information for Pub. - publish Publish the current package to pub.dartlang.org. - run Run an executable from a package. - serve Run a local web development server. - upgrade Upgrade the current package's dependencies to latest versions. - uploader Manage uploaders for a package on pub.dartlang.org. - version Print pub version. + build Apply transformers to build a package. + cache Work with the system cache. + deps Print package dependencies. + downgrade Downgrade the current package's dependencies to oldest versions. + get Get the current package's dependencies. + global Work with global packages. + help Display help information for Pub. + publish Publish the current package to pub.dartlang.org. + run Run an executable from a package. + serve Run a local web development server. + upgrade Upgrade the current package's dependencies to latest versions. + uploader Manage uploaders for a package on pub.dartlang.org. + version Print pub version. ''', exitCode: exit_codes.USAGE); }); diff --git a/test/test_pub.dart b/test/test_pub.dart index 0650075cd3ec9ec029f4c0e7630cc8a75bcd8ef1..1bbf30716558b80ec23a70b3ae1d5ec1a4d758f2 100644 --- a/test/test_pub.dart +++ b/test/test_pub.dart @@ -329,6 +329,8 @@ class RunCommand { r'Got dependencies!|Changed \d+ dependenc(y|ies)!')); static final upgrade = new RunCommand('upgrade', new RegExp( r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$')); + static final downgrade = new RunCommand('downgrade', new RegExp( + r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$')); final String name; final RegExp success; @@ -388,6 +390,12 @@ void pubUpgrade({Iterable<String> args, output, error, warning, int exitCode}) { warning: warning, exitCode: exitCode); } +void pubDowngrade({Iterable<String> args, output, error, warning, + int exitCode}) { + pubCommand(RunCommand.downgrade, args: args, output: output, error: error, + warning: warning, exitCode: exitCode); +} + /// Schedules starting the "pub [global] run" process and validates the /// expected startup output. /// diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart index b5302cb7e498c7d0b9642f53c65b9274ee5450e8..000ea2bcef5095b233ef5a52cb8d56b366892682 100644 --- a/test/version_solver_test.dart +++ b/test/version_solver_test.dart @@ -43,6 +43,7 @@ main() { group('SDK constraint', sdkConstraint); group('pre-release', prerelease); group('override', override); + group('downgrade', downgrade); } void basicGraph() { @@ -1057,25 +1058,58 @@ void override() { }); } +void downgrade() { + testResolve("downgrades a dependency to the lowest matching version", { + 'myapp 0.0.0': { + 'foo': '>=2.0.0 <3.0.0' + }, + 'foo 1.0.0': {}, + 'foo 2.0.0-dev': {}, + 'foo 2.0.0': {}, + 'foo 2.1.0': {} + }, lockfile: { + 'foo': '2.1.0' + }, result: { + 'myapp from root': '0.0.0', + 'foo': '2.0.0' + }, downgrade: true); + + testResolve('use earliest allowed prerelease if no stable versions match ' + 'while downgrading', { + 'myapp 0.0.0': { + 'a': '>=2.0.0-dev.1 <3.0.0' + }, + 'a 1.0.0': {}, + 'a 2.0.0-dev.1': {}, + 'a 2.0.0-dev.2': {}, + 'a 2.0.0-dev.3': {} + }, result: { + 'myapp from root': '0.0.0', + 'a': '2.0.0-dev.1' + }, downgrade: true); +} + testResolve(String description, Map packages, { Map lockfile, Map overrides, Map result, FailMatcherBuilder error, - int maxTries}) { + int maxTries, bool downgrade: false}) { _testResolve(test, description, packages, lockfile: lockfile, - overrides: overrides, result: result, error: error, maxTries: maxTries); + overrides: overrides, result: result, error: error, maxTries: maxTries, + downgrade: downgrade); } solo_testResolve(String description, Map packages, { Map lockfile, Map overrides, Map result, FailMatcherBuilder error, - int maxTries}) { + int maxTries, bool downgrade: false}) { log.verbosity = log.Verbosity.SOLVER; _testResolve(solo_test, description, packages, lockfile: lockfile, - overrides: overrides, result: result, error: error, maxTries: maxTries); + overrides: overrides, result: result, error: error, maxTries: maxTries, + downgrade: downgrade); } _testResolve(void testFn(String description, Function body), String description, Map packages, { Map lockfile, Map overrides, Map result, FailMatcherBuilder error, - int maxTries}) { + int maxTries, bool downgrade: false}) { if (maxTries == null) maxTries = 1; testFn(description, () { @@ -1124,7 +1158,9 @@ _testResolve(void testFn(String description, Function body), } // Resolve the versions. - var future = resolveVersions(cache.sources, root, lockFile: realLockFile); + var future = resolveVersions( + downgrade ? SolveType.DOWNGRADE : SolveType.GET, + cache.sources, root, lockFile: realLockFile); var matcher; if (result != null) { @@ -1327,6 +1363,7 @@ class MockSource extends CachedSource { final _requestedPubspecs = new Map<String, Set<Version>>(); final String name; + final hasMultipleVersions = true; MockSource(this.name);