Index: tools/patch_sdk.dart |
diff --git a/tools/patch_sdk.dart b/tools/patch_sdk.dart |
deleted file mode 100644 |
index ca547cda6f7f20ee34bf097496d1eef0aa648799..0000000000000000000000000000000000000000 |
--- a/tools/patch_sdk.dart |
+++ /dev/null |
@@ -1,504 +0,0 @@ |
-#!/usr/bin/env dart |
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-/// Command line tool to merge the SDK libraries and our patch files. |
-/// This is currently designed as an offline tool, but we could automate it. |
- |
-import 'dart:io'; |
-import 'dart:math' as math; |
- |
-import 'package:analyzer/analyzer.dart'; |
-import 'package:analyzer/src/generated/sdk.dart'; |
-import 'package:path/path.dart' as path; |
- |
-void main(List<String> argv) { |
- var base = path.fromUri(Platform.script); |
- var dartDir = path.dirname(path.dirname(path.absolute(base))); |
- |
- if (argv.length != 4 || |
- !argv.isEmpty && argv.first != 'vm' && argv.first != 'ddc') { |
- var self = path.relative(base); |
- print('Usage: $self MODE SDK_DIR PATCH_DIR OUTPUT_DIR'); |
- print('MODE must be one of ddc or vm.'); |
- |
- var toolDir = path.relative(path.dirname(base)); |
- var sdkExample = path.join(toolDir, 'input_sdk'); |
- var patchExample = path.join(sdkExample, 'patch'); |
- var outExample = |
- path.relative(path.normalize(path.join('gen', 'patched_sdk'))); |
- print('For example:'); |
- print('\$ $self ddc $sdkExample $patchExample $outExample'); |
- |
- var repositoryDir = path.relative(path.dirname(path.dirname(base))); |
- sdkExample = path.relative(path.join(repositoryDir, 'sdk')); |
- patchExample = path.relative(path.join(repositoryDir, 'out', 'DebugX64', |
- 'obj', 'gen', 'patch')); |
- outExample = path.relative(path.join(repositoryDir, 'out', 'DebugX64', |
- 'obj', 'gen', 'patched_sdk')); |
- print('or:'); |
- print('\$ $self vm $sdkExample $patchExample $outExample'); |
- |
- exit(1); |
- } |
- |
- var mode = argv[0]; |
- var input = argv[1]; |
- var sdkLibIn = path.join(input, 'lib'); |
- var patchIn = argv[2]; |
- var sdkOut = path.join(argv[3], 'lib'); |
- |
- var privateIn = path.join(input, 'private'); |
- var INTERNAL_PATH = '_internal/compiler/js_lib/'; |
- |
- // Copy libraries.dart and version |
- var libContents = new File(path.join(sdkLibIn, '_internal', |
- 'sdk_library_metadata', 'lib', 'libraries.dart')).readAsStringSync(); |
- _writeSync( |
- path.join( |
- sdkOut, '_internal', 'sdk_library_metadata', 'lib', 'libraries.dart'), |
- libContents); |
- if (mode == 'ddc') { |
- _writeSync(path.join(sdkOut, '..', 'version'), |
- new File(path.join(sdkLibIn, '..', 'version')).readAsStringSync()); |
- } |
- |
- // Parse libraries.dart |
- var sdkLibraries = _getSdkLibraries(libContents); |
- |
- // Enumerate core libraries and apply patches |
- for (SdkLibrary library in sdkLibraries) { |
- // TODO(jmesserly): analyzer does not handle the default case of |
- // "both platforms" correctly, and treats it as being supported on neither. |
- // So instead we skip explicitly marked as either VM or dart2js libs. |
- if (mode == 'ddc' ? libary.isVmLibrary : library.isDart2JsLibrary) { |
- continue; |
- } |
- |
- var libraryOut = path.join(sdkLibIn, library.path); |
- var libraryIn; |
- if (mode == 'vm' && library.path.contains('typed_data.dart')) { |
- // dart:typed_data is unlike the other libraries in the SDK. The VM does |
- // not apply a patch to the base SDK implementation of the library. |
- // Instead, the VM provides a replacement implementation and ignores the |
- // sources in the SDK. |
- libraryIn = |
- path.join(dartDir, 'runtime', 'lib', 'typed_data.dart'); |
- } else if (mode == 'ddc' && library.path.contains(INTERNAL_PATH)) { |
- libraryIn = |
- path.join(privateIn, library.path.replaceAll(INTERNAL_PATH, '')); |
- } else { |
- libraryIn = libraryOut; |
- } |
- |
- var libraryFile = new File(libraryIn); |
- if (libraryFile.existsSync()) { |
- var outPaths = <String>[libraryOut]; |
- var libraryContents = libraryFile.readAsStringSync(); |
- |
- int inputModifyTime = |
- libraryFile.lastModifiedSync().millisecondsSinceEpoch; |
- var partFiles = <File>[]; |
- for (var part in parseDirectives(libraryContents).directives) { |
- if (part is PartDirective) { |
- var partPath = part.uri.stringValue; |
- outPaths.add(path.join(path.dirname(libraryOut), partPath)); |
- |
- var partFile = new File(path.join(path.dirname(libraryIn), partPath)); |
- partFiles.add(partFile); |
- inputModifyTime = math.max(inputModifyTime, |
- partFile.lastModifiedSync().millisecondsSinceEpoch); |
- } |
- } |
- |
- // See if we can find a patch file. |
- var patchPath = path.join( |
- patchIn, path.basenameWithoutExtension(libraryIn) + '_patch.dart'); |
- |
- var patchFile = new File(patchPath); |
- bool patchExists = patchFile.existsSync(); |
- if (patchExists) { |
- inputModifyTime = math.max(inputModifyTime, |
- patchFile.lastModifiedSync().millisecondsSinceEpoch); |
- } |
- |
- // Compute output paths |
- outPaths = outPaths |
- .map((p) => path.join(sdkOut, path.relative(p, from: sdkLibIn))) |
- .toList(); |
- |
- // Compare output modify time with input modify time. |
- bool needsUpdate = false; |
- for (var outPath in outPaths) { |
- var outFile = new File(outPath); |
- if (!outFile.existsSync() || |
- outFile.lastModifiedSync().millisecondsSinceEpoch < |
- inputModifyTime) { |
- needsUpdate = true; |
- break; |
- } |
- } |
- |
- if (needsUpdate) { |
- var contents = <String>[libraryContents]; |
- contents.addAll(partFiles.map((f) => f.readAsStringSync())); |
- if (patchExists) { |
- var patchContents = patchFile.readAsStringSync(); |
- contents = _patchLibrary( |
- patchFile.toString(), contents, patchContents); |
- } |
- |
- for (var i = 0; i < outPaths.length; i++) { |
- if (path.basename(outPaths[i]) == 'internal.dart') { |
- contents[i] += ''' |
- |
-/// Marks a function as an external implementation ("native" in the Dart VM). |
-/// |
-/// Provides a backend-specific String that can be used to identify the |
-/// function's implementation |
-class ExternalName { |
- final String name; |
- const ExternalName(this.name); |
-} |
-'''; |
- } |
- |
- _writeSync(outPaths[i], contents[i]); |
- } |
- } |
- } |
- } |
- if (mode == 'vm') { |
- for (var tuple in [['nativewrappers', 'nativewrappers.dart'], |
- ['_builtin', 'builtin.dart']]) { |
- var vmLibrary = tuple[0]; |
- var dartFile = tuple[1]; |
- |
- // The "dart:_builtin" library is only available for the DartVM. |
- var builtinLibraryIn = path.join(dartDir, 'runtime', 'bin', dartFile); |
- var builtinLibraryOut = path.join(sdkOut, vmLibrary, '${vmLibrary}.dart'); |
- _writeSync(builtinLibraryOut, new File(builtinLibraryIn).readAsStringSync()); |
- } |
- |
- for (var file in ['loader.dart', 'server.dart', 'vmservice_io.dart']) { |
- var libraryIn = path.join(dartDir, 'runtime', 'bin', 'vmservice', file); |
- var libraryOut = path.join(sdkOut, 'vmservice_io', file); |
- _writeSync(libraryOut, new File(libraryIn).readAsStringSync()); |
- } |
- } |
-} |
- |
-/// Writes a file, creating the directory if needed. |
-void _writeSync(String filePath, String contents) { |
- var outDir = new Directory(path.dirname(filePath)); |
- if (!outDir.existsSync()) outDir.createSync(recursive: true); |
- |
- new File(filePath).writeAsStringSync(contents); |
-} |
- |
-/// Merges dart:* library code with code from *_patch.dart file. |
-/// |
-/// Takes a list of the library's parts contents, with the main library contents |
-/// first in the list, and the contents of the patch file. |
-/// |
-/// The result will have `@patch` implementations merged into the correct place |
-/// (e.g. the class or top-level function declaration) and all other |
-/// declarations introduced by the patch will be placed into the main library |
-/// file. |
-/// |
-/// This is purely a syntactic transformation. Unlike dart2js patch files, there |
-/// is no semantic meaning given to the *_patch files, and they do not magically |
-/// get their own library scope, etc. |
-/// |
-/// Editorializing: the dart2js approach requires a Dart front end such as |
-/// package:analyzer to semantically model a feature beyond what is specified |
-/// in the Dart language. Since this feature is only for the convenience of |
-/// writing the dart:* libraries, and not a tool given to Dart developers, it |
-/// seems like a non-ideal situation. Instead we keep the preprocessing simple. |
-List<String> _patchLibrary(String name, |
- List<String> partsContents, |
- String patchContents) { |
- var results = <StringEditBuffer>[]; |
- |
- // Parse the patch first. We'll need to extract bits of this as we go through |
- // the other files. |
- final patchFinder = new PatchFinder.parseAndVisit(name, patchContents); |
- |
- // Merge `external` declarations with the corresponding `@patch` code. |
- for (var partContent in partsContents) { |
- var partEdits = new StringEditBuffer(partContent); |
- var partUnit = parseCompilationUnit(partContent); |
- partUnit.accept(new PatchApplier(partEdits, patchFinder)); |
- results.add(partEdits); |
- } |
- return new List<String>.from(results.map((e) => e.toString())); |
-} |
- |
-/// Merge `@patch` declarations into `external` declarations. |
-class PatchApplier extends GeneralizingAstVisitor { |
- final StringEditBuffer edits; |
- final PatchFinder patch; |
- |
- bool _isLibrary = true; // until proven otherwise. |
- |
- PatchApplier(this.edits, this.patch); |
- |
- @override |
- visitCompilationUnit(CompilationUnit node) { |
- super.visitCompilationUnit(node); |
- if (_isLibrary) _mergeUnpatched(node); |
- } |
- |
- void _merge(AstNode node, int pos) { |
- var code = patch.contents.substring(node.offset, node.end); |
- edits.insert(pos, '\n' + code); |
- } |
- |
- /// Merges directives and declarations that are not `@patch` into the library. |
- void _mergeUnpatched(CompilationUnit unit) { |
- // Merge imports from the patch |
- // TODO(jmesserly): remove duplicate imports |
- |
- // To patch a library, we must have a library directive |
- var libDir = unit.directives.first as LibraryDirective; |
- int importPos = unit.directives |
- .lastWhere((d) => d is ImportDirective, orElse: () => libDir) |
- .end; |
- for (var d in patch.unit.directives.where((d) => d is ImportDirective)) { |
- _merge(d, importPos); |
- } |
- |
- int partPos = unit.directives.last.end; |
- for (var d in patch.unit.directives.where((d) => d is PartDirective)) { |
- _merge(d, partPos); |
- } |
- |
- // Merge declarations from the patch |
- int declPos = edits.original.length; |
- for (var d in patch.mergeDeclarations) { |
- _merge(d, declPos); |
- } |
- } |
- |
- @override |
- visitPartOfDirective(PartOfDirective node) { |
- _isLibrary = false; |
- } |
- |
- @override |
- visitFunctionDeclaration(FunctionDeclaration node) { |
- _maybePatch(node); |
- } |
- |
- /// Merge patches and extensions into the class |
- @override |
- visitClassDeclaration(ClassDeclaration node) { |
- node.members.forEach(_maybePatch); |
- |
- var mergeMembers = patch.mergeMembers[_qualifiedName(node)]; |
- if (mergeMembers == null) return; |
- |
- // Merge members from the patch |
- var pos = node.members.last.end; |
- for (var member in mergeMembers) { |
- var code = patch.contents.substring(member.offset, member.end); |
- edits.insert(pos, '\n\n ' + code); |
- } |
- } |
- |
- void _maybePatch(AstNode node) { |
- if (node is FieldDeclaration) return; |
- |
- var externalKeyword = (node as dynamic).externalKeyword; |
- if (externalKeyword == null) return; |
- |
- var name = _qualifiedName(node); |
- var patchNode = patch.patches[name]; |
- if (patchNode == null) { |
- print('warning: patch not found for $name: $node'); |
- return; |
- } |
- |
- Annotation patchMeta = patchNode.metadata.lastWhere(_isPatchAnnotation); |
- int start = patchMeta.endToken.next.offset; |
- var code = patch.contents.substring(start, patchNode.end); |
- |
- // For some node like static fields, the node's offset doesn't include |
- // the external keyword. Also starting from the keyword lets us preserve |
- // documentation comments. |
- edits.replace(externalKeyword.offset, node.end, code); |
- } |
-} |
- |
-class PatchFinder extends GeneralizingAstVisitor { |
- final String contents; |
- final CompilationUnit unit; |
- |
- final Map patches = <String, Declaration>{}; |
- final Map mergeMembers = <String, List<ClassMember>>{}; |
- final List mergeDeclarations = <CompilationUnitMember>[]; |
- |
- PatchFinder.parseAndVisit(String name, String contents) |
- : contents = contents, |
- unit = parseCompilationUnit(contents, name: name) { |
- visitCompilationUnit(unit); |
- } |
- |
- @override |
- visitCompilationUnitMember(CompilationUnitMember node) { |
- mergeDeclarations.add(node); |
- } |
- |
- @override |
- visitClassDeclaration(ClassDeclaration node) { |
- if (_isPatch(node)) { |
- var members = <ClassMember>[]; |
- for (var member in node.members) { |
- if (_isPatch(member)) { |
- patches[_qualifiedName(member)] = member; |
- } else { |
- members.add(member); |
- } |
- } |
- if (members.isNotEmpty) { |
- mergeMembers[_qualifiedName(node)] = members; |
- } |
- } else { |
- mergeDeclarations.add(node); |
- } |
- } |
- |
- @override |
- visitFunctionDeclaration(FunctionDeclaration node) { |
- if (_isPatch(node)) { |
- patches[_qualifiedName(node)] = node; |
- } else { |
- mergeDeclarations.add(node); |
- } |
- } |
- |
- @override |
- visitFunctionBody(node) {} // skip method bodies |
-} |
- |
-String _qualifiedName(Declaration node) { |
- var parent = node.parent; |
- var className = ''; |
- if (parent is ClassDeclaration) { |
- className = parent.name.name + '.'; |
- } |
- var name = (node as dynamic).name; |
- name = (name != null ? name.name : ''); |
- |
- var accessor = ''; |
- if (node is MethodDeclaration) { |
- if (node.isGetter) accessor = 'get:'; |
- else if (node.isSetter) accessor = 'set:'; |
- } |
- return className + accessor + name; |
-} |
- |
-bool _isPatch(AnnotatedNode node) => node.metadata.any(_isPatchAnnotation); |
- |
-bool _isPatchAnnotation(Annotation m) => |
- m.name.name == 'patch' && m.constructorName == null && m.arguments == null; |
- |
-/// Editable string buffer. |
-/// |
-/// Applies a series of edits (insertions, removals, replacements) using |
-/// original location information, and composes them into the edited string. |
-/// |
-/// For example, starting with a parsed AST with original source locations, |
-/// this type allows edits to be made without regards to other edits. |
-class StringEditBuffer { |
- final String original; |
- final _edits = <_StringEdit>[]; |
- |
- /// Creates a new transaction. |
- StringEditBuffer(this.original); |
- |
- bool get hasEdits => _edits.length > 0; |
- |
- /// Edit the original text, replacing text on the range [begin] and |
- /// exclusive [end] with the [replacement] string. |
- void replace(int begin, int end, String replacement) { |
- _edits.add(new _StringEdit(begin, end, replacement)); |
- } |
- |
- /// Insert [string] at [offset]. |
- /// Equivalent to `replace(offset, offset, string)`. |
- void insert(int offset, String string) => replace(offset, offset, string); |
- |
- /// Remove text from the range [begin] to exclusive [end]. |
- /// Equivalent to `replace(begin, end, '')`. |
- void remove(int begin, int end) => replace(begin, end, ''); |
- |
- /// Applies all pending [edit]s and returns a new string. |
- /// |
- /// This method is non-destructive: it does not discard existing edits or |
- /// change the [original] string. Further edits can be added and this method |
- /// can be called again. |
- /// |
- /// Throws [UnsupportedError] if the edits were overlapping. If no edits were |
- /// made, the original string will be returned. |
- String toString() { |
- var sb = new StringBuffer(); |
- if (_edits.length == 0) return original; |
- |
- // Sort edits by start location. |
- _edits.sort(); |
- |
- int consumed = 0; |
- for (var edit in _edits) { |
- if (consumed > edit.begin) { |
- sb = new StringBuffer(); |
- sb.write('overlapping edits. Insert at offset '); |
- sb.write(edit.begin); |
- sb.write(' but have consumed '); |
- sb.write(consumed); |
- sb.write(' input characters. List of edits:'); |
- for (var e in _edits) { |
- sb.write('\n '); |
- sb.write(e); |
- } |
- throw new UnsupportedError(sb.toString()); |
- } |
- |
- // Add characters from the original string between this edit and the last |
- // one, if any. |
- var betweenEdits = original.substring(consumed, edit.begin); |
- sb.write(betweenEdits); |
- sb.write(edit.replace); |
- consumed = edit.end; |
- } |
- |
- // Add any text from the end of the original string that was not replaced. |
- sb.write(original.substring(consumed)); |
- return sb.toString(); |
- } |
-} |
- |
-class _StringEdit implements Comparable<_StringEdit> { |
- final int begin; |
- final int end; |
- final String replace; |
- |
- _StringEdit(this.begin, this.end, this.replace); |
- |
- int get length => end - begin; |
- |
- String toString() => '(Edit @ $begin,$end: "$replace")'; |
- |
- int compareTo(_StringEdit other) { |
- int diff = begin - other.begin; |
- if (diff != 0) return diff; |
- return end - other.end; |
- } |
-} |
- |
-List<SdkLibrary> _getSdkLibraries(String contents) { |
- var libraryBuilder = new SdkLibrariesReader_LibraryBuilder(true); |
- parseCompilationUnit(contents).accept(libraryBuilder); |
- return libraryBuilder.librariesMap.sdkLibraries; |
-} |