server.dart 10.7 KB
Newer Older
1 2 3 4 5 6 7 8
library frontend_server;

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

import 'package:args/args.dart';

9 10 11 12 13 14 15 16 17 18
// front_end/src imports below that require lint `ignore_for_file`
// are a temporary state of things until frontend team builds better api
// that would replace api used below. This api was made private in
// an effort to discourage further use.
// ignore_for_file: implementation_imports
import 'package:front_end/src/api_prototype/compilation_message.dart';

import 'package:front_end/src/api_prototype/byte_store.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
19
import 'package:kernel/ast.dart';
20 21
import 'package:kernel/binary/ast_to_binary.dart';
import 'package:kernel/binary/limited_ast_to_binary.dart';
22
import 'package:kernel/target/flutter.dart';
23 24
import 'package:kernel/target/targets.dart';
import 'package:usage/uuid/uuid.dart';
25
import 'package:vm/kernel_front_end.dart' show compileToKernel;
26 27 28 29 30 31 32 33 34

ArgParser _argParser = new ArgParser(allowTrailingOptions: true)
  ..addFlag('train',
      help: 'Run through sample command line to produce snapshot',
      negatable: false)
  ..addFlag('incremental',
      help: 'Run compiler in incremental mode', defaultsTo: false)
  ..addOption('sdk-root',
      help: 'Path to sdk root',
35 36 37 38
      defaultsTo: '../../out/android_debug/flutter_patched_sdk')
  ..addOption('byte-store',
      help: 'Path to file byte store used to keep incremental compiler state.'
          ' If omitted, then memory byte store is used.',
39
      defaultsTo: null)
40 41 42
  ..addFlag('aot',
      help: 'Run compiler in AOT mode (enables whole-program transformations)',
      defaultsTo: false)
43 44 45 46 47 48 49
  ..addFlag('link-platform',
      help: 'When in batch mode, link platform kernel file into result kernel file.'
          ' Intended use is to satisfy different loading strategies implemented'
          ' by gen_snapshot(which needs platform embedded) vs'
          ' Flutter engine(which does not)',
      defaultsTo: true);

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

String _usage = '''
Usage: server [options] [input.dart]

If input dart source code is provided on the command line, then the server 
compiles it, generates dill file and exits.
If no input dart source is provided on the command line, server waits for 
instructions from stdin.

Instructions:
- compile <input.dart>
- recompile <boundary-key>
<path/to/updated/file1.dart>
<path/to/updated/file2.dart>
...
<boundary-key>
- accept
- reject
- quit

Output:
- result <boundary-key>
<compiler output>
<boundary-key> [<output.dill>]

Options:
${_argParser.usage}
''';

enum _State { READY_FOR_INSTRUCTION, RECOMPILE_LIST }

/// Actions that every compiler should implement.
abstract class CompilerInterface {
  /// Compile given Dart program identified by `filename` with given list of
84 85 86 87 88 89
  /// `options`. When `generator` parameter is omitted, new instance of
  /// `IncrementalKernelGenerator` is created by this method. Main use for this
  /// parameter is for mocking in tests.
  Future<Null> compile(String filename, ArgResults options, {
    IncrementalKernelGenerator generator,
  });
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

  /// Assuming some Dart program was previously compiled, recompile it again
  /// taking into account some changed(invalidated) sources.
  Future<Null> recompileDelta();

  /// Accept results of previous compilation so that next recompilation cycle
  /// won't recompile sources that were previously reported as changed.
  void acceptLastDelta();

  /// Reject results of previous compilation. Next recompilation cycle will
  /// recompile sources indicated as changed.
  void rejectLastDelta();

  /// This let's compiler know that source file identifed by `uri` was changed.
  void invalidate(Uri uri);
105 106 107 108

  /// Resets incremental compiler accept/reject status so that next time
  /// recompile is requested, complete kernel file is produced.
  void resetIncrementalCompiler();
109 110
}

111 112 113 114 115 116 117 118
/// Class that for test mocking purposes encapsulates creation of [BinaryPrinter].
class BinaryPrinterFactory {
  /// Creates new [BinaryPrinter] to write to [targetSink].
  BinaryPrinter newBinaryPrinter(IOSink targetSink) {
    return new LimitedBinaryPrinter(
      targetSink,
      (_) => true /* predicate */,
      false /* excludeUriToSource */);  }
119 120
}

121
class _FrontendCompiler implements CompilerInterface {
122
  _FrontendCompiler(this._outputStream, { this.printerFactory }) {
123
    _outputStream ??= stdout;
124
    printerFactory ??= new BinaryPrinterFactory();
125 126
  }

127
  StringSink _outputStream;
128
  BinaryPrinterFactory printerFactory;
129

130 131
  IncrementalKernelGenerator _generator;
  String _filename;
132
  String _kernelBinaryFilename;
133 134

  @override
135 136 137 138
  Future<Null> compile(String filename, ArgResults options, {
    IncrementalKernelGenerator generator,
  }) async {
    _filename = filename;
139
    _kernelBinaryFilename = "$filename.dill";
140 141 142
    final String boundaryKey = new Uuid().generateV4();
    _outputStream.writeln("result $boundaryKey");
    final Uri sdkRoot = _ensureFolderPath(options['sdk-root']);
143
    final String byteStorePath = options['byte-store'];
144
    final CompilerOptions compilerOptions = new CompilerOptions()
145 146 147
      ..byteStore = byteStorePath != null
          ? new FileByteStore(byteStorePath)
          : new MemoryByteStore()
148 149
      ..sdkRoot = sdkRoot
      ..strongMode = false
150
      ..target = new FlutterTarget(new TargetFlags())
151
      ..onError = (CompilationMessage message) {
152 153 154 155 156 157 158 159 160 161 162 163
        final StringBuffer outputMessage = new StringBuffer()
          ..write(_severityName(message.severity))
          ..write(': ');
        if (message.span != null) {
          outputMessage.writeln(message.span.message(message.message));
        } else {
          outputMessage.writeln(message.message);
        }
        if (message.tip != null) {
          outputMessage.writeln(message.tip);
        }
        _outputStream.write(outputMessage);
164 165 166
      };
    Program program;
    if (options['incremental']) {
167 168 169
      _generator = generator != null
        ? generator
        : await IncrementalKernelGenerator.newInstance(
170 171
          compilerOptions, Uri.base.resolve(_filename),
          useMinimalGenerator: true
172
        );
173 174 175
      final DeltaProgram deltaProgram = await _generator.computeDelta();
      program = deltaProgram.newProgram;
    } else {
176 177 178 179 180 181 182
      if (options['link-platform']) {
        // TODO(aam): Remove linkedDependencies once platform is directly embedded
        // into VM snapshot and http://dartbug.com/30111 is fixed.
        compilerOptions.linkedDependencies = <Uri>[
          sdkRoot.resolve('platform.dill')
        ];
      }
183 184 185
      program = await compileToKernel(
          Uri.base.resolve(_filename), compilerOptions,
          aot: options['aot']);
186 187
    }
    if (program != null) {
188 189 190 191
      final IOSink sink = new File(_kernelBinaryFilename).openWrite();
      final BinaryPrinter printer = printerFactory.newBinaryPrinter(sink);
      printer.writeProgramFile(program);
      await sink.close();
192
      _outputStream.writeln("$boundaryKey $_kernelBinaryFilename");
193 194 195 196 197 198 199 200 201 202
    } else
      _outputStream.writeln(boundaryKey);
    return null;
  }

  @override
  Future<Null> recompileDelta() async {
    final String boundaryKey = new Uuid().generateV4();
    _outputStream.writeln("result $boundaryKey");
    final DeltaProgram deltaProgram = await _generator.computeDelta();
203 204 205 206
    final IOSink sink = new File(_kernelBinaryFilename).openWrite();
    final BinaryPrinter printer = printerFactory.newBinaryPrinter(sink);
    printer.writeProgramFile(deltaProgram.newProgram);
    await sink.close();
207
    _outputStream.writeln("$boundaryKey $_kernelBinaryFilename");
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    return null;
  }

  @override
  void acceptLastDelta() {
    _generator.acceptLastDelta();
  }

  @override
  void rejectLastDelta() {
    _generator.rejectLastDelta();
  }

  @override
  void invalidate(Uri uri) {
    _generator.invalidate(uri);
  }

226 227 228 229 230
  @override
  void resetIncrementalCompiler() {
    _generator.reset();
  }

231
  Uri _ensureFolderPath(String path) {
232 233
    // This is a URI, not a file path, so the forward slash is correct even
    // on Windows.
234 235 236 237
    if (!path.endsWith('/'))
      path = '$path/';
    return Uri.base.resolve(path);
  }
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

  static String _severityName(Severity severity) {
    switch (severity) {
      case Severity.error:
        return "Error";
      case Severity.internalProblem:
        return "Internal problem";
      case Severity.nit:
        return "Nit";
      case Severity.warning:
        return "Warning";
      default:
        return severity.toString();
    }
  }
253 254 255 256 257 258
}

/// Entry point for this module, that creates `_FrontendCompiler` instance and
/// processes user input.
/// `compiler` is an optional parameter so it can be replaced with mocked
/// version for testing.
259 260 261 262
Future<int> starter(List<String> args, {
  CompilerInterface compiler,
  Stream<List<int>> input, StringSink output,
  IncrementalKernelGenerator generator,
263
  BinaryPrinterFactory binaryPrinterFactory,
264
}) async {
265 266 267 268
  final ArgResults options = _argParser.parse(args);
  if (options['train'])
    return 0;

269
  compiler ??= new _FrontendCompiler(output, printerFactory: binaryPrinterFactory);
270 271
  input ??= stdin;

272 273 274 275 276 277 278
  // Has to be a directory, that won't have any of the compiled application
  // sources, so that no relative paths could show up in the kernel file.
  Directory.current = Directory.systemTemp;
  final Directory workingDirectory = new Directory("flutter_frontend_server");
  workingDirectory.createSync();
  Directory.current = workingDirectory;

279
  if (options.rest.isNotEmpty) {
280
    await compiler.compile(options.rest[0], options, generator: generator);
281 282 283 284 285 286
    return 0;
  }

  _State state = _State.READY_FOR_INSTRUCTION;
  String boundaryKey;
  input
287 288 289
    .transform(UTF8.decoder)
    .transform(new LineSplitter())
    .listen((String string) async {
290 291 292 293 294
    switch (state) {
      case _State.READY_FOR_INSTRUCTION:
        const String COMPILE_INSTRUCTION_SPACE = 'compile ';
        const String RECOMPILE_INSTRUCTION_SPACE = 'recompile ';
        if (string.startsWith(COMPILE_INSTRUCTION_SPACE)) {
295 296
          final String filename = string.substring(COMPILE_INSTRUCTION_SPACE.length);
          await compiler.compile(filename, options, generator: generator);
297 298 299 300 301 302 303
        } else if (string.startsWith(RECOMPILE_INSTRUCTION_SPACE)) {
          boundaryKey = string.substring(RECOMPILE_INSTRUCTION_SPACE.length);
          state = _State.RECOMPILE_LIST;
        } else if (string == 'accept')
          compiler.acceptLastDelta();
        else if (string == 'reject')
          compiler.rejectLastDelta();
304 305
        else if (string == 'reset')
          compiler.resetIncrementalCompiler();
306 307 308 309 310 311 312 313 314 315 316 317 318 319
        else if (string == 'quit')
          exit(0);
        break;
      case _State.RECOMPILE_LIST:
        if (string == boundaryKey) {
          compiler.recompileDelta();
          state = _State.READY_FOR_INSTRUCTION;
        } else
          compiler.invalidate(Uri.base.resolve(string));
        break;
    }
  });
  return 0;
}