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 /// A wrapper on top of the [IncrementalKernelGenerator] that tracks | |
6 /// file modifications between subsequent compilation requests and only | |
7 /// invalidates those files that appear to be modified. | |
8 library front_end.example.incremental_reload.compiler_with_invalidation; | |
9 | |
10 import 'dart:io'; | |
11 import 'dart:async'; | |
12 import 'dart:convert' show JSON; | |
13 | |
14 import 'package:front_end/compiler_options.dart'; | |
15 import 'package:front_end/incremental_kernel_generator.dart'; | |
16 import 'package:front_end/src/incremental/file_byte_store.dart'; | |
17 import 'package:front_end/src/incremental/byte_store.dart'; | |
18 import 'package:kernel/ast.dart'; | |
19 import 'package:kernel/binary/limited_ast_to_binary.dart'; | |
20 | |
21 /// Create an instance of an [IncrementalCompiler] to compile a program whose | |
22 /// main entry point file is [entry]. This uses some default options | |
23 /// for the location of the sdk and temporary folder to save intermediate | |
24 /// results. | |
25 // TODO(sigmund): make this example work outside of the SDK repo. | |
26 Future<IncrementalCompiler> createIncrementalCompiler(String entry, | |
27 {bool persistent: true}) { | |
28 var entryUri = Uri.base.resolve(entry); | |
29 var dartVm = Uri.base.resolve(Platform.resolvedExecutable); | |
30 var sdkRoot = dartVm.resolve("patched_sdk/"); | |
31 var options = new CompilerOptions() | |
32 ..sdkRoot = sdkRoot | |
33 ..packagesFileUri = Uri.base.resolve('.packages') | |
34 ..strongMode = false | |
35 ..dartLibraries = loadDartLibraries(sdkRoot) | |
36 ..byteStore = persistent | |
37 ? 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.
| |
38 : new MemoryByteStore(); | |
39 return IncrementalCompiler.create(options, entryUri); | |
40 } | |
41 | |
42 /// Reads the `libraries.json` file for an SDK to provide the location of the | |
43 /// SDK files. | |
44 // TODO(sigmund): this should be handled by package:front_end internally. | |
45 Map<String, Uri> loadDartLibraries(Uri sdkRoot) { | |
46 var libraries = sdkRoot.resolve('lib/libraries.json'); | |
47 var map = | |
48 JSON.decode(new File.fromUri(libraries).readAsStringSync())['libraries']; | |
49 var dartLibraries = <String, Uri>{}; | |
50 map.forEach((k, v) => dartLibraries[k] = libraries.resolve(v)); | |
51 return dartLibraries; | |
52 } | |
53 | |
54 /// An incremental compiler that monitors file modifications on disk and | |
55 /// invalidates only files that have been modified since the previous time the | |
56 /// compiler was invoked. | |
57 class IncrementalCompiler { | |
58 /// Underlying incremental compiler implementation. | |
59 IncrementalKernelGenerator _generator; | |
60 | |
61 /// Last modification for each tracked input file. | |
62 Map<Uri, DateTime> lastModified = {}; | |
63 | |
64 /// Create an instance of [IncrementalCompiler]. | |
65 static Future<IncrementalCompiler> create( | |
66 CompilerOptions options, Uri entryUri) async { | |
67 return new IncrementalCompiler._internal( | |
68 await IncrementalKernelGenerator.newInstance(options, entryUri)); | |
69 } | |
70 | |
71 IncrementalCompiler._internal(this._generator); | |
72 | |
73 /// Callback for the [IncrementalKernelGenerator] to keep track of relevant | |
74 /// files. | |
75 Future _watch(Uri uri, bool used) { | |
76 if (used) { | |
77 if (lastModified[uri] == null) { | |
78 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.
| |
79 } | |
80 } else { | |
81 lastModified.remove(uri); | |
82 } | |
83 return new Future.value(null); | |
84 } | |
85 | |
86 /// How many files changed during the last call to [recompile]. | |
87 int changed; | |
88 | |
89 /// Time spent updating time-stamps from disk during the last call to | |
90 /// [recompile]. | |
91 int invalidateTime; | |
92 | |
93 /// Time actually spent compiling the code in the incremental compiler during | |
94 /// the last call to [recompile]. | |
95 int compileTime; | |
96 | |
97 /// Determine which files have been modified, and recompile the program | |
98 /// incrementally based on that information. | |
99 Future<Program> recompile() async { | |
100 changed = 0; | |
101 invalidateTime = 0; | |
102 compileTime = 0; | |
103 | |
104 var invalidateTimer = new Stopwatch()..start(); | |
105 for (var uri in lastModified.keys.toList()) { | |
106 var last = lastModified[uri]; | |
107 var current = new File.fromUri(uri).lastModifiedSync(); | |
108 if (last != current) { | |
109 lastModified[uri] = current; | |
110 _generator.invalidate(uri); | |
111 changed++; | |
112 } | |
113 } | |
114 invalidateTimer.stop(); | |
115 invalidateTime = invalidateTimer.elapsedMilliseconds; | |
116 if (changed == 0 && lastModified.isNotEmpty) return null; | |
117 | |
118 var compileTimer = new Stopwatch()..start(); | |
119 var delta = await _generator.computeDelta(watch: _watch); | |
120 compileTimer.stop(); | |
121 compileTime = compileTimer.elapsedMilliseconds; | |
122 var program = delta.newProgram; | |
123 return program; | |
124 } | |
125 } | |
126 | |
127 /// The result of an incremental compile and metrics collected during the | |
128 /// the compilation. | |
129 class CompilationResult { | |
130 /// How many files were modified by the time we invoked the compiler again. | |
131 int changed = 0; | |
132 | |
133 /// How many files are currently being tracked for modifications. | |
134 int totalFiles = 0; | |
135 | |
136 /// How long it took to invalidate files that have been modified. | |
137 int invalidateTime = 0; | |
138 | |
139 /// How long it took to build the incremental program. | |
140 int compileTime = 0; | |
141 | |
142 /// How long it took to do the hot-reload in the VM. | |
143 int reloadTime = 0; | |
144 | |
145 /// Whether we saw errors during compilation or reload. | |
146 bool errorSeen = false; | |
147 | |
148 /// Error message when [errorSeen] is true. | |
149 String errorDetails; | |
150 | |
151 /// The program that was generated by the incremental compiler. | |
152 Program program; | |
153 } | |
154 | |
155 /// Request a recompile and possibly a reload, and gather timing metrics. | |
156 Future<CompilationResult> rebuild( | |
157 IncrementalCompiler compiler, Uri outputUri) async { | |
158 var result = new CompilationResult(); | |
159 try { | |
160 var program = result.program = await compiler.recompile(); | |
161 if (program != null && !program.libraries.isEmpty) { | |
162 // TODO(sigmund): the incremental generator should set the main method | |
163 // directly. | |
164 var mainLib = program.libraries.last; | |
165 program.mainMethod = | |
166 mainLib.procedures.firstWhere((p) => p.name.name == 'main'); | |
167 var sink = new File.fromUri(outputUri).openWrite(); | |
168 | |
169 // TODO(sigmund): should the incremental generator always filter these | |
170 // libraries instead? | |
171 new LimitedBinaryPrinter( | |
172 sink, (library) => library.importUri.scheme != 'dart') | |
173 .writeProgramFile(program); | |
174 await sink.close(); | |
175 } | |
176 } catch (e, t) { | |
177 result.errorDetails = 'compilation error: $e, $t'; | |
178 result.errorSeen = true; | |
179 } | |
180 | |
181 result.changed = compiler.changed; | |
182 result.totalFiles = compiler.lastModified.length; | |
183 result.invalidateTime = compiler.invalidateTime; | |
184 result.compileTime = compiler.compileTime; | |
185 result.reloadTime = 0; | |
186 return result; | |
187 } | |
OLD | NEW |