flutter analyze command
Other changes in this patch:
- Make the 'flutter' tool say "Updating flutter tool..." when it calls
pub get, to avoid confusion about what the pub get output is about.
- Make the bash flutter tool call pub get when the revision has
changed. (This was already happening on Windows.)
- Fix a raft of bugs found by the analyzer.
- Fix some style nits in various bits of code that happened to be near
things the analyzer noticed.
- Remove the logic in "flutter test" that would run "pub get", since
upon further reflexion it was determined it didn't work anyway.
We'll probably have to add better diagnostics here and say to run the
updater script.
- Remove the native velocity tracker script, since it was testing code
that has since been removed.
Notes on ignored warnings:
- We ignore warnings in any packages that are not in the Flutter repo or
in the author's current directory.
- We ignore various irrelevant Strong Mode warnings. We still enable
strong mode because even though it's not really relevant to our needs,
it does (more or less accidentally) catch a few things that are
helpful to us.
- We allow CONSTANTS_LIKE_THIS, since we get some of those from other
platforms that we are copying for sanity and consistency.
- We allow one-member abstract classes since we have a number of them
where it's perfectly reasonable.
- We unfortunately still ignore warnings in mojom.dart autogenerated
files. We should really fix those but that's a separate patch.
- We verify the actual source file when we see the 'Name non-constant
identifiers using lowerCamelCase.' lint, to allow one-letter variables
that use capital letters (e.g. for physics expressions) and to allow
multiple-underscore variable names.
- We ignore all errors on lines that contain the following magic
incantation and a "#" character:
// analyzer doesn't like constructor tear-offs
- For all remaining errors, if the line contains a comment of the form
// analyzer says "..."
...then we ignore any errors that have that "..." string in them.
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 39b4e26..44cabd4 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -9,6 +9,7 @@
import 'package:logging/logging.dart';
import 'package:stack_trace/stack_trace.dart';
+import 'src/commands/analyze.dart';
import 'src/commands/build.dart';
import 'src/commands/cache.dart';
import 'src/commands/daemon.dart';
@@ -46,6 +47,7 @@
});
FlutterCommandRunner runner = new FlutterCommandRunner()
+ ..addCommand(new AnalyzeCommand())
..addCommand(new BuildCommand())
..addCommand(new CacheCommand())
..addCommand(new DaemonCommand())
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 6310851..c0e4938 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -48,9 +48,9 @@
// In the fullness of time, we'll have a consistent URL pattern for all of
// our artifacts, but, for the time being, Mac OS X artifacts are stored in a
// different cloud storage bucket.
- return 'https://storage.googleapis.com/mojo_infra/flutter/${platform}/${revision}/';
+ return 'https://storage.googleapis.com/mojo_infra/flutter/$platform/$revision/';
}
- return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${revision}/';
+ return 'https://storage.googleapis.com/mojo/sky/$category/$platform/$revision/';
}
enum ArtifactType {
diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart
new file mode 100644
index 0000000..f4fa293
--- /dev/null
+++ b/packages/flutter_tools/lib/src/commands/analyze.dart
@@ -0,0 +1,302 @@
+// Copyright 2015 The Chromium 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:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as path;
+
+import '../artifacts.dart';
+import '../build_configuration.dart';
+import '../process.dart';
+import 'flutter_command.dart';
+
+final Logger _logging = new Logger('sky_tools.analyze');
+
+class AnalyzeCommand extends FlutterCommand {
+ String get name => 'analyze';
+ String get description => 'Runs a carefully configured dartanalyzer over the current project\'s dart code.';
+
+ AnalyzeCommand() {
+ argParser.addFlag('flutter-repo', help: 'Include all the examples and tests from the Flutter repository.', defaultsTo: false);
+ argParser.addFlag('current-directory', help: 'Include all the Dart files in the current directory, if any.', defaultsTo: true);
+ argParser.addFlag('current-package', help: 'Include the lib/main.dart file from the current directory, if any.', defaultsTo: true);
+ argParser.addFlag('congratulate', help: 'Show output even when there are no errors, warnings, hints, or lints.', defaultsTo: true);
+ }
+
+ bool get requiresProjectRoot => false;
+
+ @override
+ Future<int> runInProject() async {
+ Set<String> pubSpecDirectories = new Set<String>();
+ List<String> dartFiles = argResults.rest.toList();
+
+ for (String file in dartFiles) {
+ // TODO(ianh): figure out how dartanalyzer decides which .packages file to use when given a random file
+ pubSpecDirectories.add(path.dirname(file));
+ }
+
+ if (argResults['flutter-repo']) {
+ // .../examples/*/*.dart
+ // .../examples/*/lib/main.dart
+ Directory examples = new Directory(path.join(ArtifactStore.flutterRoot, 'examples'));
+ for (FileSystemEntity entry in examples.listSync()) {
+ if (entry is Directory) {
+ bool foundOne = false;
+ for (FileSystemEntity subentry in entry.listSync()) {
+ if (subentry is File && subentry.path.endsWith('.dart')) {
+ dartFiles.add(subentry.path);
+ foundOne = true;
+ } else if (subentry is Directory && path.basename(subentry.path) == 'lib') {
+ String mainPath = path.join(subentry.path, 'main.dart');
+ if (FileSystemEntity.isFileSync(mainPath)) {
+ dartFiles.add(mainPath);
+ foundOne = true;
+ }
+ }
+ }
+ if (foundOne)
+ pubSpecDirectories.add(entry.path);
+ }
+ }
+
+ bool foundTest = false;
+ Directory flutterDir = new Directory(path.join(ArtifactStore.flutterRoot, 'packages/unit')); // See https://github.com/flutter/flutter/issues/50
+
+ // .../packages/unit/test/*/*_test.dart
+ Directory tests = new Directory(path.join(flutterDir.path, 'test'));
+ for (FileSystemEntity entry in tests.listSync()) {
+ if (entry is Directory) {
+ for (FileSystemEntity subentry in entry.listSync()) {
+ if (subentry is File && subentry.path.endsWith('_test.dart')) {
+ dartFiles.add(subentry.path);
+ foundTest = true;
+ }
+ }
+ }
+ }
+
+ // .../packages/unit/benchmark/*/*_bench.dart
+ Directory benchmarks = new Directory(path.join(flutterDir.path, 'benchmark'));
+ for (FileSystemEntity entry in benchmarks.listSync()) {
+ if (entry is Directory) {
+ for (FileSystemEntity subentry in entry.listSync()) {
+ if (subentry is File && subentry.path.endsWith('_bench.dart')) {
+ dartFiles.add(subentry.path);
+ foundTest = true;
+ }
+ }
+ }
+ }
+
+ if (foundTest)
+ pubSpecDirectories.add(flutterDir.path);
+
+ // .../packages/*/bin/*.dart
+ Directory packages = new Directory(path.join(ArtifactStore.flutterRoot, 'packages'));
+ for (FileSystemEntity entry in packages.listSync()) {
+ if (entry is Directory) {
+ bool foundOne = false;
+ Directory binDirectory = new Directory(path.join(entry.path, 'bin'));
+ if (binDirectory.existsSync()) {
+ for (FileSystemEntity subentry in binDirectory.listSync()) {
+ if (subentry is File && subentry.path.endsWith('.dart')) {
+ dartFiles.add(subentry.path);
+ foundOne = true;
+ }
+ }
+ }
+ if (foundOne)
+ pubSpecDirectories.add(entry.path);
+ }
+ }
+ }
+
+ if (argResults['current-directory']) {
+ // ./*.dart
+ Directory currentDirectory = new Directory('.');
+ bool foundOne = false;
+ for (FileSystemEntity entry in currentDirectory.listSync()) {
+ if (entry is File && entry.path.endsWith('.dart')) {
+ dartFiles.add(entry.path);
+ foundOne = true;
+ }
+ }
+ if (foundOne)
+ pubSpecDirectories.add('.');
+ }
+
+ if (argResults['current-package']) {
+ // ./lib/main.dart
+ String mainPath = 'lib/main.dart';
+ if (FileSystemEntity.isFileSync(mainPath)) {
+ dartFiles.add(mainPath);
+ pubSpecDirectories.add('.');
+ }
+ }
+
+ // prepare a Dart file that references all the above Dart files
+ StringBuffer mainBody = new StringBuffer();
+ for (int index = 0; index < dartFiles.length; index += 1) {
+ mainBody.writeln('import \'${path.normalize(path.absolute(dartFiles[index]))}\' as file$index;');
+ }
+ mainBody.writeln('void main() { }');
+
+ // prepare a union of all the .packages files
+ Map<String, String> packages = <String, String>{};
+ bool hadInconsistentRequirements = false;
+ for (Directory directory in pubSpecDirectories.map((path) => new Directory(path))) {
+ File dotPackages = new File(path.join(directory.path, '.packages'));
+ if (dotPackages.existsSync()) {
+ Map<String, String> dependencies = <String, String>{};
+ dotPackages
+ .readAsStringSync()
+ .split('\n')
+ .where((line) => !line.startsWith(new RegExp(r'^ *#')))
+ .forEach((line) {
+ int colon = line.indexOf(':');
+ if (colon > 0)
+ dependencies[line.substring(0, colon)] = path.normalize(path.absolute(directory.path, path.fromUri(line.substring(colon+1))));
+ });
+ for (String package in dependencies.keys) {
+ if (packages.containsKey(package)) {
+ if (packages[package] != dependencies[package]) {
+ _logging.warning('Inconsistent requirements for $package; using ${packages[package]} (and not ${dependencies[package]}).');
+ hadInconsistentRequirements = true;
+ }
+ } else {
+ packages[package] = dependencies[package];
+ }
+ }
+ }
+ }
+ if (hadInconsistentRequirements) {
+ if (argResults['flutter-repo'])
+ _logging.warning('You may need to run "dart ${path.normalize(path.relative(path.join(ArtifactStore.flutterRoot, 'dev/update_packages.dart')))}".');
+ if (argResults['current-directory'] || argResults['current-package'])
+ _logging.warning('You may need to run "pub get".');
+ }
+
+ String buildDir = buildConfigurations.firstWhere((BuildConfiguration config) => config.testable, orElse: () => null)?.buildDir;
+ if (buildDir != null) {
+ packages['sky_engine'] = path.join(buildDir, 'gen/dart-pkg/sky_engine/lib');
+ packages['sky_services'] = path.join(buildDir, 'gen/dart-pkg/sky_services/lib');
+ }
+
+ StringBuffer packagesBody = new StringBuffer();
+ for (String package in packages.keys)
+ packagesBody.writeln('$package:${path.toUri(packages[package])}');
+
+ // save the Dart file and the .packages file to disk
+ Directory host = Directory.systemTemp.createTempSync('flutter-analyze-');
+ File mainFile = new File(path.join(host.path, 'main.dart'))..writeAsStringSync(mainBody.toString());
+ File packagesFile = new File(path.join(host.path, '.packages'))..writeAsStringSync(packagesBody.toString());
+
+ List<String> cmd = <String>[
+ sdkBinaryName('dartanalyzer'),
+ // do not set '--warnings', since that will include the entire Dart SDK
+ '--ignore-unrecognized-flags',
+ '--supermixin',
+ '--enable-strict-call-checks',
+ '--enable_type_checks',
+ '--strong',
+ '--package-warnings',
+ '--fatal-warnings',
+ '--strong-hints',
+ '--fatal-hints',
+ '--lints',
+ '--packages', packagesFile.path,
+ mainFile.path
+ ];
+
+ _logging.info(cmd.join(' '));
+ Process process = await Process.start(
+ cmd[0],
+ cmd.sublist(1),
+ workingDirectory: host.path
+ );
+ int errorCount = 0;
+ StringBuffer output = new StringBuffer();
+ process.stdout.transform(UTF8.decoder).listen((String data) {
+ output.write(data);
+ });
+ process.stderr.transform(UTF8.decoder).listen((String data) {
+ // dartanalyzer doesn't seem to ever output anything on stderr
+ errorCount += 1;
+ print(data);
+ });
+
+ // host.deleteSync();
+
+ int exitCode = await process.exitCode;
+
+ List<Pattern> patternsToSkip = <Pattern>[
+ 'Analyzing [${mainFile.path}]...',
+ new RegExp('^\\[hint\\] Unused import \\(${mainFile.path},'),
+ new RegExp(r'^\[.+\] .+ \(.+/\.pub-cache/.+'),
+ new RegExp(r'^\[error\] Invalid override\. The type of [^ ]+ \(.+\) is not a subtype of [^ ]+ \(.+\)\.'), // we allow type narrowing
+ new RegExp(r'^\[warning\] .+ will need runtime check to cast to type .+'), // https://github.com/dart-lang/sdk/issues/24542
+ new RegExp(r'^\[error\] Type check failed: .*\(dynamic\) is not of type'), // allow unchecked casts from dynamic
+ new RegExp('^\\[error\\] Target of URI does not exist: \'dart:ui_internals\''), // https://github.com/flutter/flutter/issues/83
+ new RegExp(r'\[lint\] Prefer using lowerCamelCase for constant names.'), // sometimes we have no choice (e.g. when matching other platforms)
+ new RegExp(r'\[lint\] Avoid defining a one-member abstract class when a simple function will do.'), // too many false-positives; code review should catch real instances
+ new RegExp(r'\[0-9]+ (error|warning|hint|lint).+found\.'),
+ '',
+ ];
+
+ RegExp generalPattern = new RegExp(r'^\[(error|warning|hint|lint)\] (.+) \(([^(),]+), line ([0-9]+), col ([0-9]+)\)$');
+ RegExp ignorePattern = new RegExp(r'// analyzer says "([^"]+)"');
+ RegExp constructorTearOffsPattern = new RegExp('.+#.+// analyzer doesn\'t like constructor tear-offs');
+ RegExp allowedIdentifiers = new RegExp(r'_?([A-Z]|_+)\b');
+
+ List<String> errorLines = output.toString().split('\n');
+ for (String errorLine in errorLines) {
+ if (patternsToSkip.every((Pattern pattern) => pattern.allMatches(errorLine).isEmpty)) {
+ Match groups = generalPattern.firstMatch(errorLine);
+ if (groups != null) {
+ String level = groups[1];
+ String filename = groups[3];
+ String errorMessage = groups[2];
+ int lineNumber = int.parse(groups[4]);
+ int colNumber = int.parse(groups[5]);
+ File source = new File(filename);
+ List<String> sourceLines = source.readAsLinesSync();
+ String sourceLine = sourceLines[lineNumber-1];
+ bool shouldIgnore = false;
+ if (filename.endsWith('.mojom.dart')) {
+ shouldIgnore = true;
+ } else if (level == 'lint' && errorMessage == 'Name non-constant identifiers using lowerCamelCase.') {
+ if (allowedIdentifiers.matchAsPrefix(sourceLine, colNumber-1) != null)
+ shouldIgnore = true;
+ } else if (constructorTearOffsPattern.allMatches(sourceLine).isNotEmpty) {
+ shouldIgnore = true;
+ } else {
+ Iterable<Match> ignoreGroups = ignorePattern.allMatches(sourceLine);
+ for (Match ignoreGroup in ignoreGroups) {
+ if (errorMessage.contains(ignoreGroup[1])) {
+ shouldIgnore = true;
+ break;
+ }
+ }
+ }
+ if (shouldIgnore)
+ continue;
+ }
+ print(errorLine);
+ errorCount += 1;
+ }
+ }
+
+ if (exitCode < 0 || exitCode > 3) // 1 = hints, 2 = warnings, 3 = errors
+ return exitCode;
+
+ if (errorCount > 1)
+ return 1;
+ if (argResults['congratulate'])
+ print('No analyzer warnings!');
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart
index caee7e6..b892c10 100644
--- a/packages/flutter_tools/lib/src/commands/build.dart
+++ b/packages/flutter_tools/lib/src/commands/build.dart
@@ -93,7 +93,7 @@
}
ArchiveFile _createFile(String key, String assetBase) {
- File file = new File('${assetBase}/${key}');
+ File file = new File('$assetBase/$key');
if (!file.existsSync())
return null;
List<int> content = file.readAsBytesSync();
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index cd82c5f..e391296 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -60,7 +60,7 @@
stdout.writeln('[${JSON.encode(command)}]');
}, daemonCommand: this);
- return daemon.onExit;
+ return await daemon.onExit;
}
}
@@ -98,19 +98,19 @@
var id = command['id'];
if (id == null) {
- _logging.severe('no id for command: ${command}');
+ _logging.severe('no id for command: $command');
return;
}
try {
String event = command['event'];
if (event.indexOf('.') == -1)
- throw 'command not understood: ${event}';
+ throw 'command not understood: $event';
String prefix = event.substring(0, event.indexOf('.'));
String name = event.substring(event.indexOf('.') + 1);
if (_domains[prefix] == null)
- throw 'no domain for command: ${command}';
+ throw 'no domain for command: $command';
_domains[prefix].handleEvent(name, id, command['params']);
} catch (error, trace) {
@@ -144,7 +144,7 @@
new Future.sync(() {
if (_handlers.containsKey(name))
return _handlers[name](args);
- throw 'command not understood: ${name}';
+ throw 'command not understood: $name';
}).then((result) {
if (result == null) {
_send({'id': id});
@@ -153,7 +153,7 @@
}
}).catchError((error, trace) {
_send({'id': id, 'error': _toJsonable(error)});
- _logging.warning('error handling ${name}', error, trace);
+ _logging.warning('error handling $name', error, trace);
});
}
@@ -210,5 +210,5 @@
dynamic _toJsonable(dynamic obj) {
if (obj is String || obj is int || obj is bool || obj is Map || obj is List || obj == null)
return obj;
- return '${obj}';
+ return '$obj';
}
diff --git a/packages/flutter_tools/lib/src/commands/flutter_command.dart b/packages/flutter_tools/lib/src/commands/flutter_command.dart
index f31ad56..4ff335a 100644
--- a/packages/flutter_tools/lib/src/commands/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/commands/flutter_command.dart
@@ -56,7 +56,7 @@
}
}
- return runInProject();
+ return await runInProject();
}
Future<int> runInProject();
diff --git a/packages/flutter_tools/lib/src/commands/init.dart b/packages/flutter_tools/lib/src/commands/init.dart
index 85ef441..3c70531 100644
--- a/packages/flutter_tools/lib/src/commands/init.dart
+++ b/packages/flutter_tools/lib/src/commands/init.dart
@@ -6,12 +6,15 @@
import 'dart:io';
import 'package:args/command_runner.dart';
+import 'package:logging/logging.dart';
import 'package:mustache4dart/mustache4dart.dart' as mustache;
import 'package:path/path.dart' as p;
import '../artifacts.dart';
import '../process.dart';
+final Logger _logging = new Logger('sky_tools.init');
+
class InitCommand extends Command {
final String name = 'init';
final String description = 'Create a new Flutter project.';
@@ -20,7 +23,7 @@
argParser.addOption('out', abbr: 'o', help: 'The output directory.');
argParser.addFlag('pub',
defaultsTo: true,
- help: 'Whether to run pub after the project has been created.');
+ help: 'Whether to run "pub get" after the project has been created.');
}
@override
@@ -40,7 +43,7 @@
String flutterPackagePath = p.join(flutterRoot, 'packages', 'flutter');
if (!FileSystemEntity.isFileSync(p.join(flutterPackagePath, 'pubspec.yaml'))) {
- print('Unable to find package:flutter in ${flutterPackagePath}');
+ print('Unable to find package:flutter in $flutterPackagePath');
return 2;
}
@@ -58,11 +61,7 @@
''';
if (argResults['pub']) {
- print("Running pub get...");
- int code = await runCommandAndStreamOutput(
- [sdkBinaryName('pub'), 'get'],
- workingDirectory: out.path
- );
+ int code = await pubGet(directory: out.path);
if (code != 0)
return code;
}
@@ -70,6 +69,41 @@
print(message);
return 0;
}
+
+ Future<int> pubGet({
+ String directory: '',
+ bool skipIfAbsent: false,
+ bool verbose: true
+ }) async {
+ File pubSpecYaml = new File(p.join(directory, 'pubspec.yaml'));
+ File pubSpecLock = new File(p.join(directory, 'pubspec.lock'));
+ File dotPackages = new File(p.join(directory, '.packages'));
+
+ if (!pubSpecYaml.existsSync()) {
+ if (skipIfAbsent)
+ return 0;
+ _logging.severe('$directory: no pubspec.yaml found');
+ return 1;
+ }
+
+ if (!pubSpecLock.existsSync() || pubSpecYaml.lastModifiedSync().isAfter(pubSpecLock.lastModifiedSync())) {
+ if (verbose)
+ print("Running pub get in $directory...");
+ int code = await runCommandAndStreamOutput(
+ [sdkBinaryName('pub'), 'get'],
+ workingDirectory: directory
+ );
+ if (code != 0)
+ return code;
+ }
+
+ if ((pubSpecLock.existsSync() && pubSpecLock.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())) &&
+ (dotPackages.existsSync() && dotPackages.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())))
+ return 0;
+
+ _logging.severe('$directory: pubspec.yaml, pubspec.lock, and .packages are in an inconsistent state');
+ return 1;
+ }
}
abstract class Template {
diff --git a/packages/flutter_tools/lib/src/commands/run_mojo.dart b/packages/flutter_tools/lib/src/commands/run_mojo.dart
index ce0a187..516673e 100644
--- a/packages/flutter_tools/lib/src/commands/run_mojo.dart
+++ b/packages/flutter_tools/lib/src/commands/run_mojo.dart
@@ -34,7 +34,7 @@
String _makePathAbsolute(String relativePath) {
File file = new File(relativePath);
if (!file.existsSync()) {
- throw new Exception("Path \"${relativePath}\" does not exist");
+ throw new Exception('Path "$relativePath" does not exist');
}
return file.absolute.path;
}
@@ -79,8 +79,8 @@
final appPath = _makePathAbsolute(argResults['app']);
Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.viewer, targetPlatform: TargetPlatform.linux);
final viewerPath = _makePathAbsolute(await ArtifactStore.getPath(artifact));
- args.add('file://${appPath}');
- args.add('--url-mappings=mojo:sky_viewer=file://${viewerPath}');
+ args.add('file://$appPath');
+ args.add('--url-mappings=mojo:sky_viewer=file://$viewerPath');
}
if (useDevtools) {
@@ -115,6 +115,6 @@
return 1;
}
- return runCommandAndStreamOutput(await _getShellConfig());
+ return await runCommandAndStreamOutput(await _getShellConfig());
}
}
diff --git a/packages/flutter_tools/lib/src/commands/start.dart b/packages/flutter_tools/lib/src/commands/start.dart
index ce60e43..842d909 100644
--- a/packages/flutter_tools/lib/src/commands/start.dart
+++ b/packages/flutter_tools/lib/src/commands/start.dart
@@ -68,7 +68,7 @@
if (FileSystemEntity.isDirectorySync(target))
mainPath = path.join(target, 'lib', 'main.dart');
if (!FileSystemEntity.isFileSync(mainPath)) {
- String message = 'Tried to run ${mainPath}, but that file does not exist.';
+ String message = 'Tried to run $mainPath, but that file does not exist.';
if (!argResults.wasParsed('target'))
message += '\nConsider using the -t option to specify that Dart file to start.';
stderr.writeln(message);
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index 8c3202c..5d26834 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -11,7 +11,6 @@
import '../artifacts.dart';
import '../build_configuration.dart';
-import '../process.dart';
import '../test/loader.dart' as loader;
import 'flutter_command.dart';
@@ -49,25 +48,6 @@
testArgs.insert(0, '--no-color');
List<BuildConfiguration> configs = buildConfigurations;
bool foundOne = false;
-
- File pubSpecYaml = new File(path.join(flutterDir.path, 'pubspec.yaml'));
- File pubSpecLock = new File(path.join(flutterDir.path, 'pubspec.lock'));
-
- if (!pubSpecYaml.existsSync()) {
- print('${flutterDir.path} has no pubspec.yaml');
- return 1;
- }
-
- if (!pubSpecLock.existsSync() || pubSpecYaml.lastModifiedSync().isAfter(pubSpecLock.lastModifiedSync())) {
- print("Running pub get...");
- int code = await runCommandAndStreamOutput(
- [sdkBinaryName('pub'), 'get'],
- workingDirectory: flutterDir.path
- );
- if (code != 0)
- return code;
- }
-
String currentDirectory = Directory.current.path;
Directory.current = flutterDir.path;
loader.installHook();
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index d0ef982..47831e8 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -19,36 +19,8 @@
final String id;
static Map<String, Device> _deviceCache = {};
- factory Device._unique(String className, [String id = null]) {
- if (id == null) {
- if (className == AndroidDevice.className) {
- id = AndroidDevice.defaultDeviceID;
- } else if (className == IOSDevice.className) {
- id = IOSDevice.defaultDeviceID;
- } else if (className == IOSSimulator.className) {
- id = IOSSimulator.defaultDeviceID;
- } else {
- throw 'Attempted to create a Device of unknown type $className';
- }
- }
-
- return _deviceCache.putIfAbsent(id, () {
- if (className == AndroidDevice.className) {
- final device = new AndroidDevice._(id);
- _deviceCache[id] = device;
- return device;
- } else if (className == IOSDevice.className) {
- final device = new IOSDevice._(id);
- _deviceCache[id] = device;
- return device;
- } else if (className == IOSSimulator.className) {
- final device = new IOSSimulator._(id);
- _deviceCache[id] = device;
- return device;
- } else {
- throw 'Attempted to create a Device of unknown type $className';
- }
- });
+ static Device _unique(String id, Device constructor(String id)) {
+ return _deviceCache.putIfAbsent(id, () => constructor(id));
}
Device._(this.id);
@@ -74,7 +46,6 @@
}
class IOSDevice extends Device {
- static const String className = 'IOSDevice';
static final String defaultDeviceID = 'default_ios_id';
static const String _macInstructions =
@@ -108,7 +79,7 @@
String get name => _name;
factory IOSDevice({String id, String name}) {
- IOSDevice device = new Device._unique(className, id);
+ IOSDevice device = Device._unique(id ?? defaultDeviceID, new IOSDevice#_); // analyzer doesn't like constructor tear-offs
device._name = name;
return device;
}
@@ -224,14 +195,17 @@
}
// idevicedebug hangs forever after launching the app, so kill it after
// giving it plenty of time to send the launch command.
- return runAndKill(
- [debuggerPath, 'run', app.id], new Duration(seconds: 3)).then(
- (_) {
- return true;
- }, onError: (e) {
- _logging.info('Failure running $debuggerPath: ', e);
- return false;
- });
+ return await runAndKill(
+ [debuggerPath, 'run', app.id],
+ new Duration(seconds: 3)
+ ).then(
+ (_) {
+ return true;
+ }, onError: (e) {
+ _logging.info('Failure running $debuggerPath: ', e);
+ return false;
+ }
+ );
}
@override
@@ -274,13 +248,12 @@
if (!isConnected()) {
return 2;
}
- return runCommandAndStreamOutput([loggerPath],
+ return await runCommandAndStreamOutput([loggerPath],
prefix: 'iOS dev: ', filter: new RegExp(r'.*SkyShell.*'));
}
}
class IOSSimulator extends Device {
- static const String className = 'IOSSimulator';
static final String defaultDeviceID = 'default_ios_sim_id';
static const String _macInstructions =
@@ -299,7 +272,7 @@
String get name => _name;
factory IOSSimulator({String id, String name, String iOSSimulatorPath}) {
- IOSSimulator device = new Device._unique(className, id);
+ IOSSimulator device = Device._unique(id ?? defaultDeviceID, new IOSSimulator#_); // analyzer doesn't like constructor tear-offs
device._name = name;
if (iOSSimulatorPath == null) {
iOSSimulatorPath = path.join('/Applications', 'iOS Simulator.app',
@@ -309,7 +282,7 @@
return device;
}
- IOSSimulator._(String id) : super._(id) {}
+ IOSSimulator._(String id) : super._(id);
static String _getRunningSimulatorID([IOSSimulator mockIOS]) {
String xcrunPath = mockIOS != null ? mockIOS.xcrunPath : _xcrunPath;
@@ -380,7 +353,6 @@
}
if (id == defaultDeviceID) {
runDetached([iOSSimPath]);
-
Future<bool> checkConnection([int attempts = 20]) async {
if (attempts == 0) {
_logging.info('Timed out waiting for iOS Simulator $id to boot.');
@@ -388,12 +360,12 @@
}
if (!isConnected()) {
_logging.info('Waiting for iOS Simulator $id to boot...');
- return new Future.delayed(new Duration(milliseconds: 500),
+ return await new Future.delayed(new Duration(milliseconds: 500),
() => checkConnection(attempts - 1));
}
return true;
}
- return checkConnection();
+ return await checkConnection();
} else {
try {
runCheckedSync([xcrunPath, 'simctl', 'boot', id]);
@@ -496,7 +468,7 @@
if (clear) {
runSync(['rm', logFilePath]);
}
- return runCommandAndStreamOutput(['tail', '-f', logFilePath],
+ return await runCommandAndStreamOutput(['tail', '-f', logFilePath],
prefix: 'iOS sim: ', filter: new RegExp(r'.*SkyShell.*'));
}
}
@@ -505,7 +477,6 @@
static const String _ADB_PATH = 'adb';
static const int _observatoryPort = 8181;
- static const String className = 'AndroidDevice';
static final String defaultDeviceID = 'default_android_device';
String productID;
@@ -522,7 +493,7 @@
String productID: null,
String modelID: null,
String deviceCodeName: null}) {
- AndroidDevice device = new Device._unique(className, id);
+ AndroidDevice device = Device._unique(id ?? defaultDeviceID, new AndroidDevice#_); // analyzer doesn't like constructor tear-offs
device.productID = productID;
device.modelID = modelID;
device.deviceCodeName = deviceCodeName;
@@ -813,7 +784,7 @@
clearLogs();
}
- return runCommandAndStreamOutput(adbCommandForDevice([
+ return await runCommandAndStreamOutput(adbCommandForDevice([
'logcat',
'-v',
'tag', // Only log the tag and the message
diff --git a/packages/flutter_tools/lib/src/process.dart b/packages/flutter_tools/lib/src/process.dart
index 3259b69..c0a547f 100644
--- a/packages/flutter_tools/lib/src/process.dart
+++ b/packages/flutter_tools/lib/src/process.dart
@@ -18,33 +18,35 @@
String workingDirectory
}) async {
_logging.info(cmd.join(' '));
- Process proc = await Process.start(
+ Process process = await Process.start(
cmd[0],
- cmd.getRange(1, cmd.length).toList(),
+ cmd.sublist(1),
workingDirectory: workingDirectory
);
- proc.stdout.transform(UTF8.decoder).listen((String data) {
+ process.stdout.transform(UTF8.decoder).listen((String data) {
List<String> dataLines = data.trimRight().split('\n');
if (filter != null) {
+ // TODO(ianh): This doesn't handle IO buffering (where the data might be split half-way through a line)
dataLines = dataLines.where((String s) => filter.hasMatch(s)).toList();
}
if (dataLines.length > 0) {
stdout.write('$prefix${dataLines.join('\n$prefix')}\n');
}
});
- proc.stderr.transform(UTF8.decoder).listen((String data) {
+ process.stderr.transform(UTF8.decoder).listen((String data) {
List<String> dataLines = data.trimRight().split('\n');
if (filter != null) {
+ // TODO(ianh): This doesn't handle IO buffering (where the data might be split half-way through a line)
dataLines = dataLines.where((String s) => filter.hasMatch(s));
}
if (dataLines.length > 0) {
stderr.write('$prefix${dataLines.join('\n$prefix')}\n');
}
});
- return proc.exitCode;
+ return await process.exitCode;
}
-Future runAndKill(List<String> cmd, Duration timeout) async {
+Future runAndKill(List<String> cmd, Duration timeout) {
Future<Process> proc = runDetached(cmd);
return new Future.delayed(timeout, () async {
_logging.info('Intentionally killing ${cmd[0]}');
@@ -52,7 +54,7 @@
});
}
-Future<Process> runDetached(List<String> cmd) async {
+Future<Process> runDetached(List<String> cmd) {
_logging.info(cmd.join(' '));
Future<Process> proc = Process.start(
cmd[0], cmd.getRange(1, cmd.length).toList(),
@@ -97,6 +99,6 @@
class ProcessExit implements Exception {
final int exitCode;
ProcessExit(this.exitCode);
- String get message => 'ProcessExit: ${exitCode}';
+ String get message => 'ProcessExit: $exitCode';
String toString() => message;
}