Unverified Commit 454db9d4 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Re-organize flutter_tools code to better support proper layering. (#12957)

* executable.dart#main() depends on runner.dart#run()
* Refactor code such that non-commands don't depend on commands.

No code was actually changed in this PR - code was merely moved from
point A to point B.
parent 5af7b423
...@@ -4,23 +4,7 @@ ...@@ -4,23 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart'; import 'runner.dart' as runner;
import 'package:intl/intl_standalone.dart' as intl;
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'src/artifacts.dart';
import 'src/base/common.dart';
import 'src/base/config.dart';
import 'src/base/context.dart';
import 'src/base/file_system.dart';
import 'src/base/io.dart';
import 'src/base/logger.dart';
import 'src/base/platform.dart';
import 'src/base/process.dart';
import 'src/base/terminal.dart';
import 'src/base/utils.dart';
import 'src/cache.dart';
import 'src/commands/analyze.dart'; import 'src/commands/analyze.dart';
import 'src/commands/build.dart'; import 'src/commands/build.dart';
import 'src/commands/channel.dart'; import 'src/commands/channel.dart';
...@@ -44,17 +28,7 @@ import 'src/commands/test.dart'; ...@@ -44,17 +28,7 @@ import 'src/commands/test.dart';
import 'src/commands/trace.dart'; import 'src/commands/trace.dart';
import 'src/commands/update_packages.dart'; import 'src/commands/update_packages.dart';
import 'src/commands/upgrade.dart'; import 'src/commands/upgrade.dart';
import 'src/crash_reporting.dart';
import 'src/devfs.dart';
import 'src/device.dart';
import 'src/doctor.dart';
import 'src/globals.dart';
import 'src/ios/simulators.dart';
import 'src/run_hot.dart';
import 'src/runner/flutter_command.dart'; import 'src/runner/flutter_command.dart';
import 'src/runner/flutter_command_runner.dart';
import 'src/usage.dart';
import 'src/version.dart';
/// Main entry point for commands. /// Main entry point for commands.
/// ///
...@@ -65,7 +39,7 @@ Future<Null> main(List<String> args) async { ...@@ -65,7 +39,7 @@ Future<Null> main(List<String> args) async {
(args.isNotEmpty && args.first == 'help') || (args.length == 1 && verbose); (args.isNotEmpty && args.first == 'help') || (args.length == 1 && verbose);
final bool verboseHelp = help && verbose; final bool verboseHelp = help && verbose;
await run(args, <FlutterCommand>[ await runner.run(args, <FlutterCommand>[
new AnalyzeCommand(verboseHelp: verboseHelp), new AnalyzeCommand(verboseHelp: verboseHelp),
new BuildCommand(verboseHelp: verboseHelp), new BuildCommand(verboseHelp: verboseHelp),
new ChannelCommand(), new ChannelCommand(),
...@@ -91,240 +65,3 @@ Future<Null> main(List<String> args) async { ...@@ -91,240 +65,3 @@ Future<Null> main(List<String> args) async {
new UpgradeCommand(), new UpgradeCommand(),
], verbose: verbose, verboseHelp: verboseHelp); ], verbose: verbose, verboseHelp: verboseHelp);
} }
Future<int> run(List<String> args, List<FlutterCommand> subCommands, {
bool verbose: false,
bool verboseHelp: false,
bool reportCrashes,
String flutterVersion,
}) async {
reportCrashes ??= !isRunningOnBot;
if (verboseHelp) {
// Remove the verbose option; for help, users don't need to see verbose logs.
args = new List<String>.from(args);
args.removeWhere((String option) => option == '-v' || option == '--verbose');
}
final FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp);
subCommands.forEach(runner.addCommand);
// Construct a context.
final AppContext _executableContext = new AppContext();
// Make the context current.
return await _executableContext.runInZone(() async {
// Initialize the context with some defaults.
// NOTE: Similar lists also exist in `bin/fuchsia_builder.dart` and
// `test/src/context.dart`. If you update this list of defaults, look
// in those locations as well to see if you need a similar update there.
// Seed these context entries first since others depend on them
context.putIfAbsent(Stdio, () => const Stdio());
context.putIfAbsent(Platform, () => const LocalPlatform());
context.putIfAbsent(FileSystem, () => const LocalFileSystem());
context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
context.putIfAbsent(AnsiTerminal, () => new AnsiTerminal());
context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
context.putIfAbsent(Config, () => new Config());
// Order-independent context entries
context.putIfAbsent(DeviceManager, () => new DeviceManager());
context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
context.putIfAbsent(Doctor, () => new Doctor());
context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
context.putIfAbsent(Cache, () => new Cache());
context.putIfAbsent(Artifacts, () => new CachedArtifacts());
context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
context.putIfAbsent(SimControl, () => new SimControl());
// Initialize the system locale.
await intl.findSystemLocale();
try {
await runner.run(args);
await _exit(0);
} catch (error, stackTrace) {
String getVersion() => flutterVersion ?? FlutterVersion.getVersionString();
return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion);
}
return 0;
});
}
/// Writes the [string] to one of the standard output streams.
@visibleForTesting
typedef void WriteCallback([String string]);
/// Writes a line to STDERR.
///
/// Overwrite this in tests to avoid spurious test output.
@visibleForTesting
WriteCallback writelnStderr = stderr.writeln;
Future<int> _handleToolError(
dynamic error,
StackTrace stackTrace,
bool verbose,
List<String> args,
bool reportCrashes,
String getFlutterVersion(),
) async {
if (error is UsageException) {
writelnStderr(error.message);
writelnStderr();
writelnStderr(
"Run 'flutter -h' (or 'flutter <command> -h') for available "
'flutter commands and options.'
);
// Argument error exit code.
return _exit(64);
} else if (error is ToolExit) {
if (error.message != null)
writelnStderr(error.message);
if (verbose) {
writelnStderr();
writelnStderr(stackTrace.toString());
writelnStderr();
}
return _exit(error.exitCode ?? 1);
} else if (error is ProcessExit) {
// We've caught an exit code.
if (error.immediate) {
exit(error.exitCode);
return error.exitCode;
} else {
return _exit(error.exitCode);
}
} else {
// We've crashed; emit a log report.
writelnStderr();
if (!reportCrashes) {
// Print the stack trace on the bots - don't write a crash report.
writelnStderr('$error');
writelnStderr(stackTrace.toString());
return _exit(1);
} else {
flutterUsage.sendException(error, stackTrace);
if (error is String)
writelnStderr('Oops; flutter has exited unexpectedly: "$error".');
else
writelnStderr('Oops; flutter has exited unexpectedly.');
await CrashReportSender.instance.sendReport(
error: error,
stackTrace: stackTrace,
getFlutterVersion: getFlutterVersion,
);
try {
final File file = await _createLocalCrashReport(args, error, stackTrace);
writelnStderr(
'Crash report written to ${file.path};\n'
'please let us know at https://github.com/flutter/flutter/issues.',
);
return _exit(1);
} catch (error) {
writelnStderr(
'Unable to generate crash report due to secondary error: $error\n'
'please let us know at https://github.com/flutter/flutter/issues.',
);
// Any exception throw here (including one thrown by `_exit()`) will
// get caught by our zone's `onError` handler. In order to avoid an
// infinite error loop, we throw an error that is recognized above
// and will trigger an immediate exit.
throw new ProcessExit(1, immediate: true);
}
}
}
}
/// File system used by the crash reporting logic.
///
/// We do not want to use the file system stored in the context because it may
/// be recording. Additionally, in the case of a crash we do not trust the
/// integrity of the [AppContext].
@visibleForTesting
FileSystem crashFileSystem = const LocalFileSystem();
/// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace) async {
File crashFile = getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');
final StringBuffer buffer = new StringBuffer();
buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
buffer.writeln('## command\n');
buffer.writeln('flutter ${args.join(' ')}\n');
buffer.writeln('## exception\n');
buffer.writeln('${error.runtimeType}: $error\n');
buffer.writeln('```\n$stackTrace```\n');
buffer.writeln('## flutter doctor\n');
buffer.writeln('```\n${await _doctorText()}```');
try {
await crashFile.writeAsString(buffer.toString());
} on FileSystemException catch (_) {
// Fallback to the system temporary directory.
crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
try {
await crashFile.writeAsString(buffer.toString());
} on FileSystemException catch (e) {
printError('Could not write crash report to disk: $e');
printError(buffer.toString());
}
}
return crashFile;
}
Future<String> _doctorText() async {
try {
final BufferLogger logger = new BufferLogger();
final AppContext appContext = new AppContext();
appContext.setVariable(Logger, logger);
await appContext.runInZone(() => doctor.diagnose());
return logger.statusText;
} catch (error, trace) {
return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
}
}
Future<int> _exit(int code) async {
if (flutterUsage.isFirstRun)
flutterUsage.printWelcome();
// Send any last analytics calls that are in progress without overly delaying
// the tool's exit (we wait a maximum of 250ms).
if (flutterUsage.enabled) {
final Stopwatch stopwatch = new Stopwatch()..start();
await flutterUsage.ensureAnalyticsSent();
printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
}
// Run shutdown hooks before flushing logs
await runShutdownHooks();
final Completer<Null> completer = new Completer<Null>();
// Give the task / timer queue one cycle through before we hard exit.
Timer.run(() {
try {
printTrace('exiting with code $code');
exit(code);
completer.complete();
} catch (error, stackTrace) {
completer.completeError(error, stackTrace);
}
});
await completer.future;
return code;
}
// 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 'package:args/command_runner.dart';
import 'package:intl/intl_standalone.dart' as intl;
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'src/artifacts.dart';
import 'src/base/common.dart';
import 'src/base/config.dart';
import 'src/base/context.dart';
import 'src/base/file_system.dart';
import 'src/base/io.dart';
import 'src/base/logger.dart';
import 'src/base/platform.dart';
import 'src/base/process.dart';
import 'src/base/terminal.dart';
import 'src/base/utils.dart';
import 'src/cache.dart';
import 'src/crash_reporting.dart';
import 'src/devfs.dart';
import 'src/device.dart';
import 'src/doctor.dart';
import 'src/globals.dart';
import 'src/ios/simulators.dart';
import 'src/run_hot.dart';
import 'src/runner/flutter_command.dart';
import 'src/runner/flutter_command_runner.dart';
import 'src/usage.dart';
import 'src/version.dart';
/// Runs the Flutter tool with support for the specified list of [commands].
Future<int> run(
List<String> args,
List<FlutterCommand> commands, {
bool verbose: false,
bool verboseHelp: false,
bool reportCrashes,
String flutterVersion,
}) async {
reportCrashes ??= !isRunningOnBot;
if (verboseHelp) {
// Remove the verbose option; for help, users don't need to see verbose logs.
args = new List<String>.from(args);
args.removeWhere((String option) => option == '-v' || option == '--verbose');
}
final FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp);
commands.forEach(runner.addCommand);
// Construct a context.
final AppContext _executableContext = new AppContext();
// Make the context current.
return await _executableContext.runInZone(() async {
// Initialize the context with some defaults.
// NOTE: Similar lists also exist in `bin/fuchsia_builder.dart` and
// `test/src/context.dart`. If you update this list of defaults, look
// in those locations as well to see if you need a similar update there.
// Seed these context entries first since others depend on them
context.putIfAbsent(Stdio, () => const Stdio());
context.putIfAbsent(Platform, () => const LocalPlatform());
context.putIfAbsent(FileSystem, () => const LocalFileSystem());
context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
context.putIfAbsent(AnsiTerminal, () => new AnsiTerminal());
context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
context.putIfAbsent(Config, () => new Config());
// Order-independent context entries
context.putIfAbsent(DeviceManager, () => new DeviceManager());
context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
context.putIfAbsent(Doctor, () => new Doctor());
context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
context.putIfAbsent(Cache, () => new Cache());
context.putIfAbsent(Artifacts, () => new CachedArtifacts());
context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
context.putIfAbsent(SimControl, () => new SimControl());
// Initialize the system locale.
await intl.findSystemLocale();
try {
await runner.run(args);
await _exit(0);
} catch (error, stackTrace) {
String getVersion() => flutterVersion ?? FlutterVersion.getVersionString();
return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion);
}
return 0;
});
}
/// Writes the [string] to one of the standard output streams.
@visibleForTesting
typedef void WriteCallback([String string]);
/// Writes a line to STDERR.
///
/// Overwrite this in tests to avoid spurious test output.
@visibleForTesting
WriteCallback writelnStderr = stderr.writeln;
Future<int> _handleToolError(
dynamic error,
StackTrace stackTrace,
bool verbose,
List<String> args,
bool reportCrashes,
String getFlutterVersion(),
) async {
if (error is UsageException) {
writelnStderr(error.message);
writelnStderr();
writelnStderr(
"Run 'flutter -h' (or 'flutter <command> -h') for available "
'flutter commands and options.'
);
// Argument error exit code.
return _exit(64);
} else if (error is ToolExit) {
if (error.message != null)
writelnStderr(error.message);
if (verbose) {
writelnStderr();
writelnStderr(stackTrace.toString());
writelnStderr();
}
return _exit(error.exitCode ?? 1);
} else if (error is ProcessExit) {
// We've caught an exit code.
if (error.immediate) {
exit(error.exitCode);
return error.exitCode;
} else {
return _exit(error.exitCode);
}
} else {
// We've crashed; emit a log report.
writelnStderr();
if (!reportCrashes) {
// Print the stack trace on the bots - don't write a crash report.
writelnStderr('$error');
writelnStderr(stackTrace.toString());
return _exit(1);
} else {
flutterUsage.sendException(error, stackTrace);
if (error is String)
writelnStderr('Oops; flutter has exited unexpectedly: "$error".');
else
writelnStderr('Oops; flutter has exited unexpectedly.');
await CrashReportSender.instance.sendReport(
error: error,
stackTrace: stackTrace,
getFlutterVersion: getFlutterVersion,
);
try {
final File file = await _createLocalCrashReport(args, error, stackTrace);
writelnStderr(
'Crash report written to ${file.path};\n'
'please let us know at https://github.com/flutter/flutter/issues.',
);
return _exit(1);
} catch (error) {
writelnStderr(
'Unable to generate crash report due to secondary error: $error\n'
'please let us know at https://github.com/flutter/flutter/issues.',
);
// Any exception throw here (including one thrown by `_exit()`) will
// get caught by our zone's `onError` handler. In order to avoid an
// infinite error loop, we throw an error that is recognized above
// and will trigger an immediate exit.
throw new ProcessExit(1, immediate: true);
}
}
}
}
/// File system used by the crash reporting logic.
///
/// We do not want to use the file system stored in the context because it may
/// be recording. Additionally, in the case of a crash we do not trust the
/// integrity of the [AppContext].
@visibleForTesting
FileSystem crashFileSystem = const LocalFileSystem();
/// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace) async {
File crashFile = getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');
final StringBuffer buffer = new StringBuffer();
buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
buffer.writeln('## command\n');
buffer.writeln('flutter ${args.join(' ')}\n');
buffer.writeln('## exception\n');
buffer.writeln('${error.runtimeType}: $error\n');
buffer.writeln('```\n$stackTrace```\n');
buffer.writeln('## flutter doctor\n');
buffer.writeln('```\n${await _doctorText()}```');
try {
await crashFile.writeAsString(buffer.toString());
} on FileSystemException catch (_) {
// Fallback to the system temporary directory.
crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
try {
await crashFile.writeAsString(buffer.toString());
} on FileSystemException catch (e) {
printError('Could not write crash report to disk: $e');
printError(buffer.toString());
}
}
return crashFile;
}
Future<String> _doctorText() async {
try {
final BufferLogger logger = new BufferLogger();
final AppContext appContext = new AppContext();
appContext.setVariable(Logger, logger);
await appContext.runInZone(() => doctor.diagnose());
return logger.statusText;
} catch (error, trace) {
return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
}
}
Future<int> _exit(int code) async {
if (flutterUsage.isFirstRun)
flutterUsage.printWelcome();
// Send any last analytics calls that are in progress without overly delaying
// the tool's exit (we wait a maximum of 250ms).
if (flutterUsage.enabled) {
final Stopwatch stopwatch = new Stopwatch()..start();
await flutterUsage.ensureAnalyticsSent();
printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
}
// Run shutdown hooks before flushing logs
await runShutdownHooks();
final Completer<Null> completer = new Completer<Null>();
// Give the task / timer queue one cycle through before we hard exit.
Timer.run(() {
try {
printTrace('exiting with code $code');
exit(code);
completer.complete();
} catch (error, stackTrace) {
completer.completeError(error, stackTrace);
}
});
await completer.future;
return code;
}
...@@ -7,6 +7,7 @@ import 'dart:convert'; ...@@ -7,6 +7,7 @@ import 'dart:convert';
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../android/android_workflow.dart'; import '../android/android_workflow.dart';
import '../android/apk.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart' show throwToolExit; import '../base/common.dart' show throwToolExit;
import '../base/file_system.dart'; import '../base/file_system.dart';
...@@ -17,7 +18,6 @@ import '../base/process.dart'; ...@@ -17,7 +18,6 @@ import '../base/process.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../commands/build_apk.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../protocol_discovery.dart'; import '../protocol_discovery.dart';
......
// 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 '../base/common.dart';
import '../build_info.dart';
import '../globals.dart';
import 'android_sdk.dart';
import 'gradle.dart';
Future<Null> buildApk({
String target,
BuildInfo buildInfo: BuildInfo.debug
}) async {
if (!isProjectUsingGradle()) {
throwToolExit(
'The build process for Android has changed, and the current project configuration\n'
'is no longer valid. Please consult\n\n'
' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
'for details on how to upgrade the project.'
);
}
// Validate that we can find an android sdk.
if (androidSdk == null)
throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
final List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) {
validationResult.forEach(printError);
throwToolExit('Try re-installing or updating your Android SDK.');
}
return buildGradleProject(buildInfo, target);
}
...@@ -4,31 +4,9 @@ ...@@ -4,31 +4,9 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart' show required; import '../android/apk.dart';
import '../android/android_sdk.dart';
import '../android/gradle.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../globals.dart';
import 'build.dart'; import 'build.dart';
export '../android/android_device.dart' show AndroidDevice;
class ApkKeystoreInfo {
ApkKeystoreInfo({
@required this.keystore,
this.password,
this.keyAlias,
@required this.keyPassword,
}) : assert(keystore != null);
final String keystore;
final String password;
final String keyAlias;
final String keyPassword;
}
class BuildApkCommand extends BuildSubCommand { class BuildApkCommand extends BuildSubCommand {
BuildApkCommand() { BuildApkCommand() {
usesTargetOption(); usesTargetOption();
...@@ -53,29 +31,3 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -53,29 +31,3 @@ class BuildApkCommand extends BuildSubCommand {
await buildApk(buildInfo: getBuildInfo(), target: targetFile); await buildApk(buildInfo: getBuildInfo(), target: targetFile);
} }
} }
Future<Null> buildApk({
String target,
BuildInfo buildInfo: BuildInfo.debug
}) async {
if (!isProjectUsingGradle()) {
throwToolExit(
'The build process for Android has changed, and the current project configuration\n'
'is no longer valid. Please consult\n\n'
' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
'for details on how to upgrade the project.'
);
}
// Validate that we can find an android sdk.
if (androidSdk == null)
throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
final List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) {
validationResult.forEach(printError);
throwToolExit('Try re-installing or updating your Android SDK.');
}
return buildGradleProject(buildInfo, target);
}
...@@ -8,16 +8,10 @@ import 'dart:convert'; ...@@ -8,16 +8,10 @@ import 'dart:convert';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../vmservice.dart'; import '../tracing.dart';
// Names of some of the Timeline events we care about.
const String kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
const String kFrameworkInitEventName = 'Framework initialization';
const String kFirstUsefulFrameEventName = 'Widgets completed first useful frame';
class TraceCommand extends FlutterCommand { class TraceCommand extends FlutterCommand {
TraceCommand() { TraceCommand() {
...@@ -93,119 +87,3 @@ class TraceCommand extends FlutterCommand { ...@@ -93,119 +87,3 @@ class TraceCommand extends FlutterCommand {
printStatus('Trace file saved to ${localFile.path}'); printStatus('Trace file saved to ${localFile.path}');
} }
} }
class Tracing {
Tracing(this.vmService);
static Future<Tracing> connect(Uri uri) async {
final VMService observatory = await VMService.connect(uri);
return new Tracing(observatory);
}
final VMService vmService;
Future<Null> startTracing() async {
await vmService.vm.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
await vmService.vm.clearVMTimeline();
}
/// Stops tracing; optionally wait for first frame.
Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
bool waitForFirstFrame: false
}) async {
Map<String, dynamic> timeline;
if (!waitForFirstFrame) {
// Stop tracing immediately and get the timeline
await vmService.vm.setVMTimelineFlags(<String>[]);
timeline = await vmService.vm.getVMTimeline();
} else {
final Completer<Null> whenFirstFrameRendered = new Completer<Null>();
vmService.onTimelineEvent.listen((ServiceEvent timelineEvent) {
final List<Map<String, dynamic>> events = timelineEvent.timelineEvents;
for (Map<String, dynamic> event in events) {
if (event['name'] == kFirstUsefulFrameEventName)
whenFirstFrameRendered.complete();
}
});
await whenFirstFrameRendered.future.timeout(
const Duration(seconds: 10),
onTimeout: () {
printError(
'Timed out waiting for the first frame event. Either the '
'application failed to start, or the event was missed because '
'"flutter run" took too long to subscribe to timeline events.'
);
return null;
}
);
timeline = await vmService.vm.getVMTimeline();
await vmService.vm.setVMTimelineFlags(<String>[]);
}
return timeline;
}
}
/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
Future<Null> downloadStartupTrace(VMService observatory) async {
final String traceInfoFilePath = fs.path.join(getBuildDirectory(), 'start_up_info.json');
final File traceInfoFile = fs.file(traceInfoFilePath);
// Delete old startup data, if any.
if (await traceInfoFile.exists())
await traceInfoFile.delete();
// Create "build" directory, if missing.
if (!(await traceInfoFile.parent.exists()))
await traceInfoFile.parent.create();
final Tracing tracing = new Tracing(observatory);
final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
waitForFirstFrame: true
);
int extractInstantEventTimestamp(String eventName) {
final List<Map<String, dynamic>> events = timeline['traceEvents'];
final Map<String, dynamic> event = events.firstWhere(
(Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null
);
return event == null ? null : event['ts'];
}
final int engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName);
final int frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName);
final int firstFrameTimestampMicros = extractInstantEventTimestamp(kFirstUsefulFrameEventName);
if (engineEnterTimestampMicros == null) {
printTrace('Engine start event is missing in the timeline: $timeline');
throw 'Engine start event is missing in the timeline. Cannot compute startup time.';
}
if (firstFrameTimestampMicros == null) {
printTrace('First frame event is missing in the timeline: $timeline');
throw 'First frame event is missing in the timeline. Cannot compute startup time.';
}
final int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
final Map<String, dynamic> traceInfo = <String, dynamic>{
'engineEnterTimestampMicros': engineEnterTimestampMicros,
'timeToFirstFrameMicros': timeToFirstFrameMicros,
};
if (frameworkInitTimestampMicros != null) {
traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
}
traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
printStatus('Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.');
printStatus('Saved startup trace info in ${traceInfoFile.path}.');
}
...@@ -7,10 +7,10 @@ import 'dart:async'; ...@@ -7,10 +7,10 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'commands/trace.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
import 'tracing.dart';
class ColdRunner extends ResidentRunner { class ColdRunner extends ResidentRunner {
ColdRunner( ColdRunner(
......
// 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 'base/file_system.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'globals.dart';
import 'vmservice.dart';
// Names of some of the Timeline events we care about.
const String _kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
const String _kFrameworkInitEventName = 'Framework initialization';
const String _kFirstUsefulFrameEventName = 'Widgets completed first useful frame';
class Tracing {
Tracing(this.vmService);
static Future<Tracing> connect(Uri uri) async {
final VMService observatory = await VMService.connect(uri);
return new Tracing(observatory);
}
final VMService vmService;
Future<Null> startTracing() async {
await vmService.vm.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
await vmService.vm.clearVMTimeline();
}
/// Stops tracing; optionally wait for first frame.
Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
bool waitForFirstFrame: false
}) async {
Map<String, dynamic> timeline;
if (!waitForFirstFrame) {
// Stop tracing immediately and get the timeline
await vmService.vm.setVMTimelineFlags(<String>[]);
timeline = await vmService.vm.getVMTimeline();
} else {
final Completer<Null> whenFirstFrameRendered = new Completer<Null>();
vmService.onTimelineEvent.listen((ServiceEvent timelineEvent) {
final List<Map<String, dynamic>> events = timelineEvent.timelineEvents;
for (Map<String, dynamic> event in events) {
if (event['name'] == _kFirstUsefulFrameEventName)
whenFirstFrameRendered.complete();
}
});
await whenFirstFrameRendered.future.timeout(
const Duration(seconds: 10),
onTimeout: () {
printError(
'Timed out waiting for the first frame event. Either the '
'application failed to start, or the event was missed because '
'"flutter run" took too long to subscribe to timeline events.'
);
return null;
}
);
timeline = await vmService.vm.getVMTimeline();
await vmService.vm.setVMTimelineFlags(<String>[]);
}
return timeline;
}
}
/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
Future<Null> downloadStartupTrace(VMService observatory) async {
final String traceInfoFilePath = fs.path.join(getBuildDirectory(), 'start_up_info.json');
final File traceInfoFile = fs.file(traceInfoFilePath);
// Delete old startup data, if any.
if (await traceInfoFile.exists())
await traceInfoFile.delete();
// Create "build" directory, if missing.
if (!(await traceInfoFile.parent.exists()))
await traceInfoFile.parent.create();
final Tracing tracing = new Tracing(observatory);
final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
waitForFirstFrame: true
);
int extractInstantEventTimestamp(String eventName) {
final List<Map<String, dynamic>> events = timeline['traceEvents'];
final Map<String, dynamic> event = events.firstWhere(
(Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null
);
return event == null ? null : event['ts'];
}
final int engineEnterTimestampMicros = extractInstantEventTimestamp(_kFlutterEngineMainEnterEventName);
final int frameworkInitTimestampMicros = extractInstantEventTimestamp(_kFrameworkInitEventName);
final int firstFrameTimestampMicros = extractInstantEventTimestamp(_kFirstUsefulFrameEventName);
if (engineEnterTimestampMicros == null) {
printTrace('Engine start event is missing in the timeline: $timeline');
throw 'Engine start event is missing in the timeline. Cannot compute startup time.';
}
if (firstFrameTimestampMicros == null) {
printTrace('First frame event is missing in the timeline: $timeline');
throw 'First frame event is missing in the timeline. Cannot compute startup time.';
}
final int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
final Map<String, dynamic> traceInfo = <String, dynamic>{
'engineEnterTimestampMicros': engineEnterTimestampMicros,
'timeToFirstFrameMicros': timeToFirstFrameMicros,
};
if (frameworkInitTimestampMicros != null) {
traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
}
traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
printStatus('Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.');
printStatus('Saved startup trace info in ${traceInfoFile.path}.');
}
...@@ -13,7 +13,7 @@ import 'package:http/http.dart'; ...@@ -13,7 +13,7 @@ import 'package:http/http.dart';
import 'package:http/testing.dart'; import 'package:http/testing.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:flutter_tools/executable.dart' as tools; import 'package:flutter_tools/runner.dart' as tools;
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:file/local.dart'; import 'package:file/local.dart';
import 'package:flutter_tools/executable.dart' as tools; import 'package:flutter_tools/runner.dart' as tools;
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart' as io; import 'package:flutter_tools/src/base/io.dart' as io;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment