syncBridgeFiles function

void syncBridgeFiles(
  1. String workingDirectory, {
  2. String platform = 'ios',
})

Synchronizes generated bridge files from lib/src/generated to native project roots. platform is the platform directory name ('ios' or 'macos'). Defaults to 'ios'. This is needed for Apple development as CocoaPods/SPM normally uses copies instead of symlinks.

Implementation

void syncBridgeFiles(String workingDirectory, {String platform = 'ios'}) {
  final generatedDir = Directory(p.join(workingDirectory, 'lib', 'src', 'generated'));
  if (!generatedDir.existsSync()) return;

  // Discover Apple C++ modules (ios or macos using NativeImpl.cpp).
  // Their Swift bridge stubs must NOT be compiled alongside the direct C++ bridge
  // (duplicate-symbol guard). Any stale copies in Classes/ must be deleted.
  final appleCppModules = _discoverAppleCppModules(workingDirectory);
  final pluginName = _readPluginName(workingDirectory);
  final className = toPascalCase(pluginName);

  // Support both legacy (CocoaPods) and modern (SPM) source layouts.
  final classesDir = Directory(p.join(workingDirectory, platform, 'Classes'));
  final sourcesDir = Directory(p.join(workingDirectory, platform, 'Sources', className));

  if (!classesDir.existsSync() && !sourcesDir.existsSync()) return;

  final targetDirs = [
    if (classesDir.existsSync()) classesDir,
    if (sourcesDir.existsSync()) sourcesDir,
  ];

  // Sync Swift bridges.
  // Non-cpp modules: copy to all existing target dirs for reliability.
  // Cpp modules: delete any stale copy — the direct C++ bridge makes them unnecessary.
  final swiftSource = Directory(p.join(generatedDir.path, 'swift'));
  if (swiftSource.existsSync()) {
    for (final file in swiftSource.listSync().whereType<File>()) {
      final name = p.basename(file.path);
      if (!name.endsWith('.bridge.g.swift')) continue;
      final libName = name.replaceFirst('.bridge.g.swift', '');
      if (appleCppModules.containsKey(libName)) {
        // Delete stale Swift bridge copies for C++ modules.
        for (final targetDir in targetDirs) {
          final stale = File(p.join(targetDir.path, name));
          if (stale.existsSync()) stale.deleteSync();
        }
        continue;
      }
      for (final targetDir in targetDirs) {
        file.copySync(p.join(targetDir.path, name));
      }
    }
  }

  // The following syncs target only the CocoaPods Classes/ layout.
  if (!classesDir.existsSync()) return;

  // Sync C++/Obj-C++ bridges (.bridge.g.cpp → .bridge.g.mm for Obj-C++ support).
  final cppSource = Directory(p.join(generatedDir.path, 'cpp'));
  if (cppSource.existsSync()) {
    for (final file in cppSource.listSync().whereType<File>()) {
      final name = p.basename(file.path);
      if (name.endsWith('.bridge.g.h') || name.endsWith('.bridge.g.cpp')) {
        final targetName = name.endsWith('.bridge.g.cpp')
            ? name.replaceFirst('.bridge.g.cpp', '.bridge.g.mm')
            : name;
        file.copySync(p.join(classesDir.path, targetName));
      }
    }
  }

  // Sync HybridXxx.cpp impl stubs for Apple C++ modules.
  //
  // CocoaPods compiles everything in Classes/** — the impl stubs must be
  // there so the C++ HybridObject methods are compiled into the pod target.
  // Stale Hybrid*.cpp files for non-Apple-cpp modules are removed to prevent
  // "abstract class" compile errors.
  final srcDir = Directory(p.join(workingDirectory, 'src'));
  if (srcDir.existsSync()) {
    final expectedImplFiles = <String>{};
    for (final entry in appleCppModules.entries) {
      final libClassName = toPascalCase(entry.key);
      expectedImplFiles.add('Hybrid$libClassName.cpp');
      final moduleClassName = toPascalCase(entry.value);
      expectedImplFiles.add('Hybrid$moduleClassName.cpp');
    }

    for (final name in expectedImplFiles) {
      final srcFile = File(p.join(srcDir.path, name));
      if (srcFile.existsSync()) {
        srcFile.copySync(p.join(classesDir.path, name));
      }
    }

    for (final file in classesDir.listSync().whereType<File>()) {
      final name = p.basename(file.path);
      if (name.startsWith('Hybrid') && name.endsWith('.cpp')) {
        if (!expectedImplFiles.contains(name)) {
          file.deleteSync();
        }
      }
    }
  }
}