build method
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;
}
}