|  | // Copyright 2013 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 'dart:io'; | 
|  |  | 
|  | import 'package:args/args.dart'; | 
|  | import 'package:path/path.dart' as path; | 
|  |  | 
|  | final ArgParser argParser = ArgParser() | 
|  | ..addOption('output-dir') | 
|  | ..addOption('input-dir') | 
|  | ..addFlag('ui') | 
|  | ..addFlag('public') | 
|  | ..addOption('library-name') | 
|  | ..addOption('api-file') | 
|  | ..addMultiOption('source-file') | 
|  | ..addOption('stamp') | 
|  | ..addOption('depfile') | 
|  | ..addOption('exclude-pattern') | 
|  | ..addOption('build-dir'); | 
|  |  | 
|  | final List<Replacer> uiPatterns = <Replacer>[ | 
|  | AllReplacer(RegExp(r'library\s+ui;'), 'library dart.ui;'), | 
|  | AllReplacer(RegExp(r'part\s+of\s+ui;'), 'part of dart.ui;'), | 
|  |  | 
|  | // import 'src/engine.dart' as engine; | 
|  | AllReplacer(RegExp(r''' | 
|  | import\s*'src/engine.dart'\s*as\s+engine; | 
|  | '''), r''' | 
|  | import 'dart:_engine' as engine; | 
|  | '''), | 
|  |  | 
|  | // import 'ui_web/src/ui_web.dart' as ui_web; | 
|  | AllReplacer(RegExp(r''' | 
|  | import\s*'ui_web/src/ui_web.dart'\s*as\s+ui_web; | 
|  | '''), r''' | 
|  | import 'dart:ui_web' as ui_web; | 
|  | '''), | 
|  | ]; | 
|  |  | 
|  | List<Replacer> generateApiFilePatterns(String libraryName, bool isPublic, List<String> extraImports) { | 
|  | final String libraryPrefix = isPublic ? '' : '_'; | 
|  | return <Replacer>[ | 
|  | AllReplacer(RegExp('library\\s+$libraryName;'), ''' | 
|  | @JS() | 
|  | library dart.$libraryPrefix$libraryName; | 
|  |  | 
|  | import 'dart:async'; | 
|  | import 'dart:collection'; | 
|  | import 'dart:convert' hide Codec; | 
|  | import 'dart:developer' as developer; | 
|  | import 'dart:js_util' as js_util; | 
|  | import 'dart:_js_annotations'; | 
|  | import 'dart:js_interop' hide JS; | 
|  | import 'dart:js_interop_unsafe'; | 
|  | import 'dart:math' as math; | 
|  | import 'dart:typed_data'; | 
|  | import 'dart:ui' as ui; | 
|  | ${extraImports.join('\n')} | 
|  | ''' | 
|  | ), | 
|  | // Replace exports of engine files with "part" directives. | 
|  | MappedReplacer(RegExp(''' | 
|  | export\\s*'$libraryName/(.*)'; | 
|  | '''), (Match match) => ''' | 
|  | part '$libraryName/${match.group(1)}'; | 
|  | ''' | 
|  | ), | 
|  | ]; | 
|  | } | 
|  |  | 
|  | List<Replacer> generatePartsPatterns(String libraryName, bool isPublic) { | 
|  | final String libraryPrefix = isPublic ? '' : '_'; | 
|  | return <Replacer>[ | 
|  | AllReplacer(RegExp('part\\s+of\\s+$libraryName;'), 'part of dart.$libraryPrefix$libraryName;'), | 
|  | // Remove library-level JS annotations. | 
|  | AllReplacer(RegExp(r'\n@JS(.*)\nlibrary .+;'), ''), | 
|  | // Remove library directives. | 
|  | AllReplacer(RegExp(r'\nlibrary .+;'), ''), | 
|  | // Remove imports/exports from all part files. | 
|  | AllReplacer(RegExp(r'\nimport\s*.*'), ''), | 
|  | AllReplacer(RegExp(r'\nexport\s*.*'), ''), | 
|  | AllReplacer(RegExp(r'\n@DefaultAsset(.*)'), ''), | 
|  | ]; | 
|  | } | 
|  |  | 
|  | final List<Replacer> stripMetaPatterns = <Replacer>[ | 
|  | AllReplacer(RegExp(r"import\s*'package:meta/meta.dart';"), ''), | 
|  | AllReplacer('@required', ''), | 
|  | AllReplacer('@protected', ''), | 
|  | AllReplacer('@mustCallSuper', ''), | 
|  | AllReplacer('@immutable', ''), | 
|  | AllReplacer('@visibleForTesting', ''), | 
|  | ]; | 
|  |  | 
|  | const Set<String> rootLibraryNames = <String>{ | 
|  | 'ui_web', | 
|  | 'engine', | 
|  | 'skwasm_stub', | 
|  | 'skwasm_impl', | 
|  | }; | 
|  |  | 
|  | final Map<Pattern, String> extraImportsMap = <Pattern, String>{ | 
|  | RegExp('skwasm_(stub|impl)'): "import 'dart:_skwasm_stub' if (dart.library.ffi) 'dart:_skwasm_impl';", | 
|  | 'ui_web': "import 'dart:ui_web' as ui_web;", | 
|  | 'engine': "import 'dart:_engine';", | 
|  | 'web_unicode': "import 'dart:_web_unicode';", | 
|  | 'web_test_fonts': "import 'dart:_web_test_fonts';", | 
|  | 'web_locale_keymap': "import 'dart:_web_locale_keymap' as locale_keymap;", | 
|  | }; | 
|  |  | 
|  | // Rewrites the "package"-style web ui library into a dart:ui implementation. | 
|  | // So far this only requires a replace of the library declarations. | 
|  | void main(List<String> arguments) { | 
|  | final ArgResults results = argParser.parse(arguments); | 
|  | final Directory directory = Directory(results['output-dir'] as String); | 
|  | final String inputDirectoryPath = results['input-dir'] as String; | 
|  | final String? excludePattern = results['exclude-pattern'] as String?; | 
|  | final String stampfilePath = results['stamp'] as String; | 
|  | final String depfilePath = results['depfile'] as String; | 
|  | final String buildDirPath = results['build-dir'] as String; | 
|  |  | 
|  | String Function(String source)? preprocessor; | 
|  | List<Replacer> replacementPatterns; | 
|  | String? libraryName; | 
|  |  | 
|  | final bool isPublic = results['public'] as bool; | 
|  |  | 
|  | if (results['ui'] as bool) { | 
|  | replacementPatterns = uiPatterns; | 
|  | } else { | 
|  | libraryName = results['library-name'] as String?; | 
|  | if (libraryName == null) { | 
|  | throw Exception('library-name must be specified if not rewriting ui'); | 
|  | } | 
|  | preprocessor = (String source) => preprocessPartFile(source, libraryName!); | 
|  | replacementPatterns = generatePartsPatterns(libraryName, isPublic); | 
|  | } | 
|  |  | 
|  | final List<String> inputFiles = <String>[]; | 
|  | final List<FileSystemEntity> entries = Directory(inputDirectoryPath).listSync( | 
|  | recursive: true, followLinks: false, | 
|  | ); | 
|  | for (final File inputFile in entries.whereType<File>()) { | 
|  | if (excludePattern != null && inputFile.path.startsWith(excludePattern)) { | 
|  | continue; | 
|  | } | 
|  | if (!inputFile.path.endsWith('.dart') || inputFile.path.endsWith('_test.dart')) { | 
|  | continue; | 
|  | } | 
|  | inputFiles.add(path.relative(inputFile.path, from: buildDirPath)); | 
|  | String pathSuffix = inputFile.path.substring(inputDirectoryPath.length); | 
|  | if (libraryName != null) { | 
|  | pathSuffix = path.join(libraryName, pathSuffix); | 
|  | } | 
|  | final String outputFilePath = path.join(directory.path, pathSuffix); | 
|  | processFile(inputFile.path, outputFilePath, preprocessor, replacementPatterns); | 
|  | } | 
|  |  | 
|  | if (results['api-file'] != null) { | 
|  | if (libraryName == null) { | 
|  | throw Exception('library-name must be specified if api-file is specified'); | 
|  | } | 
|  |  | 
|  | final String inputFilePath = results['api-file'] as String; | 
|  | final String outputFilePath = path.join( | 
|  | directory.path, path.basename(inputFilePath)); | 
|  |  | 
|  | final List<String> extraImports = getExtraImportsForLibrary(libraryName); | 
|  | replacementPatterns = generateApiFilePatterns(libraryName, isPublic, extraImports); | 
|  |  | 
|  | processFile( | 
|  | inputFilePath, | 
|  | outputFilePath, | 
|  | (String source) => validateApiFile(inputFilePath, source, libraryName!), | 
|  | replacementPatterns | 
|  | ); | 
|  | } | 
|  |  | 
|  | File(stampfilePath).writeAsStringSync('stamp'); | 
|  | writeDepfile(depfilePath, stampfilePath, inputFiles); | 
|  | } | 
|  |  | 
|  | void writeDepfile(String depfilePath, String stampfilePath, List<String> inputFiles) { | 
|  | final StringBuffer outBuf = StringBuffer(); | 
|  | outBuf.write('$stampfilePath: '); | 
|  | outBuf.write(inputFiles.join(' ')); | 
|  | File(depfilePath).writeAsStringSync(outBuf.toString()); | 
|  | } | 
|  |  | 
|  | List<String> getExtraImportsForLibrary(String libraryName) { | 
|  | // Only our root libraries should have extra imports. | 
|  | if (!rootLibraryNames.contains(libraryName)) { | 
|  | return <String>[]; | 
|  | } | 
|  |  | 
|  | final List<String> extraImports = <String>[]; | 
|  | for (final MapEntry<Pattern, String> entry in extraImportsMap.entries) { | 
|  | // A library shouldn't import itself. | 
|  | if (entry.key.matchAsPrefix(libraryName) == null) { | 
|  | extraImports.add(entry.value); | 
|  | } | 
|  | } | 
|  | if (libraryName == 'skwasm_impl') { | 
|  | extraImports.add("import 'dart:_wasm';"); | 
|  | } | 
|  | return extraImports; | 
|  | } | 
|  |  | 
|  | void processFile(String inputFilePath, String outputFilePath, String Function(String source)? preprocessor, List<Replacer> replacementPatterns) { | 
|  | final File inputFile = File(inputFilePath); | 
|  | final File outputFile = File(outputFilePath) | 
|  | ..createSync(recursive: true); | 
|  | outputFile.writeAsStringSync(processSource( | 
|  | inputFile.readAsStringSync(), | 
|  | preprocessor, | 
|  | replacementPatterns)); | 
|  | } | 
|  |  | 
|  | String processSource(String source, String Function(String source)? preprocessor, List<Replacer> replacementPatterns) { | 
|  | if (preprocessor != null) { | 
|  | source = preprocessor(source); | 
|  | } | 
|  | for (final Replacer replacer in stripMetaPatterns) { | 
|  | source = replacer.perform(source); | 
|  | } | 
|  | for (final Replacer replacer in replacementPatterns) { | 
|  | source = replacer.perform(source); | 
|  | } | 
|  | return source; | 
|  | } | 
|  |  | 
|  | // Enforces a particular structure in top level api files for sublibraries. | 
|  | // | 
|  | // Code in api files must only be made of the library directive, exports, | 
|  | // and code comments. Imports are disallowed. Instead, the required imports are | 
|  | // added by this script during the rewrite. | 
|  | String validateApiFile(String apiFilePath, String apiFileCode, String libraryName) { | 
|  | final List<String> expectedLines = <String>[ | 
|  | 'library $libraryName;', | 
|  | ]; | 
|  |  | 
|  | final List<String> lines = apiFileCode.split('\n'); | 
|  | for (int i = 0; i < lines.length; i += 1) { | 
|  | final int lineNumber = i + 1; | 
|  | final String line = lines[i].trim(); | 
|  |  | 
|  | if (line.isEmpty) { | 
|  | // Emply lines are OK | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (expectedLines.contains(line)) { | 
|  | // Expected; let it pass. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (line.startsWith('//')) { | 
|  | // Comments are OK | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (line.startsWith('export')) { | 
|  | // Exports are OK | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (line.startsWith('@DefaultAsset')) { | 
|  | // Default asset annotations are OK | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (line.startsWith("import 'dart:ffi';")) { | 
|  | // dart:ffi import is an exception to the import rule, since the | 
|  | // @DefaultAsset annotation comes from dart:ffi. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | throw Exception( | 
|  | 'on line $lineNumber: unexpected code in $apiFilePath. This file ' | 
|  | 'may only contain comments and exports. Found:\n' | 
|  | '$line' | 
|  | ); | 
|  | } | 
|  | return apiFileCode; | 
|  | } | 
|  |  | 
|  | String preprocessPartFile(String source, String libraryName) { | 
|  | return 'part of $libraryName;\n$source'; | 
|  | } | 
|  |  | 
|  | /// Responsible for performing string replacements. | 
|  | abstract class Replacer { | 
|  | /// Performs the replacement in the provided [text]. | 
|  | String perform(String text); | 
|  | } | 
|  |  | 
|  | /// Replaces all occurrences of a pattern with a fixed string. | 
|  | class AllReplacer implements Replacer { | 
|  | /// Creates a new tuple with the given [pattern] and [replacement] string. | 
|  | AllReplacer(this._pattern, this._replacement); | 
|  |  | 
|  | /// The pattern to be replaced. | 
|  | final Pattern _pattern; | 
|  |  | 
|  | /// The replacement string. | 
|  | final String _replacement; | 
|  |  | 
|  | @override | 
|  | String perform(String text) { | 
|  | return text.replaceAll(_pattern, _replacement); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Uses a callback to replace each occurrence of a pattern. | 
|  | class MappedReplacer implements Replacer { | 
|  | MappedReplacer(this._pattern, this._replace); | 
|  |  | 
|  | /// The pattern to be replaced. | 
|  | final RegExp _pattern; | 
|  |  | 
|  | /// A callback to replace each occurrence of [_pattern]. | 
|  | final String Function(Match match) _replace; | 
|  |  | 
|  | @override | 
|  | String perform(String text) { | 
|  | return text.replaceAllMapped(_pattern, _replace); | 
|  | } | 
|  | } |