compile.dart 10.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';

import 'package:usage/uuid/uuid.dart';

import 'artifacts.dart';
11
import 'base/common.dart';
12
import 'base/context.dart';
13 14 15 16
import 'base/io.dart';
import 'base/process_manager.dart';
import 'globals.dart';

17 18
KernelCompiler get kernelCompiler => context[KernelCompiler];

19 20
typedef void CompilerMessageConsumer(String message);

21
class CompilerOutput {
22 23
  final String outputFilename;
  final int errorCount;
24

25
  const CompilerOutput(this.outputFilename, this.errorCount);
26 27
}

28
class _StdoutHandler {
29
  _StdoutHandler({this.consumer: printError}) {
30 31 32
    reset();
  }

33
  final CompilerMessageConsumer consumer;
34
  String boundaryKey;
35
  Completer<CompilerOutput> compilerOutput;
36 37 38 39 40 41

  void handler(String string) {
    const String kResultPrefix = 'result ';
    if (boundaryKey == null) {
      if (string.startsWith(kResultPrefix))
        boundaryKey = string.substring(kResultPrefix.length);
42 43 44 45 46 47 48 49 50 51 52
    } else if (string.startsWith(boundaryKey)) {
      if (string.length <= boundaryKey.length) {
        compilerOutput.complete(null);
        return;
      }
      final int spaceDelimiter = string.lastIndexOf(' ');
      compilerOutput.complete(
        new CompilerOutput(
          string.substring(boundaryKey.length + 1, spaceDelimiter),
          int.parse(string.substring(spaceDelimiter + 1).trim())));
    }
53
    else
54
      consumer('compiler message: $string');
55
  }
56 57 58 59 60

  // This is needed to get ready to process next compilation result output,
  // with its own boundary key and new completer.
  void reset() {
    boundaryKey = null;
61
    compilerOutput = new Completer<CompilerOutput>();
62
  }
63 64
}

65 66 67
class KernelCompiler {
  const KernelCompiler();

68 69
  Future<CompilerOutput> compile({
    String sdkRoot,
70
    String mainPath,
71
    String outputFilePath,
72
    String depFilePath,
73 74
    bool linkPlatformKernelIn: false,
    bool aot: false,
75
    List<String> entryPointsJsonFiles,
76
    bool trackWidgetCreation: false,
77
    List<String> extraFrontEndOptions,
78
    String incrementalCompilerByteStorePath,
79 80
    String packagesPath,
    List<String> fileSystemRoots,
81 82
    String fileSystemScheme,
  }) async {
83 84 85 86 87 88 89 90 91 92
    final String frontendServer = artifacts.getArtifactPath(
      Artifact.frontendServerSnapshotForEngineDartSdk
    );

    // This is a URI, not a file path, so the forward slash is correct even on Windows.
    if (!sdkRoot.endsWith('/'))
      sdkRoot = '$sdkRoot/';
    final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
    if (!processManager.canRun(engineDartPath)) {
      throwToolExit('Unable to find Dart binary at $engineDartPath');
93
    }
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
    final List<String> command = <String>[
      engineDartPath,
      frontendServer,
      '--sdk-root',
      sdkRoot,
      '--strong',
      '--target=flutter',
    ];
    if (trackWidgetCreation)
      command.add('--track-widget-creation');
    if (!linkPlatformKernelIn)
      command.add('--no-link-platform');
    if (aot) {
      command.add('--aot');
      command.add('--tfa');
    }
    if (entryPointsJsonFiles != null) {
      for (String entryPointsJson in entryPointsJsonFiles) {
        command.addAll(<String>['--entry-points', entryPointsJson]);
      }
    }
    if (incrementalCompilerByteStorePath != null) {
      command.add('--incremental');
    }
    if (packagesPath != null) {
      command.addAll(<String>['--packages', packagesPath]);
    }
    if (outputFilePath != null) {
      command.addAll(<String>['--output-dill', outputFilePath]);
    }
    if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) {
      command.addAll(<String>['--depfile', depFilePath]);
    }
    if (fileSystemRoots != null) {
      for (String root in fileSystemRoots) {
        command.addAll(<String>['--filesystem-root', root]);
      }
    }
    if (fileSystemScheme != null) {
      command.addAll(<String>['--filesystem-scheme', fileSystemScheme]);
134
    }
135

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    if (extraFrontEndOptions != null)
      command.addAll(extraFrontEndOptions);
    command.add(mainPath);
    printTrace(command.join(' '));
    final Process server = await processManager
        .start(command)
        .catchError((dynamic error, StackTrace stack) {
      printError('Failed to start frontend server $error, $stack');
    });

    final _StdoutHandler stdoutHandler = new _StdoutHandler();

    server.stderr
      .transform(utf8.decoder)
      .listen((String s) { printError('compiler message: $s'); });
    server.stdout
      .transform(utf8.decoder)
      .transform(const LineSplitter())
      .listen(stdoutHandler.handler);
    final int exitCode = await server.exitCode;
    return exitCode == 0 ? stdoutHandler.compilerOutput.future : null;
  }
158 159 160 161 162 163 164 165
}

/// Wrapper around incremental frontend server compiler, that communicates with
/// server via stdin/stdout.
///
/// The wrapper is intended to stay resident in memory as user changes, reloads,
/// restarts the Flutter app.
class ResidentCompiler {
166
  ResidentCompiler(this._sdkRoot, {bool trackWidgetCreation: false,
167 168
      String packagesPath, List<String> fileSystemRoots, String fileSystemScheme ,
      CompilerMessageConsumer compilerMessageConsumer: printError})
169
    : assert(_sdkRoot != null),
170
      _trackWidgetCreation = trackWidgetCreation,
171
      _packagesPath = packagesPath,
172 173
      _fileSystemRoots = fileSystemRoots,
      _fileSystemScheme = fileSystemScheme,
174
      stdoutHandler = new _StdoutHandler(consumer: compilerMessageConsumer) {
175 176 177 178 179
    // This is a URI, not a file path, so the forward slash is correct even on Windows.
    if (!_sdkRoot.endsWith('/'))
      _sdkRoot = '$_sdkRoot/';
  }

180
  final bool _trackWidgetCreation;
181
  final String _packagesPath;
182 183
  final List<String> _fileSystemRoots;
  final String _fileSystemScheme;
184 185
  String _sdkRoot;
  Process _server;
186
  final _StdoutHandler stdoutHandler;
187 188 189

  /// If invoked for the first time, it compiles Dart script identified by
  /// [mainPath], [invalidatedFiles] list is ignored.
190 191 192
  /// On successive runs [invalidatedFiles] indicates which files need to be
  /// recompiled. If [mainPath] is [null], previously used [mainPath] entry
  /// point that is used for recompilation.
193
  /// Binary file name is returned if compilation was successful, otherwise
194
  /// null is returned.
195
  Future<CompilerOutput> recompile(String mainPath, List<String> invalidatedFiles,
196
      {String outputPath, String packagesFilePath}) async {
197 198
    stdoutHandler.reset();

199 200 201
    // First time recompile is called we actually have to compile the app from
    // scratch ignoring list of invalidated files.
    if (_server == null)
202
      return _compile(_mapFilename(mainPath), outputPath, _mapFilename(packagesFilePath));
203 204

    final String inputKey = new Uuid().generateV4();
205
    _server.stdin.writeln('recompile ${mainPath != null ? _mapFilename(mainPath) + " ": ""}$inputKey');
206 207
    for (String fileUri in invalidatedFiles) {
      _server.stdin.writeln(_mapFileUri(fileUri));
208
    }
209 210
    _server.stdin.writeln(inputKey);

211
    return stdoutHandler.compilerOutput.future;
212 213
  }

214 215
  Future<CompilerOutput> _compile(String scriptFilename, String outputPath,
      String packagesFilePath) async {
216 217 218
    final String frontendServer = artifacts.getArtifactPath(
      Artifact.frontendServerSnapshotForEngineDartSdk
    );
219
    final List<String> command = <String>[
220
      artifacts.getArtifactPath(Artifact.engineDartBinary),
221 222 223
      frontendServer,
      '--sdk-root',
      _sdkRoot,
224
      '--incremental',
225 226
      '--strong',
      '--target=flutter',
227
    ];
228
    if (outputPath != null) {
229
      command.addAll(<String>['--output-dill', outputPath]);
230
    }
231
    if (packagesFilePath != null) {
232
      command.addAll(<String>['--packages', packagesFilePath]);
233
    }
234
    if (_trackWidgetCreation) {
235
      command.add('--track-widget-creation');
236
    }
237
    if (_packagesPath != null) {
238
      command.addAll(<String>['--packages', _packagesPath]);
239
    }
240 241
    if (_fileSystemRoots != null) {
      for (String root in _fileSystemRoots) {
242
        command.addAll(<String>['--filesystem-root', root]);
243 244 245
      }
    }
    if (_fileSystemScheme != null) {
246
      command.addAll(<String>['--filesystem-scheme', _fileSystemScheme]);
247
    }
248 249
    printTrace(command.join(' '));
    _server = await processManager.start(command);
250
    _server.stdout
251
      .transform(utf8.decoder)
252
      .transform(const LineSplitter())
253 254 255 256 257
      .listen(
        stdoutHandler.handler,
        onDone: () {
          // when outputFilename future is not completed, but stdout is closed
          // process has died unexpectedly.
258 259
          if (!stdoutHandler.compilerOutput.isCompleted) {
            stdoutHandler.compilerOutput.complete(null);
260 261 262
          }
        });

263
    _server.stderr
264
      .transform(utf8.decoder)
265
      .transform(const LineSplitter())
266
      .listen((String s) { printError('compiler message: $s'); });
267 268 269

    _server.stdin.writeln('compile $scriptFilename');

270
    return stdoutHandler.compilerOutput.future;
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
  }


  /// Should be invoked when results of compilation are accepted by the client.
  ///
  /// Either [accept] or [reject] should be called after every [recompile] call.
  void accept() {
    _server.stdin.writeln('accept');
  }

  /// Should be invoked when results of compilation are rejected by the client.
  ///
  /// Either [accept] or [reject] should be called after every [recompile] call.
  void reject() {
    _server.stdin.writeln('reject');
  }
287 288 289 290 291 292 293

  /// Should be invoked when frontend server compiler should forget what was
  /// accepted previously so that next call to [recompile] produces complete
  /// kernel file.
  void reset() {
    _server.stdin.writeln('reset');
  }
294

295 296 297 298 299 300 301 302 303 304 305 306 307
  String _mapFilename(String filename) {
    if (_fileSystemRoots != null) {
      for (String root in _fileSystemRoots) {
        if (filename.startsWith(root)) {
          return new Uri(
              scheme: _fileSystemScheme, path: filename.substring(root.length))
              .toString();
        }
      }
    }
    return filename;
  }

308 309 310 311 312 313 314 315 316 317 318 319 320 321
  String _mapFileUri(String fileUri) {
    if (_fileSystemRoots != null) {
      final String filename = Uri.parse(fileUri).toFilePath();
      for (String root in _fileSystemRoots) {
        if (filename.startsWith(root)) {
          return new Uri(
              scheme: _fileSystemScheme, path: filename.substring(root.length))
              .toString();
        }
      }
    }
    return fileUri;
  }

322 323 324 325
  Future<dynamic> shutdown() {
    _server.kill();
    return _server.exitCode;
  }
326
}