syncBridgeFiles function
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();
}
}
}
}
}