Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(976)

Unified Diff: pkg/front_end/example/incremental_reload/run.dart

Issue 2928483005: Add an incremental reloader example and a utility tool to trigger a reload by (Closed)
Patch Set: CL comments Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld 408576698