Skip to content

Commit

Permalink
const finder (flutter#15668)
Browse files Browse the repository at this point in the history
  • Loading branch information
dnfield authored Jan 17, 2020
1 parent 85a8ac4 commit 8df1757
Show file tree
Hide file tree
Showing 15 changed files with 525 additions and 1 deletion.
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion testing/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down Expand Up @@ -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."
Expand Down
38 changes: 38 additions & 0 deletions tools/const_finder/.dart_tool/package_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"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"
}
6 changes: 6 additions & 0 deletions tools/const_finder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.dill

!.dart_tool
!.dart_tool/package_config.json
!.packages
!test/fixtures/.packages
6 changes: 6 additions & 0 deletions tools/const_finder/.packages
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 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/
23 changes: 23 additions & 0 deletions tools/const_finder/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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",
]
}
11 changes: 11 additions & 0 deletions tools/const_finder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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.
86 changes: 86 additions & 0 deletions tools/const_finder/bin/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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()));
}
123 changes: 123 additions & 0 deletions tools/const_finder/lib/const_finder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// 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".';
}
35 changes: 35 additions & 0 deletions tools/const_finder/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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
Loading

0 comments on commit 8df1757

Please sign in to comment.