Skip to content

Commit 69302eb

Browse files
authored
Correct looping bug in library discovery (#3574)
* Correct looping bug in library discovery * feedback
1 parent 339efa3 commit 69302eb

File tree

1 file changed

+117
-70
lines changed

1 file changed

+117
-70
lines changed

lib/src/model/package_builder.dart

Lines changed: 117 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ abstract class PackageBuilder {
4343
/// A package builder that understands pub package format.
4444
class PubPackageBuilder implements PackageBuilder {
4545
final DartdocOptionContext config;
46-
final Set<String> _knownFiles = {};
4746
final PackageMetaProvider packageMetaProvider;
4847
final PackageConfigProvider packageConfigProvider;
4948

@@ -86,6 +85,7 @@ class PubPackageBuilder implements PackageBuilder {
8685
await getLibraries(newGraph);
8786
runtimeStats.endPerfTask();
8887

88+
logDebug('${DateTime.now()}: Initializing package graph...');
8989
runtimeStats.startPerfTask('initializePackageGraph');
9090
await newGraph.initializePackageGraph();
9191
runtimeStats.endPerfTask();
@@ -177,27 +177,6 @@ class PubPackageBuilder implements PackageBuilder {
177177
for (var filename in files) packageMetaProvider.fromFilename(filename)!,
178178
};
179179

180-
/// Adds [element]'s path and all of its part files' paths to [_knownFiles],
181-
/// and recursively adds the paths of all imported and exported libraries.
182-
void _addKnownFiles(LibraryElement? element) {
183-
if (element != null) {
184-
var path = element.source.fullName;
185-
if (_knownFiles.add(path)) {
186-
for (var import in element.libraryImports) {
187-
_addKnownFiles(import.importedLibrary);
188-
}
189-
for (var export in element.libraryExports) {
190-
_addKnownFiles(export.exportedLibrary);
191-
}
192-
for (var part in element.parts
193-
.map((e) => e.uri)
194-
.whereType<DirectiveUriWithUnit>()) {
195-
_knownFiles.add(part.source.fullName);
196-
}
197-
}
198-
}
199-
}
200-
201180
/// Whether to skip unreachable libraries when gathering all of the libraries
202181
/// for the package graph.
203182
///
@@ -212,29 +191,47 @@ class PubPackageBuilder implements PackageBuilder {
212191
/// This set is used to prevent resolving set files more than once.
213192
final _knownParts = <String>{};
214193

215-
/// Parses libraries with the analyzer and invokes [addLibrary] with each
216-
/// result.
194+
/// Discovers and resolves libraries, invoking [addLibrary] with each result.
217195
///
218-
/// Uses [processedLibraries] to prevent calling the callback more than once
196+
/// Uses [processedLibraries] to prevent calling [addLibrary] more than once
219197
/// with the same [LibraryElement]. Adds each [LibraryElement] found to
220198
/// [processedLibraries].
221-
Future<void> _parseLibraries(
199+
///
200+
/// [addingSpecials] indicates that only [SpecialClass]es are being resolved
201+
/// in this round.
202+
Future<void> _discoverLibraries(
222203
void Function(DartDocResolvedLibrary) addLibrary,
223204
Set<LibraryElement> processedLibraries,
224205
Set<String> files, {
225-
bool Function(LibraryElement)? isLibraryIncluded,
206+
bool addingSpecials = false,
226207
}) async {
227208
files = {...files};
228-
isLibraryIncluded ??= (_) => true;
229-
var lastPass = <PackageMeta>{};
230-
var current = <PackageMeta>{};
209+
// Discover Dart libraries in a loop. In each iteration of the loop, we take
210+
// a set of files (starting with the ones passed into the function), resolve
211+
// them, add them to the package graph via `addLibrary`, and then discover
212+
// which additional files need to be processed in the next loop. This
213+
// discovery depends on various options (TODO: which?), but the basic idea
214+
// is to take a file we've just processed, and add all of the files which
215+
// that file references via imports or exports, and add them to the set of
216+
// files to be processed.
217+
//
218+
// This loop may execute a few times. We know to stop looping when we have
219+
// added zero new files to process. This is tracked with `filesInLastPass`
220+
// and `filesInCurrentPass`.
221+
var filesInLastPass = <String>{};
222+
var filesInCurrentPass = <String>{};
231223
var processedFiles = <String>{};
224+
// When the loop discovers new files in a new package, it does extra work to
225+
// find all documentable files in that package, for the universal reference
226+
// scope. This variable tracks which packages we've seen so far.
227+
var knownPackages = <PackageMeta>{};
232228
do {
233-
lastPass = current;
234-
235-
// Be careful here; not to accidentally stack up multiple
229+
filesInLastPass = filesInCurrentPass;
230+
var newFiles = <String>{};
231+
// Be careful here, not to accidentally stack up multiple
236232
// [DartDocResolvedLibrary]s, as those eat our heap.
237233
var libraryFiles = files.difference(_knownParts);
234+
238235
for (var file in libraryFiles) {
239236
if (processedFiles.contains(file)) {
240237
continue;
@@ -246,54 +243,72 @@ class PubPackageBuilder implements PackageBuilder {
246243
_knownParts.add(file);
247244
continue;
248245
}
249-
_addKnownFiles(resolvedLibrary.element);
250-
if (!processedLibraries.contains(resolvedLibrary.element) &&
251-
isLibraryIncluded(resolvedLibrary.element)) {
246+
newFiles.addFilesReferencedBy(resolvedLibrary.element);
247+
if (processedLibraries.contains(resolvedLibrary.element)) {
248+
continue;
249+
}
250+
if (addingSpecials || _shouldIncludeLibrary(resolvedLibrary.element)) {
252251
addLibrary(resolvedLibrary);
253252
processedLibraries.add(resolvedLibrary.element);
254253
}
255254
}
255+
files.addAll(newFiles);
256+
if (!addingSpecials) {
257+
files.addAll(_includedExternalsFrom(newFiles));
258+
}
256259

257-
files.addAll(_knownFiles);
258-
files.addAll(_includeExternalsFrom(_knownFiles));
259-
260-
current = _packageMetasForFiles(files.difference(_knownParts));
261-
// To get canonicalization correct for non-locally documented packages
262-
// (so we can generate the right hyperlinks), it's vital that we add all
263-
// libraries in dependent packages. So if the analyzer discovers some
264-
// files in a package we haven't seen yet, add files for that package.
265-
for (var meta in current.difference(lastPass)) {
266-
if (meta.isSdk) {
267-
if (!_skipUnreachableSdkLibraries) {
268-
files.addAll(_sdkFilesToDocument);
260+
var packages = _packageMetasForFiles(files.difference(_knownParts));
261+
filesInCurrentPass = {...files.difference(_knownParts)};
262+
263+
if (!addingSpecials) {
264+
// To get canonicalization correct for non-locally documented packages
265+
// (so we can generate the right hyperlinks), it's vital that we add all
266+
// libraries in dependent packages. So if the analyzer discovers some
267+
// files in a package we haven't seen yet, add files for that package.
268+
for (var meta in packages.difference(knownPackages)) {
269+
if (meta.isSdk) {
270+
if (!_skipUnreachableSdkLibraries) {
271+
files.addAll(_sdkFilesToDocument);
272+
}
273+
} else {
274+
files.addAll(await _findFilesToDocumentInPackage(
275+
meta.dir.path,
276+
includeDependencies: false,
277+
filterExcludes: false,
278+
).toList());
269279
}
270-
} else {
271-
files.addAll(await _findFilesToDocumentInPackage(meta.dir.path,
272-
includeDependencies: false, filterExcludes: false)
273-
.toList());
274280
}
281+
knownPackages.addAll(packages);
275282
}
276-
} while (!lastPass.containsAll(current));
283+
} while (!filesInLastPass.containsAll(filesInCurrentPass));
277284
}
278285

286+
/// Whether [libraryElement] should be included in the libraries-to-document.
287+
bool _shouldIncludeLibrary(LibraryElement libraryElement) =>
288+
config.include.isEmpty || config.include.contains(libraryElement.name);
289+
279290
/// Returns all top level library files in the 'lib/' directory of the given
280291
/// package root directory.
281292
///
282293
/// If [includeDependencies], then all top level library files in the 'lib/'
283294
/// directory of every package in [basePackageDir]'s package config are also
284295
/// included.
285-
Stream<String> _findFilesToDocumentInPackage(String basePackageDir,
286-
{required bool includeDependencies, bool filterExcludes = true}) async* {
296+
Stream<String> _findFilesToDocumentInPackage(
297+
String basePackageDir, {
298+
required bool includeDependencies,
299+
required bool filterExcludes,
300+
}) async* {
287301
var packageDirs = {basePackageDir};
288302

289303
if (includeDependencies) {
290304
var packageConfig = (await packageConfigProvider
291305
.findPackageConfig(resourceProvider.getFolder(basePackageDir)))!;
292306
for (var package in packageConfig.packages) {
293-
if (!filterExcludes || !config.exclude.contains(package.name)) {
294-
packageDirs.add(_pathContext.dirname(_pathContext
295-
.fromUri(packageConfig[package.name]!.packageUriRoot)));
307+
if (filterExcludes && config.exclude.contains(package.name)) {
308+
continue;
296309
}
310+
packageDirs.add(_pathContext.dirname(
311+
_pathContext.fromUri(packageConfig[package.name]!.packageUriRoot)));
297312
}
298313
}
299314

@@ -368,7 +383,7 @@ class PubPackageBuilder implements PackageBuilder {
368383
/// Assumes each file might be part of a [DartdocOptionContext], and loads
369384
/// those objects to find any [DartdocOptionContext.includeExternal]
370385
/// configurations therein.
371-
List<String> _includeExternalsFrom(Iterable<String> files) => [
386+
List<String> _includedExternalsFrom(Iterable<String> files) => [
372387
for (var file in files)
373388
...DartdocOptionContext.fromContext(
374389
config,
@@ -380,10 +395,12 @@ class PubPackageBuilder implements PackageBuilder {
380395
Future<Set<String>> _getFiles() async {
381396
var files = config.topLevelPackageMeta.isSdk
382397
? _sdkFilesToDocument
383-
: await _findFilesToDocumentInPackage(config.inputDir,
384-
includeDependencies: config.autoIncludeDependencies)
385-
.toList();
386-
files = [...files, ..._includeExternalsFrom(files)];
398+
: await _findFilesToDocumentInPackage(
399+
config.inputDir,
400+
includeDependencies: config.autoIncludeDependencies,
401+
filterExcludes: true,
402+
).toList();
403+
files = [...files, ..._includedExternalsFrom(files)];
387404
return {
388405
...files.map((s) => resourceProvider.pathContext
389406
.absolute(resourceProvider.getFile(s).path)),
@@ -417,15 +434,25 @@ class PubPackageBuilder implements PackageBuilder {
417434
var files = await _getFiles();
418435
var specialFiles = specialLibraryFiles(findSpecialsSdk);
419436

437+
logDebug('${DateTime.now()}: Discovering Dart libraries...');
420438
var foundLibraries = <LibraryElement>{};
421-
await _parseLibraries(
439+
await _discoverLibraries(
422440
uninitializedPackageGraph.addLibraryToGraph,
423441
foundLibraries,
424442
files,
425-
isLibraryIncluded: (LibraryElement libraryElement) =>
426-
config.include.isEmpty ||
427-
config.include.contains(libraryElement.name),
428443
);
444+
_checkForMissingIncludedFiles(foundLibraries);
445+
await _discoverLibraries(
446+
uninitializedPackageGraph.addSpecialLibraryToGraph,
447+
foundLibraries,
448+
specialFiles.difference(files),
449+
addingSpecials: true,
450+
);
451+
}
452+
453+
/// Throws an exception if any configured-to-be-included files were not found
454+
/// while gathering libraries.
455+
void _checkForMissingIncludedFiles(Set<LibraryElement> foundLibraries) {
429456
if (config.include.isNotEmpty) {
430457
var knownLibraryNames = foundLibraries.map((l) => l.name);
431458
var notFound = config.include
@@ -436,9 +463,6 @@ class PubPackageBuilder implements PackageBuilder {
436463
'known libraries: [${knownLibraryNames.join(', ')}]');
437464
}
438465
}
439-
// Include directive does not apply to special libraries.
440-
await _parseLibraries(uninitializedPackageGraph.addSpecialLibraryToGraph,
441-
foundLibraries, specialFiles.difference(files));
442466
}
443467

444468
p.Context get _pathContext => resourceProvider.pathContext;
@@ -472,3 +496,26 @@ class DartDocResolvedLibrary {
472496
: element = result.element,
473497
units = result.units.map((unit) => unit.unit).toList();
474498
}
499+
500+
extension on Set<String> {
501+
/// Adds [element]'s path and all of its part files' paths to `this`, and
502+
/// recursively adds the paths of all imported and exported libraries.
503+
void addFilesReferencedBy(LibraryElement? element) {
504+
if (element != null) {
505+
var path = element.source.fullName;
506+
if (add(path)) {
507+
for (var import in element.libraryImports) {
508+
addFilesReferencedBy(import.importedLibrary);
509+
}
510+
for (var export in element.libraryExports) {
511+
addFilesReferencedBy(export.exportedLibrary);
512+
}
513+
for (var part in element.parts
514+
.map((e) => e.uri)
515+
.whereType<DirectiveUriWithUnit>()) {
516+
add(part.source.fullName);
517+
}
518+
}
519+
}
520+
}
521+
}

0 commit comments

Comments
 (0)