Allow dartdoc to customize package builder and package meta creation (#2209)

diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart
index fa2d136..917ee03 100644
--- a/bin/dartdoc.dart
+++ b/bin/dartdoc.dart
@@ -5,134 +5,27 @@
 library dartdoc.bin;
 
 import 'dart:async';
-import 'dart:io';
 
-import 'package:args/args.dart';
 import 'package:dartdoc/dartdoc.dart';
-import 'package:dartdoc/src/logging.dart';
-import 'package:dartdoc/src/tool_runner.dart';
-import 'package:stack_trace/stack_trace.dart';
-
-class DartdocProgramOptionContext extends DartdocGeneratorOptionContext
-    with LoggingContext {
-  DartdocProgramOptionContext(DartdocOptionSet optionSet, Directory dir)
-      : super(optionSet, dir);
-
-  bool get asyncStackTraces => optionSet['asyncStackTraces'].valueAt(context);
-  bool get generateDocs => optionSet['generateDocs'].valueAt(context);
-  bool get help => optionSet['help'].valueAt(context);
-  bool get version => optionSet['version'].valueAt(context);
-}
-
-Future<List<DartdocOption>> createDartdocProgramOptions() async {
-  return <DartdocOption>[
-    DartdocOptionArgOnly<bool>('asyncStackTraces', false,
-        help: 'Display coordinated asynchronous stack traces (slow)',
-        negatable: true),
-    DartdocOptionArgOnly<bool>('generateDocs', true,
-        help:
-            'Generate docs into the output directory (or only display warnings if false).',
-        negatable: true),
-    DartdocOptionArgOnly<bool>('help', false,
-        abbr: 'h', help: 'Show command help.', negatable: false),
-    DartdocOptionArgOnly<bool>('version', false,
-        help: 'Display the version for $programName.', negatable: false),
-  ];
-}
+import 'package:dartdoc/options.dart';
 
 /// Analyzes Dart files and generates a representation of included libraries,
 /// classes, and members. Uses the current directory to look for libraries.
 Future<void> main(List<String> arguments) async {
-  var optionSet = await DartdocOptionSet.fromOptionGenerators('dartdoc', [
-    createDartdocOptions,
-    createDartdocProgramOptions,
-    createLoggingOptions,
-    createGeneratorOptions,
-  ]);
-
-  try {
-    optionSet.parseArguments(arguments);
-  } on FormatException catch (e) {
-    stderr.writeln(' fatal error: ${e.message}');
-    stderr.writeln('');
-    _printUsage(optionSet.argParser);
-    // Do not use exit() as this bypasses --pause-isolates-on-exit
-    // TODO(jcollins-g): use exit once dart-lang/sdk#31747 is fixed.
-    exitCode = 64;
+  var config = await parseOptions(arguments);
+  if (config == null) {
+    // There was an error while parsing options.
     return;
   }
-  if (optionSet['help'].valueAt(Directory.current)) {
-    _printHelp(optionSet.argParser);
-    exitCode = 0;
-    return;
-  }
-  if (optionSet['version'].valueAt(Directory.current)) {
-    _printVersion(optionSet.argParser);
-    exitCode = 0;
-    return;
-  }
-
-  DartdocProgramOptionContext config;
-  try {
-    config = DartdocProgramOptionContext(optionSet, null);
-  } on DartdocOptionError catch (e) {
-    stderr.writeln(' fatal error: ${e.message}');
-    stderr.writeln('');
-    await stderr.flush();
-    _printUsage(optionSet.argParser);
-    exitCode = 64;
-    return;
-  }
-  startLogging(config);
-
-  var dartdoc = config.generateDocs
-      ? await Dartdoc.fromContext(config)
-      : await Dartdoc.withEmptyGenerator(config);
-  dartdoc.onCheckProgress.listen(logProgress);
-  try {
-    await Chain.capture(() async {
-      await runZoned(dartdoc.generateDocs,
-          zoneSpecification: ZoneSpecification(
-              print: (Zone self, ZoneDelegate parent, Zone zone, String line) =>
-                  logPrint(line)));
-    }, onError: (e, Chain chain) {
-      if (e is DartdocFailure) {
-        stderr.writeln('\ndartdoc failed: ${e}.');
-        if (config.verboseWarnings) {
-          stderr.writeln(chain.terse);
-        }
-        exitCode = 1;
-        return;
-      } else {
-        stderr.writeln('\ndartdoc failed: ${e}\n${chain.terse}');
-        exitCode = 255;
-        return;
-      }
-    }, when: config.asyncStackTraces);
-  } finally {
-    // Clear out any cached tool snapshots and temporary directories.
-    // ignore: unawaited_futures
-    SnapshotCache.instance.dispose();
-    // ignore: unawaited_futures
-    ToolTempFileTracker.instance.dispose();
-  }
-  exitCode = 0;
-  return;
-}
-
-/// Print help if we are passed the help option.
-void _printHelp(ArgParser parser) {
-  print('Generate HTML documentation for Dart libraries.\n');
-  print(parser.usage);
-}
-
-/// Print usage information on invalid command lines.
-void _printUsage(ArgParser parser) {
-  print('Usage: dartdoc [OPTIONS]\n');
-  print(parser.usage);
-}
-
-/// Print version information.
-void _printVersion(ArgParser parser) {
-  print('dartdoc version: ${dartdocVersion}');
+  // Set the default way to construct [PackageMeta].
+  PackageMeta.setPackageMetaFactories(
+    PubPackageMeta.fromElement,
+    PubPackageMeta.fromFilename,
+    PubPackageMeta.fromDir,
+  );
+  final packageBuilder = PubPackageBuilder(config);
+  final dartdoc = config.generateDocs
+      ? await Dartdoc.fromContext(config, packageBuilder)
+      : await Dartdoc.withEmptyGenerator(config, packageBuilder);
+  dartdoc.executeGuarded();
 }
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 4b7345b..c1cac90 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -20,6 +20,7 @@
 import 'package:dartdoc/src/logging.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart';
+import 'package:dartdoc/src/tool_runner.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/utils.dart';
 import 'package:dartdoc/src/version.dart';
@@ -94,8 +95,10 @@
 
 /// Generates Dart documentation for all public Dart libraries in the given
 /// directory.
-class Dartdoc extends PackageBuilder {
+class Dartdoc {
   final Generator generator;
+  final PackageBuilder packageBuilder;
+  final DartdocOptionContext config;
   final Set<String> writtenFiles = {};
   Directory outputDir;
 
@@ -103,7 +106,7 @@
   final StreamController<String> _onCheckProgress =
       StreamController(sync: true);
 
-  Dartdoc._(DartdocOptionContext config, this.generator) : super(config) {
+  Dartdoc._(this.config, this.generator, this.packageBuilder) {
     outputDir = Directory(config.output)..createSync(recursive: true);
   }
 
@@ -111,19 +114,34 @@
   /// and returns a Dartdoc object with them.
   @Deprecated('Prefer fromContext() instead')
   static Future<Dartdoc> withDefaultGenerators(
-      DartdocGeneratorOptionContext config) async {
-    return Dartdoc._(config, await initHtmlGenerator(config));
+    DartdocGeneratorOptionContext config,
+    PackageBuilder packageBuilder,
+  ) async {
+    return Dartdoc._(
+      config,
+      await initHtmlGenerator(config),
+      packageBuilder,
+    );
   }
 
   /// Asynchronous factory method that builds Dartdoc with an empty generator.
-  static Future<Dartdoc> withEmptyGenerator(DartdocOptionContext config) async {
-    return Dartdoc._(config, await initEmptyGenerator(config));
+  static Future<Dartdoc> withEmptyGenerator(
+    DartdocOptionContext config,
+    PackageBuilder packageBuilder,
+  ) async {
+    return Dartdoc._(
+      config,
+      await initEmptyGenerator(config),
+      packageBuilder,
+    );
   }
 
   /// Asynchronous factory method that builds Dartdoc with a generator
   /// determined by the given context.
   static Future<Dartdoc> fromContext(
-      DartdocGeneratorOptionContext context) async {
+    DartdocGeneratorOptionContext context,
+    PackageBuilder packageBuilder,
+  ) async {
     Generator generator;
     switch (context.format) {
       case 'html':
@@ -135,7 +153,11 @@
       default:
         throw DartdocFailure('Unsupported output format: ${context.format}');
     }
-    return Dartdoc._(context, generator);
+    return Dartdoc._(
+      context,
+      generator,
+      packageBuilder,
+    );
   }
 
   Stream<String> get onCheckProgress => _onCheckProgress.stream;
@@ -150,7 +172,7 @@
   Future<DartdocResults> generateDocsBase() async {
     var _stopwatch = Stopwatch()..start();
     double seconds;
-    packageGraph = await buildPackageGraph();
+    packageGraph = await packageBuilder.buildPackageGraph();
     seconds = _stopwatch.elapsedMilliseconds / 1000.0;
     var libs = packageGraph.libraries.length;
     logInfo("Initialized dartdoc with ${libs} librar${libs == 1 ? 'y' : 'ies'} "
@@ -188,22 +210,31 @@
   }
 
   Future<DartdocResults> generateDocs() async {
-    logInfo('Documenting ${config.topLevelPackageMeta}...');
+    try {
+      logInfo('Documenting ${config.topLevelPackageMeta}...');
 
-    var dartdocResults = await generateDocsBase();
-    if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) {
-      throw DartdocFailure('dartdoc could not find any libraries to document');
-    }
+      var dartdocResults = await generateDocsBase();
+      if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) {
+        throw DartdocFailure(
+            'dartdoc could not find any libraries to document');
+      }
 
-    final errorCount =
-        dartdocResults.packageGraph.packageWarningCounter.errorCount;
-    if (errorCount > 0) {
-      throw DartdocFailure(
-          'dartdoc encountered $errorCount errors while processing.');
+      final errorCount =
+          dartdocResults.packageGraph.packageWarningCounter.errorCount;
+      if (errorCount > 0) {
+        throw DartdocFailure(
+            'dartdoc encountered $errorCount errors while processing.');
+      }
+      logInfo(
+          'Success! Docs generated into ${dartdocResults.outDir.absolute.path}');
+      return dartdocResults;
+    } finally {
+      // Clear out any cached tool snapshots and temporary directories.
+      // ignore: unawaited_futures
+      SnapshotCache.instance.dispose();
+      // ignore: unawaited_futures
+      ToolTempFileTracker.instance.dispose();
     }
-    logInfo(
-        'Success! Docs generated into ${dartdocResults.outDir.absolute.path}');
-    return dartdocResults;
   }
 
   /// Warn on file paths.
@@ -429,6 +460,37 @@
     _doOrphanCheck(packageGraph, origin, visited);
     _doSearchIndexCheck(packageGraph, origin, visited);
   }
+
+  /// Runs [generateDocs] function and properly handles the errors.
+  void executeGuarded() {
+    onCheckProgress.listen(logProgress);
+    // This function should *never* await `runZonedGuarded` because the errors
+    // thrown in generateDocs are uncaught. We want this because uncaught errors
+    // cause IDE debugger to automatically stop at the exception.
+    //
+    // If you await the zone, the code that comes after the await is not
+    // executed if the zone dies due to uncaught error. To avoid this confusion,
+    // never await `runZonedGuarded` and never change the return value of
+    // [executeGuarded].
+    runZonedGuarded(
+      generateDocs,
+      (e, chain) {
+        if (e is DartdocFailure) {
+          stderr.writeln('\ndartdoc failed: ${e}.');
+          if (config.verboseWarnings) {
+            stderr.writeln(chain);
+          }
+          exitCode = 1;
+        } else {
+          stderr.writeln('\ndartdoc failed: ${e}\n${chain}');
+          exitCode = 255;
+        }
+      },
+      zoneSpecification: ZoneSpecification(
+        print: (_, __, ___, String line) => logPrint(line),
+      ),
+    );
+  }
 }
 
 /// This class is returned if dartdoc fails in an expected way (for instance, if
diff --git a/lib/options.dart b/lib/options.dart
new file mode 100644
index 0000000..9383a6e
--- /dev/null
+++ b/lib/options.dart
@@ -0,0 +1,90 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:dartdoc/dartdoc.dart';
+import 'package:dartdoc/src/logging.dart';
+
+class DartdocProgramOptionContext extends DartdocGeneratorOptionContext
+    with LoggingContext {
+  DartdocProgramOptionContext(DartdocOptionSet optionSet, Directory dir)
+      : super(optionSet, dir);
+
+  bool get generateDocs => optionSet['generateDocs'].valueAt(context);
+  bool get help => optionSet['help'].valueAt(context);
+  bool get version => optionSet['version'].valueAt(context);
+}
+
+Future<List<DartdocOption>> createDartdocProgramOptions() async {
+  return <DartdocOption>[
+    DartdocOptionArgOnly<bool>('generateDocs', true,
+        help:
+            'Generate docs into the output directory (or only display warnings if false).',
+        negatable: true),
+    DartdocOptionArgOnly<bool>('help', false,
+        abbr: 'h', help: 'Show command help.', negatable: false),
+    DartdocOptionArgOnly<bool>('version', false,
+        help: 'Display the version for $programName.', negatable: false),
+  ];
+}
+
+Future<DartdocProgramOptionContext> parseOptions(List<String> arguments) async {
+  var optionSet = await DartdocOptionSet.fromOptionGenerators('dartdoc', [
+    createDartdocOptions,
+    createDartdocProgramOptions,
+    createLoggingOptions,
+    createGeneratorOptions,
+  ]);
+
+  try {
+    optionSet.parseArguments(arguments);
+  } on FormatException catch (e) {
+    stderr.writeln(' fatal error: ${e.message}');
+    stderr.writeln('');
+    _printUsage(optionSet.argParser);
+    // Do not use exit() as this bypasses --pause-isolates-on-exit
+    exitCode = 64;
+    return null;
+  }
+  if (optionSet['help'].valueAt(Directory.current)) {
+    _printHelp(optionSet.argParser);
+    exitCode = 0;
+    return null;
+  }
+  if (optionSet['version'].valueAt(Directory.current)) {
+    _printVersion(optionSet.argParser);
+    exitCode = 0;
+    return null;
+  }
+
+  DartdocProgramOptionContext config;
+  try {
+    config = DartdocProgramOptionContext(optionSet, null);
+  } on DartdocOptionError catch (e) {
+    stderr.writeln(' fatal error: ${e.message}');
+    stderr.writeln('');
+    await stderr.flush();
+    _printUsage(optionSet.argParser);
+    exitCode = 64;
+    return null;
+  }
+  startLogging(config);
+  return config;
+}
+
+/// Print help if we are passed the help option.
+void _printHelp(ArgParser parser) {
+  print('Generate HTML documentation for Dart libraries.\n');
+  print(parser.usage);
+}
+
+/// Print usage information on invalid command lines.
+void _printUsage(ArgParser parser) {
+  print('Usage: dartdoc [OPTIONS]\n');
+  print(parser.usage);
+}
+
+/// Print version information.
+void _printVersion(ArgParser parser) {
+  print('dartdoc version: ${dartdocVersion}');
+}
diff --git a/lib/src/model/package_builder.dart b/lib/src/model/package_builder.dart
index ee0a67b..12466ea 100644
--- a/lib/src/model/package_builder.dart
+++ b/lib/src/model/package_builder.dart
@@ -34,11 +34,18 @@
 import 'package:quiver/iterables.dart' as quiver;
 
 /// Everything you need to instantiate a PackageGraph object for documenting.
-class PackageBuilder {
+abstract class PackageBuilder {
+  // Builds package graph to be used by documentation generator.
+  Future<PackageGraph> buildPackageGraph();
+}
+
+/// A package builder that understands pub package format.
+class PubPackageBuilder implements PackageBuilder {
   final DartdocOptionContext config;
 
-  PackageBuilder(this.config);
+  PubPackageBuilder(this.config);
 
+  @override
   Future<PackageGraph> buildPackageGraph() async {
     if (!config.sdkDocs) {
       if (config.topLevelPackageMeta.needsPubGet &&
diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart
index b51d4b7..d751d8a 100644
--- a/lib/src/package_meta.dart
+++ b/lib/src/package_meta.dart
@@ -19,7 +19,7 @@
 
 Directory get defaultSdkDir {
   var sdkDir = File(Platform.resolvedExecutable).parent.parent;
-  assert(path.equals(sdkDir.path, PackageMeta.sdkDirParent(sdkDir).path));
+  assert(path.equals(sdkDir.path, PubPackageMeta.sdkDirParent(sdkDir).path));
   return sdkDir;
 }
 
@@ -35,11 +35,121 @@
   ['lib/core/core.dart'],
 ];
 
+/// Describes a single package in the context of `dartdoc`.
+///
+/// The primary function of this class is to allow canonicalization of packages
+/// by returning the same [PackageMeta] for a given filename, library or path
+/// if they belong to the same package.
+///
+/// Overriding this is typically done by overriding factories as rest of
+/// `dartdoc` creates this object by calling these static factories.
 abstract class PackageMeta {
   final Directory dir;
 
   PackageMeta(this.dir);
 
+  @override
+  bool operator ==(other) {
+    if (other is! PackageMeta) return false;
+    return path.equals(dir.absolute.path, other.dir.absolute.path);
+  }
+
+  @override
+  int get hashCode => path.hash(dir.absolute.path);
+
+  /// Returns true if this represents a 'Dart' SDK.  A package can be part of
+  /// Dart and Flutter at the same time, but if we are part of a Dart SDK
+  /// sdkType should never return null.
+  bool get isSdk;
+
+  /// Returns 'Dart' or 'Flutter' (preferentially, 'Flutter' when the answer is
+  /// "both"), or null if this package is not part of a SDK.
+  String sdkType(String flutterRootPath);
+
+  bool get needsPubGet => false;
+
+  bool get requiresFlutter;
+
+  void runPubGet(String flutterRoot);
+
+  String get name;
+
+  /// null if not a hosted pub package.  If set, the hostname
+  /// that the package is hosted at -- usually 'pub.dartlang.org'.
+  String get hostedAt;
+
+  String get version;
+
+  String get description;
+
+  String get homepage;
+
+  FileContents getReadmeContents();
+
+  FileContents getLicenseContents();
+
+  FileContents getChangelogContents();
+
+  /// Returns true if we are a valid package, valid enough to generate docs.
+  bool get isValid => getInvalidReasons().isEmpty;
+
+  /// Returns resolved directory.
+  String get resolvedDir;
+
+  /// Returns a list of reasons this package is invalid, or an
+  /// empty list if no reasons found.
+  ///
+  /// If the list is empty, this package is valid.
+  List<String> getInvalidReasons();
+
+  @override
+  String toString() => name;
+
+  /// Sets the supported ways of constructing [PackageMeta] objects.
+  ///
+  /// These objects can be constructed from a filename, a directory
+  /// or a [LibraryElement]. We allow different dartdoc implementations to
+  /// provide their own [PackageMeta] types.
+  ///
+  /// By calling this function, these implementations can control how
+  /// [PackageMeta] is built.
+  static void setPackageMetaFactories(
+    PackageMeta Function(LibraryElement, String) fromElementFactory,
+    PackageMeta Function(String) fromFilenameFactory,
+    PackageMeta Function(Directory) fromDirFactory,
+  ) {
+    assert(fromElementFactory != null);
+    assert(fromFilenameFactory != null);
+    assert(fromDirFactory != null);
+    if (_fromElement == fromElementFactory &&
+        _fromFilename == fromFilenameFactory &&
+        _fromDir == fromDirFactory) {
+      // Nothing to do.
+      return;
+    }
+    if (_fromElement != null || _fromFilename != null || _fromDir != null) {
+      throw StateError('PackageMeta factories cannot be changed once defined.');
+    }
+    _fromElement = fromElementFactory;
+    _fromFilename = fromFilenameFactory;
+    _fromDir = fromDirFactory;
+  }
+
+  static PackageMeta Function(LibraryElement, String) _fromElement;
+  static PackageMeta Function(String) _fromFilename;
+  static PackageMeta Function(Directory) _fromDir;
+  static PackageMeta Function(LibraryElement, String) get fromElement =>
+      _fromElement ?? PubPackageMeta.fromElement;
+  static PackageMeta Function(String) get fromFilename =>
+      _fromFilename ?? PubPackageMeta.fromFilename;
+  static PackageMeta Function(Directory) get fromDir =>
+      _fromDir ?? PubPackageMeta.fromDir;
+}
+
+/// Default implementation of [PackageMeta] depends on pub packages.
+abstract class PubPackageMeta extends PackageMeta {
+  PubPackageMeta(Directory dir) : super(dir);
+
   static List<List<String>> __sdkDirFilePaths;
 
   static List<List<String>> get _sdkDirFilePaths {
@@ -83,34 +193,25 @@
     return _sdkDirParent[dirPathCanonical];
   }
 
-  @override
-  bool operator ==(other) {
-    if (other is! PackageMeta) return false;
-    return path.equals(dir.absolute.path, other.dir.absolute.path);
-  }
-
-  @override
-  int get hashCode => path.hash(dir.absolute.path);
-
   /// Use this instead of fromDir where possible.
-  factory PackageMeta.fromElement(
+  static PubPackageMeta fromElement(
       LibraryElement libraryElement, String sdkDir) {
     if (libraryElement.isInSdk) {
-      return PackageMeta.fromDir(Directory(sdkDir));
+      return PubPackageMeta.fromDir(Directory(sdkDir));
     }
-    return PackageMeta.fromDir(
+    return PubPackageMeta.fromDir(
         File(path.canonicalize(libraryElement.source.fullName)).parent);
   }
 
-  factory PackageMeta.fromFilename(String filename) {
-    return PackageMeta.fromDir(File(filename).parent);
+  static PubPackageMeta fromFilename(String filename) {
+    return PubPackageMeta.fromDir(File(filename).parent);
   }
 
   /// This factory is guaranteed to return the same object for any given
   /// [dir.absolute.path].  Multiple [dir.absolute.path]s will resolve to the
   /// same object if they are part of the same package.  Returns null
   /// if the directory is not part of a known package.
-  factory PackageMeta.fromDir(Directory dir) {
+  static PubPackageMeta fromDir(Directory dir) {
     var original = dir.absolute;
     dir = original;
     if (!original.existsSync()) {
@@ -142,13 +243,7 @@
     return _packageMetaCache[dir.absolute.path];
   }
 
-  /// Returns true if this represents a 'Dart' SDK.  A package can be part of
-  /// Dart and Flutter at the same time, but if we are part of a Dart SDK
-  /// sdkType should never return null.
-  bool get isSdk;
-
-  /// Returns 'Dart' or 'Flutter' (preferentially, 'Flutter' when the answer is
-  /// "both"), or null if this package is not part of a SDK.
+  @override
   String sdkType(String flutterRootPath) {
     if (flutterRootPath != null) {
       var flutterPackages = path.join(flutterRootPath, 'packages');
@@ -166,48 +261,13 @@
     return isSdk ? 'Dart' : null;
   }
 
-  bool get needsPubGet => false;
-
-  bool get requiresFlutter;
-
-  void runPubGet(String flutterRoot);
-
-  String get name;
-
-  /// null if not a hosted pub package.  If set, the hostname
-  /// that the package is hosted at -- usually 'pub.dartlang.org'.
-  String get hostedAt;
-
-  String get version;
-
-  String get description;
-
-  String get homepage;
-
   String _resolvedDir;
 
+  @override
   String get resolvedDir {
     _resolvedDir ??= dir.resolveSymbolicLinksSync();
     return _resolvedDir;
   }
-
-  FileContents getReadmeContents();
-
-  FileContents getLicenseContents();
-
-  FileContents getChangelogContents();
-
-  /// Returns true if we are a valid package, valid enough to generate docs.
-  bool get isValid => getInvalidReasons().isEmpty;
-
-  /// Returns a list of reasons this package is invalid, or an
-  /// empty list if no reasons found.
-  ///
-  /// If the list is empty, this package is valid.
-  List<String> getInvalidReasons();
-
-  @override
-  String toString() => name;
 }
 
 class FileContents {
@@ -225,7 +285,7 @@
   String toString() => file.path;
 }
 
-class _FilePackageMeta extends PackageMeta {
+class _FilePackageMeta extends PubPackageMeta {
   FileContents _readme;
   FileContents _license;
   FileContents _changelog;
@@ -374,7 +434,7 @@
   return null;
 }
 
-class _SdkMeta extends PackageMeta {
+class _SdkMeta extends PubPackageMeta {
   String sdkReadmePath;
 
   _SdkMeta(Directory dir) : super(dir) {
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index d1d7ee9..4c4c0f3 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -45,8 +45,12 @@
 
     Future<Dartdoc> buildDartdoc(
         List<String> argv, Directory packageRoot, Directory tempDir) async {
-      return await Dartdoc.fromContext(await generatorContextFromArgv(argv
-        ..addAll(['--input', packageRoot.path, '--output', tempDir.path])));
+      var context = await generatorContextFromArgv(argv
+        ..addAll(['--input', packageRoot.path, '--output', tempDir.path]));
+      return await Dartdoc.fromContext(
+        context,
+        PubPackageBuilder(context),
+      );
     }
 
     group('Option handling', () {
diff --git a/test/src/utils.dart b/test/src/utils.dart
index da3d2b1..66cb3a3 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -114,7 +114,7 @@
 }
 
 Future<PackageGraph> bootSdkPackage() async {
-  return PackageBuilder(await contextFromArgv(['--input', sdkDir.path]))
+  return PubPackageBuilder(await contextFromArgv(['--input', sdkDir.path]))
       .buildPackageGraph();
 }
 
@@ -123,7 +123,7 @@
     {List<String> additionalArguments}) async {
   var dir = Directory(dirPath);
   additionalArguments ??= <String>[];
-  return PackageBuilder(await contextFromArgv([
+  return PubPackageBuilder(await contextFromArgv([
             '--input',
             dir.path,
             '--sdk-dir',