mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
Remove package analyzer_fe_comparison.
This package has outlived its usefulness and is beginning to bit-rot. Change-Id: I823a1ed4d31765c54633a868d5dfc36106aca562 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/139024 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
92b1057f3f
commit
4b1fd4f670
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) 2018, 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 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer/src/command_line/arguments.dart';
|
||||
import 'package:analyzer_fe_comparison/comparison.dart';
|
||||
import 'package:args/args.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/// Compares the analyzer and front_end behavior when compiling a program.
|
||||
main(List<String> args) async {
|
||||
ArgResults options = _parseArgs(args);
|
||||
var sourcePaths = options.rest;
|
||||
if (sourcePaths.length != 1) {
|
||||
throw new StateError('Exactly one source file must be specified.');
|
||||
}
|
||||
var sourcePath = sourcePaths[0];
|
||||
var scriptPath = Platform.script.toFilePath();
|
||||
var sdkRepoPath =
|
||||
path.normalize(path.join(path.dirname(scriptPath), '..', '..', '..'));
|
||||
var buildPath = await _findBuildDir(sdkRepoPath, 'ReleaseX64');
|
||||
var dillPath = path.join(buildPath, 'vm_platform_strong.dill');
|
||||
var packagesFilePath = path.join(sdkRepoPath, '.packages');
|
||||
|
||||
await compareTestPrograms(sourcePath, dillPath, packagesFilePath);
|
||||
}
|
||||
|
||||
Future<String> _findBuildDir(String sdkRepoPath, String targetName) async {
|
||||
for (var subdirName in ['out', 'xcodebuild']) {
|
||||
var candidatePath = path.join(sdkRepoPath, subdirName, targetName);
|
||||
if (await new Directory(candidatePath).exists()) {
|
||||
return candidatePath;
|
||||
}
|
||||
}
|
||||
throw new StateError('Cannot find build directory');
|
||||
}
|
||||
|
||||
ArgResults _parseArgs(List<String> args) {
|
||||
var parser = new ArgParser(allowTrailingOptions: true);
|
||||
parser.addOption('dart-sdk', help: 'The path to the Dart SDK.');
|
||||
if (args.contains('--ignore-unrecognized-flags')) {
|
||||
args = filterUnknownArguments(args, parser);
|
||||
}
|
||||
return parser.parse(args);
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2018, 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.
|
||||
|
||||
# Run analyzer_fe_comparison on the Dart VM. This script assumes the Dart
|
||||
# repo's directory structure.
|
||||
|
||||
function follow_links() {
|
||||
file="$1"
|
||||
while [ -h "$file" ]; do
|
||||
# On Mac OS, readlink -f doesn't work.
|
||||
file="$(readlink "$file")"
|
||||
done
|
||||
echo "$file"
|
||||
}
|
||||
|
||||
# Unlike $0, $BASH_SOURCE points to the absolute path of this file.
|
||||
PROG_NAME="$(follow_links "$BASH_SOURCE")"
|
||||
|
||||
# Find directories.
|
||||
PKG_BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
|
||||
DART_ROOT="$(cd "${PKG_BIN_DIR}/../../.." ; pwd -P)"
|
||||
SDK_DIR="${DART_ROOT}/sdk"
|
||||
BIN_DIR="${SDK_DIR}/bin"
|
||||
|
||||
SDK_ARG="--dart-sdk=$SDK_DIR"
|
||||
|
||||
DART="$BIN_DIR/dart"
|
||||
|
||||
unset EXTRA_VM_OPTIONS
|
||||
declare -a EXTRA_VM_OPTIONS
|
||||
|
||||
# We allow extra vm options to be passed in through an environment variable.
|
||||
if [[ $DART_VM_OPTIONS ]]; then
|
||||
read -a OPTIONS <<< "$DART_VM_OPTIONS"
|
||||
EXTRA_VM_OPTIONS+=("${OPTIONS[@]}")
|
||||
fi
|
||||
|
||||
COMPARE_PROGRAMS="$PKG_BIN_DIR/compare_programs.dart"
|
||||
|
||||
exec "$DART" "--packages=$DART_ROOT/.packages" "${EXTRA_VM_OPTIONS[@]}" "$COMPARE_PROGRAMS" "$SDK_ARG" "$@"
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright (c) 2018, 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:analyzer_fe_comparison/src/analyzer.dart' as analyzer;
|
||||
import 'package:analyzer_fe_comparison/src/comparison_node.dart';
|
||||
import 'package:analyzer_fe_comparison/src/kernel.dart' as kernel;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/// Compares the analyzer and kernel representations of a package, and prints
|
||||
/// the resulting diff.
|
||||
void comparePackages(
|
||||
String platformPath, String projectLibPath, String packagesFilePath) async {
|
||||
ComparisonNode analyzerNode = await analyzer.analyzePackage(projectLibPath);
|
||||
var packagesFileUri = Uri.file(packagesFilePath);
|
||||
var inputs = <Uri>[];
|
||||
for (var library in analyzerNode.children) {
|
||||
inputs.add(Uri.parse(library.text));
|
||||
}
|
||||
var platformUri = Uri.file(platformPath);
|
||||
ComparisonNode kernelNode =
|
||||
await kernel.analyzePackage(inputs, packagesFileUri, platformUri);
|
||||
print(ComparisonNode.diff(kernelNode, analyzerNode, 'CFE', 'analyzer'));
|
||||
}
|
||||
|
||||
/// Compares the analyzer and kernel representations of a test file, and prints
|
||||
/// the resulting diff.
|
||||
///
|
||||
/// Only libraries reached by a "file:" URI are compared.
|
||||
void compareTestPrograms(
|
||||
String sourcePath, String platformPath, String packagesFilePath) async {
|
||||
var packagesFileUri = Uri.file(packagesFilePath);
|
||||
var platformUri = Uri.file(platformPath);
|
||||
ComparisonNode kernelNode = await kernel.analyzeProgram(
|
||||
path.toUri(sourcePath),
|
||||
packagesFileUri,
|
||||
platformUri,
|
||||
(uri) => uri.scheme == 'file');
|
||||
if (kernelNode.text == 'Error occurred') {
|
||||
// TODO(paulberry): really we should verify that the analyzer detects an
|
||||
// error as well. But that's not easy to do right now because we use the
|
||||
// front end to chase imports so that we know which files to pass to the
|
||||
// analyzer, and we can't rely on the front end import chasing when an error
|
||||
// occurred.
|
||||
print('No differences found (skipped due to front end compilation error)');
|
||||
return;
|
||||
}
|
||||
String startingPath;
|
||||
var inputs = <String>[];
|
||||
for (var library in kernelNode.children) {
|
||||
var filePath = path.fromUri(Uri.parse(library.text));
|
||||
if (startingPath == null) {
|
||||
startingPath = path.dirname(filePath);
|
||||
} else {
|
||||
while (!path.isWithin(startingPath, filePath)) {
|
||||
startingPath = path.dirname(startingPath);
|
||||
}
|
||||
}
|
||||
inputs.add(filePath);
|
||||
}
|
||||
ComparisonNode analyzerNode =
|
||||
await analyzer.analyzeFiles(startingPath, inputs);
|
||||
if (kernelNode == analyzerNode) {
|
||||
print('No differences found!');
|
||||
} else {
|
||||
print('Differences found:');
|
||||
print(ComparisonNode.diff(kernelNode, analyzerNode, 'CFE', 'analyzer'));
|
||||
}
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
// Copyright (c) 2018, 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 'dart:async';
|
||||
|
||||
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
|
||||
import 'package:analyzer/dart/analysis/context_root.dart';
|
||||
import 'package:analyzer/dart/analysis/session.dart';
|
||||
import 'package:analyzer/dart/analysis/uri_converter.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/generated/resolver.dart';
|
||||
import 'package:analyzer/src/generated/source.dart' show SourceKind;
|
||||
import 'package:analyzer_fe_comparison/src/comparison_node.dart';
|
||||
|
||||
/// Analyzes the files in [filePaths] using the analyzer, and returns a
|
||||
/// [ComparisonNode] representing them.
|
||||
Future<ComparisonNode> analyzeFiles(
|
||||
String startingPath, List<String> filePaths) async {
|
||||
var driver = await _AnalyzerDriver.create(startingPath);
|
||||
return driver.analyzeFiles(filePaths);
|
||||
}
|
||||
|
||||
/// Analyzes the package located at [libPath] using the analyzer, and returns a
|
||||
/// [ComparisonNode] representing it.
|
||||
Future<ComparisonNode> analyzePackage(String libPath) async {
|
||||
var driver = await _AnalyzerDriver.create(libPath);
|
||||
return driver.analyzePackage();
|
||||
}
|
||||
|
||||
class _AnalyzerDriver {
|
||||
final AnalysisSession _session;
|
||||
|
||||
final TypeProvider _typeProvider;
|
||||
|
||||
final UriConverter _uriConverter;
|
||||
|
||||
final ContextRoot _contextRoot;
|
||||
|
||||
_AnalyzerDriver._(
|
||||
this._session, this._typeProvider, this._uriConverter, this._contextRoot);
|
||||
|
||||
Future<ComparisonNode> analyzeFiles(Iterable<String> filePaths) async {
|
||||
var libraryNodes = <ComparisonNode>[];
|
||||
for (var filePath in filePaths) {
|
||||
var kind = await _session.getSourceKind(filePath);
|
||||
if (kind == SourceKind.LIBRARY) {
|
||||
var importUri = _uriConverter.pathToUri(filePath);
|
||||
var libraryElement =
|
||||
await _session.getLibraryByUri(importUri.toString());
|
||||
var childNodes = <ComparisonNode>[];
|
||||
if (libraryElement.name.isNotEmpty) {
|
||||
childNodes.add(ComparisonNode('name=${libraryElement.name}'));
|
||||
}
|
||||
for (var compilationUnit in libraryElement.units) {
|
||||
var unitResult =
|
||||
await _session.getResolvedAst(compilationUnit.source.fullName);
|
||||
_AnalyzerVisitor(_typeProvider, childNodes)
|
||||
._visitList(unitResult.unit.declarations);
|
||||
}
|
||||
libraryNodes
|
||||
.add(ComparisonNode.sorted(importUri.toString(), childNodes));
|
||||
}
|
||||
}
|
||||
return ComparisonNode.sorted('Component', libraryNodes);
|
||||
}
|
||||
|
||||
Future<ComparisonNode> analyzePackage() async {
|
||||
return analyzeFiles(_contextRoot.analyzedFiles());
|
||||
}
|
||||
|
||||
static Future<_AnalyzerDriver> create(String startingPath) async {
|
||||
var contextCollection =
|
||||
AnalysisContextCollection(includedPaths: [startingPath]);
|
||||
var contexts = contextCollection.contexts;
|
||||
if (contexts.length != 1) {
|
||||
throw new StateError('Expected exactly one context');
|
||||
}
|
||||
var context = contexts[0];
|
||||
var session = context.currentSession;
|
||||
var typeProvider = await session.typeProvider;
|
||||
var uriConverter = session.uriConverter;
|
||||
var contextRoot = context.contextRoot;
|
||||
return _AnalyzerDriver._(session, typeProvider, uriConverter, contextRoot);
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor for serializing the contents of an analyzer AST into
|
||||
/// ComparisonNodes.
|
||||
///
|
||||
/// Results are accumulated into [_resultNodes].
|
||||
class _AnalyzerVisitor extends UnifyingAstVisitor<void> {
|
||||
final TypeProvider _typeProvider;
|
||||
|
||||
final List<ComparisonNode> _resultNodes;
|
||||
|
||||
_AnalyzerVisitor(this._typeProvider, this._resultNodes);
|
||||
|
||||
@override
|
||||
void visitClassDeclaration(ClassDeclaration node) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._handleClassOrClassTypeAlias(node.declaredElement);
|
||||
visitor._visitList(node.members);
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('Class ${node.name.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassTypeAlias(ClassTypeAlias node) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._handleClassOrClassTypeAlias(node.declaredElement);
|
||||
_resultNodes.add(
|
||||
ComparisonNode.sorted('MixinApplication ${node.name.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._visitParameters(node.parameters);
|
||||
_resultNodes.add(ComparisonNode.sorted(
|
||||
'Constructor ${node.name?.name ?? '(unnamed)'}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitEnumDeclaration(EnumDeclaration node) {
|
||||
var children = <ComparisonNode>[];
|
||||
for (var enumValue in node.constants) {
|
||||
children.add(ComparisonNode('EnumValue ${enumValue.name.name}'));
|
||||
}
|
||||
_resultNodes.add(ComparisonNode.sorted('Enum ${node.name.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldDeclaration(FieldDeclaration node) {
|
||||
node.fields.accept(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
String kind;
|
||||
if (node.isGetter) {
|
||||
kind = 'Getter';
|
||||
} else if (node.isSetter) {
|
||||
kind = 'Setter';
|
||||
} else {
|
||||
// Kernel calls top level functions "methods".
|
||||
kind = 'Method';
|
||||
}
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._visitTypeParameters(node.declaredElement.typeParameters);
|
||||
visitor._visitParameters(node.functionExpression.parameters);
|
||||
children
|
||||
.add(_translateType('Return type: ', node.declaredElement.returnType));
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('$kind ${node.name.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
||||
_visitTypedef(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitGenericTypeAlias(GenericTypeAlias node) {
|
||||
_visitTypedef(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMethodDeclaration(MethodDeclaration node) {
|
||||
var name = node.name.name;
|
||||
String kind;
|
||||
if (node.isGetter) {
|
||||
kind = 'Getter';
|
||||
} else if (node.isSetter) {
|
||||
kind = 'Setter';
|
||||
} else if (node.isOperator) {
|
||||
kind = 'Operator';
|
||||
if (name == '-' && node.declaredElement.parameters.isEmpty) {
|
||||
name = 'unary-';
|
||||
}
|
||||
} else {
|
||||
kind = 'Method';
|
||||
}
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._visitTypeParameters(node.declaredElement.typeParameters);
|
||||
visitor._visitParameters(node.parameters);
|
||||
children
|
||||
.add(_translateType('Return type: ', node.declaredElement.returnType));
|
||||
_resultNodes.add(ComparisonNode.sorted('$kind $name', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMixinDeclaration(MixinDeclaration node) {
|
||||
// At present, kernel doesn't distinguish between mixin and class
|
||||
// declarations. So treat the mixin as a class.
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._handleClassOrClassTypeAlias(node.declaredElement);
|
||||
visitor._visitList(node.members);
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('Mixin ${node.name.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
Null visitNode(AstNode node) {
|
||||
throw new UnimplementedError('AnalyzerVisitor: ${node.runtimeType}');
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
||||
node.variables.accept(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitVariableDeclarationList(VariableDeclarationList node) {
|
||||
for (var variableDeclaration in node.variables) {
|
||||
var children = <ComparisonNode>[];
|
||||
children.add(
|
||||
_translateType('Type: ', variableDeclaration.declaredElement.type));
|
||||
// Kernel calls both fields and top level variable declarations "fields".
|
||||
_resultNodes.add(ComparisonNode.sorted(
|
||||
'Field ${variableDeclaration.name.name}', children));
|
||||
}
|
||||
}
|
||||
|
||||
void _handleClassOrClassTypeAlias(ClassElement element) {
|
||||
_visitTypeParameters(element.typeParameters);
|
||||
if (element.isMixin) {
|
||||
for (int i = 0; i < element.superclassConstraints.length; i++) {
|
||||
_resultNodes
|
||||
.add(_translateType('On $i: ', element.superclassConstraints[i]));
|
||||
}
|
||||
} else {
|
||||
if (element.supertype != null) {
|
||||
_resultNodes.add(_translateType('Extends: ', element.supertype));
|
||||
}
|
||||
for (int i = 0; i < element.mixins.length; i++) {
|
||||
_resultNodes.add(_translateType('Mixin $i: ', element.mixins[i]));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < element.interfaces.length; i++) {
|
||||
_resultNodes
|
||||
.add(_translateType('Implements $i: ', element.interfaces[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the analyzer representation of a type into a ComparisonNode.
|
||||
ComparisonNode _translateType(String prefix, DartType type) {
|
||||
if (type is InterfaceType) {
|
||||
var children = <ComparisonNode>[];
|
||||
children
|
||||
.add(ComparisonNode('Library: ${type.element.librarySource.uri}'));
|
||||
for (int i = 0; i < type.typeArguments.length; i++) {
|
||||
children.add(_translateType('Type arg $i: ', type.typeArguments[i]));
|
||||
}
|
||||
return ComparisonNode('${prefix}InterfaceType ${type.name}', children);
|
||||
}
|
||||
if (type is TypeParameterType) {
|
||||
// TODO(paulberry): disambiguate if needed.
|
||||
return ComparisonNode('${prefix}TypeParameterType: ${type.name}');
|
||||
}
|
||||
if (type.isDynamic) {
|
||||
return ComparisonNode('${prefix}Dynamic');
|
||||
}
|
||||
if (type.isVoid) {
|
||||
return ComparisonNode('${prefix}Void');
|
||||
}
|
||||
if (type is FunctionType) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
visitor._visitTypeParameters(type.typeFormals);
|
||||
children.add(_translateType('Return type: ', type.returnType));
|
||||
int positionalParameterIndex = 0;
|
||||
for (var parameterElement in type.parameters) {
|
||||
var kind = parameterElement.isNotOptional
|
||||
? 'Required'
|
||||
: parameterElement.isOptionalPositional ? 'Optional' : 'Named';
|
||||
var name = parameterElement.isNamed
|
||||
? parameterElement.name
|
||||
: '${positionalParameterIndex++}';
|
||||
children.add(
|
||||
_translateType('$kind parameter $name: ', parameterElement.type));
|
||||
}
|
||||
return ComparisonNode.sorted('${prefix}FunctionType', children);
|
||||
}
|
||||
throw new UnimplementedError('_translateType: ${type.runtimeType}');
|
||||
}
|
||||
|
||||
/// Visits all the nodes in [nodes].
|
||||
void _visitList(List<AstNode> nodes) {
|
||||
for (var astNode in nodes) {
|
||||
astNode.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitParameters(FormalParameterList parameters) {
|
||||
var children = <ComparisonNode>[];
|
||||
// Note: parameters == null for getters
|
||||
if (parameters != null) {
|
||||
for (var parameter in parameters.parameters) {
|
||||
var element = parameter.declaredElement;
|
||||
var kind = element.isNotOptional
|
||||
? 'Required'
|
||||
: element.isOptionalPositional ? 'Optional' : 'Named';
|
||||
var parameterChildren = <ComparisonNode>[];
|
||||
parameterChildren.add(_translateType('Type: ', element.type));
|
||||
children.add(
|
||||
ComparisonNode.sorted('$kind: ${element.name}', parameterChildren));
|
||||
}
|
||||
}
|
||||
_resultNodes.add(ComparisonNode('Parameters', children));
|
||||
}
|
||||
|
||||
void _visitTypedef(TypeAlias node) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _AnalyzerVisitor(_typeProvider, children);
|
||||
GenericTypeAliasElement element = node.declaredElement;
|
||||
visitor._visitTypeParameters(element.typeParameters);
|
||||
children.add(_translateType('Type: ', element.function.type));
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('Typedef ${node.name.name}', children));
|
||||
}
|
||||
|
||||
void _visitTypeParameters(List<TypeParameterElement> typeParameters) {
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
_resultNodes.add(ComparisonNode(
|
||||
'Type parameter $i: ${typeParameters[i].name}', [
|
||||
_translateType(
|
||||
'Bound: ', typeParameters[i].bound ?? _typeProvider.objectType)
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
// Copyright (c) 2018, 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 'dart:math';
|
||||
|
||||
/// [ComparisonNode] defines a simple tree structure that can be used to compare
|
||||
/// two representations of Dart code.
|
||||
///
|
||||
/// Each node contains a textual string and a list of child nodes.
|
||||
class ComparisonNode {
|
||||
final String text;
|
||||
final List<ComparisonNode> children;
|
||||
|
||||
ComparisonNode(this.text, [List<ComparisonNode> children])
|
||||
: children = children ?? <ComparisonNode>[];
|
||||
|
||||
factory ComparisonNode.sorted(
|
||||
String text, Iterable<ComparisonNode> children) =>
|
||||
ComparisonNode(text, sortList(children));
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is ComparisonNode) {
|
||||
if (text != other.text) return false;
|
||||
if (children.length != other.children.length) return false;
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
if (children[i] != other.children[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String toString({String newline: '\n'}) {
|
||||
var lines = ['$text'];
|
||||
var indentedNewline = '$newline ';
|
||||
for (var child in children) {
|
||||
lines.add(child.toString(newline: indentedNewline));
|
||||
}
|
||||
return lines.join(indentedNewline);
|
||||
}
|
||||
|
||||
static ComparisonNode diff(
|
||||
ComparisonNode a, ComparisonNode b, String aName, String bName) {
|
||||
if (a.text == b.text) {
|
||||
return ComparisonNode(
|
||||
a.text, diffLists(a.children, b.children, aName, bName));
|
||||
} else {
|
||||
return ComparisonNode('Root nodes differ',
|
||||
[_prefix('In $aName: ', a), _prefix('In $bName: ', b)]);
|
||||
}
|
||||
}
|
||||
|
||||
static List<ComparisonNode> diffLists(List<ComparisonNode> a,
|
||||
List<ComparisonNode> b, String aName, String bName) {
|
||||
// Note: this is an O(n) "poor man's" diff algorithm; it produces optimal
|
||||
// results if the incoming results are sorted by text or if there is just
|
||||
// one contiguous hunk of differences. Otherwise it may not find the
|
||||
// shortest diff. This should be sufficient for our purposes, since we are
|
||||
// not expecting many diffs.
|
||||
|
||||
// We'll exclude common nodes at the beginning of both lists
|
||||
var shorterLength = min(a.length, b.length);
|
||||
var commonInitialNodes = 0;
|
||||
while (commonInitialNodes < shorterLength &&
|
||||
a[commonInitialNodes] == b[commonInitialNodes]) {
|
||||
commonInitialNodes++;
|
||||
}
|
||||
|
||||
// Fast exit if a == b
|
||||
if (commonInitialNodes == a.length && a.length == b.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// We'll exclude common nodes at the end of both lists (note: we don't want
|
||||
// to overcount by re-testing the common nodes identified above)
|
||||
var commonFinalNodes = 0;
|
||||
while (commonInitialNodes + commonFinalNodes < shorterLength &&
|
||||
a[a.length - commonFinalNodes - 1] ==
|
||||
b[b.length - commonFinalNodes - 1]) {
|
||||
commonFinalNodes++;
|
||||
}
|
||||
|
||||
// Walk the remaining nodes starting at the first node that's different,
|
||||
// matching up nodes by their text.
|
||||
var aIndex = commonInitialNodes;
|
||||
var bIndex = commonInitialNodes;
|
||||
var aEnd = a.length - commonFinalNodes;
|
||||
var bEnd = b.length - commonFinalNodes;
|
||||
var result = <ComparisonNode>[];
|
||||
while (aIndex < aEnd && bIndex < bEnd) {
|
||||
var comparisonResult = a[aIndex].text.compareTo(b[bIndex].text);
|
||||
if (comparisonResult < 0) {
|
||||
// a[aIndex].text sorts before b[bIndex].text. Assume that this means
|
||||
// a[aIndex] was removed.
|
||||
result.add(_prefix('Only in $aName: ', a[aIndex++]));
|
||||
} else if (comparisonResult > 0) {
|
||||
// b[bIndex].text sorts before a[aIndex].text. Assume that this means
|
||||
// b[bIndex] was added.
|
||||
result.add(_prefix('Only in $bName: ', b[bIndex++]));
|
||||
} else {
|
||||
// a[aIndex].text matches b[bIndex].text, so diff the nodes if
|
||||
// necessary.
|
||||
var aNode = a[aIndex++];
|
||||
var bNode = b[bIndex++];
|
||||
if (aNode != bNode) {
|
||||
result.add(diff(aNode, bNode, aName, bName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with any nodes left over.
|
||||
while (aIndex < aEnd) {
|
||||
result.add(_prefix('Only in $aName: ', a[aIndex++]));
|
||||
}
|
||||
while (bIndex < bEnd) {
|
||||
result.add(_prefix('Only in $bName: ', b[bIndex++]));
|
||||
}
|
||||
|
||||
// If we get here and we haven't added any nodes, something has gone wrong.
|
||||
if (result.isEmpty) {
|
||||
throw StateError('Diff produced empty diff for non-matching lists');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static List<ComparisonNode> sortList(Iterable<ComparisonNode> nodes) {
|
||||
var result = nodes.toList();
|
||||
result.sort((a, b) => a.text.compareTo(b.text));
|
||||
return result;
|
||||
}
|
||||
|
||||
static ComparisonNode _prefix(String prefixString, ComparisonNode node) {
|
||||
return ComparisonNode(prefixString + node.text, node.children);
|
||||
}
|
||||
}
|
|
@ -1,360 +0,0 @@
|
|||
// Copyright (c) 2018, 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 'dart:async';
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
|
||||
show DiagnosticMessage, DiagnosticMessageHandler, getMessageHeaderText;
|
||||
import 'package:analyzer_fe_comparison/src/comparison_node.dart';
|
||||
import 'package:front_end/src/api_prototype/compiler_options.dart'
|
||||
show CompilerOptions;
|
||||
import 'package:front_end/src/api_prototype/kernel_generator.dart';
|
||||
import 'package:front_end/src/api_prototype/standard_file_system.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/target/targets.dart';
|
||||
|
||||
/// Compiles the given [inputs] to kernel using the front_end, and returns a
|
||||
/// [ComparisonNode] representing them.
|
||||
Future<ComparisonNode> analyzePackage(
|
||||
List<Uri> inputs, Uri packagesFileUri, Uri platformUri) async {
|
||||
var messages = <DiagnosticMessage>[];
|
||||
var component = (await kernelForModule(inputs,
|
||||
_makeCompilerOptions(packagesFileUri, platformUri, messages.add)))
|
||||
.component;
|
||||
if (messages.isNotEmpty) {
|
||||
return ComparisonNode(
|
||||
'Error occurred', messages.map(_diagnosticMessageToNode).toList());
|
||||
}
|
||||
var libraryNodes = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(libraryNodes);
|
||||
for (var library in component.libraries) {
|
||||
if (inputs.contains(library.importUri)) {
|
||||
library.accept(visitor);
|
||||
}
|
||||
}
|
||||
return ComparisonNode.sorted('Component', libraryNodes);
|
||||
}
|
||||
|
||||
/// Compiles the given [input] to kernel using the front_end, and returns a
|
||||
/// [ComparisonNode] representing it.
|
||||
///
|
||||
/// Only libraries whose URI passes the [uriFilter] are included in the results.
|
||||
Future<ComparisonNode> analyzeProgram(Uri input, Uri packagesFileUri,
|
||||
Uri platformUri, bool uriFilter(Uri uri)) async {
|
||||
var messages = <DiagnosticMessage>[];
|
||||
var component = (await kernelForProgram(input,
|
||||
_makeCompilerOptions(packagesFileUri, platformUri, messages.add)))
|
||||
.component;
|
||||
if (messages.isNotEmpty) {
|
||||
return ComparisonNode(
|
||||
'Error occurred', messages.map(_diagnosticMessageToNode).toList());
|
||||
}
|
||||
var libraryNodes = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(libraryNodes);
|
||||
for (var library in component.libraries) {
|
||||
if (uriFilter(library.importUri)) {
|
||||
library.accept(visitor);
|
||||
}
|
||||
}
|
||||
return ComparisonNode.sorted('Component', libraryNodes);
|
||||
}
|
||||
|
||||
ComparisonNode _diagnosticMessageToNode(DiagnosticMessage message) {
|
||||
return ComparisonNode(getMessageHeaderText(message));
|
||||
}
|
||||
|
||||
CompilerOptions _makeCompilerOptions(Uri packagesFileUri, Uri platformUri,
|
||||
DiagnosticMessageHandler onDiagnostic) {
|
||||
var targetFlags = TargetFlags();
|
||||
var target = NoneTarget(targetFlags);
|
||||
var fileSystem = StandardFileSystem.instance;
|
||||
|
||||
return CompilerOptions()
|
||||
..fileSystem = fileSystem
|
||||
..packagesFileUri = packagesFileUri
|
||||
..sdkSummary = platformUri
|
||||
..target = target
|
||||
..throwOnErrorsForDebugging = false
|
||||
..embedSourceText = false
|
||||
..onDiagnostic = onDiagnostic;
|
||||
}
|
||||
|
||||
/// Visitor for serializing a kernel representation of a program into
|
||||
/// ComparisonNodes.
|
||||
///
|
||||
/// Results are accumulated into [_resultNodes].
|
||||
class _KernelVisitor extends TreeVisitor<void> {
|
||||
final List<ComparisonNode> _resultNodes;
|
||||
|
||||
_KernelVisitor(this._resultNodes);
|
||||
|
||||
@override
|
||||
void defaultTreeNode(TreeNode node) {
|
||||
throw new UnimplementedError('KernelVisitor: ${node.runtimeType}');
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClass(Class class_) {
|
||||
if (class_.isAnonymousMixin) return null;
|
||||
var kind = class_.isEnum
|
||||
? 'Enum'
|
||||
: class_.isMixinApplication
|
||||
? 'MixinApplication'
|
||||
: class_.isMixinDeclaration ? 'Mixin' : 'Class';
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(children);
|
||||
if (class_.isEnum) {
|
||||
for (var field in class_.fields) {
|
||||
if (!field.isStatic) continue;
|
||||
if (field.name.name == 'values') continue;
|
||||
// TODO(paulberry): handle index
|
||||
children.add(ComparisonNode('EnumValue ${field.name.name}'));
|
||||
}
|
||||
} else {
|
||||
visitor._visitTypeParameters(class_.typeParameters);
|
||||
if (class_.supertype != null) {
|
||||
var declaredSupertype = class_.supertype.asInterfaceType;
|
||||
if (class_.isMixinDeclaration) {
|
||||
var constraints = <DartType>[];
|
||||
// Kernel represents a mixin declaration such as:
|
||||
// mixin M on S0, S1, S2 {...}
|
||||
// By desugaring it to:
|
||||
// abstract class _M&S0&S1 implements S0, S1 {}
|
||||
// abstract class _M&S0&S1&S2 implements _M&S0&S1 {}
|
||||
// abstract class M extends M&S0&S1&S2 {...}
|
||||
// (See dartbug.com/34783)
|
||||
while (declaredSupertype.classNode.isAnonymousMixin) {
|
||||
// Since we're walking up the class hierarchy, we encounter the
|
||||
// mixins in the reverse order that they were declared, so we have
|
||||
// to use [List.insert] to add them to [constraints].
|
||||
constraints.insert(
|
||||
0,
|
||||
declaredSupertype
|
||||
.classNode.implementedTypes[1].asInterfaceType);
|
||||
declaredSupertype =
|
||||
declaredSupertype.classNode.implementedTypes[0].asInterfaceType;
|
||||
}
|
||||
constraints.insert(0, declaredSupertype);
|
||||
for (int i = 0; i < constraints.length; i++) {
|
||||
children.add(_TypeVisitor.translate('On $i: ', constraints[i]));
|
||||
}
|
||||
} else {
|
||||
var mixedInTypes = <DartType>[];
|
||||
if (class_.isMixinApplication) {
|
||||
mixedInTypes.add(class_.mixedInType.asInterfaceType);
|
||||
}
|
||||
while (declaredSupertype.classNode.isAnonymousMixin) {
|
||||
// Since we're walking from the class to its declared supertype, we
|
||||
// encounter the mixins in the reverse order that they were declared,
|
||||
// so we have to use [List.insert] to add them to [mixedInTypes].
|
||||
mixedInTypes.insert(
|
||||
0, declaredSupertype.classNode.mixedInType.asInterfaceType);
|
||||
declaredSupertype =
|
||||
declaredSupertype.classNode.supertype.asInterfaceType;
|
||||
}
|
||||
children.add(_TypeVisitor.translate('Extends: ', declaredSupertype));
|
||||
for (int i = 0; i < mixedInTypes.length; i++) {
|
||||
children.add(_TypeVisitor.translate('Mixin $i: ', mixedInTypes[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < class_.implementedTypes.length; i++) {
|
||||
children.add(_TypeVisitor.translate(
|
||||
'Implements $i: ', class_.implementedTypes[i].asInterfaceType));
|
||||
}
|
||||
visitor._visitList(class_.fields);
|
||||
visitor._visitList(class_.constructors);
|
||||
visitor._visitList(class_.procedures);
|
||||
}
|
||||
// TODO(paulberry): handle more fields from Class
|
||||
_resultNodes.add(ComparisonNode.sorted('$kind ${class_.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConstructor(Constructor constructor) {
|
||||
if (constructor.isSynthetic) return null;
|
||||
var name = constructor.name.name;
|
||||
if (name.isEmpty) {
|
||||
name = '(unnamed)';
|
||||
}
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(children);
|
||||
constructor.function.accept(visitor);
|
||||
// TODO(paulberry): handle more fields from Constructor
|
||||
_resultNodes.add(ComparisonNode.sorted('Constructor $name', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitField(Field field) {
|
||||
if (field.name.name == '_redirecting#') return null;
|
||||
if (field.name.name == '_exports#') return null;
|
||||
var children = <ComparisonNode>[];
|
||||
children.add(_TypeVisitor.translate('Type: ', field.type));
|
||||
// TODO(paulberry): handle more fields from Field
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('Field ${field.name.name}', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionNode(FunctionNode node) {
|
||||
var parent = node.parent;
|
||||
if (!(parent is Constructor || parent is Procedure && parent.isFactory)) {
|
||||
_visitTypeParameters(node.typeParameters);
|
||||
_resultNodes
|
||||
.add(_TypeVisitor.translate('Return type: ', node.returnType));
|
||||
}
|
||||
var parameterChildren = <ComparisonNode>[];
|
||||
var parameterVisitor = _KernelVisitor(parameterChildren);
|
||||
for (int i = 0; i < node.positionalParameters.length; i++) {
|
||||
parameterVisitor._visitParameter(node.positionalParameters[i],
|
||||
i < node.requiredParameterCount ? 'Required' : 'Optional');
|
||||
}
|
||||
for (int i = 0; i < node.namedParameters.length; i++) {
|
||||
parameterVisitor._visitParameter(node.namedParameters[i], 'Named');
|
||||
}
|
||||
_resultNodes.add(ComparisonNode('Parameters', parameterChildren));
|
||||
// TODO(paulberry): handle more fields from FunctionNode
|
||||
}
|
||||
|
||||
@override
|
||||
void visitLibrary(Library library) {
|
||||
var children = <ComparisonNode>[];
|
||||
if (library.name != null) {
|
||||
children.add(ComparisonNode('name=${library.name}'));
|
||||
}
|
||||
var visitor = _KernelVisitor(children);
|
||||
visitor._visitList(library.typedefs);
|
||||
visitor._visitList(library.classes);
|
||||
visitor._visitList(library.procedures);
|
||||
visitor._visitList(library.fields);
|
||||
// TODO(paulberry): handle more fields from Library
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted(library.importUri.toString(), children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitProcedure(Procedure procedure) {
|
||||
if (procedure.isSyntheticForwarder) {
|
||||
return null;
|
||||
}
|
||||
if (procedure.name.name.startsWith('_#loadLibrary_')) {
|
||||
// Sometimes the front end generates procedures with this name that don't
|
||||
// correspond to anything in the source file. Ignore them.
|
||||
return null;
|
||||
}
|
||||
// TODO(paulberry): add an annotation to the ComparisonNode when the
|
||||
// procedure is a factory.
|
||||
var kind = procedure.isFactory
|
||||
? 'Constructor'
|
||||
: procedure.kind.toString().replaceAll('ProcedureKind.', '');
|
||||
var name = procedure.name.name;
|
||||
if (name.isEmpty) {
|
||||
name = '(unnamed)';
|
||||
}
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(children);
|
||||
procedure.function.accept(visitor);
|
||||
// TODO(paulberry): handle more fields from Procedure
|
||||
_resultNodes.add(ComparisonNode.sorted('$kind $name', children));
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTypedef(Typedef typedef) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(children);
|
||||
visitor._visitTypeParameters(typedef.typeParameters);
|
||||
children.add(_TypeVisitor.translate('Type: ', typedef.type));
|
||||
// TODO(paulberry): handle more fields from Typedef
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('Typedef ${typedef.name}', children));
|
||||
}
|
||||
|
||||
/// Visits all the nodes in [nodes].
|
||||
void _visitList(List<TreeNode> nodes) {
|
||||
for (var node in nodes) {
|
||||
node.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitParameter(VariableDeclaration parameter, String kind) {
|
||||
var children = <ComparisonNode>[];
|
||||
children.add(_TypeVisitor.translate('Type: ', parameter.type));
|
||||
// TODO(paulberry): handle more fields from VariableDeclaration
|
||||
_resultNodes
|
||||
.add(ComparisonNode.sorted('$kind: ${parameter.name}', children));
|
||||
}
|
||||
|
||||
void _visitTypeParameters(List<TypeParameter> typeParameters) {
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
_resultNodes.add(ComparisonNode(
|
||||
'Type parameter $i: ${typeParameters[i].name}',
|
||||
[_TypeVisitor.translate('Bound: ', typeParameters[i].bound)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor for serializing a kernel representation of a type into
|
||||
/// ComparisonNodes.
|
||||
class _TypeVisitor extends DartTypeVisitor<ComparisonNode> {
|
||||
/// Text to prepend to the node text.
|
||||
String _prefix;
|
||||
|
||||
_TypeVisitor(this._prefix);
|
||||
|
||||
@override
|
||||
ComparisonNode defaultDartType(DartType node) {
|
||||
throw new UnimplementedError('_TypeVisitor: ${node.runtimeType}');
|
||||
}
|
||||
|
||||
@override
|
||||
ComparisonNode visitDynamicType(DynamicType node) {
|
||||
return ComparisonNode('${_prefix}Dynamic');
|
||||
}
|
||||
|
||||
@override
|
||||
ComparisonNode visitFunctionType(FunctionType node) {
|
||||
var children = <ComparisonNode>[];
|
||||
var visitor = _KernelVisitor(children);
|
||||
visitor._visitTypeParameters(node.typeParameters);
|
||||
children.add(translate('Return type: ', node.returnType));
|
||||
for (int i = 0; i < node.positionalParameters.length; i++) {
|
||||
var kind = i < node.requiredParameterCount ? 'Required' : 'Optional';
|
||||
children
|
||||
.add(translate('$kind parameter $i: ', node.positionalParameters[i]));
|
||||
}
|
||||
for (var namedType in node.namedParameters) {
|
||||
children.add(
|
||||
translate('Named parameter ${namedType.name}: ', namedType.type));
|
||||
}
|
||||
return ComparisonNode.sorted('${_prefix}FunctionType', children);
|
||||
}
|
||||
|
||||
@override
|
||||
ComparisonNode visitInterfaceType(InterfaceType node) {
|
||||
var children = <ComparisonNode>[];
|
||||
children.add(ComparisonNode(
|
||||
'Library: ${node.classNode.enclosingLibrary.importUri}'));
|
||||
for (int i = 0; i < node.typeArguments.length; i++) {
|
||||
children.add(translate('Type arg $i: ', node.typeArguments[i]));
|
||||
}
|
||||
return ComparisonNode(
|
||||
'${_prefix}InterfaceType ${node.classNode.name}', children);
|
||||
}
|
||||
|
||||
@override
|
||||
ComparisonNode visitTypeParameterType(TypeParameterType node) {
|
||||
// TODO(paulberry): disambiguate if needed.
|
||||
return ComparisonNode(
|
||||
'${_prefix}TypeParameterType: ${node.parameter.name}');
|
||||
}
|
||||
|
||||
@override
|
||||
ComparisonNode visitVoidType(VoidType node) {
|
||||
return ComparisonNode('${_prefix}Void');
|
||||
}
|
||||
|
||||
static ComparisonNode translate(String prefix, DartType type) {
|
||||
return type.accept(new _TypeVisitor(prefix));
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
name: analyzer_fe_comparison
|
||||
publish_to: none
|
||||
author: Dart Team <misc@dartlang.org>
|
||||
description: Tool for comparing the behavior of the analyzer and the front end.
|
||||
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_fe_comparison
|
||||
environment:
|
||||
sdk: '>=2.0.0-dev-48.0 <3.0.0'
|
||||
dependencies:
|
||||
kernel: ^0.3.4
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) 2018, 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 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer_fe_comparison/comparison.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/// Compares the analyzer and front_end behavior when compiling a package.
|
||||
///
|
||||
/// Currently hardcoded to use the package "analyzer".
|
||||
main() async {
|
||||
var scriptPath = Platform.script.toFilePath();
|
||||
var sdkRepoPath =
|
||||
path.normalize(path.join(path.dirname(scriptPath), '..', '..', '..'));
|
||||
var buildPath = await _findBuildDir(sdkRepoPath, 'ReleaseX64');
|
||||
var dillPath = path.join(buildPath, 'vm_platform_strong.dill');
|
||||
var analyzerLibPath = path.join(sdkRepoPath, 'pkg', 'analyzer', 'lib');
|
||||
var packagesFilePath = path.join(sdkRepoPath, '.packages');
|
||||
comparePackages(dillPath, analyzerLibPath, packagesFilePath);
|
||||
}
|
||||
|
||||
Future<String> _findBuildDir(String sdkRepoPath, String targetName) async {
|
||||
for (var subdirName in ['out', 'xcodebuild']) {
|
||||
var candidatePath = path.join(sdkRepoPath, subdirName, targetName);
|
||||
if (await new Directory(candidatePath).exists()) {
|
||||
return candidatePath;
|
||||
}
|
||||
}
|
||||
throw new StateError('Cannot find build directory');
|
||||
}
|
Loading…
Reference in a new issue