| Index: pkg/front_end/example/incremental_reload/run.dart
|
| diff --git a/pkg/front_end/example/incremental_reload/run.dart b/pkg/front_end/example/incremental_reload/run.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0e196d78e14f77467d2ef1a2cff7dc0601bc5b33
|
| --- /dev/null
|
| +++ b/pkg/front_end/example/incremental_reload/run.dart
|
| @@ -0,0 +1,284 @@
|
| +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +/// Example that illustrates how to use the incremental compiler and trigger a
|
| +/// hot-reload on the VM after recompiling the application.
|
| +///
|
| +/// This example resembles the `run` command in flutter-tools. It creates an
|
| +/// interactive command-line program that waits for the user to tap a key to
|
| +/// trigger a recompile and reload.
|
| +///
|
| +/// The following instructions assume a linux checkout of the SDK:
|
| +/// * Build the SDK
|
| +///
|
| +/// ```
|
| +/// ./tools/build.py -m release
|
| +/// ```
|
| +///
|
| +/// * On one terminal (terminal A), start this script and point it to an
|
| +/// example program "foo.dart" and keep the job running. A good example
|
| +/// program would do something periodically, so you can see the effect
|
| +/// of a hot-reload while the app is running.
|
| +///
|
| +/// ```
|
| +/// out/ReleaseX64/dart pkg/front_end/example/incremental_reload/run.dart foo.dart out.dill
|
| +/// ```
|
| +///
|
| +/// * Trigger an initial compile of the program by hitting the "c" key in
|
| +/// terminal A.
|
| +///
|
| +/// * On another terminal (terminal B), start the program on the VM, with the
|
| +/// service-protocol enabled and provide the precompiled platform libraries:
|
| +///
|
| +/// ```
|
| +/// out/ReleaseX64/dart --enable-vm-service --platform=out/ReleaseX64/patched_sdk/platform.dill out.dill
|
| +/// ```
|
| +///
|
| +/// * Modify the orginal program
|
| +///
|
| +/// * In terminal A, hit the "r" key to trigger a recompile and hot reload.
|
| +///
|
| +/// * See the changed program in terminal B
|
| +library front_end.example.incremental_reload.run;
|
| +
|
| +import 'dart:io';
|
| +import 'dart:async';
|
| +import 'dart:convert' show ASCII;
|
| +
|
| +import 'package:front_end/src/vm/reload.dart';
|
| +
|
| +import 'compiler_with_invalidation.dart';
|
| +
|
| +VmReloader reloader = new VmReloader();
|
| +AnsiTerminal terminal = new AnsiTerminal();
|
| +
|
| +main(List<String> args) async {
|
| + if (args.length <= 1) {
|
| + print('usage: dart incremental_compile.dart input.dart out.dill');
|
| + exit(1);
|
| + }
|
| +
|
| + var compiler = await createIncrementalCompiler(args[0]);
|
| + var outputUri = Uri.base.resolve(args[1]);
|
| +
|
| + showHeader();
|
| + listenOnKeyPress(compiler, outputUri)
|
| + .whenComplete(() => reloader.disconnect());
|
| +}
|
| +
|
| +/// Implements the interactive UI by listening for input keys from the user.
|
| +Future listenOnKeyPress(IncrementalCompiler compiler, Uri outputUri) {
|
| + var completer = new Completer();
|
| + terminal.singleCharMode = true;
|
| + StreamSubscription subscription;
|
| + subscription = terminal.onCharInput.listen((String char) async {
|
| + try {
|
| + CompilationResult compilationResult;
|
| + ReloadResult reloadResult;
|
| + switch (char) {
|
| + case 'r':
|
| + compilationResult = await rebuild(compiler, outputUri);
|
| + if (!compilationResult.errorSeen &&
|
| + compilationResult.program != null &&
|
| + compilationResult.program.libraries.isNotEmpty) {
|
| + reloadResult = await reload(outputUri);
|
| + }
|
| + break;
|
| + case 'c':
|
| + compilationResult = await rebuild(compiler, outputUri);
|
| + break;
|
| + case 'l':
|
| + reloadResult = await reload(outputUri);
|
| + break;
|
| + case 'q':
|
| + terminal.singleCharMode = false;
|
| + print('');
|
| + subscription.cancel();
|
| + completer.complete(null);
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + if (compilationResult != null || reloadResult != null) {
|
| + reportStats(compilationResult, reloadResult, outputUri);
|
| + }
|
| + } catch (e) {
|
| + terminal.singleCharMode = false;
|
| + subscription.cancel();
|
| + completer.completeError(null);
|
| + rethrow;
|
| + }
|
| + }, onError: (e) {
|
| + terminal.singleCharMode = false;
|
| + subscription.cancel();
|
| + completer.completeError(null);
|
| + });
|
| +
|
| + return completer.future;
|
| +}
|
| +
|
| +/// Request a reload and gather timing metrics.
|
| +Future<ReloadResult> reload(outputUri) async {
|
| + var result = new ReloadResult();
|
| + var reloadTimer = new Stopwatch()..start();
|
| + var reloadResult = await reloader.reload(outputUri);
|
| + reloadTimer.stop();
|
| + result.reloadTime = reloadTimer.elapsedMilliseconds;
|
| + result.errorSeen = false;
|
| + result.errorDetails;
|
| + if (!reloadResult['success']) {
|
| + result.errorSeen = true;
|
| + result.errorDetails = reloadResult['details']['notices'].first['message'];
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +/// Results from requesting a hot reload.
|
| +class ReloadResult {
|
| + /// How long it took to do the hot-reload in the VM.
|
| + int reloadTime = 0;
|
| +
|
| + /// Whether we saw errors during compilation or reload.
|
| + bool errorSeen = false;
|
| +
|
| + /// Error message when [errorSeen] is true.
|
| + String errorDetails;
|
| +}
|
| +
|
| +/// This script shows stats about each reload on the terminal in a table form.
|
| +/// This function prints out the header of such table.
|
| +showHeader() {
|
| + print(terminal.bolden('Press a key to trigger a command:'));
|
| + print(terminal.bolden(' r: incremental compile + reload'));
|
| + print(terminal.bolden(' c: incremental compile w/o reload'));
|
| + print(terminal.bolden(' l: reload w/o recompile'));
|
| + print(terminal.bolden(' q: quit'));
|
| + print(terminal.bolden(
|
| + '# Files Files % ------- Time ------------------------- Binary\n'
|
| + ' Modified Sent Total Check Compile Reload Total Avg Size '));
|
| +}
|
| +
|
| +/// Whether to show stats as a single line (override metrics on each request)
|
| +const bool singleLine = false;
|
| +
|
| +var total = 0;
|
| +var iter = 0;
|
| +var timeSum = 0;
|
| +var lastLine = 0;
|
| +
|
| +/// Show stats about a recompile and reload.
|
| +reportStats(CompilationResult compilationResult, ReloadResult reloadResult,
|
| + Uri outputUri) {
|
| + compilationResult ??= new CompilationResult();
|
| + reloadResult ??= new ReloadResult();
|
| + int changed = compilationResult.changed;
|
| + int updated = compilationResult.program?.libraries?.length ?? 0;
|
| + int totalFiles = compilationResult.totalFiles;
|
| + int invalidateTime = compilationResult.invalidateTime;
|
| + int compileTime = compilationResult.compileTime;
|
| + int reloadTime = reloadResult.reloadTime;
|
| + bool errorSeen = compilationResult.errorSeen || reloadResult.errorSeen;
|
| + String errorDetails =
|
| + compilationResult.errorDetails ?? reloadResult.errorDetails;
|
| +
|
| + var totalTime = invalidateTime + compileTime + reloadTime;
|
| + timeSum += totalTime;
|
| + total++;
|
| + iter++;
|
| + var avgTime = (timeSum / total).truncate();
|
| + var size = new File.fromUri(outputUri).statSync().size;
|
| +
|
| + var percent = (100 * updated / totalFiles).toStringAsFixed(0);
|
| + var line = '${_padl(iter, 3)}: '
|
| + '${_padl(changed, 8)} ${_padl(updated, 5)} ${_padl(percent, 4)}% '
|
| + '${_padl(invalidateTime, 5)} ms '
|
| + '${_padl(compileTime, 5)} ms '
|
| + '${_padl(reloadTime, 5)} ms '
|
| + '${_padl(totalTime, 5)} ms '
|
| + '${_padl(avgTime, 5)} ms '
|
| + '${_padl(size, 5)}b';
|
| + var len = line.length;
|
| + if (singleLine) stdout.write('\r');
|
| + stdout.write((errorSeen) ? terminal.red(line) : terminal.green(line));
|
| + if (!singleLine) stdout.write('\n');
|
| + if (errorSeen) {
|
| + if (!singleLine) errorDetails = ' error: $errorDetails\n';
|
| + len += errorDetails.length;
|
| + stdout.write(errorDetails);
|
| + }
|
| + if (singleLine) {
|
| + var diff = " " * (lastLine - len);
|
| + stdout.write(diff);
|
| + }
|
| + lastLine = len;
|
| +}
|
| +
|
| +_padl(x, n) {
|
| + var s = '$x';
|
| + return ' ' * (n - s.length) + s;
|
| +}
|
| +
|
| +/// Helper to control an ANSI terminal (adapted from flutter_tools)
|
| +class AnsiTerminal {
|
| + static const String _bold = '\u001B[1m';
|
| + static const String _green = '\u001B[32m';
|
| + static const String _red = '\u001B[31m';
|
| + static const String _reset = '\u001B[0m';
|
| + static const String _clear = '\u001B[2J\u001B[H';
|
| +
|
| + static const int _ENXIO = 6;
|
| + static const int _ENOTTY = 25;
|
| + static const int _ENETRESET = 102;
|
| + static const int _INVALID_HANDLE = 6;
|
| +
|
| + /// Setting the line mode can throw for some terminals (with "Operation not
|
| + /// supported on socket"), but the error can be safely ignored.
|
| + static const List<int> _lineModeIgnorableErrors = const <int>[
|
| + _ENXIO,
|
| + _ENOTTY,
|
| + _ENETRESET,
|
| + _INVALID_HANDLE,
|
| + ];
|
| +
|
| + String bolden(String message) => wrap(message, _bold);
|
| + String green(String message) => wrap(message, _green);
|
| + String red(String message) => wrap(message, _red);
|
| +
|
| + String wrap(String message, String escape) {
|
| + final StringBuffer buffer = new StringBuffer();
|
| + for (String line in message.split('\n'))
|
| + buffer.writeln('$escape$line$_reset');
|
| + final String result = buffer.toString();
|
| + // avoid introducing a new newline to the emboldened text
|
| + return (!message.endsWith('\n') && result.endsWith('\n'))
|
| + ? result.substring(0, result.length - 1)
|
| + : result;
|
| + }
|
| +
|
| + String clearScreen() => _clear;
|
| +
|
| + set singleCharMode(bool value) {
|
| + // TODO(goderbauer): instead of trying to set lineMode and then catching
|
| + // [_ENOTTY] or [_INVALID_HANDLE], we should check beforehand if stdin is
|
| + // connected to a terminal or not.
|
| + // (Requires https://github.com/dart-lang/sdk/issues/29083 to be resolved.)
|
| + try {
|
| + // The order of setting lineMode and echoMode is important on Windows.
|
| + if (value) {
|
| + stdin.echoMode = false;
|
| + stdin.lineMode = false;
|
| + } else {
|
| + stdin.lineMode = true;
|
| + stdin.echoMode = true;
|
| + }
|
| + } on StdinException catch (error) {
|
| + if (!_lineModeIgnorableErrors.contains(error.osError?.errorCode)) rethrow;
|
| + }
|
| + }
|
| +
|
| + /// Return keystrokes from the console.
|
| + ///
|
| + /// Useful when the console is in [singleCharMode].
|
| + Stream<String> get onCharInput => stdin.transform(ASCII.decoder);
|
| +}
|
|
|