
Reverts flutter/flutter#134031 context: b/301051367 Looked at the error message from the broken TAP target, but seems like the failure might be non trivial to resolve. Would it be okay if we revert this for now while it is being triaged?
242 lines
9.0 KiB
Dart
242 lines
9.0 KiB
Dart
// Copyright 2014 The Flutter 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 'package:meta/meta.dart';
|
|
|
|
import '../artifacts.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/platform.dart';
|
|
import '../build_info.dart';
|
|
import '../bundle.dart';
|
|
import '../compile.dart';
|
|
import '../flutter_plugins.dart';
|
|
import '../globals.dart' as globals;
|
|
import '../macos/native_assets.dart';
|
|
import '../native_assets.dart';
|
|
import '../project.dart';
|
|
import 'test_time_recorder.dart';
|
|
|
|
/// A request to the [TestCompiler] for recompilation.
|
|
class CompilationRequest {
|
|
CompilationRequest(this.mainUri, this.result);
|
|
|
|
Uri mainUri;
|
|
Completer<String?> result;
|
|
}
|
|
|
|
/// A frontend_server wrapper for the flutter test runner.
|
|
///
|
|
/// This class is a wrapper around compiler that allows multiple isolates to
|
|
/// enqueue compilation requests, but ensures only one compilation at a time.
|
|
class TestCompiler {
|
|
/// Creates a new [TestCompiler] which acts as a frontend_server proxy.
|
|
///
|
|
/// [trackWidgetCreation] configures whether the kernel transform is applied
|
|
/// to the output. This also changes the output file to include a '.track`
|
|
/// extension.
|
|
///
|
|
/// [flutterProject] is the project for which we are running tests.
|
|
///
|
|
/// If [precompiledDillPath] is passed, it will be used to initialize the
|
|
/// compiler.
|
|
///
|
|
/// If [testTimeRecorder] is passed, times will be recorded in it.
|
|
TestCompiler(
|
|
this.buildInfo,
|
|
this.flutterProject,
|
|
{ String? precompiledDillPath, this.testTimeRecorder }
|
|
) : testFilePath = precompiledDillPath ?? globals.fs.path.join(
|
|
flutterProject!.directory.path,
|
|
getBuildDirectory(),
|
|
'test_cache',
|
|
getDefaultCachedKernelPath(
|
|
trackWidgetCreation: buildInfo.trackWidgetCreation,
|
|
dartDefines: buildInfo.dartDefines,
|
|
extraFrontEndOptions: buildInfo.extraFrontEndOptions,
|
|
)),
|
|
shouldCopyDillFile = precompiledDillPath == null {
|
|
// Compiler maintains and updates single incremental dill file.
|
|
// Incremental compilation requests done for each test copy that file away
|
|
// for independent execution.
|
|
final Directory outputDillDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_test_compiler.');
|
|
outputDill = outputDillDirectory.childFile('output.dill');
|
|
globals.printTrace('Compiler will use the following file as its incremental dill file: ${outputDill.path}');
|
|
globals.printTrace('Listening to compiler controller...');
|
|
compilerController.stream.listen(_onCompilationRequest, onDone: () {
|
|
globals.printTrace('Deleting ${outputDillDirectory.path}...');
|
|
outputDillDirectory.deleteSync(recursive: true);
|
|
});
|
|
}
|
|
|
|
final StreamController<CompilationRequest> compilerController = StreamController<CompilationRequest>();
|
|
final List<CompilationRequest> compilationQueue = <CompilationRequest>[];
|
|
final FlutterProject? flutterProject;
|
|
final BuildInfo buildInfo;
|
|
final String testFilePath;
|
|
final bool shouldCopyDillFile;
|
|
final TestTimeRecorder? testTimeRecorder;
|
|
|
|
|
|
ResidentCompiler? compiler;
|
|
late File outputDill;
|
|
|
|
Future<String?> compile(Uri mainDart) {
|
|
final Completer<String?> completer = Completer<String?>();
|
|
if (compilerController.isClosed) {
|
|
return Future<String?>.value();
|
|
}
|
|
compilerController.add(CompilationRequest(mainDart, completer));
|
|
return completer.future;
|
|
}
|
|
|
|
Future<void> _shutdown() async {
|
|
// Check for null in case this instance is shut down before the
|
|
// lazily-created compiler has been created.
|
|
if (compiler != null) {
|
|
await compiler!.shutdown();
|
|
compiler = null;
|
|
}
|
|
}
|
|
|
|
Future<void> dispose() async {
|
|
await compilerController.close();
|
|
await _shutdown();
|
|
}
|
|
|
|
/// Create the resident compiler used to compile the test.
|
|
@visibleForTesting
|
|
Future<ResidentCompiler?> createCompiler() async {
|
|
final ResidentCompiler residentCompiler = ResidentCompiler(
|
|
globals.artifacts!.getArtifactPath(Artifact.flutterPatchedSdkPath),
|
|
artifacts: globals.artifacts!,
|
|
logger: globals.logger,
|
|
processManager: globals.processManager,
|
|
buildMode: buildInfo.mode,
|
|
trackWidgetCreation: buildInfo.trackWidgetCreation,
|
|
initializeFromDill: testFilePath,
|
|
dartDefines: buildInfo.dartDefines,
|
|
packagesPath: buildInfo.packagesPath,
|
|
extraFrontEndOptions: buildInfo.extraFrontEndOptions,
|
|
platform: globals.platform,
|
|
testCompilation: true,
|
|
fileSystem: globals.fs,
|
|
fileSystemRoots: buildInfo.fileSystemRoots,
|
|
fileSystemScheme: buildInfo.fileSystemScheme,
|
|
);
|
|
return residentCompiler;
|
|
}
|
|
|
|
// Handle a compilation request.
|
|
Future<void> _onCompilationRequest(CompilationRequest request) async {
|
|
final bool isEmpty = compilationQueue.isEmpty;
|
|
compilationQueue.add(request);
|
|
// Only trigger processing if queue was empty - i.e. no other requests
|
|
// are currently being processed. This effectively enforces "one
|
|
// compilation request at a time".
|
|
if (!isEmpty) {
|
|
return;
|
|
}
|
|
while (compilationQueue.isNotEmpty) {
|
|
final CompilationRequest request = compilationQueue.first;
|
|
globals.printTrace('Compiling ${request.mainUri}');
|
|
final Stopwatch compilerTime = Stopwatch()..start();
|
|
final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Compile);
|
|
bool firstCompile = false;
|
|
if (compiler == null) {
|
|
compiler = await createCompiler();
|
|
firstCompile = true;
|
|
}
|
|
|
|
final List<Uri> invalidatedRegistrantFiles = <Uri>[];
|
|
if (flutterProject != null) {
|
|
// Update the generated registrant to use the test target's main.
|
|
final String mainUriString = buildInfo.packageConfig.toPackageUri(request.mainUri)?.toString()
|
|
?? request.mainUri.toString();
|
|
await generateMainDartWithPluginRegistrant(
|
|
flutterProject!,
|
|
buildInfo.packageConfig,
|
|
mainUriString,
|
|
globals.fs.file(request.mainUri),
|
|
);
|
|
invalidatedRegistrantFiles.add(flutterProject!.dartPluginRegistrant.absolute.uri);
|
|
}
|
|
|
|
Uri? nativeAssetsYaml;
|
|
final Uri projectUri = FlutterProject.current().directory.uri;
|
|
final NativeAssetsBuildRunner buildRunner = NativeAssetsBuildRunnerImpl(
|
|
projectUri,
|
|
buildInfo.packageConfig,
|
|
globals.fs,
|
|
globals.logger,
|
|
);
|
|
if (globals.platform.isMacOS) {
|
|
(nativeAssetsYaml, _) = await buildNativeAssetsMacOS(
|
|
buildMode: BuildMode.debug,
|
|
projectUri: projectUri,
|
|
flutterTester: true,
|
|
fileSystem: globals.fs,
|
|
buildRunner: buildRunner,
|
|
);
|
|
} else {
|
|
await ensureNoNativeAssetsOrOsIsSupported(
|
|
projectUri,
|
|
const LocalPlatform().operatingSystem,
|
|
globals.fs,
|
|
buildRunner,
|
|
);
|
|
}
|
|
|
|
final CompilerOutput? compilerOutput = await compiler!.recompile(
|
|
request.mainUri,
|
|
<Uri>[request.mainUri, ...invalidatedRegistrantFiles],
|
|
outputPath: outputDill.path,
|
|
packageConfig: buildInfo.packageConfig,
|
|
projectRootPath: flutterProject?.directory.absolute.path,
|
|
checkDartPluginRegistry: true,
|
|
fs: globals.fs,
|
|
nativeAssetsYaml: nativeAssetsYaml,
|
|
);
|
|
final String? outputPath = compilerOutput?.outputFilename;
|
|
|
|
// In case compiler didn't produce output or reported compilation
|
|
// errors, pass [null] upwards to the consumer and shutdown the
|
|
// compiler to avoid reusing compiler that might have gotten into
|
|
// a weird state.
|
|
if (outputPath == null || compilerOutput!.errorCount > 0) {
|
|
request.result.complete();
|
|
await _shutdown();
|
|
} else {
|
|
if (shouldCopyDillFile) {
|
|
final String path = request.mainUri.toFilePath(windows: globals.platform.isWindows);
|
|
final File outputFile = globals.fs.file(outputPath);
|
|
final File kernelReadyToRun = await outputFile.copy('$path.dill');
|
|
final File testCache = globals.fs.file(testFilePath);
|
|
if (firstCompile || !testCache.existsSync() || (testCache.lengthSync() < outputFile.lengthSync())) {
|
|
// The idea is to keep the cache file up-to-date and include as
|
|
// much as possible in an effort to re-use as many packages as
|
|
// possible.
|
|
if (!testCache.parent.existsSync()) {
|
|
testCache.parent.createSync(recursive: true);
|
|
}
|
|
await outputFile.copy(testFilePath);
|
|
}
|
|
request.result.complete(kernelReadyToRun.path);
|
|
} else {
|
|
request.result.complete(outputPath);
|
|
}
|
|
compiler!.accept();
|
|
compiler!.reset();
|
|
}
|
|
globals.printTrace('Compiling ${request.mainUri} took ${compilerTime.elapsedMilliseconds}ms');
|
|
testTimeRecorder?.stop(TestTimePhases.Compile, testTimeRecorderStopwatch!);
|
|
// Only remove now when we finished processing the element
|
|
compilationQueue.removeAt(0);
|
|
}
|
|
}
|
|
}
|