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 {
  final libraryElement = await buildStep.inputLibrary;
  final library = LibraryReader(libraryElement);

  try {
    // extractAny handles both @NitroModule files and type-only files.
    // Returns null when the file has no Nitrogen-relevant content.
    final spec = SpecExtractor.extractAny(library);
    if (spec == null) return;

    // ── Validate before generating (module files only) ─────────────────
    if (!spec.isTypeOnly) {
      final issues = SpecValidator.validate(spec);
      for (final issue in issues) {
        if (issue.isError) {
          log.severe('nitrogen: ${buildStep.inputId.path}\n  $issue');
        } else {
          log.warning('nitrogen: ${buildStep.inputId.path}\n  $issue');
        }
      }
      if (issues.any((i) => i.isError)) {
        log.severe(
          'nitrogen: Aborting generation for ${buildStep.inputId.path} due to validation errors above.',
        );
        return;
      }
    }

    // We can use buildStep.allowedOutputs to find the EXACT output paths
    // generated by build_runner based on our map.
    final outputs = buildStep.allowedOutputs;

    for (final outId in outputs) {
      // Type-only files skip implementation outputs (no bridge class, no CMake, etc.).
      if (spec.isTypeOnly) {
        if (outId.path.endsWith('.bridge.g.cpp') || outId.path.endsWith('.CMakeLists.g.txt') || outId.path.endsWith('.native.g.h') || outId.path.endsWith('.mock.g.h') || outId.path.endsWith('.test.g.cpp')) {
          // Write a minimal placeholder so build_runner is satisfied.
          await buildStep.writeAsString(outId, '// Type-only file — no bridge implementation generated.\n');
          continue;
        }
      }

      if (outId.path.endsWith('.g.dart')) {
        final rawCode = DartFfiGenerator.generate(spec);
        String formattedCode;
        try {
          formattedCode = _formatter.format(rawCode);
        } catch (e) {
          log.warning('nitrogen: Could not format ${outId.path}:\n$e');
          formattedCode = rawCode;
        }
        await buildStep.writeAsString(outId, formattedCode);
      } else if (outId.path.endsWith('.bridge.g.kt')) {
        await buildStep.writeAsString(outId, KotlinGenerator.generate(spec));
      } else if (outId.path.endsWith('.bridge.g.swift')) {
        await buildStep.writeAsString(outId, SwiftGenerator.generate(spec));
      } else if (outId.path.endsWith('.bridge.g.h')) {
        await buildStep.writeAsString(outId, CppHeaderGenerator.generate(spec));
      } else if (outId.path.endsWith('.bridge.g.cpp')) {
        await buildStep.writeAsString(outId, CppBridgeGenerator.generate(spec));
      } else if (outId.path.endsWith('.CMakeLists.g.txt')) {
        await buildStep.writeAsString(outId, CMakeGenerator.generate(spec));
        // ── NativeImpl.cpp outputs ─────────────────────────────────────
      } else if (outId.path.endsWith('.native.g.h')) {
        await buildStep.writeAsString(outId, CppInterfaceGenerator.generate(spec));
      } else if (outId.path.endsWith('.mock.g.h')) {
        await buildStep.writeAsString(outId, CppMockGenerator.generateMockHeader(spec));
      } else if (outId.path.endsWith('.test.g.cpp')) {
        await buildStep.writeAsString(outId, CppMockGenerator.generateTestStarter(spec));
      }
    }
  } catch (e, st) {
    log.severe('nitrogen: Could not process ${buildStep.inputId}:\n$e\n$st');
  }
}