| 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.'); |
| } |
| } |
| } |