blob: 1d2a3bcbccc3ddf410f7f02333900aa7478a2087 [file] [log] [blame]
// 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 'package:package_config/package_config.dart';
import 'package:package_config/package_config_types.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/config.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../bundle.dart';
import '../cache.dart';
import '../compile.dart';
import '../dart/language_version.dart';
import '../web/bootstrap.dart';
import '../web/compile.dart';
import '../web/memory_fs.dart';
import 'test_config.dart';
/// A web compiler for the test runner.
class WebTestCompiler {
WebTestCompiler({
required FileSystem fileSystem,
required Logger logger,
required Artifacts artifacts,
required Platform platform,
required ProcessManager processManager,
required Config config,
}) : _logger = logger,
_fileSystem = fileSystem,
_artifacts = artifacts,
_platform = platform,
_processManager = processManager,
_config = config;
final Logger _logger;
final FileSystem _fileSystem;
final Artifacts _artifacts;
final Platform _platform;
final ProcessManager _processManager;
final Config _config;
Future<File> _generateTestEntrypoint({
required List<String> testFiles,
required Directory projectDirectory,
required Directory outputDirectory,
required LanguageVersion languageVersion,
}) async {
final List<WebTestInfo> testInfos = testFiles.map((String testFilePath) {
final List<String> relativeTestSegments = _fileSystem.path.split(
_fileSystem.path.relative(
testFilePath,
from: projectDirectory.childDirectory('test').path
)
);
final File? testConfigFile = findTestConfigFile(_fileSystem.file(testFilePath), _logger);
String? testConfigPath;
if (testConfigFile != null) {
testConfigPath = _fileSystem.path.split(
_fileSystem.path.relative(
testConfigFile.path,
from: projectDirectory.childDirectory('test').path
)
).join('/');
}
return (
entryPoint: relativeTestSegments.join('/'),
configFile: testConfigPath,
goldensUri: Uri.file(testFilePath),
);
}).toList();
return _fileSystem.file(_fileSystem.path.join(outputDirectory.path, 'main.dart'))
..createSync(recursive: true)
..writeAsStringSync(generateTestEntrypoint(
testInfos: testInfos,
languageVersion: languageVersion
)
);
}
Future<WebMemoryFS> initialize({
required Directory projectDirectory,
required String testOutputDir,
required List<String> testFiles,
required BuildInfo buildInfo,
required WebRendererMode webRenderer,
required bool useWasm,
}) async {
return useWasm ? _compileWasm(
projectDirectory: projectDirectory,
testOutputDir: testOutputDir,
testFiles: testFiles,
buildInfo: buildInfo,
webRenderer: webRenderer,
) : _compileJS(
projectDirectory: projectDirectory,
testOutputDir: testOutputDir,
testFiles: testFiles,
buildInfo: buildInfo,
webRenderer: webRenderer,
);
}
Future<WebMemoryFS> _compileJS({
required Directory projectDirectory,
required String testOutputDir,
required List<String> testFiles,
required BuildInfo buildInfo,
required WebRendererMode webRenderer,
}) async {
LanguageVersion languageVersion = LanguageVersion(2, 8);
late final String platformDillName;
// TODO(zanderso): to support autodetect this would need to partition the source code into
// a sound and unsound set and perform separate compilations
final List<String> extraFrontEndOptions = List<String>.of(buildInfo.extraFrontEndOptions);
switch (buildInfo.nullSafetyMode) {
case NullSafetyMode.unsound || NullSafetyMode.autodetect:
platformDillName = 'ddc_outline.dill';
if (!extraFrontEndOptions.contains('--no-sound-null-safety')) {
extraFrontEndOptions.add('--no-sound-null-safety');
}
case NullSafetyMode.sound:
languageVersion = currentLanguageVersion(_fileSystem, Cache.flutterRoot!);
platformDillName = 'ddc_outline_sound.dill';
if (!extraFrontEndOptions.contains('--sound-null-safety')) {
extraFrontEndOptions.add('--sound-null-safety');
}
}
final String platformDillPath = _fileSystem.path.join(
_artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path,
platformDillName
);
final Directory outputDirectory = _fileSystem.directory(testOutputDir)
..createSync(recursive: true);
final File testFile = await _generateTestEntrypoint(
testFiles: testFiles,
projectDirectory: projectDirectory,
outputDirectory: outputDirectory,
languageVersion: languageVersion
);
final String cachedKernelPath = getDefaultCachedKernelPath(
trackWidgetCreation: buildInfo.trackWidgetCreation,
dartDefines: buildInfo.dartDefines,
extraFrontEndOptions: extraFrontEndOptions,
fileSystem: _fileSystem,
config: _config,
);
final List<String> dartDefines = webRenderer.updateDartDefines(buildInfo.dartDefines);
final ResidentCompiler residentCompiler = ResidentCompiler(
_artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path,
buildMode: buildInfo.mode,
trackWidgetCreation: buildInfo.trackWidgetCreation,
fileSystemRoots: <String>[
projectDirectory.childDirectory('test').path,
testOutputDir,
],
// Override the filesystem scheme so that the frontend_server can find
// the generated entrypoint code.
fileSystemScheme: 'org-dartlang-app',
initializeFromDill: cachedKernelPath,
targetModel: TargetModel.dartdevc,
extraFrontEndOptions: extraFrontEndOptions,
platformDill: _fileSystem.file(platformDillPath).absolute.uri.toString(),
dartDefines: dartDefines,
librariesSpec: _artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson).uri.toString(),
packagesPath: buildInfo.packagesPath,
artifacts: _artifacts,
processManager: _processManager,
logger: _logger,
platform: _platform,
fileSystem: _fileSystem,
);
final CompilerOutput? output = await residentCompiler.recompile(
Uri.parse('org-dartlang-app:///${testFile.basename}'),
<Uri>[],
outputPath: outputDirectory.childFile('out').path,
packageConfig: buildInfo.packageConfig,
fs: _fileSystem,
projectRootPath: projectDirectory.absolute.path,
);
if (output == null || output.errorCount > 0) {
throwToolExit('Failed to compile');
}
// Cache the output kernel file to speed up subsequent compiles.
_fileSystem.file(cachedKernelPath).parent.createSync(recursive: true);
_fileSystem.file(output.outputFilename).copySync(cachedKernelPath);
final File codeFile = outputDirectory.childFile('${output.outputFilename}.sources');
final File manifestFile = outputDirectory.childFile('${output.outputFilename}.json');
final File sourcemapFile = outputDirectory.childFile('${output.outputFilename}.map');
final File metadataFile = outputDirectory.childFile('${output.outputFilename}.metadata');
return WebMemoryFS()
..write(codeFile, manifestFile, sourcemapFile, metadataFile);
}
Future<WebMemoryFS> _compileWasm({
required Directory projectDirectory,
required String testOutputDir,
required List<String> testFiles,
required BuildInfo buildInfo,
required WebRendererMode webRenderer,
}) async {
final Directory outputDirectory = _fileSystem.directory(testOutputDir)
..createSync(recursive: true);
final File testFile = await _generateTestEntrypoint(
testFiles: testFiles,
projectDirectory: projectDirectory,
outputDirectory: outputDirectory,
languageVersion: currentLanguageVersion(_fileSystem, Cache.flutterRoot!),
);
final String dartSdkPath = _artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript);
final String platformBinariesPath = _artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path;
final String platformFilePath = _fileSystem.path.join(platformBinariesPath, 'dart2wasm_platform.dill');
final List<String> dartDefines = webRenderer.updateDartDefines(buildInfo.dartDefines);
final File outputWasmFile = outputDirectory.childFile('main.dart.wasm');
final List<String> compilationArgs = <String>[
_artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript),
'compile',
'wasm',
'--packages=.dart_tool/package_config.json',
'--extra-compiler-option=--dart-sdk=$dartSdkPath',
'--extra-compiler-option=--platform=$platformFilePath',
'--extra-compiler-option=--multi-root-scheme=org-dartlang-app',
'--extra-compiler-option=--multi-root=${projectDirectory.childDirectory('test').path}',
'--extra-compiler-option=--multi-root=${outputDirectory.path}',
'--extra-compiler-option=--enable-asserts',
'--extra-compiler-option=--no-inlining',
if (webRenderer == WebRendererMode.skwasm) ...<String>[
'--extra-compiler-option=--import-shared-memory',
'--extra-compiler-option=--shared-memory-max-pages=32768',
],
...buildInfo.extraFrontEndOptions,
for (final String dartDefine in dartDefines)
'-D$dartDefine',
'-O0',
'-o',
outputWasmFile.path,
testFile.path, // dartfile
];
final ProcessUtils processUtils = ProcessUtils(
logger: _logger,
processManager: _processManager,
);
await processUtils.stream(
compilationArgs,
);
return WebMemoryFS();
}
}