OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// Example that illustrates how to use the incremental compiler and trigger a |
| 6 /// hot-reload on the VM after recompiling the application. |
| 7 /// |
| 8 /// This example resembles the `run` command in flutter-tools. It creates an |
| 9 /// interactive command-line program that waits for the user to tap a key to |
| 10 /// trigger a recompile and reload. |
| 11 /// |
| 12 /// The following instructions assume a linux checkout of the SDK: |
| 13 /// * Build the SDK |
| 14 /// |
| 15 /// ``` |
| 16 /// ./tools/build.py -m release |
| 17 /// ``` |
| 18 /// |
| 19 /// * On one terminal (terminal A), start this script and point it to an |
| 20 /// example program "foo.dart" and keep the job running. A good example |
| 21 /// program would do something periodically, so you can see the effect |
| 22 /// of a hot-reload while the app is running. |
| 23 /// |
| 24 /// ``` |
| 25 /// out/ReleaseX64/dart pkg/front_end/example/incremental_reload/run.dart foo
.dart out.dill |
| 26 /// ``` |
| 27 /// |
| 28 /// * Trigger an initial compile of the program by hitting the "c" key in |
| 29 /// terminal A. |
| 30 /// |
| 31 /// * On another terminal (terminal B), start the program on the VM, with the |
| 32 /// service-protocol enabled and provide the precompiled platform libraries: |
| 33 /// |
| 34 /// ``` |
| 35 /// out/ReleaseX64/dart --enable-vm-service --platform=out/ReleaseX64/patched
_sdk/platform.dill out.dill |
| 36 /// ``` |
| 37 /// |
| 38 /// * Modify the orginal program |
| 39 /// |
| 40 /// * In terminal A, hit the "r" key to trigger a recompile and hot reload. |
| 41 /// |
| 42 /// * See the changed program in terminal B |
| 43 library front_end.example.incremental_reload.run; |
| 44 |
| 45 import 'dart:io'; |
| 46 import 'dart:async'; |
| 47 import 'dart:convert' show ASCII; |
| 48 |
| 49 import 'package:front_end/src/vm/reload.dart'; |
| 50 |
| 51 import 'compiler_with_invalidation.dart'; |
| 52 |
| 53 VmReloader reloader = new VmReloader(); |
| 54 AnsiTerminal terminal = new AnsiTerminal(); |
| 55 |
| 56 main(List<String> args) async { |
| 57 if (args.length <= 1) { |
| 58 print('usage: dart incremental_compile.dart input.dart out.dill'); |
| 59 exit(1); |
| 60 } |
| 61 |
| 62 var compiler = await createIncrementalCompiler(args[0]); |
| 63 var outputUri = Uri.base.resolve(args[1]); |
| 64 |
| 65 showHeader(); |
| 66 listenOnKeyPress(compiler, outputUri) |
| 67 .whenComplete(() => reloader.disconnect()); |
| 68 } |
| 69 |
| 70 /// Implements the interactive UI by listening for input keys from the user. |
| 71 Future listenOnKeyPress(IncrementalCompiler compiler, Uri outputUri) { |
| 72 var completer = new Completer(); |
| 73 terminal.singleCharMode = true; |
| 74 StreamSubscription subscription; |
| 75 subscription = terminal.onCharInput.listen((String char) async { |
| 76 try { |
| 77 CompilationResult compilationResult; |
| 78 ReloadResult reloadResult; |
| 79 switch (char) { |
| 80 case 'r': |
| 81 compilationResult = await rebuild(compiler, outputUri); |
| 82 if (!compilationResult.errorSeen && |
| 83 compilationResult.program != null && |
| 84 compilationResult.program.libraries.isNotEmpty) { |
| 85 reloadResult = await reload(outputUri); |
| 86 } |
| 87 break; |
| 88 case 'c': |
| 89 compilationResult = await rebuild(compiler, outputUri); |
| 90 break; |
| 91 case 'l': |
| 92 reloadResult = await reload(outputUri); |
| 93 break; |
| 94 case 'q': |
| 95 terminal.singleCharMode = false; |
| 96 print(''); |
| 97 subscription.cancel(); |
| 98 completer.complete(null); |
| 99 break; |
| 100 default: |
| 101 break; |
| 102 } |
| 103 if (compilationResult != null || reloadResult != null) { |
| 104 reportStats(compilationResult, reloadResult, outputUri); |
| 105 } |
| 106 } catch (e) { |
| 107 terminal.singleCharMode = false; |
| 108 subscription.cancel(); |
| 109 completer.completeError(null); |
| 110 rethrow; |
| 111 } |
| 112 }, onError: (e) { |
| 113 terminal.singleCharMode = false; |
| 114 subscription.cancel(); |
| 115 completer.completeError(null); |
| 116 }); |
| 117 |
| 118 return completer.future; |
| 119 } |
| 120 |
| 121 /// Request a reload and gather timing metrics. |
| 122 Future<ReloadResult> reload(outputUri) async { |
| 123 var result = new ReloadResult(); |
| 124 var reloadTimer = new Stopwatch()..start(); |
| 125 var reloadResult = await reloader.reload(outputUri); |
| 126 reloadTimer.stop(); |
| 127 result.reloadTime = reloadTimer.elapsedMilliseconds; |
| 128 result.errorSeen = false; |
| 129 result.errorDetails; |
| 130 if (!reloadResult['success']) { |
| 131 result.errorSeen = true; |
| 132 result.errorDetails = reloadResult['details']['notices'].first['message']; |
| 133 } |
| 134 return result; |
| 135 } |
| 136 |
| 137 /// Results from requesting a hot reload. |
| 138 class ReloadResult { |
| 139 /// How long it took to do the hot-reload in the VM. |
| 140 int reloadTime = 0; |
| 141 |
| 142 /// Whether we saw errors during compilation or reload. |
| 143 bool errorSeen = false; |
| 144 |
| 145 /// Error message when [errorSeen] is true. |
| 146 String errorDetails; |
| 147 } |
| 148 |
| 149 /// This script shows stats about each reload on the terminal in a table form. |
| 150 /// This function prints out the header of such table. |
| 151 showHeader() { |
| 152 print(terminal.bolden('Press a key to trigger a command:')); |
| 153 print(terminal.bolden(' r: incremental compile + reload')); |
| 154 print(terminal.bolden(' c: incremental compile w/o reload')); |
| 155 print(terminal.bolden(' l: reload w/o recompile')); |
| 156 print(terminal.bolden(' q: quit')); |
| 157 print(terminal.bolden( |
| 158 '# Files Files % ------- Time ------------------------- Binar
y\n' |
| 159 ' Modified Sent Total Check Compile Reload Total Avg
Size ')); |
| 160 } |
| 161 |
| 162 /// Whether to show stats as a single line (override metrics on each request) |
| 163 const bool singleLine = false; |
| 164 |
| 165 var total = 0; |
| 166 var iter = 0; |
| 167 var timeSum = 0; |
| 168 var lastLine = 0; |
| 169 |
| 170 /// Show stats about a recompile and reload. |
| 171 reportStats(CompilationResult compilationResult, ReloadResult reloadResult, |
| 172 Uri outputUri) { |
| 173 compilationResult ??= new CompilationResult(); |
| 174 reloadResult ??= new ReloadResult(); |
| 175 int changed = compilationResult.changed; |
| 176 int updated = compilationResult.program?.libraries?.length ?? 0; |
| 177 int totalFiles = compilationResult.totalFiles; |
| 178 int invalidateTime = compilationResult.invalidateTime; |
| 179 int compileTime = compilationResult.compileTime; |
| 180 int reloadTime = reloadResult.reloadTime; |
| 181 bool errorSeen = compilationResult.errorSeen || reloadResult.errorSeen; |
| 182 String errorDetails = |
| 183 compilationResult.errorDetails ?? reloadResult.errorDetails; |
| 184 |
| 185 var totalTime = invalidateTime + compileTime + reloadTime; |
| 186 timeSum += totalTime; |
| 187 total++; |
| 188 iter++; |
| 189 var avgTime = (timeSum / total).truncate(); |
| 190 var size = new File.fromUri(outputUri).statSync().size; |
| 191 |
| 192 var percent = (100 * updated / totalFiles).toStringAsFixed(0); |
| 193 var line = '${_padl(iter, 3)}: ' |
| 194 '${_padl(changed, 8)} ${_padl(updated, 5)} ${_padl(percent, 4)}% ' |
| 195 '${_padl(invalidateTime, 5)} ms ' |
| 196 '${_padl(compileTime, 5)} ms ' |
| 197 '${_padl(reloadTime, 5)} ms ' |
| 198 '${_padl(totalTime, 5)} ms ' |
| 199 '${_padl(avgTime, 5)} ms ' |
| 200 '${_padl(size, 5)}b'; |
| 201 var len = line.length; |
| 202 if (singleLine) stdout.write('\r'); |
| 203 stdout.write((errorSeen) ? terminal.red(line) : terminal.green(line)); |
| 204 if (!singleLine) stdout.write('\n'); |
| 205 if (errorSeen) { |
| 206 if (!singleLine) errorDetails = ' error: $errorDetails\n'; |
| 207 len += errorDetails.length; |
| 208 stdout.write(errorDetails); |
| 209 } |
| 210 if (singleLine) { |
| 211 var diff = " " * (lastLine - len); |
| 212 stdout.write(diff); |
| 213 } |
| 214 lastLine = len; |
| 215 } |
| 216 |
| 217 _padl(x, n) { |
| 218 var s = '$x'; |
| 219 return ' ' * (n - s.length) + s; |
| 220 } |
| 221 |
| 222 /// Helper to control an ANSI terminal (adapted from flutter_tools) |
| 223 class AnsiTerminal { |
| 224 static const String _bold = '\u001B[1m'; |
| 225 static const String _green = '\u001B[32m'; |
| 226 static const String _red = '\u001B[31m'; |
| 227 static const String _reset = '\u001B[0m'; |
| 228 static const String _clear = '\u001B[2J\u001B[H'; |
| 229 |
| 230 static const int _ENXIO = 6; |
| 231 static const int _ENOTTY = 25; |
| 232 static const int _ENETRESET = 102; |
| 233 static const int _INVALID_HANDLE = 6; |
| 234 |
| 235 /// Setting the line mode can throw for some terminals (with "Operation not |
| 236 /// supported on socket"), but the error can be safely ignored. |
| 237 static const List<int> _lineModeIgnorableErrors = const <int>[ |
| 238 _ENXIO, |
| 239 _ENOTTY, |
| 240 _ENETRESET, |
| 241 _INVALID_HANDLE, |
| 242 ]; |
| 243 |
| 244 String bolden(String message) => wrap(message, _bold); |
| 245 String green(String message) => wrap(message, _green); |
| 246 String red(String message) => wrap(message, _red); |
| 247 |
| 248 String wrap(String message, String escape) { |
| 249 final StringBuffer buffer = new StringBuffer(); |
| 250 for (String line in message.split('\n')) |
| 251 buffer.writeln('$escape$line$_reset'); |
| 252 final String result = buffer.toString(); |
| 253 // avoid introducing a new newline to the emboldened text |
| 254 return (!message.endsWith('\n') && result.endsWith('\n')) |
| 255 ? result.substring(0, result.length - 1) |
| 256 : result; |
| 257 } |
| 258 |
| 259 String clearScreen() => _clear; |
| 260 |
| 261 set singleCharMode(bool value) { |
| 262 // TODO(goderbauer): instead of trying to set lineMode and then catching |
| 263 // [_ENOTTY] or [_INVALID_HANDLE], we should check beforehand if stdin is |
| 264 // connected to a terminal or not. |
| 265 // (Requires https://github.com/dart-lang/sdk/issues/29083 to be resolved.) |
| 266 try { |
| 267 // The order of setting lineMode and echoMode is important on Windows. |
| 268 if (value) { |
| 269 stdin.echoMode = false; |
| 270 stdin.lineMode = false; |
| 271 } else { |
| 272 stdin.lineMode = true; |
| 273 stdin.echoMode = true; |
| 274 } |
| 275 } on StdinException catch (error) { |
| 276 if (!_lineModeIgnorableErrors.contains(error.osError?.errorCode)) rethrow; |
| 277 } |
| 278 } |
| 279 |
| 280 /// Return keystrokes from the console. |
| 281 /// |
| 282 /// Useful when the console is in [singleCharMode]. |
| 283 Stream<String> get onCharInput => stdin.transform(ASCII.decoder); |
| 284 } |
OLD | NEW |