dart2js: switch to use dart2js_info/info.dart

R=het@google.com

Review URL: https://codereview.chromium.org//1298553002 .
This commit is contained in:
Sigmund Cherem 2015-08-18 15:44:38 -07:00
parent 07b6dad1f5
commit 8896ccd385
11 changed files with 10 additions and 1569 deletions

3
DEPS
View file

@ -49,6 +49,7 @@ vars = {
"collection_rev": "@1da9a07f32efa2ba0c391b289e2037391e31da0e",
"crypto_rev" : "@2df57a1e26dd88e8d0614207d4b062c73209917d",
"csslib_tag" : "@0.12.0",
"dart2js_info_rev" : "@5902be71c24bbda96675f646ba7d3ff911d26eae",
"dartdoc_tag" : "@v0.5.0",
"dart_services_rev" : "@7aea2574e6f3924bf409a80afb8ad52aa2be4f97",
"dart_style_tag": "@0.2.0",
@ -195,6 +196,8 @@ deps = {
(Var("github_mirror") % "csslib") + Var("csslib_tag"),
Var("dart_root") + "/third_party/pkg_tested/dart_style":
(Var("github_mirror") % "dart_style") + Var("dart_style_tag"),
Var("dart_root") + "/third_party/pkg/dart2js_info":
(Var("github_mirror") % "dart2js_info") + Var("dart2js_info_rev"),
Var("dart_root") + "/third_party/pkg/dartdoc":
(Var("github_mirror") % "dartdoc") + Var("dartdoc_tag"),
Var("dart_root") + "/third_party/pkg/dev_compiler":

View file

@ -7,6 +7,8 @@ library dump_info;
import 'dart:convert'
show HtmlEscape, JsonEncoder, StringConversionSink, ChunkedConversionSink;
import 'package:dart2js_info/info.dart';
import 'common/tasks.dart' show
CompilerTask;
import 'compiler.dart' show
@ -21,7 +23,6 @@ import 'types/types.dart' show
TypeMask;
import 'deferred_load.dart' show
OutputUnit;
import 'info/info.dart';
import 'js_backend/js_backend.dart' show
JavaScriptBackend;
import 'js_emitter/full_emitter/emitter.dart' as full show

View file

@ -1,745 +0,0 @@
// Copyright (c) 2015, 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.
/// Data collected by the dump-info task.
library compiler.src.lib.info;
// Note: this file intentionally doesn't import anything from the compiler. That
// should make it easier for tools to depend on this library. The idea is that
// by using this library, tools can consume the information in the same way it
// is produced by the compiler.
// TODO(sigmund): make this a proper public API (export this explicitly at the
// lib folder level.)
/// Common interface to many pieces of information generated by the compiler.
abstract class Info {
/// An identifier for the kind of information.
InfoKind get kind;
/// Name of the element associated with this info.
String name;
/// An id to uniquely identify this info among infos of the same [kind].
int get id;
/// A globally unique id combining [kind] and [id] together.
String get serializedId;
/// Id used by the compiler when instrumenting code for code coverage.
// TODO(sigmund): It would be nice if we could use the same id for
// serialization and for coverage. Could we unify them?
String coverageId;
/// Bytes used in the generated code for the corresponding element.
int size;
/// Info of the enclosing element.
Info parent;
/// Serializes the information into a JSON format.
// TODO(sigmund): refactor and put toJson outside the class, so we can have 2
// different serializer/deserializers at once.
Map toJson();
void accept(InfoVisitor visitor);
}
/// Common information used for most kind of elements.
// TODO(sigmund): add more:
// - inputSize: bytes used in the Dart source program
abstract class BasicInfo implements Info {
final InfoKind kind;
final int id;
String coverageId;
int size;
Info parent;
String get serializedId => '${_kindToString(kind)}/$id';
String name;
/// If using deferred libraries, where the element associated with this info
/// is generated.
OutputUnitInfo outputUnit;
BasicInfo(this.kind, this.id, this.name, this.outputUnit, this.size,
this.coverageId);
BasicInfo._fromId(String serializedId)
: kind = _kindFromSerializedId(serializedId),
id = _idFromSerializedId(serializedId);
Map toJson() {
var res = {
'id': serializedId,
'kind': _kindToString(kind),
'name': name,
'size': size,
};
// TODO(sigmund): Omit this also when outputUnit.id == 0 (most code is in
// the main output unit by default).
if (outputUnit != null) res['outputUnit'] = outputUnit.serializedId;
if (coverageId != null) res['coverageId'] = coverageId;
if (parent != null) res['parent'] = parent.serializedId;
return res;
}
String toString() => '$serializedId $name [$size]';
}
/// Info associated with elements containing executable code (like fields and
/// methods)
abstract class CodeInfo implements Info {
/// How does this function or field depend on others.
final List<DependencyInfo> uses = <DependencyInfo>[];
}
/// The entire information produced while compiling a program.
class AllInfo {
/// Summary information about the program.
ProgramInfo program;
/// Information about each library processed by the compiler.
List<LibraryInfo> libraries = <LibraryInfo>[];
/// Information about each function (includes methods and getters in any
/// library)
List<FunctionInfo> functions = <FunctionInfo>[];
/// Information about type defs in the program.
List<TypedefInfo> typedefs = <TypedefInfo>[];
/// Information about each class (in any library).
List<ClassInfo> classes = <ClassInfo>[];
/// Information about fields (in any class).
List<FieldInfo> fields = <FieldInfo>[];
/// Information about output units (should be just one entry if not using
/// deferred loading).
List<OutputUnitInfo> outputUnits = <OutputUnitInfo>[];
/// Details about all deferred imports and what files would be loaded when the
/// import is resolved.
// TODO(sigmund): use a different format for dump-info. This currently emits
// the same map that is created for the `--deferred-map` flag.
Map<String, Map<String, dynamic>> deferredFiles;
/// A new representation of dependencies form one info to another. An entry in
/// this map indicates that an Info depends on another (e.g. a function
/// invokes another). Please note that the data in this field might not be
/// accurate yet (this is work in progress).
Map<Info, List<Info>> dependencies = {};
/// Major version indicating breaking changes in the format. A new version
/// means that an old deserialization algorithm will not work with the new
/// format.
final int version = 3;
/// Minor version indicating non-breaking changes in the format. A change in
/// this version number means that the json parsing in this library from a
/// previous will continue to work after the change. This is typically
/// increased when adding new entries to the file format.
// Note: the dump-info.viewer app was written using a json parser version 3.2.
final int minorVersion = 5;
AllInfo();
static AllInfo parseFromJson(Map map) => new _ParseHelper().parseAll(map);
Map _listAsJsonMap(List<Info> list) {
var map = <String, Map>{};
for (var info in list) {
map['${info.id}'] = info.toJson();
}
return map;
}
Map _extractHoldingInfo() {
var map = <String, List>{};
void helper(CodeInfo info) {
if (info.uses.isEmpty) return;
map[info.serializedId] = info.uses.map((u) => u.toJson()).toList();
}
functions.forEach(helper);
fields.forEach(helper);
return map;
}
Map _extractDependencies() {
var map = <String, List>{};
dependencies.forEach((k, v) {
map[k.serializedId] = v.map((i) => i.serializedId).toList();
});
return map;
}
Map toJson() => {
'elements': {
'library': _listAsJsonMap(libraries),
'class': _listAsJsonMap(classes),
'function': _listAsJsonMap(functions),
'typedef': _listAsJsonMap(typedefs),
'field': _listAsJsonMap(fields),
},
'holding': _extractHoldingInfo(),
'dependencies': _extractDependencies(),
'outputUnits': outputUnits.map((u) => u.toJson()).toList(),
'dump_version': version,
'deferredFiles': deferredFiles,
'dump_minor_version': '$minorVersion',
// TODO(sigmund): change viewer to accept an int?
'program': program.toJson(),
};
void accept(InfoVisitor visitor) => visitor.visitAll(this);
}
class ProgramInfo {
int size;
String dart2jsVersion;
DateTime compilationMoment;
Duration compilationDuration;
// TODO(sigmund): use Duration.
int toJsonDuration;
int dumpInfoDuration;
bool noSuchMethodEnabled;
bool minified;
ProgramInfo(
{this.size,
this.dart2jsVersion,
this.compilationMoment,
this.compilationDuration,
this.toJsonDuration,
this.dumpInfoDuration,
this.noSuchMethodEnabled,
this.minified});
Map toJson() => {
'size': size,
'dart2jsVersion': dart2jsVersion,
'compilationMoment': '$compilationMoment',
'compilationDuration': '${compilationDuration}',
'toJsonDuration': toJsonDuration,
'dumpInfoDuration': '$dumpInfoDuration',
'noSuchMethodEnabled': noSuchMethodEnabled,
'minified': minified,
};
void accept(InfoVisitor visitor) => visitor.visitProgram(this);
}
// TODO(sigmund): add unit tests.
class _ParseHelper {
Map<String, Info> registry = {};
AllInfo parseAll(Map json) {
var result = new AllInfo();
var elements = json['elements'];
result.libraries.addAll(elements['library'].values.map(parseLibrary));
result.classes.addAll(elements['class'].values.map(parseClass));
result.functions.addAll(elements['function'].values.map(parseFunction));
result.fields.addAll(elements['field'].values.map(parseField));
result.typedefs.addAll(elements['typedef'].values.map(parseTypedef));
var idMap = {};
for (var f in result.functions) {
idMap[f.serializedId] = f;
}
for (var f in result.fields) {
idMap[f.serializedId] = f;
}
json['holding'].forEach((k, deps) {
var src = idMap[k];
assert (src != null);
for (var dep in deps) {
var target = idMap[dep['id']];
assert (target != null);
src.uses.add(new DependencyInfo(target, dep['mask']));
}
});
json['dependencies']?.forEach((k, deps) {
result.dependencies[idMap[k]] = deps.map((d) => idMap[d]).toList();
});
result.program = parseProgram(json['program']);
// todo: version, etc
return result;
}
LibraryInfo parseLibrary(Map json) {
LibraryInfo result = parseId(json['id']);
result..name = json['name']
..uri = Uri.parse(json['canonicalUri'])
..outputUnit = parseId(json['outputUnit'])
..size = json['size'];
for (var child in json['children'].map(parseId)) {
if (child is FunctionInfo) {
result.topLevelFunctions.add(child);
} else if (child is FieldInfo) {
result.topLevelVariables.add(child);
} else if (child is ClassInfo) {
result.classes.add(child);
} else {
assert(child is TypedefInfo);
result.typedefs.add(child);
}
}
return result;
}
ClassInfo parseClass(Map json) {
ClassInfo result = parseId(json['id']);
result..name = json['name']
..parent = parseId(json['parent'])
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..isAbstract = json['modifiers']['abstract'] == true;
assert(result is ClassInfo);
for (var child in json['children'].map(parseId)) {
if (child is FunctionInfo) {
result.functions.add(child);
} else {
assert(child is FieldInfo);
result.fields.add(child);
}
}
return result;
}
FieldInfo parseField(Map json) {
FieldInfo result = parseId(json['id']);
return result..name = json['name']
..parent = parseId(json['parent'])
..coverageId = json['coverageId']
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..type = json['type']
..inferredType = json['inferredType']
..code = json['code']
..closures = json['children'].map(parseId).toList();
}
TypedefInfo parseTypedef(Map json) {
TypedefInfo result = parseId(json['id']);
return result..name = json['name']
..parent = parseId(json['parent'])
..type = json['type']
..size = 0;
}
ProgramInfo parseProgram(Map json) =>
new ProgramInfo()..size = json['size'];
FunctionInfo parseFunction(Map json) {
FunctionInfo result = parseId(json['id']);
return result..name = json['name']
..parent = parseId(json['parent'])
..coverageId = json['coverageId']
..outputUnit = parseId(json['outputUnit'])
..size = json['size']
..type = json['type']
..returnType = json['returnType']
..inferredReturnType = json['inferredReturnType']
..parameters = json['parameters'].map(parseParameter).toList()
..code = json['code']
..sideEffects = json['sideEffects']
..modifiers = parseModifiers(json['modifiers'])
..closures = json['children'].map(parseId).toList();
}
ParameterInfo parseParameter(Map json) =>
new ParameterInfo(json['name'], json['type'], json['declaredType']);
FunctionModifiers parseModifiers(Map<String, bool> json) {
return new FunctionModifiers(
isStatic: json['static'] == true,
isConst: json['const'] == true,
isFactory: json['factory'] == true,
isExternal: json['external'] == true);
}
Info parseId(String serializedId) => registry.putIfAbsent(serializedId, () {
if (serializedId == null) {
return null;
} else if (serializedId.startsWith('function/')) {
return new FunctionInfo._(serializedId);
} else if (serializedId.startsWith('library/')) {
return new LibraryInfo._(serializedId);
} else if (serializedId.startsWith('class/')) {
return new ClassInfo._(serializedId);
} else if (serializedId.startsWith('field/')) {
return new FieldInfo._(serializedId);
} else if (serializedId.startsWith('typedef/')) {
return new TypedefInfo._(serializedId);
} else if (serializedId.startsWith('outputUnit/')) {
return new OutputUnitInfo._(serializedId);
}
assert(false);
});
}
/// Info associated with a library element.
class LibraryInfo extends BasicInfo {
/// Canonical uri that identifies the library.
Uri uri;
/// Top level functions defined within the library.
final List<FunctionInfo> topLevelFunctions = <FunctionInfo>[];
/// Top level fields defined within the library.
final List<FieldInfo> topLevelVariables = <FieldInfo>[];
/// Classes defined within the library.
final List<ClassInfo> classes = <ClassInfo>[];
/// Typedefs defined within the library.
final List<TypedefInfo> typedefs = <TypedefInfo>[];
static int _id = 0;
/// Whether there is any information recorded for this library.
bool get isEmpty =>
topLevelFunctions.isEmpty && topLevelVariables.isEmpty && classes.isEmpty;
LibraryInfo(String name, this.uri, OutputUnitInfo outputUnit, int size)
: super(InfoKind.library, _id++, name, outputUnit, size, null);
LibraryInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
'children': []
..addAll(topLevelFunctions.map((f) => f.serializedId))
..addAll(topLevelVariables.map((v) => v.serializedId))
..addAll(classes.map((c) => c.serializedId))
..addAll(typedefs.map((t) => t.serializedId)),
'canonicalUri': '$uri',
});
void accept(InfoVisitor visitor) => visitor.visitLibrary(this);
}
/// Information about an output unit. Normally there is just one for the entire
/// program unless the application uses deferred imports, in which case there
/// would be an additional output unit per deferred chunk.
class OutputUnitInfo extends BasicInfo {
static int _ids = 0;
OutputUnitInfo(String name, int size)
: super(InfoKind.outputUnit, _ids++, name, null, size, null);
OutputUnitInfo._(String serializedId) : super._fromId(serializedId);
void accept(InfoVisitor visitor) => visitor.visitOutput(this);
}
/// Information about a class element.
class ClassInfo extends BasicInfo {
/// Whether the class is abstract.
bool isAbstract;
// TODO(sigmund): split static vs instance vs closures
/// Functions (static or instance) defined in the class.
final List<FunctionInfo> functions = <FunctionInfo>[];
/// Fields defined in the class.
// TODO(sigmund): currently appears to only be populated with instance fields,
// but this should be fixed.
final List<FieldInfo> fields = <FieldInfo>[];
static int _ids = 0;
ClassInfo(
{String name, this.isAbstract, OutputUnitInfo outputUnit, int size: 0})
: super(InfoKind.clazz, _ids++, name, outputUnit, size, null);
ClassInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
// TODO(sigmund): change format, include only when abstract is true.
'modifiers': {'abstract': isAbstract},
'children': []
..addAll(fields.map((f) => f.serializedId))
..addAll(functions.map((m) => m.serializedId))
});
void accept(InfoVisitor visitor) => visitor.visitClass(this);
}
/// Information about a field element.
class FieldInfo extends BasicInfo with CodeInfo {
/// The type of the field.
String type;
/// The type inferred by dart2js's whole program analysis
String inferredType;
/// Nested closures seen in the field initializer.
List<FunctionInfo> closures;
/// The actual generated code for the field.
String code;
static int _ids = 0;
FieldInfo(
{String name,
String coverageId,
int size: 0,
this.type,
this.inferredType,
this.closures,
this.code,
OutputUnitInfo outputUnit})
: super(InfoKind.field, _ids++, name, outputUnit, size, coverageId);
FieldInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
'children': closures.map((i) => i.serializedId).toList(),
'inferredType': inferredType,
'code': code,
'type': type,
});
void accept(InfoVisitor visitor) => visitor.visitField(this);
}
/// Information about a typedef declaration.
class TypedefInfo extends BasicInfo {
/// The declared type.
String type;
static int _ids = 0;
TypedefInfo(String name, this.type, OutputUnitInfo outputUnit)
: super(InfoKind.typedef, _ids++, name, outputUnit, 0, null);
TypedefInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()..['type'] = '$type';
void accept(InfoVisitor visitor) => visitor.visitTypedef(this);
}
/// Information about a function or method.
class FunctionInfo extends BasicInfo with CodeInfo {
static const int TOP_LEVEL_FUNCTION_KIND = 0;
static const int CLOSURE_FUNCTION_KIND = 1;
static const int METHOD_FUNCTION_KIND = 2;
static const int CONSTRUCTOR_FUNCTION_KIND = 3;
static int _ids = 0;
/// Kind of function (top-level function, closure, method, or constructor).
int functionKind;
/// Modifiers applied to this function.
FunctionModifiers modifiers;
/// Nested closures that appear within the body of this function.
List<FunctionInfo> closures;
/// The type of this function.
String type;
/// The declared return type.
String returnType;
/// The inferred return type.
String inferredReturnType;
/// Name and type information for each parameter.
List<ParameterInfo> parameters;
/// Side-effects.
// TODO(sigmund): serialize more precisely, not just a string representation.
String sideEffects;
/// How many function calls were inlined into this function.
int inlinedCount;
/// The actual generated code.
String code;
FunctionInfo(
{String name,
String coverageId,
OutputUnitInfo outputUnit,
int size: 0,
this.functionKind,
this.modifiers,
this.closures,
this.type,
this.returnType,
this.inferredReturnType,
this.parameters,
this.sideEffects,
this.inlinedCount,
this.code})
: super(InfoKind.function, _ids++, name, outputUnit, size, coverageId);
FunctionInfo._(String serializedId) : super._fromId(serializedId);
Map toJson() => super.toJson()
..addAll({
'children': closures.map((i) => i.serializedId).toList(),
'modifiers': modifiers.toJson(),
'returnType': returnType,
'inferredReturnType': inferredReturnType,
'parameters': parameters.map((p) => p.toJson()).toList(),
'sideEffects': sideEffects,
'inlinedCount': inlinedCount,
'code': code,
'type': type,
// Note: version 3.2 of dump-info serializes `uses` in a section called
// `holding` at the top-level.
});
void accept(InfoVisitor visitor) => visitor.visitFunction(this);
}
/// Information about how a dependency is used.
class DependencyInfo {
/// The dependency, either a FunctionInfo or FieldInfo.
final Info target;
/// Either a selector mask indicating how this is used, or 'inlined'.
// TODO(sigmund): split mask into an enum or something more precise to really
// describe the dependencies in detail.
final String mask;
DependencyInfo(this.target, this.mask);
Map toJson() => {'id': target.serializedId, 'mask': mask};
}
/// Name and type information about a function parameter.
class ParameterInfo {
final String name;
final String type;
final String declaredType;
ParameterInfo(this.name, this.type, this.declaredType);
Map toJson() => {'name': name, 'type': type, 'declaredType': declaredType};
}
/// Modifiers that may apply to methods.
class FunctionModifiers {
final bool isStatic;
final bool isConst;
final bool isFactory;
final bool isExternal;
FunctionModifiers(
{this.isStatic: false,
this.isConst: false,
this.isFactory: false,
this.isExternal: false});
// TODO(sigmund): exclude false values (requires bumping the format version):
// Map toJson() {
// var res = <String, bool>{};
// if (isStatic) res['static'] = true;
// if (isConst) res['const'] = true;
// if (isFactory) res['factory'] = true;
// if (isExternal) res['external'] = true;
// return res;
// }
Map toJson() => {
'static': isStatic,
'const': isConst,
'factory': isFactory,
'external': isExternal,
};
}
/// Possible values of the `kind` field in the serialied infos.
enum InfoKind {
library,
clazz,
function,
field,
outputUnit,
typedef,
}
String _kindToString(InfoKind kind) {
switch(kind) {
case InfoKind.library: return 'library';
case InfoKind.clazz: return 'class';
case InfoKind.function: return 'function';
case InfoKind.field: return 'field';
case InfoKind.outputUnit: return 'outputUnit';
case InfoKind.typedef: return 'typedef';
default: return null;
}
}
int _idFromSerializedId(String serializedId) =>
int.parse(serializedId.substring(serializedId.indexOf('/') + 1));
InfoKind _kindFromSerializedId(String serializedId) =>
_kindFromString(serializedId.substring(0, serializedId.indexOf('/')));
InfoKind _kindFromString(String kind) {
switch(kind) {
case 'library': return InfoKind.library;
case 'class': return InfoKind.clazz;
case 'function': return InfoKind.function;
case 'field': return InfoKind.field;
case 'outputUnit': return InfoKind.outputUnit;
case 'typedef': return InfoKind.typedef;
default: return null;
}
}
/// A simple visitor for information produced by the dart2js compiler.
class InfoVisitor {
visitAll(AllInfo info) {}
visitProgram(ProgramInfo info) {}
visitLibrary(LibraryInfo info) {}
visitClass(ClassInfo info) {}
visitField(FieldInfo info) {}
visitFunction(FunctionInfo info) {}
visitTypedef(TypedefInfo info) {}
visitOutput(OutputUnitInfo info) {}
}
/// A visitor that recursively walks each portion of the program. Because the
/// info representation is redundant, this visitor only walks the structure of
/// the program and skips some redundant links. For example, even though
/// visitAll contains references to functions, this visitor only recurses to
/// visit libraries, then from each library we visit functions and classes, and
/// so on.
class RecursiveInfoVisitor extends InfoVisitor {
visitAll(AllInfo info) {
// Note: we don't visit functions, fields, classes, and typedefs because
// they are reachable from the library info.
info.libraries.forEach(visitLibrary);
}
visitLibrary(LibraryInfo info) {
info.topLevelFunctions.forEach(visitFunction);
info.topLevelVariables.forEach(visitField);
info.classes.forEach(visitClass);
info.typedefs.forEach(visitTypedef);
}
visitClass(ClassInfo info) {
info.functions.forEach(visitFunction);
info.fields.forEach(visitField);
}
visitField(FieldInfo info) {
info.closures.forEach(visitFunction);
}
visitFunction(FunctionInfo info) {
info.closures.forEach(visitFunction);
}
}

View file

@ -10,11 +10,8 @@ dependencies:
path: ../../sdk/lib/_internal/js_runtime
sdk_library_metadata:
path: ../../sdk/lib/_internal/sdk_library_metadata
dev_dependencies:
path: ^1.3.6
args: ^0.13.2
shelf: ^0.6.1+2
yaml: ^2.1.3
dart2js_info: ^0.0.1
# Uncomment if running gclient, so you can depend directly on the downloaded
# versions of dart2js's transitive dependencies:

View file

@ -1,105 +0,0 @@
// Copyright (c) 2015, 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.
/// Command-line tool to query for code dependencies.
library compiler.tool.code_deps;
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:compiler/src/info/info.dart';
import 'graph.dart';
import 'util.dart';
main(args) {
if (args.length < 2) {
print('usage: dart tool/code_deps.dart path-to-info.json <query>');
print(' where <query> can be:');
print(' - some_path elementA elementB');
// TODO(sigmund): add other queries, such as 'all_paths'.
exit(1);
}
var json = JSON.decode(new File(args[0]).readAsStringSync());
var info = AllInfo.parseFromJson(json);
var graph = graphFromInfo(info);
var queryName = args[1];
if (queryName == 'some_path') {
if (args.length < 4) {
print('missing arguments for `some_path`');
exit(1);
}
var source =
info.functions.firstWhere(_longNameMatcher(new RegExp(args[2])),
orElse: () => null);
var target =
info.functions.firstWhere(_longNameMatcher(new RegExp(args[3])),
orElse: () => null);
print('query: some_path');
if (source == null) {
print("source '${args[2]}' not found in '${args[0]}'");
exit(1);
}
print('source: ${longName(source)}');
if (target == null) {
print("target '${args[3]}' not found in '${args[0]}'");
exit(1);
}
print('target: ${longName(target)}');
var path = new SomePathQuery(source, target).run(graph);
if (path.isEmpty) {
print('result: no path found');
} else {
print('result:');
for (int i = 0; i < path.length; i++) {
print(' $i. ${longName(path[i])}');
}
}
} else {
print('unrecognized query: $queryName');
}
}
/// A query supported by this tool.
abstract class Query {
run(Graph<Info> graph);
}
/// Query that searches for a single path between two elements.
class SomePathQuery {
/// The info associated with the source element.
Info source;
/// The info associated with the target element.
Info target;
SomePathQuery(this.source, this.target);
List<Info> run(Graph<Info> graph) {
var seen = {source: null};
var queue = new Queue();
queue.addLast(source);
while (queue.isNotEmpty) {
var node = queue.removeFirst();
if (identical(node, target)) {
var result = new Queue();
while (node != null) {
result.addFirst(node);
node = seen[node];
}
return result.toList();
}
for (var neighbor in graph.targetsOf(node)) {
if (seen.containsKey(neighbor)) continue;
seen[neighbor] = node;
queue.addLast(neighbor);
}
}
return [];
}
}
_longNameMatcher(RegExp regexp) => (e) => regexp.hasMatch(longName(e));

View file

@ -1,190 +0,0 @@
// Copyright (c) 2015, 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.
/// A tool to gather coverage data from an app generated with dart2js.
/// This tool starts a server that answers to mainly 2 requests:
/// * a GET request to retrieve the application
/// * POST requests to record coverage data.
///
/// It is intended to be used as follows:
/// * generate an app by running dart2js with the environment boolean
/// -DinstrumentForCoverage=true provided to the vm, and the --dump-info
/// flag provided to dart2js.
/// * start this server, and proxy requests from your normal frontend
/// server to this one.
library compiler.tool.coverage_log_server;
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:path/path.dart' as path;
import 'package:args/args.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf;
const _DEFAULT_OUT_TEMPLATE = '<dart2js-out-file>.coverage.json';
main(argv) async {
var parser = new ArgParser()
..addOption('port', abbr: 'p', help: 'port number', defaultsTo: "8080")
..addOption('host', help: 'host name (use 0.0.0.0 for all interfaces)',
defaultsTo: 'localhost')
..addFlag('help', abbr: 'h', help: 'show this help message',
negatable: false)
..addOption('uri-prefix',
help: 'uri path prefix that will hit this server. This will be injected'
' into the .js file',
defaultsTo: '')
..addOption('out', abbr: 'o', help: 'output log file',
defaultsTo: _DEFAULT_OUT_TEMPLATE);
var args = parser.parse(argv);
if (args['help'] == true || args.rest.isEmpty) {
print('usage: dart coverage_logging.dart [options] '
'<dart2js-out-file> [<html-file>]');
print(parser.usage);
exit(1);
}
var jsPath = args.rest[0];
var htmlPath = null;
if (args.rest.length > 1) {
htmlPath = args.rest[1];
}
var outPath = args['out'];
if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json';
var server = new _Server(args['host'], int.parse(args['port']), jsPath,
htmlPath, outPath, args['uri-prefix']);
await server.run();
}
class _Server {
/// Server hostname, typically `localhost`, but can be `0.0.0.0`.
final String hostname;
/// Port the server will listen to.
final int port;
/// JS file (previously generated by dart2js) to serve.
final String jsPath;
/// HTML file to serve, if any.
final String htmlPath;
/// Contents of jsPath, adjusted to use the appropriate server url.
String jsCode;
/// Location where we'll dump the coverage data.
final String outPath;
/// Uri prefix used on all requests to this server. This will be injected into
/// the .js file.
final String prefix;
// TODO(sigmund): add support to load also simple HTML files to test small
// simple apps.
/// Data received so far. The data is just an array of pairs, showing the
/// hashCode and name of the element used. This can be later cross-checked
/// against dump-info data.
Map data = {};
String get _serializedData => new JsonEncoder.withIndent(' ').convert(data);
_Server(this.hostname, this.port, String jsPath, this.htmlPath,
this.outPath, String prefix)
: jsPath = jsPath,
jsCode = _adjustRequestUrl(new File(jsPath).readAsStringSync(), prefix),
prefix = _normalize(prefix);
run() async {
await shelf.serve(_handler, hostname, port);
var urlBase = "http://$hostname:$port${prefix == '' ? '/' : '/$prefix/'}";
var htmlFilename = htmlPath == null ? '' : path.basename(htmlPath);
print("Server is listening\n"
" - html page: $urlBase$htmlFilename\n"
" - js code: $urlBase${path.basename(jsPath)}\n"
" - coverage reporting: ${urlBase}coverage\n");
}
_expectedPath(String tail) => prefix == '' ? tail : '$prefix/$tail';
_handler(shelf.Request request) async {
var urlPath = request.url.path;
print('received request: $urlPath');
var baseJsName = path.basename(jsPath);
var baseHtmlName = htmlPath == null ? '' : path.basename(htmlPath);
// Serve an HTML file at the default prefix, or a path matching the HTML
// file name
if (urlPath == prefix || urlPath == '$prefix/'
|| urlPath == _expectedPath(baseHtmlName)) {
var contents = htmlPath == null
? '<html><script src="$baseJsName"></script>'
: await new File(htmlPath).readAsString();
return new shelf.Response.ok(contents, headers: HTML_HEADERS);
}
if (urlPath == _expectedPath(baseJsName)) {
return new shelf.Response.ok(jsCode, headers: JS_HEADERS);
}
// Handle POST requests to record coverage data, and GET requests to display
// the currently coverage resutls.
if (urlPath == _expectedPath('coverage')) {
if (request.method == 'GET') {
return new shelf.Response.ok(_serializedData, headers: TEXT_HEADERS);
}
if (request.method == 'POST') {
_record(JSON.decode(await request.readAsString()));
return new shelf.Response.ok("Thanks!");
}
}
// Any other request is not supported.
return new shelf.Response.notFound('Not found: "$urlPath"');
}
_record(List entries) {
for (var entry in entries) {
var id = entry[0];
data.putIfAbsent('$id', () => {'name': entry[1], 'count': 0});
data['$id']['count']++;
}
_enqueueSave();
}
bool _savePending = false;
int _total = 0;
_enqueueSave() async {
if (!_savePending) {
_savePending = true;
await new Future.delayed(new Duration(seconds: 3));
await new File(outPath).writeAsString(_serializedData);
var diff = data.length - _total;
print(diff ? ' - no new element covered'
: ' - $diff new elements covered');
_savePending = false;
_total = data.length;
}
}
}
/// Removes leading and trailing slashes of [uriPath].
_normalize(String uriPath) {
if (uriPath.startsWith('/')) uriPath = uriPath.substring(1);
if (uriPath.endsWith('/')) uriPath = uriPath.substring(0, uriPath.length - 1);
return uriPath;
}
_adjustRequestUrl(String code, String prefix) {
var newUrl = prefix == '' ? 'coverage' : '$prefix/coverage';
return code.replaceFirst(
'"/coverage_uri_to_amend_by_server"',
'"/$newUrl" /*url-prefix updated!*/');
}
const HTML_HEADERS = const {'content-type': 'text/html'};
const JS_HEADERS = const {'content-type': 'text/javascript'};
const TEXT_HEADERS = const {'content-type': 'text/plain'};

View file

@ -1,292 +0,0 @@
// Copyright (c) 2015, 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.
/// A library to work with graphs. It contains a couple algorithms, including
/// Tarjan's algorithm to compute strongest connected components in a graph and
/// Cooper et al's dominator algorithm.
///
/// Portions of the code in this library was adapted from
/// `package:analyzer/src/generated/collection_utilities.dart`.
// TODO(sigmund): move this into a shared place?
library compiler.tool.graph;
import 'dart:math' as math;
abstract class Graph<N> {
Iterable<N> get nodes;
bool get isEmpty;
int get nodeCount;
Iterable<N> targetsOf(N source);
Iterable<N> sourcesOf(N source);
/// Run a topological sort of the graph. Since the graph may contain cycles,
/// this results in a list of strongly connected components rather than a list
/// of nodes. The nodes in each strongly connected components only have edges
/// that point to nodes in the same component or earlier components.
List<List<N>> computeTopologicalSort() {
_SccFinder<N> finder = new _SccFinder<N>(this);
return finder.computeTopologicalSort();
}
/// Whether [source] can transitively reach [target].
bool containsPath(N source, N target) {
Set<N> seen = new Set<N>();
bool helper(N node) {
if (identical(node, target)) return true;
if (!seen.add(node)) return false;
return targetsOf(node).any(helper);
}
return helper(source);
}
/// Returns all nodes reachable from [root] in post order.
List<N> postOrder(N root) {
var seen = new Set<N>();
var result = <N>[];
helper(n) {
if (!seen.add(n)) return;
targetsOf(n).forEach(helper);
result.add(n);
}
helper(root);
return result;
}
/// Return a list of nodes that form a cycle containing the given node. If the
/// node is not part of this graph, then a list containing only the node
/// itself will be returned.
List<N> findCycleContaining(N node) {
assert(node != null);
_SccFinder<N> finder = new _SccFinder<N>(this);
return finder._componentContaining(node);
}
Graph<N> dominatorTree(N root) {
var iDom = (new _DominatorFinder(this)..run(root)).immediateDominators;
var graph = new EdgeListGraph<N>();
for (N node in iDom.keys) {
if (node != root) graph.addEdge(iDom[node], node);
}
return graph;
}
}
class EdgeListGraph<N> extends Graph<N> {
/// Edges in the graph.
Map<N, Set<N>> _edges = new Map<N, Set<N>>();
/// The reverse of _edges.
Map<N, Set<N>> _revEdges = new Map<N, Set<N>>();
Iterable<N> get nodes => _edges.keys;
bool get isEmpty => _edges.isEmpty;
int get nodeCount => _edges.length;
final _empty = new Set<N>();
Iterable<N> targetsOf(N source) => _edges[source] ?? _empty;
Iterable<N> sourcesOf(N source) => _revEdges[source] ?? _empty;
void addEdge(N source, N target) {
assert(source != null);
assert(target != null);
addNode(source);
addNode(target);
_edges[source].add(target);
_revEdges[target].add(source);
}
void addNode(N node) {
assert(node != null);
_edges.putIfAbsent(node, () => new Set<N>());
_revEdges.putIfAbsent(node, () => new Set<N>());
}
/// Remove the edge from the given [source] node to the given [target] node.
/// If there was no such edge then the graph will be unmodified: the number of
/// edges will be the same and the set of nodes will be the same (neither node
/// will either be added or removed).
void removeEdge(N source, N target) {
_edges[source]?.remove(target);
}
/// Remove the given node from this graph. As a consequence, any edges for
/// which that node was either a head or a tail will also be removed.
void removeNode(N node) {
_edges.remove(node);
var sources = _revEdges[node];
if (sources == null) return;
for (var source in sources) {
_edges[source].remove(node);
}
}
/// Remove all of the given nodes from this graph. As a consequence, any edges
/// for which those nodes were either a head or a tail will also be removed.
void removeAllNodes(List<N> nodes) => nodes.forEach(removeNode);
}
/// Used by the [SccFinder] to maintain information about the nodes that have
/// been examined. There is an instance of this class per node in the graph.
class _NodeInfo<N> {
/// Depth of the node corresponding to this info.
int index = 0;
/// Depth of the first node in a cycle.
int lowlink = 0;
/// Whether the corresponding node is on the stack. Used to remove the need
/// for searching a collection for the node each time the question needs to be
/// asked.
bool onStack = false;
/// Component that contains the corresponding node.
List<N> component;
_NodeInfo(int depth)
: index = depth, lowlink = depth, onStack = false;
}
/// Implements Tarjan's Algorithm for finding the strongly connected components
/// in a graph.
class _SccFinder<N> {
/// The graph to process.
final Graph<N> _graph;
/// The index used to uniquely identify the depth of nodes.
int _index = 0;
/// Nodes that are being visited in order to identify components.
List<N> _stack = new List<N>();
/// Information associated with each node.
Map<N, _NodeInfo<N>> _info = <N, _NodeInfo<N>>{};
/// All strongly connected components found, in topological sort order (each
/// node in a strongly connected component only has edges that point to nodes
/// in the same component or earlier components).
List<List<N>> _allComponents = new List<List<N>>();
_SccFinder(this._graph) : super();
/// Return a list containing the nodes that are part of the strongly connected
/// component that contains the given node.
List<N> _componentContaining(N node) => _strongConnect(node).component;
/// Run Tarjan's algorithm and return the resulting list of strongly connected
/// components. The list is in topological sort order (each node in a strongly
/// connected component only has edges that point to nodes in the same
/// component or earlier components).
List<List<N>> computeTopologicalSort() {
for (N node in _graph.nodes) {
var nodeInfo = _info[node];
if (nodeInfo == null) _strongConnect(node);
}
return _allComponents;
}
/// Remove and return the top-most element from the stack.
N _pop() {
N node = _stack.removeAt(_stack.length - 1);
_info[node].onStack = false;
return node;
}
/// Add the given node to the stack.
void _push(N node) {
_info[node].onStack = true;
_stack.add(node);
}
/// Compute the strongly connected component that contains the given node as
/// well as any components containing nodes that are reachable from the given
/// component.
_NodeInfo<N> _strongConnect(N v) {
// Set the depth index for v to the smallest unused index
var vInfo = new _NodeInfo<N>(_index++);
_info[v] = vInfo;
_push(v);
for (N w in _graph.targetsOf(v)) {
var wInfo = _info[w];
if (wInfo == null) {
// Successor w has not yet been visited; recurse on it
wInfo = _strongConnect(w);
vInfo.lowlink = math.min(vInfo.lowlink, wInfo.lowlink);
} else if (wInfo.onStack) {
// Successor w is in stack S and hence in the current SCC
vInfo.lowlink = math.min(vInfo.lowlink, wInfo.index);
}
}
// If v is a root node, pop the stack and generate an SCC
if (vInfo.lowlink == vInfo.index) {
var component = new List<N>();
N w;
do {
w = _pop();
component.add(w);
_info[w].component = component;
} while (!identical(w, v));
_allComponents.add(component);
}
return vInfo;
}
}
/// Computes dominators using (Cooper, Harvey, and Kennedy's
/// algorithm)[http://www.cs.rice.edu/~keith/EMBED/dom.pdf].
class _DominatorFinder<N> {
final Graph<N> _graph;
Map<N, N> immediateDominators = {};
Map<N, int> postOrderId = {};
_DominatorFinder(this._graph);
run(N root) {
immediateDominators[root] = root;
bool changed = true;
int i = 0;
var nodesInPostOrder = _graph.postOrder(root);
for (var n in nodesInPostOrder) {
postOrderId[n] = i++;
}
var nodesInReversedPostOrder = nodesInPostOrder.reversed;
while (changed) {
changed = false;
for (var n in nodesInReversedPostOrder) {
if (n == root) continue;
bool first = true;
var idom = null;
for (var p in _graph.sourcesOf(n)) {
if (immediateDominators[p] != null) {
if (first) {
idom = p;
first = false;
} else {
idom = _intersect(p, idom);
}
}
}
if (immediateDominators[n] != idom) {
immediateDominators[n] = idom;
changed = true;
}
}
}
}
N _intersect(N b1, N b2) {
var finger1 = b1;
var finger2 = b2;
while (finger1 != finger2) {
while (postOrderId[finger1] < postOrderId[finger2]) {
finger1 = immediateDominators[finger1];
}
while (postOrderId[finger2] < postOrderId[finger1]) {
finger2 = immediateDominators[finger2];
}
}
return finger1;
}
}

View file

@ -1,132 +0,0 @@
// Copyright (c) 2015, 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.
/// Command-line tool to show the size distribution of generated code among
/// libraries. Libraries can be grouped using regular expressions. See
/// [defaultGrouping] for an example.
library compiler.tool.library_size_split;
import 'dart:convert';
import 'dart:io';
import 'dart:math' show max;
import 'package:compiler/src/info/info.dart';
import 'package:yaml/yaml.dart';
main(args) {
if (args.length < 1) {
print('usage: dart tool/library_size_split.dart '
'path-to-info.json [grouping.yaml]');
exit(1);
}
var filename = args[0];
var json = JSON.decode(new File(filename).readAsStringSync());
var info = AllInfo.parseFromJson(json);
var groupingText = args.length > 1
? new File(args[1]).readAsStringSync() : defaultGrouping;
var groupingYaml = loadYaml(groupingText);
var groups = [];
for (var group in groupingYaml['groups']) {
groups.add(new _Group(group['name'],
new RegExp(group['regexp']),
group['cluster'] ?? 0));
}
var sizes = {};
for (LibraryInfo lib in info.libraries) {
groups.forEach((group) {
var match = group.matcher.firstMatch('${lib.uri}');
if (match != null) {
var name = group.name;
if (name == null && match.groupCount > 0) name = match.group(1);
if (name == null) name = match.group(0);
sizes.putIfAbsent(name, () => new _SizeEntry(name, group.cluster));
sizes[name].size += lib.size;
}
});
}
var all = sizes.keys.toList();
all.sort((a, b) => sizes[a].compareTo(sizes[b]));
var realTotal = info.program.size;
var longest = all.fold(0, (count, value) => max(count, value.length));
longest = max(longest, 'Program Size'.length);
var lastCluster = 0;
for (var name in all) {
var entry = sizes[name];
if (lastCluster < entry.cluster) {
print(' ' + ('-' * (longest + 18)));
lastCluster = entry.cluster;
}
var size = entry.size;
var percent = (size * 100 / realTotal).toStringAsFixed(2);
print(' ${_pad(name, longest + 1, right: true)}'
' ${_pad(size, 8)} ${_pad(percent, 6)}%');
}
print(' ${_pad("Program Size", longest + 1, right: true)}'
' ${_pad(realTotal, 8)} ${_pad(100, 6)}%');
}
/// A group defined in the configuration.
class _Group {
/// Name of the group. May be null if the name is derived from the matcher. In
/// that case, the name would be group(1) of the matched expression if it
/// exist, or group(0) otherwise.
final String name;
/// Regular expression matching members of the group.
final RegExp matcher;
/// Index used to cluster groups together. Useful when the grouping
/// configuration describes some coarser groups than orders (e.g. summary of
/// packages would be in a different cluster than a summary of libraries).
final int cluster;
_Group(this.name, this.matcher, this.cluster);
}
class _SizeEntry {
final String name;
final int cluster;
int size = 0;
_SizeEntry(this.name, this.cluster);
int compareTo(_SizeEntry other) =>
cluster == other.cluster ? size - other.size : cluster - other.cluster;
}
_pad(value, n, {bool right: false}) {
var s = '$value';
if (s.length >= n) return s;
var pad = ' ' * (n - s.length);
return right ? '$s$pad' : '$pad$s';
}
/// Example grouping specification: a yaml format containing a list of
/// group specifications. A group is specified by 3 parameters:
/// - name: the name that will be shown in the table of results
/// - regexp: a regexp used to match entries that belong to the group
/// - cluster: a clustering index, the higher the value, the later it will be
/// shown in the results.
/// Both cluster and name are optional. If cluster is omitted, the default value
/// is 0. If the name is omitted, it is extracted from the regexp, either as
/// group(1) if it is available or group(0) otherwise.
final defaultGrouping = """
groups:
- { name: "Total (excludes preambles, statics & consts)", regexp: ".*", cluster: 3}
- { name: "Loose files", regexp: "file://.*", cluster: 2}
- { name: "All packages", regexp: "package:.*", cluster: 2}
- { name: "Core libs", regexp: "dart:.*", cluster: 2}
# We omitted `name` to extract the group name from the regexp directly.
# Here the name is the name of the package:
- { regexp: "package:([^/]*)", cluster: 1}
# Here the name is the url of the package and dart core libraries:
- { regexp: "package:.*"}
- { regexp: "dart:.*"}
# Here the name is the relative path of loose files:
- { regexp: "file://${Directory.current.path}/(.*)" }
""";

View file

@ -1,96 +0,0 @@
library compiler.tool.util;
import 'package:compiler/src/info/info.dart';
import 'graph.dart';
/// Computes a graph of dependencies from [info].
Graph<Info> graphFromInfo(AllInfo info) {
// Note: currently we build two graphs to debug the differences between to
// places where we collect this information in dump-info.
// TODO(sigmund): fix inconsistencies between graphs, stick with one of them.
// TODO(sigmund): create a concrete implementation of InfoGraph, instead of
// using the EdgeListGraph.
var g1 = new EdgeListGraph<Info>();
var g2 = new EdgeListGraph<Info>();
var g3 = new EdgeListGraph<Info>();
for (var f in info.functions) {
g1.addNode(f);
g3.addNode(f);
for (var g in f.uses) {
g1.addEdge(f, g.target);
g3.addEdge(f, g.target);
}
g2.addNode(f);
if (info.dependencies[f] != null) {
for (var g in info.dependencies[f]) {
g2.addEdge(f, g);
g3.addEdge(f, g);
}
}
}
for (var f in info.fields) {
g1.addNode(f);
g3.addNode(f);
for (var g in f.uses) {
g1.addEdge(f, g.target);
g3.addEdge(f, g.target);
}
g2.addNode(f);
if (info.dependencies[f] != null) {
for (var g in info.dependencies[f]) {
g2.addEdge(f, g);
g3.addEdge(f, g);
}
}
}
// Note: these checks right now show that 'uses' links are computed
// differently than 'deps' links
int more1 = 0;
int more2 = 0;
int more1b = 0;
int more2b = 0;
_sameEdges(f) {
var targets1 = g1.targetsOf(f).toSet();
var targets2 = g2.targetsOf(f).toSet();
var len1 = targets1.length;
var len2 = targets2.length;
if (len1 > len2) more1b++;
if (len1 < len2) more2b++;
var diff1 = targets1.difference(targets2);
var diff2 = targets2.difference(targets1);
if (diff1.isNotEmpty) {
more1++;
}
if (diff2.isNotEmpty) {
more2++;
}
return true;
}
info.functions.forEach(_sameEdges);
info.fields.forEach(_sameEdges);
if (more1 > 0 || more2 > 0 || more1b > 0 || more2b > 0) {
print("Dep graph is not consistent: $more1 $more2");
}
return g3;
}
/// Provide a unique long name associated with [info].
// TODO(sigmund): guarantee that the name is actually unique.
String longName(Info info) {
var sb = new StringBuffer();
helper(i) {
if (i.parent == null) {
// TODO(sigmund): ensure `i is LibraryInfo`, we still don't set parents
// for closure classes correctly.
sb.write('${i.name}..');
} else {
helper(i.parent);
sb.write('.${i.name}');
}
}
helper(info);
return sb.toString();
}

View file

@ -44,9 +44,6 @@ const Map<String, List<String>> WHITE_LIST = const {
"accept", "FunctionExpression", "CreateFunction"
],
// AllInfo.fromJson and visit methods are not used yet.
"lib/src/info/info.dart": const [ "is never" ],
"lib/src/universe/universe.dart": const [
"The method 'getterInvocationsByName' is never called.",
"The method 'setterInvocationsByName' is never called."],

View file

@ -21,6 +21,7 @@ vars.update({
"collection_rev": "@1da9a07f32efa2ba0c391b289e2037391e31da0e",
"crypto_rev" : "@2df57a1e26dd88e8d0614207d4b062c73209917d",
"csslib_tag" : "@0.12.0",
"dart2js_info_rev" : "@5902be71c24bbda96675f646ba7d3ff911d26eae",
"glob_rev": "@704cf75e4f26b417505c5c611bdaacd8808467dd",
"html_tag" : "@0.12.1+1",
"http_rev" : "@9b93e1542c753090c50b46ef1592d44bc858bfe7",
@ -93,6 +94,8 @@ deps.update({
"https://github.com/dart-lang/crypto.git" + Var("crypto_rev"),
"src/dart/third_party/pkg/csslib":
"https://github.com/dart-lang/csslib.git" + Var("csslib_tag"),
"src/dart/third_party/pkg/dart2js_info":
"https://github.com/dart-lang/dart2js_info.git" + Var("dart2js_info_rev"),
"src/dart/third_party/pkg/collection":
"https://github.com/dart-lang/collection.git" + Var("collection_rev"),
"src/dart/third_party/pkg/glob":