flutter/packages/flutter_tools/lib/src/test/test_compiler.dart
Daco Harkes 4e70bfae2b
Reland "Move native assets to isolated/ directory" (#143055)
Reland of https://github.com/flutter/flutter/pull/142709.

The revert of the revert is in the first commit, the fix in the commit on top.

The move of the fakes for packages/flutter_tools/test/general.shard/resident_runner_test.dart was erroneous before, as it was trying to use setters instead of a private field. This PR changes the private `_devFS` field in the fake to be a public `fakeDevFS` in line with other fakes.

## Original PR description

Native assets in other build systems are not built with `package:native_assets_builder` invoking `build.dart` scripts. Instead all packages have their own blaze rules. Therefore we'd like to not depend on `package:native_assets_builder` from flutter tools in g3 at all.

This PR aims to move the imports of `native_assets_builder` and `native_assets_cli` into the `isolated/` directory and into the files with a `main` function that are not used in with other build systems.

In order to be able to remove all imports in files used by other build systems, two new interfaces are added `HotRunnerNativeAssetsBuilder` and `TestCompilerNativeAssetsBuilder`. New parameters are then piped all the way through from the entry points:

* bin/fuchsia_tester.dart
* lib/executable.dart

The build_system/targets dir is already excluded in other build systems.

So, after this PR only the two above files and build_system/targets import from `isolated/native_assets/` and only `isolated/native_assets/` import `package:native_assets_cli` and `package:native_assets_builder`.

Context:

* https://github.com/flutter/flutter/issues/142041
2024-02-08 17:49:48 +00:00

222 lines
8.5 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 '../build_info.dart';
import '../bundle.dart';
import '../compile.dart';
import '../flutter_plugins.dart';
import '../globals.dart' as globals;
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,
TestCompilerNativeAssetsBuilder? nativeAssetsBuilder,
}) : 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,
_nativeAssetsBuilder = nativeAssetsBuilder {
// 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;
final TestCompilerNativeAssetsBuilder? _nativeAssetsBuilder;
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,
frontendServerStarterPath: buildInfo.frontendServerStarterPath,
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);
}
final Uri? nativeAssetsYaml = await _nativeAssetsBuilder?.build(buildInfo);
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);
}
}
}