blob: be3c8e592945b0052ac3e01043702758a6d679d3 [file] [log] [blame]
// Copyright (c) 2014, 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.
/// This library is a wrapper around the Dart to JavaScript (dart2js) compiler.
library services.compiler;
import 'dart:async';
import 'dart:io';
import 'package:bazel_worker/driver.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'common.dart';
import 'flutter_web.dart';
import 'pub.dart';
Logger _logger = Logger('compiler');
/// An interface to the dart2js compiler. A compiler object can process one
/// compile at a time.
class Compiler {
final String sdkPath;
final FlutterWebManager flutterWebManager;
final BazelWorkerDriver _ddcDriver;
String _sdkVersion;
Compiler(this.sdkPath, this.flutterWebManager)
: _ddcDriver = BazelWorkerDriver(
() => Process.start(path.join(sdkPath, 'bin', 'dartdevc'),
<String>['--persistent_worker']),
maxWorkers: 1) {
_sdkVersion = File('dart-sdk.version').readAsStringSync().trim();
}
bool importsOkForCompile(Set<String> imports) {
return !flutterWebManager.hasUnsupportedImport(imports);
}
/// The version of the SDK this copy of dart2js is based on.
String get version {
return File(path.join(sdkPath, 'version')).readAsStringSync().trim();
}
Future<CompilationResults> warmup({bool useHtml = false}) {
return compile(useHtml ? sampleCodeWeb : sampleCode);
}
/// Compile the given string and return the resulting [CompilationResults].
Future<CompilationResults> compile(
String input, {
bool returnSourceMap = false,
}) async {
Set<String> imports = getAllImportsFor(input);
if (!importsOkForCompile(imports)) {
return CompilationResults(problems: <CompilationProblem>[
CompilationProblem._(
'unsupported import: ${flutterWebManager.getUnsupportedImport(imports)}',
),
]);
}
Directory temp = Directory.systemTemp.createTempSync('dartpad');
try {
List<String> arguments = <String>[
'--suppress-hints',
'--terse',
];
if (!returnSourceMap) arguments.add('--no-source-maps');
arguments.add('--packages=${flutterWebManager.packagesFilePath}');
arguments.add('-o$kMainDart.js');
arguments.add(kMainDart);
String compileTarget = path.join(temp.path, kMainDart);
File mainDart = File(compileTarget);
mainDart.writeAsStringSync(input);
File mainJs = File(path.join(temp.path, '$kMainDart.js'));
File mainSourceMap = File(path.join(temp.path, '$kMainDart.js.map'));
final String dart2JSPath = path.join(sdkPath, 'bin', 'dart2js');
_logger.info('About to exec: $dart2JSPath $arguments');
ProcessResult result =
Process.runSync(dart2JSPath, arguments, workingDirectory: temp.path);
if (result.exitCode != 0) {
final CompilationResults results =
CompilationResults(problems: <CompilationProblem>[
CompilationProblem._(result.stdout as String),
]);
return results;
} else {
String sourceMap;
if (returnSourceMap && mainSourceMap.existsSync()) {
sourceMap = mainSourceMap.readAsStringSync();
}
final CompilationResults results = CompilationResults(
compiledJS: mainJs.readAsStringSync(),
sourceMap: sourceMap,
);
return results;
}
} catch (e, st) {
_logger.warning('Compiler failed: $e\n$st');
rethrow;
} finally {
temp.deleteSync(recursive: true);
_logger.info('temp folder removed: ${temp.path}');
}
}
/// Compile the given string and return the resulting [DDCCompilationResults].
Future<DDCCompilationResults> compileDDC(String input) async {
Set<String> imports = getAllImportsFor(input);
if (!importsOkForCompile(imports)) {
return DDCCompilationResults.failed(<CompilationProblem>[
CompilationProblem._(
'unsupported import: ${flutterWebManager.getUnsupportedImport(imports)}',
),
]);
}
Directory temp = Directory.systemTemp.createTempSync('dartpad');
try {
List<String> arguments = <String>[
'--modules=amd',
];
if (flutterWebManager.usesFlutterWeb(imports)) {
arguments.addAll(<String>['-s', flutterWebManager.summaryFilePath]);
}
String compileTarget = path.join(temp.path, kMainDart);
File mainDart = File(compileTarget);
mainDart.writeAsStringSync(input);
arguments.addAll(<String>['-o', path.join(temp.path, '$kMainDart.js')]);
arguments.add('--single-out-file');
arguments.addAll(<String>['--module-name', 'dartpad_main']);
arguments.add(compileTarget);
arguments.addAll(<String>['--library-root', temp.path]);
File mainJs = File(path.join(temp.path, '$kMainDart.js'));
_logger.info('About to exec dartdevc with: $arguments');
final WorkResponse response =
await _ddcDriver.doWork(WorkRequest()..arguments.addAll(arguments));
if (response.exitCode != 0) {
return DDCCompilationResults.failed(<CompilationProblem>[
CompilationProblem._(response.output),
]);
} else {
final DDCCompilationResults results = DDCCompilationResults(
compiledJS: mainJs.readAsStringSync(),
modulesBaseUrl: 'https://storage.googleapis.com/'
'compilation_artifacts/$_sdkVersion/',
);
return results;
}
} catch (e, st) {
_logger.warning('Compiler failed: $e\n$st');
rethrow;
} finally {
temp.deleteSync(recursive: true);
_logger.info('temp folder removed: ${temp.path}');
}
}
Future<void> dispose() => _ddcDriver.terminateWorkers();
}
/// The result of a dart2js compile.
class CompilationResults {
final String compiledJS;
final String sourceMap;
final List<CompilationProblem> problems;
CompilationResults({
this.compiledJS,
this.problems = const <CompilationProblem>[],
this.sourceMap,
});
bool get hasOutput => compiledJS != null && compiledJS.isNotEmpty;
/// This is true if there were no errors.
bool get success => problems.isEmpty;
@override
String toString() => success
? 'CompilationResults: Success'
: 'Compilation errors: ${problems.join('\n')}';
}
/// The result of a DDC compile.
class DDCCompilationResults {
final String compiledJS;
final String modulesBaseUrl;
final List<CompilationProblem> problems;
DDCCompilationResults({this.compiledJS, this.modulesBaseUrl})
: problems = const <CompilationProblem>[];
DDCCompilationResults.failed(this.problems)
: compiledJS = null,
modulesBaseUrl = null;
bool get hasOutput => compiledJS != null && compiledJS.isNotEmpty;
/// This is true if there were no errors.
bool get success => problems.isEmpty;
@override
String toString() => success
? 'CompilationResults: Success'
: 'Compilation errors: ${problems.join('\n')}';
}
/// An issue associated with [CompilationResults].
class CompilationProblem implements Comparable<CompilationProblem> {
final String message;
CompilationProblem._(this.message);
@override
int compareTo(CompilationProblem other) => message.compareTo(other.message);
@override
String toString() => message;
}