build method

  1. @override
Future<void> build(
  1. BuildStep buildStep
)

Generates the outputs for a given BuildStep.

Implementation

@override
Future<void> build(BuildStep buildStep) async {
  if (config == null) {
    log.warning('D4rt bridge builder: no configuration found in build.yaml');
    return;
  }

  final packageName = buildStep.inputId.package;
  log.info('Generating D4rt bridges for $packageName...');

  final fileWriter = BuildRunnerFileWriter(buildStep, packageName);
  final projectRoot = p.current; // build_runner runs from package root

  try {
    var totalClasses = 0;
    final outputFiles = <String>[];

    // Determine library path for per-package files
    // Use configured path or default based on project structure
    final libraryPath =
        config!.libraryPath ?? _getDefaultLibraryPath(projectRoot);

    // Create config with resolved libraryPath
    final effectiveConfig = config!.libraryPath != null
        ? config!
        : config!.copyWith(libraryPath: libraryPath);

    // Use orchestrator for per-package deduplication
    final orchestrator = PerPackageBridgeOrchestrator(
      config: effectiveConfig,
      projectRoot: projectRoot,
      fileWriter: fileWriter,
      buildPackageName: packageName,
      onWarning: (msg) => log.info('  $msg'),
    );

    log.info('  Per-package library path: $libraryPath');

    // Phase 0: Scan for user bridge files in build package
    await orchestrator.scanUserBridges();

    // Phase 1: Collect package info from all modules
    await orchestrator.collectPackageInfo();

    // Phase 1.5: Build global class lookup for cross-package inheritance
    await orchestrator.buildGlobalClassLookup();

    // Phase 2: Generate per-package bridge files
    final packageFiles = await orchestrator.generatePerPackageFiles();
    outputFiles.addAll(packageFiles.values);
    log.info('  Generated ${packageFiles.length} per-package bridge files');

    // Phase 3: Generate delegating barrel files
    await orchestrator.generateDelegatingBarrelFiles(packageFiles);
    for (final module in config!.modules) {
      outputFiles.add(module.outputPath);
    }
    log.info(
      '  Generated ${config!.modules.length} delegating barrel files',
    );

    // RC-2 / GEN-079: Generate the relaxer file. The dartscript.b.dart
    // template unconditionally imports `relaxers.b.dart` and calls
    // registerGenericConstructors() / registerRelaxers() whenever the config
    // has modules, so the build_runner path must emit it too (the standalone
    // CLI path already does). generateRelaxers emits a no-op stub when no
    // relaxers are reachable, so the import always resolves and compiles.
    if (config!.modules.isNotEmpty) {
      await generateRelaxers(
        config: config!,
        projectPath: projectRoot,
        globalClassLookup: orchestrator.relaxerClassLookup,
        genericExtractionSites: orchestrator.genericExtractionSites,
        gen075Classes: orchestrator.gen075Classes,
        onWarning: (msg) => log.info('  $msg'),
        writeFile: (relativePath, content) async {
          await fileWriter.writeFile(
            FileId(packageName, ensureBDartExtension(relativePath)),
            content,
          );
          outputFiles.add(relativePath);
        },
      );
    }

    // Count total classes from package files (approximate)
    totalClasses = packageFiles.length * 10; // Approximation for now

    // Generate barrel file if requested
    if (config!.generateBarrel && config!.barrelPath != null) {
      final normalizedBarrelPath = ensureBDartExtension(config!.barrelPath!);
      final barrelContent = generateBarrelFileContent(config!);
      await fileWriter.writeFile(
        FileId(packageName, normalizedBarrelPath),
        barrelContent,
      );
      outputFiles.add(normalizedBarrelPath);
    }

    // Generate dartscript file if requested
    if (config!.generateDartscript && config!.dartscriptPath != null) {
      final normalizedDartscriptPath = ensureBDartExtension(config!.dartscriptPath!);
      final dartscriptContent = generateDartscriptFileContent(
        config!,
        dartscriptPath: normalizedDartscriptPath,
      );
      await fileWriter.writeFile(
        FileId(packageName, normalizedDartscriptPath),
        dartscriptContent,
      );
      outputFiles.add(normalizedDartscriptPath);
    }

    // Generate test runner file if requested
    if (config!.generateTestRunner && config!.testRunnerPath != null) {
      final normalizedTestRunnerPath = ensureBDartExtension(config!.testRunnerPath!);
      final testRunnerContent = generateTestRunnerContent(
        config!,
        testRunnerPath: normalizedTestRunnerPath,
      );
      await fileWriter.writeFile(
        FileId(packageName, normalizedTestRunnerPath),
        testRunnerContent,
      );
      outputFiles.add(normalizedTestRunnerPath);
      log.info('  Generated test runner: $normalizedTestRunnerPath');
    }

    // Write a trigger file to the source tree that can be deleted to force regeneration
    // This is necessary because build_runner doesn't detect changes in external packages
    final triggerPath = '$libraryPath/bridges_trigger.b.dart';
    final triggerContent = '''
// GENERATED FILE - DO NOT EDIT
// This file triggers bridge regeneration when deleted before build_runner.
// Delete this file to force all bridges to be regenerated.
// Generated: ${DateTime.now().toIso8601String()}

/// Marker class for bridge regeneration trigger.
///
/// Delete this file to force bridge regeneration when source packages
/// (like tom_vscode_scripting_api) have changed but build_runner doesn't
/// detect the changes because they're external dependencies.
class BridgesTrigger {
BridgesTrigger._();

/// Last bridge build timestamp.
static const String lastBuildTime = '${DateTime.now().toIso8601String()}';

/// Number of modules generated.
static const int moduleCount = ${config!.modules.length};
}
''';
    await fileWriter.writeFile(
      FileId(packageName, triggerPath),
      triggerContent,
    );
    outputFiles.add(triggerPath);

    // Write a marker file to track that generation completed
    final markerContent = '''
// D4rt bridge generation marker
// Generated: ${DateTime.now().toIso8601String()}
// Classes: $totalClasses
// Modules: ${config!.modules.length}
// Outputs: ${outputFiles.join(', ')}
''';
    await buildStep.writeAsString(
      AssetId(packageName, 'lib/d4rt_bridges.g.info'),
      markerContent,
    );

    log.info(
        '✓ Generated $totalClasses classes across ${config!.modules.length} modules');
  } catch (e, stackTrace) {
    log.severe('Error generating D4rt bridges', e, stackTrace);
    rethrow;
  }
}