| // Copyright (c) 2019, 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. | |
| // ignore_for_file: implementation_imports | |
| import 'package:front_end/src/api_unstable/vm.dart' show FileSystem; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/util/graph.dart'; | |
| /// Whether [library] has to be compiled to JavaScript. | |
| /// | |
| /// Libraries in [loadedLibraries] were loaded from a dill file and are already | |
| /// compiled into an existing JavaScript library bundle, and libraries from the | |
| /// platform are provided by the Dart SDK JavaScript library bundle. | |
| bool shouldCompileToJavaScript(Library library, Set<Library> loadedLibraries) => | |
| !loadedLibraries.contains(library) && !library.importUri.isScheme('dart'); | |
| /// Compute the strongly connected components for JavaScript compilation. | |
| /// | |
| /// Implements a Path-based strong component algorithm. | |
| /// See https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm | |
| /// | |
| /// The file URI for each library is used as an identifier for the library | |
| /// bundle name. [libraryImportToLibraryBundleImport] will be populated with a | |
| /// mapping of library URI to library bundle name, while | |
| /// [libraryBundleImportToLibraries] will be populated with a mapping of library | |
| /// bundle name to library set. | |
| /// | |
| /// JavaScript import semantics do not permit circular imports in the same | |
| /// manner that Dart does. When compiling a set of libraries with circular | |
| /// imports, these must be combined into a single JavaScript library bundle. | |
| /// | |
| /// On incremental updates, we completely recompute the strongly connected | |
| /// components, but only for the partial component produced. | |
| class StrongComponents { | |
| new(this.component, this.loadedLibraries, this.mainUri, [this.fileSystem]); | |
| /// The Component that is being compiled. | |
| /// | |
| /// On incremental compiles, this will only contain the invalidated | |
| /// libraries. | |
| final Component component; | |
| /// The libraries loaded from a dill file that should not be processed. | |
| final Set<Library> loadedLibraries; | |
| /// The main URI for this application. | |
| final Uri mainUri; | |
| /// The filesystem instance for resolving files. | |
| final FileSystem? fileSystem; | |
| /// Libraries for each library bundle URI. | |
| /// | |
| /// This is populated after calling [computeLibraryBundles] once. | |
| final Map<Uri, List<Library>> libraryBundleImportToLibraries = | |
| <Uri, List<Library>>{}; | |
| /// The library bundle URI for each library URI. | |
| /// | |
| /// This is populated after calling [computeLibraryBundles] once. | |
| final Map<Uri, Uri> libraryImportToLibraryBundleImport = <Uri, Uri>{}; | |
| /// Compute the strongly connected components for the current program. | |
| /// | |
| /// Allows providing a set of libraries to replace what is defined in the | |
| /// component via [partialComponent]. When traversing the import graph, | |
| /// instead of loading the library defined in a [LibraryDependency], if | |
| /// present the library in the partial component will replace it. | |
| /// | |
| /// Throws an [Exception] if [mainUri] cannot be located in the given | |
| /// component. | |
| Future<void> computeLibraryBundles([ | |
| Map<Uri, Library>? partialComponent, | |
| ]) async { | |
| assert(libraryBundleImportToLibraries.isEmpty); | |
| if (component.libraries.isEmpty) { | |
| return; | |
| } | |
| Library? entrypoint; | |
| for (Library library in component.libraries) { | |
| if (library.fileUri == mainUri || library.importUri == mainUri) { | |
| entrypoint = library; | |
| break; | |
| } | |
| } | |
| if (entrypoint == null) { | |
| throw new Exception('Could not find entrypoint $mainUri in Component.'); | |
| } | |
| final List<List<Library>> results = computeStrongComponents( | |
| new _LibraryGraph(entrypoint, loadedLibraries, partialComponent), | |
| ); | |
| for (List<Library> component in results) { | |
| assert(component.isNotEmpty); | |
| // Pick a Uri to associate this component with. We choose the entrypoint | |
| // if it exists or just the first library's Uri. | |
| final Uri componentUri = component | |
| .firstWhere( | |
| (lib) => lib.importUri == mainUri, | |
| orElse: () => component.first, | |
| ) | |
| .importUri; | |
| libraryBundleImportToLibraries[componentUri] = component; | |
| for (Library componentLibrary in component) { | |
| libraryImportToLibraryBundleImport[componentLibrary.importUri] = | |
| componentUri; | |
| } | |
| } | |
| } | |
| } | |
| class _LibraryGraph implements Graph<Library> { | |
| new(this.library, this.loadedLibraries, [this._partialComponent]); | |
| final Library library; | |
| final Set<Library> loadedLibraries; | |
| final Map<Uri, Library>? _partialComponent; | |
| @override | |
| Iterable<Library> neighborsOf(Library vertex) { | |
| return <Library>[ | |
| for (LibraryDependency dependency in vertex.dependencies) | |
| if (shouldCompileToJavaScript( | |
| dependency.targetLibrary, | |
| loadedLibraries, | |
| )) | |
| _partialComponent == null | |
| ? dependency.targetLibrary | |
| : _partialComponent[dependency.targetLibrary.importUri] ?? | |
| dependency.targetLibrary, | |
| ]; | |
| } | |
| @override | |
| Iterable<Library> get vertices => <Library>[library]; | |
| } |