blob: 21bcf55ecd37b097da507bbd3984b313a188850d [file] [log] [blame]
import 'dart:convert';
import 'dart:io';
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, sdk full dill, and sdk summary files.
///
/// Generates following missing assets if needed:
/// - sound null safety: js, source map, full dill.
/// - weak null safety: js, source map, full dill, summary.
class SdkAssetGenerator {
static bool _sdkAssetsGenerated = false;
final _logger = Logger('SdkAssetGenerator');
final FileSystem fileSystem;
final bool verboseCompiler;
late final TestSdkLayout sdkLayout;
SdkAssetGenerator({
this.fileSystem = const LocalFileSystem(),
required this.sdkLayout,
this.verboseCompiler = false,
});
/// Generate all SDK assets, once for the current executable run.
Future<void> generateSdkAssets() async {
if (!_sdkAssetsGenerated) {
_sdkAssetsGenerated = true;
// SDK contains sound summary, but SDK js and full dill are
// normally generated by setup tools and their builds,
// i.e. flutter SDK or build_web_compilers.
// Generate missing files for tests if needed.
await _generateSdkJavaScript(soundNullSafety: true);
// SDK does not contain any weak assets, generate them.
await _generateSdkJavaScript(soundNullSafety: false);
await _generateSdkSummary(soundNullSafety: false);
}
}
Future<void> _generateSdkJavaScript({required bool soundNullSafety}) async {
Directory? outputDir;
try {
// Files to copy generated files to.
final outputJsPath =
soundNullSafety ? sdkLayout.soundJsPath : sdkLayout.weakJsPath;
final outputJsMapPath =
soundNullSafety ? sdkLayout.soundJsMapPath : sdkLayout.weakJsMapPath;
final outputFullDillPath = soundNullSafety
? sdkLayout.soundFullDillPath
: sdkLayout.weakFullDillPath;
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 = soundNullSafety
? p.join(outputDir.path, sdkLayout.soundJsFileName)
: p.join(outputDir.path, sdkLayout.weakJsFileName);
final jsMapPath = p.setExtension(jsPath, '.js.map');
final fullDillPath = p.setExtension(jsPath, '.dill');
_logger.info('Generating js and full dill SDK files...');
final sdkDirectoryUri = fileSystem.directory(sdkLayout.sdkDirectory).uri;
final args = <String>[
sdkLayout.dartdevcSnapshotPath,
'--compile-sdk',
'--multi-root',
'$sdkDirectoryUri',
'--multi-root-scheme',
'org-dartlang-sdk',
'--libraries-file',
'org-dartlang-sdk:///lib/libraries.json',
'--modules',
'amd',
if (soundNullSafety)
'--sound-null-safety'
else
'--no-sound-null-safety',
'dart:core',
'-o',
jsPath,
];
final output = <String>[];
_logger.fine('Executing dart ${args.join(' ')}');
final process = await Process.start(Platform.resolvedExecutable, 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);
await _moveAndValidate(fullDillPath, outputFullDillPath);
_logger.info('Done generating js and full dill SDK files.');
} catch (e, s) {
_logger.severe(
'Failed to generate SDK js, source map, and full dill', e, s);
rethrow;
} finally {
outputDir?.deleteSync(recursive: true);
}
}
Future<void> _generateSdkSummary({required bool soundNullSafety}) async {
Directory? outputDir;
try {
// Files to copy generated files to.
final outputSummaryPath = soundNullSafety
? sdkLayout.soundSummaryPath
: sdkLayout.weakSummaryPath;
final hasAssets = _exists(outputSummaryPath);
// Files already exist.
if (hasAssets) return;
// Generate missing files.
outputDir = fileSystem.systemTempDirectory.createTempSync();
final summaryPath = soundNullSafety
? p.join(outputDir.path, sdkLayout.soundSummaryFileName)
: p.join(outputDir.path, sdkLayout.weakSummaryFileName);
_logger.info('Generating SDK summary files...');
final sdkDirectoryUri = fileSystem.directory(sdkLayout.sdkDirectory).uri;
final args = <String>[
sdkLayout.kernelWorkerSnapshotPath,
'--target',
'ddc',
'--multi-root',
'$sdkDirectoryUri',
'--multi-root-scheme',
'org-dartlang-sdk',
'--libraries-file',
'org-dartlang-sdk:///lib/libraries.json',
'--source',
'dart:core',
'--summary-only',
if (soundNullSafety)
'--sound-null-safety'
else
'--no-sound-null-safety',
'--output',
summaryPath,
if (verboseCompiler) '--verbose',
];
_logger.fine('Executing dart ${args.join(' ')}');
final process = await Process.start(Platform.resolvedExecutable, args,
workingDirectory: sdkLayout.sdkDirectory);
final output = <String>[];
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 $summaryPath: ${output.join('\n')}');
throw Exception('The Dart kernel worker exited unexpectedly');
}
});
await _moveAndValidate(summaryPath, outputSummaryPath);
_logger.info('Done generating SDK summary files.');
} catch (e, s) {
_logger.severe('Failed to generate SDK summary', 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(to)) _delete(to);
await fileSystem.file(from).rename(to);
if (!_exists(to)) {
_logger.severe('Failed to generate SDK asset at $to');
throw Exception('File does not exist.');
}
}
}