blob: b406e15c048b3e0b4210a5970f5bf0c5261a00e6 [file] [log] [blame]
// Copyright (c) 2024, 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.
/// Implementation of the [TargetExecutor] for dart2wasm.
library;
import 'dart:io';
import '../common/testing.dart' as helper;
import 'model.dart';
import 'util.dart';
import 'target.dart';
/// Logic to build and execute dynamic modules in dart2wasm.
///
/// In particular:
/// * The initial app is built as a regular dart2wasm target, except that
/// a dynamic interface is used to define hooks that dynamic modules update
/// when loaded. The dynamic interface also prevents treeshaking of some
/// entities.
/// * For dynamic modules, dart2wasm validates that the module only accesses
/// what's allowed by the dynamic interface.
/// * For dynamic modules, dart2wasm exports a specific entry point function.
class Dart2wasmExecutor implements TargetExecutor {
static const rootScheme = 'dev-dart-app';
late final bool _shouldCleanup;
late final Directory _tmp;
final Logger _logger;
Dart2wasmExecutor(this._logger) {
/// Allow using an environment variable to run tests on a fixed directory.
/// This prevents the directory from getting deleted too.
var path = Platform.environment['TMP_DIR'] ?? '';
if (path.isEmpty) {
_tmp = Directory.systemTemp.createTempSync('_dynamic_module-');
_shouldCleanup = false;
} else {
_tmp = Directory(path);
if (!_tmp.existsSync()) _tmp.createSync();
_shouldCleanup = false;
}
}
@override
Future<void> suiteComplete() async {
if (!_shouldCleanup) return;
try {
_tmp.delete(recursive: true);
} on FileSystemException {
// Windows bots sometimes fail to delete folders, and can make tests
// flaky. It is OK in those cases to leak some files in the tmp folder,
// these will eventually be cleared when a new bot instance is created.
_logger.warning('Error trying to delete $_tmp');
}
}
Future _compile(
String testName,
String source,
Uri sourceDir,
bool isMain,
) async {
var testDir = _tmp.uri.resolve(testName).toFilePath();
var args = [
'--compiler-asserts',
'--packages=${repoRoot.toFilePath()}/.dart_tool/package_config.json',
'--multi-root=${sourceDir.resolve('../../').toFilePath()}',
'--multi-root-scheme=$rootScheme',
// This is required while binaryen lacks support for partially closed
// world optimizations.
'-O0',
'--extra-compiler-option=--dynamic-module-type=${isMain ? "main" : "submodule"}',
'--extra-compiler-option=--dynamic-module-main=main.dart.dill',
'--extra-compiler-option=--dynamic-module-interface='
'$rootScheme:/data/$testName/dynamic_interface.yaml',
'--extra-compiler-option=--minify',
'$rootScheme:/data/$testName/$source',
'$source.wasm',
];
await runProcess(
compileBenchmark.toFilePath(),
args,
testDir,
_logger,
'compile $testName/$source',
);
}
@override
Future compileApplication(DynamicModuleTest test) async {
_ensureDirectory(test.name);
_logger.info('Compile ${test.name} app');
await _compile(test.name, test.main, test.folder, true);
}
@override
Future compileDynamicModule(DynamicModuleTest test, String name) async {
_logger.info('Compile module ${test.name}.$name');
_ensureDirectory(test.name);
await _compile(test.name, test.dynamicModules[name]!, test.folder, false);
}
@override
Future executeApplication(DynamicModuleTest test) async {
_logger.info('Execute ${test.name}');
_ensureDirectory(test.name);
// We generate a self contained script that loads necessary preambles,
// dart2wasm module loader, the necessary modules (the SDK and the main
// module), and finally launches the app.
var testDir = _tmp.uri.resolve('${test.name}/');
var result = await runProcess(
runBenchmark.toFilePath(),
['${test.main}.wasm'],
testDir.toFilePath(),
_logger,
'run_benchmark ${test.main}.wasm',
);
var stdout = result.stdout as String;
if (!stdout.contains(helper.successToken)) {
_logger.error(
'Error: test didn\'t complete as expected.\n'
'Make sure the test finishes and calls `helper.done()`.\n'
'Test output:\n$stdout',
);
throw Exception('missing helper.done');
}
}
void _ensureDirectory(String name) {
var dir = Directory.fromUri(_tmp.uri.resolve(name));
if (!dir.existsSync()) {
dir.createSync();
}
}
}