import 'dart:async';
import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/terminal.dart';
import '../build_info.dart';
import '../bundle.dart';
import '../codegen.dart';
import '../compile.dart';
import '../dart/package_map.dart';
import '../globals.dart';
import '../project.dart';
class _CompilationRequest {
_CompilationRequest(this.path, this.result);
String path;
Completer<String> result;
}
class TestCompiler {
TestCompiler(
this.trackWidgetCreation,
this.flutterProject,
) : testFilePath = getKernelPathForTransformerOptions(
fs.path.join(flutterProject.directory.path, getBuildDirectory(), 'testfile.dill'),
trackWidgetCreation: trackWidgetCreation,
) {
final Directory outputDillDirectory = fs.systemTempDirectory.createTempSync('flutter_test_compiler.');
outputDill = outputDillDirectory.childFile('output.dill');
printTrace('Compiler will use the following file as its incremental dill file: ${outputDill.path}');
printTrace('Listening to compiler controller...');
compilerController.stream.listen(_onCompilationRequest, onDone: () {
printTrace('Deleting ${outputDillDirectory.path}...');
outputDillDirectory.deleteSync(recursive: true);
});
}
final StreamController<_CompilationRequest> compilerController = StreamController<_CompilationRequest>();
final List<_CompilationRequest> compilationQueue = <_CompilationRequest>[];
final FlutterProject flutterProject;
final bool trackWidgetCreation;
final String testFilePath;
ResidentCompiler compiler;
File outputDill;
bool _suppressOutput = false;
Future<String> compile(String mainDart) {
final Completer<String> completer = Completer<String>();
compilerController.add(_CompilationRequest(mainDart, completer));
return completer.future;
}
Future<void> _shutdown() async {
if (compiler != null) {
await compiler.shutdown();
compiler = null;
}
}
Future<void> dispose() async {
await compilerController.close();
await _shutdown();
}
@visibleForTesting
Future<ResidentCompiler> createCompiler() async {
if (flutterProject.hasBuilders) {
return CodeGeneratingResidentCompiler.create(
flutterProject: flutterProject,
trackWidgetCreation: trackWidgetCreation,
compilerMessageConsumer: _reportCompilerMessage,
initializeFromDill: testFilePath,
runCold: true,
);
}
return ResidentCompiler(
artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
packagesPath: PackageMap.globalPackagesPath,
trackWidgetCreation: trackWidgetCreation,
compilerMessageConsumer: _reportCompilerMessage,
initializeFromDill: testFilePath,
unsafePackageSerialization: false,
);
}
Future<void> _onCompilationRequest(_CompilationRequest request) async {
final bool isEmpty = compilationQueue.isEmpty;
compilationQueue.add(request);
if (!isEmpty) {
return;
}
while (compilationQueue.isNotEmpty) {
final _CompilationRequest request = compilationQueue.first;
printTrace('Compiling ${request.path}');
final Stopwatch compilerTime = Stopwatch()..start();
bool firstCompile = false;
if (compiler == null) {
compiler = await createCompiler();
firstCompile = true;
}
_suppressOutput = false;
final CompilerOutput compilerOutput = await compiler.recompile(
request.path,
<Uri>[Uri.parse(request.path)],
outputPath: outputDill.path,
);
final String outputPath = compilerOutput?.outputFilename;
if (outputPath == null || compilerOutput.errorCount > 0) {
request.result.complete(null);
await _shutdown();
} else {
final File outputFile = fs.file(outputPath);
final File kernelReadyToRun = await outputFile.copy('${request.path}.dill');
final File testCache = fs.file(testFilePath);
if (firstCompile || !testCache.existsSync() || (testCache.lengthSync() < outputFile.lengthSync())) {
ensureDirectoryExists(testFilePath);
await outputFile.copy(testFilePath);
}
request.result.complete(kernelReadyToRun.path);
compiler.accept();
compiler.reset();
}
printTrace('Compiling ${request.path} took ${compilerTime.elapsedMilliseconds}ms');
compilationQueue.removeAt(0);
}
}
void _reportCompilerMessage(String message, {bool emphasis, TerminalColor color}) {
if (_suppressOutput) {
return;
}
if (message.startsWith('Error: Could not resolve the package \'flutter_test\'')) {
printTrace(message);
printError('\n\nFailed to load test harness. Are you missing a dependency on flutter_test?\n',
emphasis: emphasis,
color: color,
);
_suppressOutput = true;
return;
}
printError('$message');
}
}