| 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..3951e00712dceea6ef441fd305fccdbfbecd2bee
|
| --- /dev/null
|
| +++ b/pkg/front_end/example/incremental_reload/compiler_with_invalidation.dart
|
| @@ -0,0 +1,185 @@
|
| +// 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 tmpDir = Directory.systemTemp.createTempSync('ikg_cache');
|
| + var options = new CompilerOptions()
|
| + ..sdkRoot = sdkRoot
|
| + ..packagesFileUri = Uri.base.resolve('.packages')
|
| + ..strongMode = false
|
| + ..dartLibraries = loadDartLibraries(sdkRoot)
|
| + ..byteStore =
|
| + persistent ? new FileByteStore(tmpDir.path) : 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) {
|
| + lastModified[uri] ??= new File.fromUri(uri).lastModifiedSync();
|
| + } 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;
|
| +}
|
|
|