linkPodspec function
void
linkPodspec(})
Implementation
void linkPodspec(
String pluginName,
List<String> moduleLibs, {
String baseDir = '.',
List<ModuleInfo>? moduleInfos,
}) {
final nitroNativePath = resolveNitroNativePath(baseDir);
final podspecFile = File(p.join(baseDir, 'ios', '$pluginName.podspec'));
if (!podspecFile.existsSync()) return;
var content = podspecFile.readAsStringSync();
bool modified = false;
// Normalize source_files to 'Classes/**/*'.
// Flutter's SPM-first template generates paths like '<plugin>/Sources/<plugin>/**/*'
// which point to non-existent directories when CocoaPods is the build system,
// causing "No files found matching ..." warnings and empty pod targets.
final sourceFilesMatch = RegExp(r"s\.source_files\s*=\s*'([^']+)'").firstMatch(content);
if (sourceFilesMatch != null && sourceFilesMatch.group(1) != 'Classes/**/*') {
final badPath = sourceFilesMatch.group(1)!;
// Only fix when the first segment is not 'Classes' and doesn't exist on disk.
final firstSegment = badPath.split('/').first;
final firstDir = Directory(p.join(podspecFile.parent.path, firstSegment));
if (firstSegment != 'Classes' && !firstDir.existsSync()) {
content = content.replaceFirst(
sourceFilesMatch.group(0)!,
"s.source_files = 'Classes/**/*'",
);
modified = true;
}
}
if (!content.contains("s.swift_version = '5.9'")) {
content = content.replaceFirst(
RegExp(r"s\.swift_version\s*=\s*'.+?'"),
"s.swift_version = '5.9'",
);
modified = true;
}
if (!content.contains("s.platform = :ios, '13.0'")) {
content = content.replaceFirst(
RegExp(r"s\.platform\s*=\s*:ios,\s*'.+?'"),
"s.platform = :ios, '13.0'",
);
modified = true;
}
if (!content.contains('HEADER_SEARCH_PATHS')) {
content = content.replaceFirst(
's.pod_target_xcconfig = {',
"s.pod_target_xcconfig = {\n 'HEADER_SEARCH_PATHS' => '\$(inherited) \"\${PODS_ROOT}/../.symlinks/plugins/nitro/src/native\" \"\${PODS_TARGET_SRCROOT}/../src\" \"\${PODS_TARGET_SRCROOT}/../lib/src/generated/cpp\"',",
);
modified = true;
} else {
// If it exists, ensure it has the src/ and generated/cpp/ paths.
if (!content.contains('PODS_TARGET_SRCROOT}/../src') ||
!content.contains('lib/src/generated/cpp')) {
final match = RegExp(
r"'HEADER_SEARCH_PATHS'\s*=>\s*'([^']+)'",
).firstMatch(content);
if (match != null) {
var paths = match.group(1)!;
if (!paths.contains('PODS_TARGET_SRCROOT}/../src')) {
paths += ' "\${PODS_TARGET_SRCROOT}/../src"';
}
if (!paths.contains('lib/src/generated/cpp')) {
paths += ' "\${PODS_TARGET_SRCROOT}/../lib/src/generated/cpp"';
}
content = content.replaceFirst(
match.group(0)!,
"'HEADER_SEARCH_PATHS' => '$paths'",
);
modified = true;
}
}
}
if (!content.contains("'DEFINES_MODULE' => 'YES'")) {
content = content.replaceFirst(
's.pod_target_xcconfig = {',
"s.pod_target_xcconfig = {\n 'DEFINES_MODULE' => 'YES',",
);
modified = true;
}
if (!content.contains("'CLANG_CXX_LANGUAGE_STANDARD'") &&
!content.contains('c++17')) {
content = content.replaceFirst(
's.pod_target_xcconfig = {',
"s.pod_target_xcconfig = {\n 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',",
);
modified = true;
}
if (!content.contains("s.dependency 'nitro'")) {
content = content.replaceFirst(
's.pod_target_xcconfig = {',
"s.dependency 'nitro'\n s.pod_target_xcconfig = {",
);
modified = true;
}
// Sync generated Swift bridges into ios/Classes/ so Xcode can compile them
// in the same module scope as the plugin's other Swift files.
// Using a podspec source_files glob to ../lib/src/generated/swift/ does NOT
// reliably work — types defined there are not always in scope for Classes/ files.
if (modified) podspecFile.writeAsStringSync(content);
createSharedHeaders(nitroNativePath, baseDir: baseDir);
final classesDir = Directory(p.join(baseDir, 'ios', 'Classes'))
..createSync(recursive: true);
File(
p.join(classesDir.path, 'dart_api_dl.c'),
).writeAsStringSync(classesDartApiDlForwarder);
syncBridgeFiles(baseDir);
_copySwiftBridgesToClasses(classesDir, baseDir);
// Remove the outer lib/src/generated/swift glob from the podspec if present,
// since the bridge is now copied directly into Classes/ (avoids duplicate symbols).
_removeSwiftGlobFromPodspec(podspecFile);
// Link the main project source files.
final cppInSrc = File(p.join(baseDir, 'src', '$pluginName.cpp'));
if (cppInSrc.existsSync()) {
cleanRedundantIncludes(cppInSrc);
File(p.join(classesDir.path, '$pluginName.cpp')).writeAsStringSync(
managedCppForwarder('../../src/$pluginName.cpp'),
);
}
final cInSrc = File(p.join(baseDir, 'src', '$pluginName.c'));
if (cInSrc.existsSync()) {
cleanRedundantIncludes(cInSrc);
File(
p.join(classesDir.path, '$pluginName.c'),
).writeAsStringSync(classesCForwarder(pluginName));
}
// Link C++ module implementation files for iOS.
// On Android each module is a separate .so via CMake. On iOS everything is
// compiled into one pod binary, so only ios:NativeImpl.cpp modules need
// a Hybrid*.cpp forwarder in ios/Classes/.
// Windows-only or macos-only C++ modules must NOT get a forwarder here.
if (moduleInfos != null) {
// Discover specs for iOS-cpp filtering (per-platform, not broad Apple check).
final libDir = Directory(p.join(baseDir, 'lib'));
final specFiles = libDir.existsSync()
? libDir
.listSync(recursive: true)
.whereType<File>()
.where((f) => f.path.endsWith('.native.dart'))
.toList()
: <File>[];
final appleCppLibs = specFiles.where(isIosCppModule).map((f) {
final stem = p
.basename(f.path)
.replaceAll(RegExp(r'\.native\.dart$'), '');
return extractLibNameFromSpec(f) ?? stem;
}).toSet();
// Write forwarders only for Apple cpp modules.
for (final m in moduleInfos.where((m) => m.isCpp)) {
final className = _toPascalCase(m.lib);
final forwarderFile = File(
p.join(classesDir.path, 'Hybrid$className.cpp'),
);
if (appleCppLibs.contains(m.lib)) {
// Apple C++ module — ensure forwarder is present/up-to-date.
final implSrc = File(p.join(baseDir, 'src', 'Hybrid$className.cpp'));
if (implSrc.existsSync()) {
forwarderFile.writeAsStringSync(
managedCppForwarder('../../src/Hybrid$className.cpp'),
);
}
} else {
// Non-Apple C++ module (e.g. Windows-only) — remove any stale forwarder.
if (forwarderFile.existsSync()) forwarderFile.deleteSync();
}
}
}
ensureIosPackageSwift(pluginName, baseDir: baseDir, moduleInfos: moduleInfos);
// Re-affirm the correct ../../src/ relative paths AFTER ensureIosPackageSwift,
// which may write forwarders into Sources/NitroPubTestCpp/ with ../../../src/.
// These are two different files, but belt-and-suspenders: always end with the
// definitive Classes/ versions so a stale copy can never win.
File(
p.join(classesDir.path, 'dart_api_dl.c'),
).writeAsStringSync(classesDartApiDlForwarder);
if (cppInSrc.existsSync()) {
File(p.join(classesDir.path, '$pluginName.cpp')).writeAsStringSync(
managedCppForwarder('../../src/$pluginName.cpp'),
);
}
if (cInSrc.existsSync()) {
File(
p.join(classesDir.path, '$pluginName.c'),
).writeAsStringSync(classesCForwarder(pluginName));
}
}