Index: sdk/lib/_internal/pub/lib/src/solver/solve_report.dart |
diff --git a/sdk/lib/_internal/pub/lib/src/solver/solve_report.dart b/sdk/lib/_internal/pub/lib/src/solver/solve_report.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..086e7aeaf3366e3d7d1a690c27ee738b4ff56ae2 |
--- /dev/null |
+++ b/sdk/lib/_internal/pub/lib/src/solver/solve_report.dart |
@@ -0,0 +1,262 @@ |
+// 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.solver.solve_report; |
+ |
+import '../lock_file.dart'; |
+import '../log.dart' as log; |
+import '../package.dart'; |
+import '../source_registry.dart'; |
+import '../utils.dart'; |
+import 'version_solver.dart'; |
+ |
+int show(SourceRegistry sources, Package root, LockFile previousLockFile, |
nweiz
2013/12/12 21:20:10
Add a doc comment (maybe just combine the existing
|
+ SolveResult result, {bool showAll: false}) { |
+ var report = new _SolveReport(sources, root, previousLockFile, result); |
+ return report.show(showAll: showAll); |
+} |
+ |
+/// Generates and displays nicely formatted reports for the results of running |
+/// a version resolution. |
+/// |
+/// Unlike [SolveResult] which is the static data describing a resolution, this |
+/// class contains the mutable state used while generating the report itself. |
+/// It's a report builder. |
+class _SolveReport { |
+ final SourceRegistry _sources; |
+ final Package _root; |
+ final LockFile _previousLockFile; |
+ final SolveResult _result; |
+ |
+ /// The dependencies in [_result], keyed by package name. |
+ final _dependencies = new Map<String, PackageId>(); |
+ |
+ final _output = new StringBuffer(); |
+ |
+ /// To avoid emitting trailing newlines, we track if any are needed and only |
+ /// emit then when text on the next line is about to be written. |
+ // TODO(rnystrom): Move this into a separate class that wraps any StringSink |
+ // with this logic. |
+ int _pendingLines = 0; |
+ |
+ _SolveReport(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; |
+ } |
+ } |
+ |
+ /// Displays a report of the results of the version resolution relative to |
+ /// the previous lock file. |
+ /// |
+ /// If [showAll] is true, then all of the previous and current dependencies |
+ /// are shown and their changes relative to the previous lock file are |
+ /// highlighted. Otherwise, only overrides are shown. |
+ /// |
+ /// Returns the number of changed dependencies. |
+ int show({bool showAll: false}) { |
+ if (showAll) _reportChanges(); |
+ _reportOverrides(); |
+ |
+ // Count how many dependencies actually changed. |
+ var dependencies = _dependencies.keys.toSet(); |
+ dependencies.addAll(_previousLockFile.packages.keys); |
+ dependencies.remove(_root.name); |
+ |
+ return dependencies.where((name) { |
+ var oldId = _previousLockFile.packages[name]; |
+ var newId = _dependencies[name]; |
+ |
+ // Added or removed dependencies count. |
+ if (oldId == null) return true; |
+ if (newId == null) return true; |
+ |
+ // The dependency existed before, so see if it was modified. |
+ return !_descriptionsEqual(oldId, newId) || |
+ oldId.version != newId.version; |
+ }).length; |
+ } |
+ |
+ /// Displays a report of all of the previous and current dependencies and |
+ /// how they have changed. |
+ void _reportChanges() { |
+ _output.clear(); |
+ |
+ // Show the new set of dependencies ordered by name. |
+ var names = _result.packages.map((id) => id.name).toList(); |
+ names.remove(_root.name); |
+ names.sort(); |
+ names.forEach(_reportPackage); |
+ |
+ // Show any removed ones. |
+ var removed = _previousLockFile.packages.keys.toSet(); |
+ removed.removeAll(names); |
+ if (removed.isNotEmpty) { |
+ _writeln("These packages are no longer being depended on:"); |
+ removed = removed.toList(); |
+ removed.sort(); |
+ removed.forEach(_reportPackage); |
+ } |
+ |
+ log.message(_output.toString()); |
+ } |
+ |
+ /// Displays a warning about the overrides currently in effect. |
+ void _reportOverrides() { |
+ _output.clear(); |
+ |
+ if (_result.overrides.isNotEmpty) { |
+ _writeln("Warning: You are using these overridden dependencies:"); |
+ var overrides = _result.overrides.map((dep) => dep.name).toList(); |
+ overrides.sort((a, b) => a.compareTo(b)); |
+ |
+ overrides.forEach( |
+ (name) => _reportPackage(name, highlightOverride: false)); |
+ |
+ log.warning(_output.toString()); |
+ } |
+ } |
+ |
+ /// Reports the results of the upgrade on the package named [name]. |
+ /// |
+ /// If [highlightOverride] is true (or absent), writes "(override)" next to |
+ /// overridden packages. |
+ void _reportPackage(String name, {bool highlightOverride}) { |
+ if (highlightOverride == null) highlightOverride = true; |
+ |
+ var newId = _dependencies[name]; |
+ var oldId = _previousLockFile.packages[name]; |
+ var id = newId != null ? newId : oldId; |
+ |
+ var isOverridden = _result.overrides.map( |
+ (dep) => dep.name).contains(id.name); |
+ |
+ var changed = false; |
+ |
+ // Show a one-character "icon" describing the change. They are: |
+ // |
+ // ! The package is being overridden. |
+ // - The package was removed. |
+ // + The package was added. |
+ // > The package was upgraded from a lower version. |
+ // < The package was downgraded from a higher version. |
+ // * Any other change between the old and new package. |
+ if (isOverridden) { |
+ _write(log.magenta("! ")); |
+ } else if (newId == null) { |
+ _write(log.red("- ")); |
+ } else if (oldId == null) { |
+ _write(log.green("+ ")); |
+ } else if (!_descriptionsEqual(oldId, newId)) { |
+ _write(log.cyan("* ")); |
+ changed = true; |
+ } else if (oldId.version < newId.version) { |
+ _write(log.green("> ")); |
+ changed = true; |
+ } else if (oldId.version > newId.version) { |
+ _write(log.cyan("< ")); |
+ changed = true; |
+ } else { |
+ // Unchanged. |
+ _write(" "); |
+ } |
+ |
+ _write(log.bold(id.name)); |
+ _write(" "); |
+ _writeId(id); |
+ |
+ // If the package was upgraded, show what it was upgraded from. |
+ if (changed) { |
+ _write(" (was "); |
+ _writeId(oldId); |
+ _write(")"); |
+ } |
+ |
+ // Highlight overridden packages. |
+ if (isOverridden && highlightOverride) { |
+ _write(" ${log.magenta('(overridden)')}"); |
+ } |
+ |
+ // See if there are any newer versions of the package that we were |
+ // unable to upgrade to. |
+ if (newId != null) { |
+ var versions = _result.availableVersions[newId.name]; |
+ var newerStable = 0; |
+ var newerUnstable = 0; |
+ |
+ for (var version in versions) { |
+ if (version > newId.version) { |
+ if (version.isPreRelease) { |
+ newerUnstable++; |
+ } else { |
+ newerStable++; |
+ } |
+ } |
+ } |
+ |
+ // If there are newer stable versions, only show those. |
+ var message; |
+ if (newerStable > 0) { |
+ message = "($newerStable newer " |
+ "${pluralize('version', newerStable)} available)"; |
+ } else if (newerUnstable > 0) { |
+ message = "($newerUnstable newer unstable " |
+ "${pluralize('version', newerUnstable)} available)"; |
+ } |
+ |
+ if (message != null) _write(" ${log.cyan(message)}"); |
+ } |
+ |
+ _writeln(); |
+ } |
+ |
+ /// Returns `true` if [a] and [b] are from the same source and have the same |
+ /// description. |
+ bool _descriptionsEqual(PackageId a, PackageId b) { |
+ if (a.source != b.source) return false; |
+ |
+ if (_sources.contains(a.source)) { |
+ var source = _sources[a.source]; |
+ return source.descriptionsEqual(a.description, b.description); |
+ } else { |
+ // Unknown source, so just do a literal comparison. |
+ return a.description == b.description; |
+ } |
+ } |
+ |
+ /// Writes a terse description of [id] (not including its name) to the output. |
+ void _writeId(PackageId id) { |
+ _write(id.version); |
+ |
+ var source = null; |
+ if (_sources.contains(id.source)) { |
+ source = _sources[id.source]; |
+ } |
+ |
+ if (source != null && source != _sources.defaultSource) { |
+ var description = source.formatDescription(_root.dir, id.description); |
+ _write(" from ${id.source} $description"); |
+ } |
+ } |
+ |
+ /// Writes [obj] to the output. |
+ void _write(Object obj) { |
+ while (_pendingLines > 0) { |
+ _output.writeln(); |
+ _pendingLines--; |
+ } |
+ _output.write(obj); |
+ } |
+ |
+ /// Writes [obj] (if not null) followed by a newline to the output. |
+ /// |
+ /// Doesn't actually immediately write a newline. Instead, it waits until |
+ /// output is written on the next line. That way, trailing newlines aren't |
+ /// displayed. |
+ void _writeln([Object obj]) { |
+ if (obj != null) _write(obj); |
+ _pendingLines++; |
+ } |
+} |