| // 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. |
| library initialize.mirror_loader; |
| |
| import 'dart:collection' show Queue; |
| import 'dart:mirrors'; |
| import 'package:path/path.dart' as path; |
| import 'package:initialize/initialize.dart'; |
| |
| final _root = currentMirrorSystem().isolate.rootLibrary; |
| |
| Queue<Function> loadInitializers( |
| {List<Type> typeFilter, InitializerFilter customFilter}) { |
| return new InitializationCrawler(typeFilter, customFilter).run(); |
| } |
| |
| // Crawls a library and all its dependencies for `Initializer` annotations using |
| // mirrors |
| class InitializationCrawler { |
| // Set of all visited annotations, keys are the declarations that were |
| // annotated, values are the annotations that have been processed. |
| static final _annotationsFound = |
| new Map<DeclarationMirror, Set<InstanceMirror>>(); |
| |
| // If non-null, then only these annotations should be processed. |
| final List<Type> typeFilter; |
| |
| // If non-null, then only annotations which return true when passed to this |
| // function will be processed. |
| final InitializerFilter customFilter; |
| |
| InitializationCrawler(this.typeFilter, this.customFilter); |
| |
| // The primary function in this class, invoke it to crawl and collect all the |
| // annotations into a queue of init functions. |
| Queue<Function> run() { |
| var librariesSeen = new Set<LibraryMirror>(); |
| var queue = new Queue<Function>(); |
| var libraries = currentMirrorSystem().libraries; |
| |
| var trampolineUri = Uri.parse('${_root.uri}\$trampoline'); |
| if (libraries.containsKey(trampolineUri)) { |
| // In dartium, process all relative libraries in reverse order of when |
| // they were seen. |
| // TODO(jakemac): This is an approximation of what we actually want. |
| // https://github.com/dart-lang/initialize/issues/25 |
| var relativeLibraryUris = new List.from(libraries.keys.where( |
| (uri) => uri.scheme != 'package' && uri.scheme != 'dart')); |
| |
| for (var import in relativeLibraryUris.reversed) { |
| // Always load the package: version of a library if available for |
| // canonicalization purposes. |
| var libToRun; |
| if (_isHttpStylePackageUrl(import)) { |
| var packageUri = _packageUriFor(import); |
| libToRun = libraries[packageUri]; |
| } |
| if (libToRun == null) libToRun = libraries[import]; |
| |
| // Dartium creates an extra trampoline lib that loads the main dart script |
| // and breaks our ordering, we should skip it. |
| if (librariesSeen.contains(libToRun) || |
| libToRun.uri.path.endsWith('\$trampoline')) { |
| continue; |
| } |
| _readLibraryDeclarations(libToRun, librariesSeen, queue); |
| } |
| } else { |
| // Not in dartium, just process from the root library. |
| _readLibraryDeclarations(_root, librariesSeen, queue); |
| } |
| |
| return queue; |
| } |
| |
| |
| /// Whether [uri] is an http URI that contains a 'packages' segment, and |
| /// therefore could be converted into a 'package:' URI. |
| bool _isHttpStylePackageUrl(Uri uri) { |
| var uriPath = uri.path; |
| return uri.scheme == _root.uri.scheme && |
| // Don't process cross-domain uris. |
| uri.authority == _root.uri.authority && |
| uriPath.endsWith('.dart') && |
| (uriPath.contains('/packages/') || uriPath.startsWith('packages/')); |
| } |
| |
| Uri _packageUriFor(Uri httpUri) { |
| var packagePath = httpUri.path.substring( |
| httpUri.path.lastIndexOf('packages/') + 'packages/'.length); |
| return Uri.parse('package:$packagePath'); |
| } |
| |
| // Reads Initializer annotations on this library and all its dependencies in |
| // post-order. |
| Queue<Function> _readLibraryDeclarations(LibraryMirror lib, |
| Set<LibraryMirror> librariesSeen, Queue<Function> queue) { |
| librariesSeen.add(lib); |
| |
| // First visit all our dependencies. |
| for (var dependency in _sortedLibraryDependencies(lib)) { |
| // Skip dart: imports, they never use this package. |
| if (dependency.targetLibrary.uri.toString().startsWith('dart:')) continue; |
| if (librariesSeen.contains(dependency.targetLibrary)) continue; |
| |
| _readLibraryDeclarations(dependency.targetLibrary, librariesSeen, queue); |
| } |
| |
| // Second parse the library directive annotations. |
| _readAnnotations(lib, queue); |
| |
| // Last, parse all class and method annotations. |
| for (var declaration in _sortedLibraryDeclarations(lib)) { |
| _readAnnotations(declaration, queue); |
| // Check classes for static annotations which are not supported |
| if (declaration is ClassMirror) { |
| for (var classDeclaration in declaration.declarations.values) { |
| _readAnnotations(classDeclaration, queue); |
| } |
| } |
| } |
| |
| return queue; |
| } |
| |
| Iterable<LibraryDependencyMirror> _sortedLibraryDependencies( |
| LibraryMirror lib) => new List.from(lib.libraryDependencies) |
| ..sort((a, b) { |
| var aScheme = a.targetLibrary.uri.scheme; |
| var bScheme = b.targetLibrary.uri.scheme; |
| if (aScheme != 'file' && bScheme == 'file') return -1; |
| if (bScheme != 'file' && aScheme == 'file') return 1; |
| return _relativeLibraryUri(a).compareTo(_relativeLibraryUri(b)); |
| }); |
| |
| String _relativeLibraryUri(LibraryDependencyMirror lib) { |
| if (lib.targetLibrary.uri.scheme == 'file' && |
| lib.sourceLibrary.uri.scheme == 'file') { |
| return path.relative(lib.targetLibrary.uri.path, |
| from: path.dirname(lib.sourceLibrary.uri.path)); |
| } |
| return lib.targetLibrary.uri.toString(); |
| } |
| |
| Iterable<DeclarationMirror> _sortedLibraryDeclarations(LibraryMirror lib) => |
| lib.declarations.values |
| .where((d) => d is ClassMirror || d is MethodMirror) |
| .toList() |
| ..sort((a, b) { |
| if (a is MethodMirror && b is ClassMirror) return -1; |
| if (a is ClassMirror && b is MethodMirror) return 1; |
| return _declarationName(a).compareTo(_declarationName(b)); |
| }); |
| |
| String _declarationName(DeclarationMirror declaration) => |
| MirrorSystem.getName(declaration.qualifiedName); |
| |
| // Reads annotations on declarations and adds them to `_initQueue` if they are |
| // initializers. |
| void _readAnnotations(DeclarationMirror declaration, Queue<Function> queue) { |
| var annotations = |
| declaration.metadata.where((m) => _filterMetadata(declaration, m)); |
| for (var meta in annotations) { |
| _annotationsFound[declaration].add(meta); |
| |
| // Initialize super classes first, if they are in the same library, |
| // otherwise we throw an error. This can only be the case if there are |
| // cycles in the imports. |
| if (declaration is ClassMirror && declaration.superclass != null) { |
| if (declaration.superclass.owner == declaration.owner) { |
| _readAnnotations(declaration.superclass, queue); |
| } else { |
| var superMetas = declaration.superclass.metadata |
| .where((m) => _filterMetadata(declaration.superclass, m)) |
| .toList(); |
| if (superMetas.isNotEmpty) { |
| throw new UnsupportedError( |
| 'We have detected a cycle in your import graph when running ' |
| 'initializers on ${declaration.qualifiedName}. This means the ' |
| 'super class ${declaration.superclass.qualifiedName} has a ' |
| 'dependency on this library (possibly transitive).'); |
| } |
| } |
| } |
| |
| var annotatedValue; |
| if (declaration is ClassMirror) { |
| annotatedValue = declaration.reflectedType; |
| } else if (declaration is MethodMirror) { |
| if (declaration.owner is! LibraryMirror) { |
| // TODO(jakemac): Support static class methods. |
| throw _TOP_LEVEL_FUNCTIONS_ONLY; |
| } |
| annotatedValue = (declaration.owner as ObjectMirror) |
| .getField(declaration.simpleName).reflectee; |
| } else if (declaration is LibraryMirror) { |
| var package; |
| var filePath; |
| Uri uri = declaration.uri; |
| // Convert to a package style uri if possible. |
| if (_isHttpStylePackageUrl(uri)) { |
| uri = _packageUriFor(uri); |
| } |
| if (uri.scheme == 'file' || uri.scheme.startsWith('http')) { |
| filePath = path.url.relative(uri.path, |
| from: path.url.dirname(_root.uri.path)); |
| } else if (uri.scheme == 'package') { |
| var segments = uri.pathSegments; |
| package = segments[0]; |
| filePath = path.url.joinAll(segments.getRange(1, segments.length)); |
| } else { |
| throw new UnsupportedError('Unsupported uri scheme ${uri.scheme} for ' |
| 'library ${declaration}.'); |
| } |
| annotatedValue = |
| new LibraryIdentifier(declaration.qualifiedName, package, filePath); |
| } else { |
| throw _UNSUPPORTED_DECLARATION; |
| } |
| queue.addLast(() => meta.reflectee.initialize(annotatedValue)); |
| } |
| } |
| |
| // Filter function that returns true only if `meta` is an `Initializer`, |
| // it passes the `typeFilter` and `customFilter` if they exist, and it has not |
| // yet been seen. |
| bool _filterMetadata(DeclarationMirror declaration, InstanceMirror meta) { |
| if (meta.reflectee is! Initializer) return false; |
| if (typeFilter != null && |
| !typeFilter.any((t) => meta.reflectee.runtimeType == t)) { |
| return false; |
| } |
| if (customFilter != null && !customFilter(meta.reflectee)) return false; |
| if (!_annotationsFound.containsKey(declaration)) { |
| _annotationsFound[declaration] = new Set<InstanceMirror>(); |
| } |
| if (_annotationsFound[declaration].contains(meta)) return false; |
| return true; |
| } |
| } |
| |
| final _TOP_LEVEL_FUNCTIONS_ONLY = new UnsupportedError( |
| 'Only top level methods are supported for initializers'); |
| |
| final _UNSUPPORTED_DECLARATION = new UnsupportedError( |
| 'Initializers are only supported on libraries, classes, and top level ' |
| 'methods'); |