未验证 提交 8df1757d 编写于 作者: D Dan Field 提交者: GitHub

const finder (#15668)

上级 85a8ac42
......@@ -37,6 +37,7 @@ group("flutter") {
if (current_toolchain == host_toolchain) {
public_deps += [ "$flutter_root/shell/testing" ]
public_deps += [ "//flutter/tools/const_finder" ]
}
if (is_fuchsia && using_fuchsia_sdk) {
......
......@@ -307,8 +307,16 @@ def RunDartTests(build_dir, filter, verbose_dart_snapshot):
RunDartTest(build_dir, dart_test_file, verbose_dart_snapshot, True)
RunDartTest(build_dir, dart_test_file, verbose_dart_snapshot, False)
def RunConstFinderTests(build_dir):
test_dir = os.path.join(buildroot_dir, 'flutter', 'tools', 'const_finder', 'test')
opts = [
os.path.join(test_dir, 'const_finder_test.dart'),
os.path.join(build_dir, 'gen', 'frontend_server.dart.snapshot'),
os.path.join(build_dir, 'flutter_patched_sdk')]
RunEngineExecutable(build_dir, os.path.join('dart-sdk', 'bin', 'dart'), None, flags=opts, cwd=test_dir)
def main():
parser = argparse.ArgumentParser();
parser = argparse.ArgumentParser()
parser.add_argument('--variant', dest='variant', action='store',
default='host_debug_unopt', help='The engine build variant to run the tests for.');
......@@ -344,6 +352,7 @@ def main():
assert not IsWindows(), "Dart tests can't be run on windows. https://github.com/flutter/flutter/issues/36301."
dart_filter = args.dart_filter.split(',') if args.dart_filter else None
RunDartTests(build_dir, dart_filter, args.verbose_dart_snapshot)
RunConstFinderTests(build_dir)
if 'java' in types:
assert not IsWindows(), "Android engine files can't be compiled on Windows."
......
{
"configVersion": 2,
"packages": [
{
"name": "args",
"rootUri": "../../../../third_party/dart/third_party/pkg/args",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "kernel",
"rootUri": "../../../../third_party/dart/pkg/kernel",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "meta",
"rootUri": "../../../../third_party/dart/pkg/meta",
"packageUri": "lib/",
"languageVersion": "1.12"
},
{
"name": "path",
"rootUri": "../../../../third_party/dart/third_party/pkg/path",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "const_finder",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "2.4"
}
],
"generated": "2020-01-16T19:11:54.963296Z",
"generator": "pub",
"generatorVersion": "2.7.0"
}
*.dill
!.dart_tool
!.dart_tool/package_config.json
!.packages
!test/fixtures/.packages
# Generated by pub on 2020-01-16 11:11:54.947929.
args:../../../third_party/dart/third_party/pkg/args/lib/
kernel:../../../third_party/dart/pkg/kernel/lib/
meta:../../../third_party/dart/pkg/meta/lib/
path:../../../third_party/dart/third_party/pkg/path/lib/
const_finder:lib/
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//third_party/dart/utils/application_snapshot.gni")
application_snapshot("const_finder") {
main_dart = "bin/main.dart"
dot_packages = ".packages"
training_args = [ "--help" ]
inputs = [
"bin/main.dart",
"lib/const_finder.dart",
".packages",
".dart_tool/package_config.json",
]
deps = [
"//flutter/flutter_frontend_server:frontend_server",
]
}
# Const Finder
This program uses package:kernel from the Dart SDK in //third_party.
A snapshot is created via the build rules in BUILD.gn. This is then vended
to the Flutter tool, which uses it to find `const` creations of `IconData`
classes. The information from this can then be passed to the `font-subset` tool
to create a smaller icon font file specific to the application.
Once [flutter/flutter#47162](https://github.com/flutter/flutter/issues/47162) is
resolved, this package should be moved to the flutter tool.
// Copyright 2013 The Flutter Authors. 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:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:const_finder/const_finder.dart';
void main(List<String> args) {
final ArgParser parser = ArgParser();
parser
..addSeparator('Finds constant instances of a specified class from the\n'
'specified package, and outputs JSON like the following:')
..addSeparator('''
{
"constantInstances": [
{
"codePoint": 59470,
"fontFamily": "MaterialIcons",
"fontPackage": null,
"matchTextDirection": false
}
],
"nonConstantInstances": [
{
"file": "file:///Path/to/hello_world/lib/file.dart",
"line": 19,
"column": 11
}
]
}''')
..addSeparator('Where the "constantInstances" is a list of objects containing\n'
'the properties passed to the const constructor of the class, and\n'
'"nonConstantInstances" is a list of source locations of non-constant\n'
'creation of the specified class. Non-constant creation cannot be\n'
'statically evaluated by this tool, and callers may wish to treat them\n'
'as errors. The non-constant creation may include entries that are not\n'
'reachable at runtime.')
..addSeparator('Required arguments:')
..addOption('kernel-file',
valueHelp: 'path/to/main.dill',
help: 'The path to a kernel file to parse, which was created from the '
'main-package-uri library.')
..addOption('main-library-uri',
help: 'The package: URI to treat as the main entrypoint library '
'(the same package used to create the kernel-file).',
valueHelp: 'package:hello_world/main.dart')
..addOption('class-library-uri',
help: 'The package: URI of the class to find.',
valueHelp: 'package:flutter/src/widgets/icon_data.dart')
..addOption('class-name',
help: 'The class name for the class to find.', valueHelp: 'IconData')
..addSeparator('Optional arguments:')
..addFlag('pretty',
defaultsTo: false,
negatable: false,
help: 'Pretty print JSON output (defaults to false).')
..addFlag('help',
abbr: 'h',
defaultsTo: false,
negatable: false,
help: 'Print usage and exit');
final ArgResults argResults = parser.parse(args);
T getArg<T>(String name) => argResults[name] as T;
if (getArg<bool>('help')) {
stdout.writeln(parser.usage);
exit(0);
}
final ConstFinder finder = ConstFinder(
kernelFilePath: getArg<String>('kernel-file'),
targetLibraryUri: getArg<String>('main-library-uri'),
classLibraryUri: getArg<String>('class-library-uri'),
className: getArg<String>('class-name'),
);
final JsonEncoder encoder = getArg<bool>('pretty')
? const JsonEncoder.withIndent(' ')
: const JsonEncoder();
stdout.writeln(encoder.convert(finder.findInstances()));
}
// Copyright 2013 The Flutter Authors. 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:kernel/kernel.dart' hide MapEntry;
import 'package:meta/meta.dart';
class _ConstVisitor extends RecursiveVisitor<void> {
_ConstVisitor(
this.kernelFilePath,
this.targetLibraryUri,
this.classLibraryUri,
this.className,
) : assert(kernelFilePath != null),
assert(targetLibraryUri != null),
assert(classLibraryUri != null),
assert(className != null),
constantInstances = <Map<String, dynamic>>[],
nonConstantLocations = <Map<String, dynamic>>[];
/// The path to the file to open.
final String kernelFilePath;
/// The library URI for the main entrypoint of the target library.
final String targetLibraryUri;
/// The library URI for the class to find.
final String classLibraryUri;
/// The name of the class to find.
final String className;
final List<Map<String, dynamic>> constantInstances;
final List<Map<String, dynamic>> nonConstantLocations;
bool _matches(Class node) {
return node.enclosingLibrary.canonicalName.name == classLibraryUri &&
node.name == className;
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
final Class parentClass = node.target.parent as Class;
if (!_matches(parentClass)) {
super.visitConstructorInvocation(node);
}
nonConstantLocations.add(<String, dynamic>{
'file': node.location.file.toString(),
'line': node.location.line,
'column': node.location.column,
});
}
@override
void visitInstanceConstantReference(InstanceConstant node) {
if (!_matches(node.classNode)) {
return;
}
final Map<String, dynamic> instance = <String, dynamic>{};
for (MapEntry<Reference, Constant> kvp in node.fieldValues.entries) {
final PrimitiveConstant<dynamic> value = kvp.value as PrimitiveConstant<dynamic>;
instance[kvp.key.canonicalName.name] = value.value;
}
constantInstances.add(instance);
}
}
/// A kernel AST visitor that finds const references.
class ConstFinder {
/// Creates a new ConstFinder class. All arguments are required and must not
/// be null.
///
/// The `kernelFilePath` is the path to a dill (kernel) file to process.
///
/// The `targetLibraryUri` is the `package:` URI of the main entrypoint to
/// search from.
///
///
///
ConstFinder({
@required String kernelFilePath,
@required String targetLibraryUri,
@required String classLibraryUri,
@required String className,
}) : _visitor = _ConstVisitor(
kernelFilePath,
targetLibraryUri,
classLibraryUri,
className,
);
final _ConstVisitor _visitor;
Library _getRoot() {
final Component binary = loadComponentFromBinary(_visitor.kernelFilePath);
return binary.libraries.firstWhere(
(Library library) => library.canonicalName.name == _visitor.targetLibraryUri,
orElse: () => throw LibraryNotFoundException._(_visitor.targetLibraryUri),
);
}
/// Finds all instances
Map<String, dynamic> findInstances() {
final Library root = _getRoot();
root.visitChildren(_visitor);
return <String, dynamic>{
'constantInstances': _visitor.constantInstances,
'nonConstantLocations': _visitor.nonConstantLocations,
};
}
}
/// Exception thrown by [ConstFinder.findInstances] when the target library
/// is not found.
class LibraryNotFoundException implements Exception {
const LibraryNotFoundException._(this.targetLibraryUri);
/// The library target URI that could not be found.
final String targetLibraryUri;
@override
String toString() => 'Could not find target library for "$targetLibraryUri".';
}
# Copyright 2013 The Flutter Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
name: const_finder
publish_to: none
environment:
sdk: ">=2.4.0 <3.0.0"
# Do not add any dependencies that require more than what is provided in
# //third_party/dart/pkg or //third_party/dart/third_party/pkg.
# In particular, package:test is not usable here.
# If you do add packages here, make sure you can run `pub get --offline`, and
# check the .packages and .package_config to make sure all the paths are
# relative to this directory into //third_party/dart
dependencies:
args: any
meta: any
kernel: any
dev_dependencies:
path: any
dependency_overrides:
args:
path: ../../../third_party/dart/third_party/pkg/args
kernel:
path: ../../../third_party/dart/pkg/kernel
meta:
path: ../../../third_party/dart/pkg/meta
path:
path: ../../../third_party/dart/third_party/pkg/path
// Copyright 2013 The Flutter Authors. 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:convert' show jsonEncode;
import 'dart:io';
import 'package:const_finder/const_finder.dart';
import 'package:path/path.dart' as path;
void expect<T>(T value, T expected) {
if (value != expected) {
stderr.writeln('Expected: $expected');
stderr.writeln('Actual: $value');
exitCode = -1;
}
}
final String basePath =
path.canonicalize(path.join(path.dirname(Platform.script.path), '..'));
final String fixtures = path.join(basePath, 'test', 'fixtures');
final String consts = path.join(fixtures, 'lib', 'consts.dart');
final String dotPackages = path.join(fixtures, '.packages');
final String constsAndNon = path.join(fixtures, 'lib', 'consts_and_non.dart');
final String constsDill = path.join(fixtures, 'consts.dill');
final String constsAndNonDill = path.join(fixtures, 'consts_and_non.dill');
// This test is assuming the `dart` used to invoke the tests is compatible
// with the version of package:kernel in //third-party/dart/pkg/kernel
final String dart = Platform.resolvedExecutable;
final String bat = Platform.isWindows ? '.bat' : '';
void _checkConsts() {
print('Checking for expected constants.');
final ConstFinder finder = ConstFinder(
kernelFilePath: constsDill,
targetLibraryUri: 'package:const_finder_fixtures/consts.dart',
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'Target',
);
expect<String>(
jsonEncode(finder.findInstances()),
jsonEncode(<String, dynamic>{
'constantInstances': <Map<String, dynamic>>[
<String, dynamic>{'stringValue': '1', 'intValue': 1},
<String, dynamic>{'stringValue': '2', 'intValue': 2}
],
'nonConstantLocations': <dynamic>[],
}),
);
}
void _checkNonConsts() {
print('Checking for non-constant instances.');
final ConstFinder finder = ConstFinder(
kernelFilePath: constsAndNonDill,
targetLibraryUri: 'package:const_finder_fixtures/consts_and_non.dart',
classLibraryUri: 'package:const_finder_fixtures/target.dart',
className: 'Target',
);
expect<String>(
jsonEncode(finder.findInstances()),
jsonEncode(<String, dynamic>{
'constantInstances': <dynamic>[
<String, dynamic>{'stringValue': '1', 'intValue': 1}
],
'nonConstantLocations': <dynamic>[
<String, dynamic>{
'file': 'file://$fixtures/lib/consts_and_non.dart',
'line': 12,
'column': 26
},
<String, dynamic>{
'file': 'file://$fixtures/lib/consts_and_non.dart',
'line': 14,
'column': 26
},
]
}),
);
}
Future<void> main(List<String> args) async {
if (args.length != 2) {
stderr.writeln('The first argument must be the path to the forntend server dill.');
stderr.writeln('The second argument must be the path to the flutter_patched_sdk');
exit(-1);
}
final String frontendServer = args[0];
final String sdkRoot = args[1];
try {
void _checkProcessResult(ProcessResult result) {
if (result.exitCode != 0) {
stdout.writeln(result.stdout);
stderr.writeln(result.stderr);
}
expect(result.exitCode, 0);
}
stdout.writeln('Generating kernel fixtures...');
stdout.writeln(consts);
_checkProcessResult(Process.runSync(dart, <String>[
frontendServer,
'--sdk-root=$sdkRoot',
'--target=flutter',
'--aot',
'--tfa',
'--packages=$dotPackages',
'--output-dill=$constsDill',
consts,
]));
_checkProcessResult(Process.runSync(dart, <String>[
frontendServer,
'--sdk-root=$sdkRoot',
'--target=flutter',
'--aot',
'--tfa',
'--packages=$dotPackages',
'--output-dill=$constsAndNonDill',
constsAndNon,
]));
_checkConsts();
_checkNonConsts();
} finally {
try {
File(constsDill).deleteSync();
File(constsAndNonDill).deleteSync();
} finally {
stdout.writeln('Tests ${exitCode == 0 ? 'succeeded' : 'failed'} - exit code: $exitCode');
}
}
}
# Generated by pub on 2020-01-15 10:08:29.776333.
const_finder_fixtures:lib/
// Copyright 2013 The Flutter Authors. 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:core';
import 'target.dart';
void main() {
const Target target1 = Target('1', 1);
const Target target2 = Target('2', 2);
// ignore: unused_local_variable
const Target target3 = Target('3', 3); // should be tree shaken out.
target1.hit();
target2.hit();
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: prefer_const_constructors
import 'dart:core';
import 'target.dart';
void main() {
const Target target1 = Target('1', 1);
final Target target2 = Target('2', 2);
// ignore: unused_local_variable
final Target target3 = Target('3', 3); // should be tree shaken out.
target1.hit();
target2.hit();
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class Target {
const Target(this.stringValue, this.intValue);
final String stringValue;
final int intValue;
void hit() {
print('$stringValue $intValue');
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册