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

Side by Side Diff: lib/src/server/server.dart

Issue 1788973002: Remove code that requires whole-program compile (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: merged Created 4 years, 9 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 unified diff | Download patch
« no previous file with comments | « lib/src/server/dependency_graph.dart ('k') | lib/src/summary.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2015, 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 /// Development server that compiles Dart to JS on the fly.
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:io';
10
11 import 'package:analyzer/file_system/file_system.dart' show ResourceUriResolver;
12 import 'package:analyzer/file_system/memory_file_system.dart';
13 import 'package:analyzer/src/generated/engine.dart'
14 show AnalysisContext, ChangeSet;
15 import 'package:analyzer/src/generated/error.dart';
16 import 'package:analyzer/src/generated/source.dart';
17 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
18 import 'package:path/path.dart' as path;
19 import 'package:shelf/shelf.dart' as shelf;
20 import 'package:shelf/shelf_io.dart' as shelf;
21 import 'package:shelf_static/shelf_static.dart' as shelf_static;
22
23 import '../codegen/code_generator.dart' show CodeGenerator;
24 import '../codegen/html_codegen.dart' show generateEntryHtml;
25 import '../codegen/js_codegen.dart';
26 import '../report/html_reporter.dart' show HtmlReporter;
27 import '../analysis_context.dart';
28 import '../compiler.dart' show AbstractCompiler, createErrorReporter;
29 import '../info.dart'
30 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
31 import '../options.dart';
32 import '../report.dart';
33 import '../utils.dart';
34
35 import 'dependency_graph.dart';
36
37 /// Encapsulates the logic when the compiler is run as a development server.
38 class ServerCompiler extends AbstractCompiler {
39 SourceNode _entryNode;
40 List<LibraryInfo> _libraries = <LibraryInfo>[];
41 final _generators = <CodeGenerator>[];
42 bool _hashing;
43 bool _failure = false;
44
45 factory ServerCompiler(AnalysisContext context, CompilerOptions options,
46 {AnalysisErrorListener reporter}) {
47 var srcOpts = options.sourceOptions;
48 var inputFiles = options.inputs;
49 var inputUris = inputFiles.map((String inputFile) =>
50 inputFile.startsWith('dart:') || inputFile.startsWith('package:')
51 ? Uri.parse(inputFile)
52 : new Uri.file(path.absolute(srcOpts.useImplicitHtml
53 ? SourceResolverOptions.implicitHtmlFile
54 : inputFile)));
55 var graph = new SourceGraph(context, reporter, options);
56 var entryNodes =
57 inputUris.map((inputUri) => graph.nodeFromUri(inputUri)).toList();
58
59 return new ServerCompiler._(context, options, reporter, graph, entryNodes);
60 }
61
62 ServerCompiler._(
63 AnalysisContext context,
64 CompilerOptions options,
65 AnalysisErrorListener reporter,
66 SourceGraph graph,
67 List<SourceNode> entryNodes)
68 : super(context, options, reporter) {
69 _entryNode = entryNodes.length == 1
70 ? entryNodes.first
71 : new EntryNode(graph, new Uri.file(inputBaseDir), entryNodes);
72
73 if (outputDir != null) {
74 _generators.add(new JSGenerator(this));
75 }
76 // TODO(sigmund): refactor to support hashing of the dart output?
77 _hashing = options.enableHashing && _generators.length == 1;
78 }
79
80 CheckerResults run() {
81 var clock = new Stopwatch()..start();
82
83 // TODO(sigmund): we are missing a couple failures here. The
84 // dependency_graph now detects broken imports or unsupported features
85 // like more than one script tag (see .severe messages in
86 // dependency_graph.dart). Such failures should be reported back
87 // here so we can mark failure=true in the CheckerResults.
88 rebuild(_entryNode, _buildSource);
89 clock.stop();
90 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
91 _log.fine('Compiled ${_libraries.length} libraries in ${time} s\n');
92 return new CheckerResults(
93 _libraries, _failure || options.codegenOptions.forceCompile);
94 }
95
96 bool _buildSource(SourceNode node) {
97 node.clearSummary();
98 if (node is HtmlSourceNode) {
99 _buildHtmlFile(node);
100 } else if (node is DartSourceNode) {
101 _buildDartLibrary(node);
102 } else if (node is ResourceSourceNode) {
103 _buildResourceFile(node);
104 } else {
105 assert(false); // should not get a build request on PartSourceNode
106 }
107
108 // TODO(sigmund): don't always return true.
109 // Use summaries to determine when rebuilding is needed.
110 return true;
111 }
112
113 void _buildHtmlFile(HtmlSourceNode node) {
114 if (outputDir == null) return;
115 var output = generateEntryHtml(node, this);
116 if (output == null) {
117 _failure = true;
118 return;
119 }
120
121 var filepath =
122 resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir);
123 String outputFile = path.join(outputDir, filepath);
124 new File(outputFile)
125 ..createSync(recursive: true)
126 ..writeAsStringSync(output);
127 }
128
129 void _buildResourceFile(ResourceSourceNode node) {
130 // ResourceSourceNodes files that just need to be copied over to the output
131 // location. These can be external dependencies or pieces of the
132 // dev_compiler runtime.
133 if (outputDir == null) return;
134 var filepath =
135 resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir);
136 assert(filepath != null);
137 filepath = path.join(outputDir, filepath);
138 var dir = path.dirname(filepath);
139 new Directory(dir).createSync(recursive: true);
140 new File.fromUri(node.source.uri).copySync(filepath);
141 if (_hashing) node.cachingHash = computeHashFromFile(filepath);
142 }
143
144 void _buildDartLibrary(DartSourceNode node) {
145 print('Compiling ${node.uri}');
146 var source = node.source;
147 // TODO(sigmund): find out from analyzer team if there is a better way
148 context.applyChanges(new ChangeSet()..changedSource(source));
149 var entryUnit = context.resolveCompilationUnit2(source, source);
150 var lib = entryUnit.element.enclosingElement;
151 if (!options.checkSdk && lib.source.isInSystemLibrary) return;
152 var current = node.info;
153 if (current != null) {
154 assert(current.library == lib);
155 } else {
156 node.info = current = new LibraryInfo(lib);
157 }
158 _libraries.add(current);
159
160 var resolvedParts = node.parts
161 .map((p) => context.resolveCompilationUnit2(p.source, source))
162 .toList(growable: false);
163 var libraryUnit = new LibraryUnit(entryUnit, resolvedParts);
164 bool failureInLib = false;
165 for (var unit in libraryUnit.libraryThenParts) {
166 var unitSource = unit.element.source;
167 // TODO(sigmund): integrate analyzer errors with static-info (issue #6).
168 failureInLib = computeErrors(unitSource) || failureInLib;
169 }
170 if (failureInLib) {
171 _failure = true;
172 if (!options.codegenOptions.forceCompile) return;
173 }
174
175 for (var cg in _generators) {
176 var hash = cg.generateLibrary(libraryUnit);
177 if (_hashing) node.cachingHash = hash;
178 }
179 }
180
181 void _runAgain() {
182 var clock = new Stopwatch()..start();
183 _libraries = <LibraryInfo>[];
184 int changed = 0;
185
186 // TODO(sigmund): propagate failures here (see TODO in run).
187 rebuild(_entryNode, (n) {
188 changed++;
189 return _buildSource(n);
190 });
191 clock.stop();
192 if (changed > 0) _dumpInfoIfRequested();
193 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
194 _log.fine("Compiled ${changed} libraries in ${time} s\n");
195 }
196
197 _dumpInfoIfRequested() {
198 var reporter = this.reporter;
199 if (reporter is HtmlReporter) {
200 reporter.finish(options);
201 } else if (reporter is SummaryReporter) {
202 var result = reporter.result;
203 if (outputDir != null) {
204 var filepath = path.join(outputDir, 'messages.json');
205 new File(filepath).writeAsStringSync(JSON.encode(result.toJsonMap()));
206 } else {
207 print(summaryToString(result));
208 }
209 }
210 }
211 }
212
213 class DevServer {
214 final ServerCompiler compiler;
215 final String outDir;
216 final String host;
217 final int port;
218 final String _entryPath;
219
220 factory DevServer(CompilerOptions options) {
221 assert(options.inputs.isNotEmpty);
222
223 var fileResolvers = createFileResolvers(options.sourceOptions);
224 if (options.sourceOptions.useImplicitHtml) {
225 fileResolvers.insert(0, _createImplicitEntryResolver(options.inputs[0]));
226 }
227
228 var context = createAnalysisContextWithSources(options.sourceOptions,
229 fileResolvers: fileResolvers);
230
231 var entryPath = path.basename(options.inputs[0]);
232 var extension = path.extension(entryPath);
233 if (extension != '.html' && !options.sourceOptions.useImplicitHtml) {
234 print('error: devc in server mode requires an HTML or Dart entry point.');
235 exit(1);
236 }
237
238 // TODO(sigmund): allow running without a dir, but keep output in memory?
239 var outDir = options.codegenOptions.outputDir;
240 if (outDir == null) {
241 print('error: devc in server mode also requires specifying and '
242 'output location for generated code.');
243 exit(1);
244 }
245 var port = options.port;
246 var host = options.host;
247 var reporter = createErrorReporter(context, options);
248 var compiler = new ServerCompiler(context, options, reporter: reporter);
249 return new DevServer._(compiler, outDir, host, port, entryPath);
250 }
251
252 DevServer._(ServerCompiler compiler, this.outDir, this.host, this.port,
253 String entryPath)
254 : this.compiler = compiler,
255 // TODO(jmesserly): this logic is duplicated in a few places
256 this._entryPath = compiler.options.sourceOptions.useImplicitHtml
257 ? SourceResolverOptions.implicitHtmlFile
258 : entryPath;
259
260 Future start() async {
261 // Create output directory if needed. shelf_static will fail otherwise.
262 var out = new Directory(outDir);
263 if (!await out.exists()) await out.create(recursive: true);
264
265 var generatedHandler =
266 shelf_static.createStaticHandler(outDir, defaultDocument: _entryPath);
267 var sourceHandler = shelf_static.createStaticHandler(compiler.inputBaseDir,
268 serveFilesOutsidePath: true);
269 // TODO(vsm): Is there a better builtin way to compose these handlers?
270 var topLevelHandler = (shelf.Request request) {
271 // Prefer generated code
272 var response = generatedHandler(request);
273 if (response.statusCode == 404) {
274 // Fall back on original sources
275 response = sourceHandler(request);
276 }
277 return response;
278 };
279
280 var handler = const shelf.Pipeline()
281 .addMiddleware(rebuildAndCache)
282 .addHandler(topLevelHandler);
283 await shelf.serve(handler, host, port);
284 print('Serving $_entryPath at http://$host:$port/');
285 // Give the compiler a head start. This is not needed for correctness,
286 // but will likely speed up the first load. Regardless of whether compile
287 // succeeds we should still start the server.
288 compiler.run();
289 // Server has started so this future will complete.
290 }
291
292 shelf.Handler rebuildAndCache(shelf.Handler handler) => (request) {
293 // Trigger recompile only when requesting the HTML page.
294 var segments = request.url.pathSegments;
295 bool isEntryPage = segments.length == 0 || segments[0] == _entryPath;
296 if (isEntryPage) compiler._runAgain();
297
298 // To help browsers cache resources that don't change, we serve these
299 // resources by adding a query parameter containing their hash:
300 // /{path-to-file.js}?____cached={hash}
301 var hash = request.url.queryParameters['____cached'];
302 var response = handler(request);
303 var policy = hash != null ? 'max-age=${24 * 60 * 60}' : 'no-cache';
304 var headers = {'cache-control': policy};
305 if (hash != null) {
306 // Note: the cache-control header should be enough, but this doesn't
307 // hurt and can help renew the policy after it expires.
308 headers['ETag'] = hash;
309 }
310 return response.change(headers: headers);
311 };
312 }
313
314 UriResolver _createImplicitEntryResolver(String entryPath) {
315 var entry = path.toUri(path.absolute(SourceResolverOptions.implicitHtmlFile));
316 var src = path.toUri(path.absolute(entryPath));
317 var provider = new MemoryResourceProvider();
318 provider.newFile(
319 entry.path, '<body><script type="application/dart" src="$src"></script>');
320 return new _ExistingSourceUriResolver(new ResourceUriResolver(provider));
321 }
322
323 /// A UriResolver that continues to the next one if it fails to find an existing
324 /// source file. This is unlike normal URI resolvers, that always return
325 /// something, even if it is a non-existing file.
326 class _ExistingSourceUriResolver implements UriResolver {
327 final UriResolver resolver;
328 _ExistingSourceUriResolver(this.resolver);
329
330 Source resolveAbsolute(Uri uri, [Uri actualUri]) {
331 var src = resolver.resolveAbsolute(uri, actualUri);
332 return src.exists() ? src : null;
333 }
334
335 Uri restoreAbsolute(Source source) => resolver.restoreAbsolute(source);
336 }
337
338 final _log = new Logger('dev_compiler.src.server');
339 final _earlyErrorResult = new CheckerResults(const [], true);
OLDNEW
« no previous file with comments | « lib/src/server/dependency_graph.dart ('k') | lib/src/summary.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698