Chromium Code Reviews| Index: pkg/front_end/example/incremental_reload/compiler_with_invalidation.dart |
| diff --git a/pkg/front_end/example/incremental_reload/compiler_with_invalidation.dart b/pkg/front_end/example/incremental_reload/compiler_with_invalidation.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4047aaca7e9c7dae6a5421a14e6012ce78abcaa7 |
| --- /dev/null |
| +++ b/pkg/front_end/example/incremental_reload/compiler_with_invalidation.dart |
| @@ -0,0 +1,187 @@ |
| +// 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. |
| + |
| +/// A wrapper on top of the [IncrementalKernelGenerator] that tracks |
| +/// file modifications between subsequent compilation requests and only |
| +/// invalidates those files that appear to be modified. |
| +library front_end.example.incremental_reload.compiler_with_invalidation; |
| + |
| +import 'dart:io'; |
| +import 'dart:async'; |
| +import 'dart:convert' show JSON; |
| + |
| +import 'package:front_end/compiler_options.dart'; |
| +import 'package:front_end/incremental_kernel_generator.dart'; |
| +import 'package:front_end/src/incremental/file_byte_store.dart'; |
| +import 'package:front_end/src/incremental/byte_store.dart'; |
| +import 'package:kernel/ast.dart'; |
| +import 'package:kernel/binary/limited_ast_to_binary.dart'; |
| + |
| +/// Create an instance of an [IncrementalCompiler] to compile a program whose |
| +/// main entry point file is [entry]. This uses some default options |
| +/// for the location of the sdk and temporary folder to save intermediate |
| +/// results. |
| +// TODO(sigmund): make this example work outside of the SDK repo. |
| +Future<IncrementalCompiler> createIncrementalCompiler(String entry, |
| + {bool persistent: true}) { |
| + var entryUri = Uri.base.resolve(entry); |
| + var dartVm = Uri.base.resolve(Platform.resolvedExecutable); |
| + var sdkRoot = dartVm.resolve("patched_sdk/"); |
| + var options = new CompilerOptions() |
| + ..sdkRoot = sdkRoot |
| + ..packagesFileUri = Uri.base.resolve('.packages') |
| + ..strongMode = false |
| + ..dartLibraries = loadDartLibraries(sdkRoot) |
| + ..byteStore = persistent |
| + ? new FileByteStore('/tmp/ikg_cache') |
|
scheglov
2017/06/07 01:04:42
This will not work on Windows.
Siggi Cherem (dart-lang)
2017/06/07 20:17:11
Done.
|
| + : new MemoryByteStore(); |
| + return IncrementalCompiler.create(options, entryUri); |
| +} |
| + |
| +/// Reads the `libraries.json` file for an SDK to provide the location of the |
| +/// SDK files. |
| +// TODO(sigmund): this should be handled by package:front_end internally. |
| +Map<String, Uri> loadDartLibraries(Uri sdkRoot) { |
| + var libraries = sdkRoot.resolve('lib/libraries.json'); |
| + var map = |
| + JSON.decode(new File.fromUri(libraries).readAsStringSync())['libraries']; |
| + var dartLibraries = <String, Uri>{}; |
| + map.forEach((k, v) => dartLibraries[k] = libraries.resolve(v)); |
| + return dartLibraries; |
| +} |
| + |
| +/// An incremental compiler that monitors file modifications on disk and |
| +/// invalidates only files that have been modified since the previous time the |
| +/// compiler was invoked. |
| +class IncrementalCompiler { |
| + /// Underlying incremental compiler implementation. |
| + IncrementalKernelGenerator _generator; |
| + |
| + /// Last modification for each tracked input file. |
| + Map<Uri, DateTime> lastModified = {}; |
| + |
| + /// Create an instance of [IncrementalCompiler]. |
| + static Future<IncrementalCompiler> create( |
| + CompilerOptions options, Uri entryUri) async { |
| + return new IncrementalCompiler._internal( |
| + await IncrementalKernelGenerator.newInstance(options, entryUri)); |
| + } |
| + |
| + IncrementalCompiler._internal(this._generator); |
| + |
| + /// Callback for the [IncrementalKernelGenerator] to keep track of relevant |
| + /// files. |
| + Future _watch(Uri uri, bool used) { |
| + if (used) { |
| + if (lastModified[uri] == null) { |
| + lastModified[uri] = new File.fromUri(uri).lastModifiedSync(); |
|
scheglov
2017/06/07 01:04:42
??=
Siggi Cherem (dart-lang)
2017/06/07 20:17:11
Done.
|
| + } |
| + } else { |
| + lastModified.remove(uri); |
| + } |
| + return new Future.value(null); |
| + } |
| + |
| + /// How many files changed during the last call to [recompile]. |
| + int changed; |
| + |
| + /// Time spent updating time-stamps from disk during the last call to |
| + /// [recompile]. |
| + int invalidateTime; |
| + |
| + /// Time actually spent compiling the code in the incremental compiler during |
| + /// the last call to [recompile]. |
| + int compileTime; |
| + |
| + /// Determine which files have been modified, and recompile the program |
| + /// incrementally based on that information. |
| + Future<Program> recompile() async { |
| + changed = 0; |
| + invalidateTime = 0; |
| + compileTime = 0; |
| + |
| + var invalidateTimer = new Stopwatch()..start(); |
| + for (var uri in lastModified.keys.toList()) { |
| + var last = lastModified[uri]; |
| + var current = new File.fromUri(uri).lastModifiedSync(); |
| + if (last != current) { |
| + lastModified[uri] = current; |
| + _generator.invalidate(uri); |
| + changed++; |
| + } |
| + } |
| + invalidateTimer.stop(); |
| + invalidateTime = invalidateTimer.elapsedMilliseconds; |
| + if (changed == 0 && lastModified.isNotEmpty) return null; |
| + |
| + var compileTimer = new Stopwatch()..start(); |
| + var delta = await _generator.computeDelta(watch: _watch); |
| + compileTimer.stop(); |
| + compileTime = compileTimer.elapsedMilliseconds; |
| + var program = delta.newProgram; |
| + return program; |
| + } |
| +} |
| + |
| +/// The result of an incremental compile and metrics collected during the |
| +/// the compilation. |
| +class CompilationResult { |
| + /// How many files were modified by the time we invoked the compiler again. |
| + int changed = 0; |
| + |
| + /// How many files are currently being tracked for modifications. |
| + int totalFiles = 0; |
| + |
| + /// How long it took to invalidate files that have been modified. |
| + int invalidateTime = 0; |
| + |
| + /// How long it took to build the incremental program. |
| + int compileTime = 0; |
| + |
| + /// 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; |
| + |
| + /// The program that was generated by the incremental compiler. |
| + Program program; |
| +} |
| + |
| +/// Request a recompile and possibly a reload, and gather timing metrics. |
| +Future<CompilationResult> rebuild( |
| + IncrementalCompiler compiler, Uri outputUri) async { |
| + var result = new CompilationResult(); |
| + try { |
| + var program = result.program = await compiler.recompile(); |
| + if (program != null && !program.libraries.isEmpty) { |
| + // TODO(sigmund): the incremental generator should set the main method |
| + // directly. |
| + var mainLib = program.libraries.last; |
| + program.mainMethod = |
| + mainLib.procedures.firstWhere((p) => p.name.name == 'main'); |
| + var sink = new File.fromUri(outputUri).openWrite(); |
| + |
| + // TODO(sigmund): should the incremental generator always filter these |
| + // libraries instead? |
| + new LimitedBinaryPrinter( |
| + sink, (library) => library.importUri.scheme != 'dart') |
| + .writeProgramFile(program); |
| + await sink.close(); |
| + } |
| + } catch (e, t) { |
| + result.errorDetails = 'compilation error: $e, $t'; |
| + result.errorSeen = true; |
| + } |
| + |
| + result.changed = compiler.changed; |
| + result.totalFiles = compiler.lastModified.length; |
| + result.invalidateTime = compiler.invalidateTime; |
| + result.compileTime = compiler.compileTime; |
| + result.reloadTime = 0; |
| + return result; |
| +} |