| // 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('engine') |
| ..addMultiOption('input') |
| ..addOption('stamp'); |
| |
| 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;'), |
| AllReplacer(RegExp(r''' |
| import\s*'src/engine.dart'\s*as\s+engine; |
| '''), r''' |
| import 'dart:_engine' as engine; |
| '''), |
| AllReplacer(RegExp( |
| r''' |
| export\s*'src/engine.dart' |
| '''), |
| r''' |
| export 'dart:_engine' |
| ''', |
| ), |
| ]; |
| |
| final List<Replacer> engineLibraryPatterns = <Replacer>[ |
| AllReplacer(RegExp(r'library\s+engine;'), ''' |
| @JS() |
| library dart._engine; |
| |
| 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:math' as math; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui; |
| '''), |
| // Replace exports of engine files with "part" directives. |
| MappedReplacer(RegExp(r''' |
| export\s*'engine/(.*)'; |
| '''), (Match match) => ''' |
| part 'engine/${match.group(1)}'; |
| '''), |
| ]; |
| |
| final List<Replacer> enginePartsPatterns = <Replacer>[ |
| AllReplacer(RegExp(r'part\s+of\s+engine;'), 'part of dart._engine;'), |
| // Remove library-level JS annotations. |
| AllReplacer(RegExp(r'\n@JS(.*)\nlibrary .+;'), ''), |
| // Remove library directives. |
| AllReplacer(RegExp(r'\nlibrary .+;'), ''), |
| // Remove imports/exports from all engine parts. |
| AllReplacer(RegExp(r'\nimport\s*.*'), ''), |
| AllReplacer(RegExp(r'\nexport\s*.*'), ''), |
| ]; |
| |
| final List<Replacer> sharedPatterns = <Replacer>[ |
| AllReplacer(RegExp(r"import\s*'package:meta/meta.dart';"), ''), |
| AllReplacer('@required', ''), |
| AllReplacer('@protected', ''), |
| AllReplacer('@mustCallSuper', ''), |
| AllReplacer('@immutable', ''), |
| AllReplacer('@visibleForTesting', ''), |
| ]; |
| |
| // 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; |
| for (final String inputFilePath in results['input'] as Iterable<String>) { |
| final File inputFile = File(inputFilePath); |
| final File outputFile = File(path.join( |
| directory.path, inputFile.path.substring(inputDirectoryPath.length))) |
| ..createSync(recursive: true); |
| final String source = inputFile.readAsStringSync(); |
| final String rewrittenContent = rewriteFile( |
| source, |
| filePath: inputFilePath, |
| isUi: results['ui'] as bool, |
| isEngine: results['engine'] as bool, |
| ); |
| outputFile.writeAsStringSync(rewrittenContent); |
| if (results['stamp'] != null) { |
| File(results['stamp'] as String).writeAsStringSync('stamp'); |
| } |
| } |
| } |
| |
| String rewriteFile(String source, {required String filePath, required bool isUi, required bool isEngine}) { |
| final List<Replacer> replacementPatterns = <Replacer>[]; |
| replacementPatterns.addAll(sharedPatterns); |
| if (isUi) { |
| replacementPatterns.addAll(uiPatterns); |
| } else if (isEngine) { |
| if (filePath.endsWith('lib/src/engine.dart')) { |
| _validateEngineSource(filePath, source); |
| replacementPatterns.addAll(engineLibraryPatterns); |
| } else { |
| source = _preprocessEnginePartFile(source); |
| replacementPatterns.addAll(enginePartsPatterns); |
| } |
| } |
| for (final Replacer replacer in replacementPatterns) { |
| source = replacer.perform(source); |
| } |
| return source; |
| } |
| |
| // Enforces a particular structure in engine.dart source code. |
| // |
| // Code in `engine.dart` 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. |
| void _validateEngineSource(String engineDartPath, String engineDartCode) { |
| const List<String> expectedLines = <String>[ |
| 'library engine;', |
| ]; |
| |
| final List<String> lines = engineDartCode.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; |
| } |
| |
| throw Exception( |
| 'on line $lineNumber: unexpected code in $engineDartPath. This file ' |
| 'may only contain comments and exports. Found:\n' |
| '$line' |
| ); |
| } |
| } |
| |
| String _preprocessEnginePartFile(String source) { |
| if (source.startsWith('part of engine;') || source.contains('\npart of engine;')) { |
| // The file hasn't been migrated yet. |
| // Do nothing. |
| } else { |
| // Insert the part directive at the beginning of the file. |
| source = 'part of engine;\n$source'; |
| } |
| return 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); |
| } |
| } |