blob: e5768752db17d169dba4f72424bb475b154fe9bd [file] [log] [blame] [edit]
import 'dart:convert';
import 'dart:io';
import 'package:dwds/expression_compiler.dart';
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:test_common/test_sdk_layout.dart';
/// Generates sdk.js, sdk.map, files.
class SdkAssetGenerator {
bool _sdkAssetsGenerated = false;
final _logger = Logger('SdkAssetGenerator');
final FileSystem fileSystem;
final bool canaryFeatures;
final ModuleFormat ddcModuleFormat;
final bool verbose;
late final TestSdkLayout sdkLayout;
SdkAssetGenerator({
this.fileSystem = const LocalFileSystem(),
required this.sdkLayout,
required this.canaryFeatures,
required this.ddcModuleFormat,
this.verbose = false,
});
/// Generate all SDK assets, once for the current executable run.
Future<void> generateSdkAssets() async {
if (!_sdkAssetsGenerated) {
_sdkAssetsGenerated = true;
// SDK full and outline .dill files are shipped with the SDK,
// but the JavaScript and sourcemaps are generated by other tooling
// i.e. flutter SDK or build_web_compilers.
// Generate missing files for tests if needed.
await _generateSdkJavaScript(canaryFeatures: canaryFeatures);
}
}
String resolveSdkJsPath({required bool canaryFeatures}) =>
switch (ddcModuleFormat) {
ModuleFormat.amd => sdkLayout.amdJsPath,
ModuleFormat.ddc => sdkLayout.ddcJsPath,
_ => throw Exception('Unsupported DDC module format $ddcModuleFormat.'),
};
String resolveSdkSourcemapPath({required bool canaryFeatures}) =>
switch (ddcModuleFormat) {
ModuleFormat.amd => sdkLayout.amdJsMapPath,
ModuleFormat.ddc => sdkLayout.ddcJsMapPath,
_ => throw Exception('Unsupported DDC module format $ddcModuleFormat.'),
};
String resolveSdkJsFilename({required bool canaryFeatures}) =>
switch (ddcModuleFormat) {
ModuleFormat.amd => sdkLayout.amdJsFileName,
ModuleFormat.ddc => sdkLayout.ddcJsFileName,
_ => throw Exception('Unsupported DDC module format $ddcModuleFormat.'),
};
Future<void> _generateSdkJavaScript({required bool canaryFeatures}) async {
Directory? outputDir;
try {
// Files to copy generated files to.
final outputJsPath = resolveSdkJsPath(canaryFeatures: canaryFeatures);
final outputJsMapPath = resolveSdkSourcemapPath(
canaryFeatures: canaryFeatures,
);
final outputFullDillPath = sdkLayout.fullDillPath;
final hasJsAsset = _exists(outputJsPath);
final hasJsMapAsset = _exists(outputJsMapPath);
final hasFullDillAsset = _exists(outputFullDillPath);
final hasAssets = hasFullDillAsset && hasJsAsset && hasJsMapAsset;
// Files already exist.
if (hasAssets) return;
// Generate missing files.
outputDir = fileSystem.systemTempDirectory.createTempSync();
// Files to generate
final jsPath = p.join(
outputDir.path,
resolveSdkJsFilename(canaryFeatures: canaryFeatures),
);
final jsMapPath = p.setExtension(jsPath, '.js.map');
_logger.info('Generating SDK JavaScript and sourcemap files...');
final sdkDirectoryUri = fileSystem.directory(sdkLayout.sdkDirectory).uri;
final args = <String>[
'compile',
'js-dev',
'--compile-sdk',
'--multi-root',
'$sdkDirectoryUri',
'--multi-root-scheme',
'org-dartlang-sdk',
'--libraries-file',
'org-dartlang-sdk:///lib/libraries.json',
'--modules',
ddcModuleFormat.name,
'dart:core',
'-o',
jsPath,
if (canaryFeatures) '--canary',
];
final output = <String>[];
_logger.fine('Executing dart ${args.join(' ')}');
final process = await Process.start(
sdkLayout.dartPath,
args,
workingDirectory: sdkLayout.sdkDirectory,
);
process.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((line) {
_logger.fine(line);
output.add(line);
});
process.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((line) {
_logger.warning(line);
output.add(line);
});
await process.exitCode.then((int code) {
if (code != 0) {
_logger.warning('Error generating $jsPath: ${output.join('\n')}');
throw Exception('The Dart compiler exited unexpectedly');
}
});
final outputJsDir = fileSystem.directory(p.dirname(outputJsPath));
if (!outputJsDir.existsSync()) {
outputJsDir.createSync(recursive: true);
}
await _moveAndValidate(jsPath, outputJsPath);
await _moveAndValidate(jsMapPath, outputJsMapPath);
_logger.info('Done generating SDK JavaScript and sourcemap files.');
} catch (e, s) {
_logger.severe(
'Failed to generate SDK JavaScript and sourcemap files',
e,
s,
);
rethrow;
} finally {
outputDir?.deleteSync(recursive: true);
}
}
bool _exists(String path) => fileSystem.file(path).existsSync();
void _delete(String path) => fileSystem.file(path).deleteSync();
Future<void> _moveAndValidate(String from, String to) async {
_logger.fine('Renaming $from to $to');
if (!_exists(from)) {
_logger.severe('Failed to generate SDK asset at $to');
throw Exception('File "$from" does not exist.');
}
if (_exists(to)) _delete(to);
await fileSystem.file(from).rename(to);
if (!_exists(to)) {
_logger.severe('Failed to generate SDK asset at $to');
throw Exception('File "$to" does not exist.');
}
}
}