checkBridgeFreshness function

Future<BridgeFreshness> checkBridgeFreshness(
  1. String projectPath
)

Regenerate projectPath's bridges into a scratch tree and compare them with what the package has committed.

The scratch tree lives under the package's own .dart_tool/, which is gitignored and per-package, and is removed afterwards. A unique subdirectory per call keeps two concurrent checks of one package apart.

Implementation

Future<BridgeFreshness> checkBridgeFreshness(String projectPath) async {
  final packageRoot = p.normalize(p.absolute(projectPath));
  final config = BuildConfigLoader.loadFromTomBuildYaml(packageRoot);
  if (config == null) {
    throw ArgumentError('No d4rtgen configuration found in $packageRoot');
  }

  // `generateBridges` runs `dart pub get` for a package without a package
  // config. That is a subprocess, which no `dart:io` overlay can redirect, and
  // it would rewrite `pubspec.lock` in place. An unresolved package is refused.
  final packageConfig = File(
    p.join(packageRoot, '.dart_tool', 'package_config.json'),
  );
  if (!packageConfig.existsSync()) {
    return BridgeFreshness(
      checked: const [],
      stale: const [],
      errors: [
        '$packageRoot is not resolved (no .dart_tool/package_config.json). '
            'Run `dart pub get` first; the check does not run it, because '
            'that would write to the package.',
      ],
    );
  }

  final preview = await previewGeneration(
    packageRoot: packageRoot,
    purpose: 'freshness',
    generate: () => generateBridges(config: config, projectPath: packageRoot),
  );
  final writes = {for (final w in preview.writes) w.path: w.change};

  final checked = <String>[];
  final stale = <StaleBridge>[];
  for (final reportedPath in preview.result.outputFiles) {
    final relative = p.relative(
      p.normalize(p.absolute(reportedPath)),
      from: packageRoot,
    );
    checked.add(relative);
    // The generator reports destinations in the package; the overlay sent the
    // writes to the scratch tree. A reported file the preview did not see was
    // written some other way — possibly in place — so nothing can be
    // concluded, and the package itself should be inspected.
    final change = writes[relative];
    if (change == null) {
      return BridgeFreshness(
        checked: checked,
        stale: stale,
        errors: [
          'The generator reported $relative, but it is not in the scratch '
              'tree. The write was not redirected, so the package itself may '
              'have been modified — inspect it before trusting any result.',
        ],
      );
    }
    switch (change) {
      case PreviewedChange.created:
        stale.add(StaleBridge(relative, StaleReason.notCommitted));
      case PreviewedChange.changed:
        stale.add(StaleBridge(relative, StaleReason.differs));
      case PreviewedChange.unchanged:
        break;
    }
  }
  return BridgeFreshness(
    checked: checked,
    stale: stale,
    errors: preview.result.errors,
  );
}