blob: 08121172430107d7dda9db40b4b37696aaf531d4 [file] [log] [blame]
// 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';
/// 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 {
StrongComponents(
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> {
_LibraryGraph(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 (!loadedLibraries.contains(dependency.targetLibrary) &&
!dependency.targetLibrary.importUri.isScheme('dart'))
_partialComponent == null
? dependency.targetLibrary
: _partialComponent[dependency.targetLibrary.importUri] ??
dependency.targetLibrary
];
}
@override
Iterable<Library> get vertices => <Library>[library];
}