linkDesktopPubspecFfiOnly function
Implementation
void linkDesktopPubspecFfiOnly(
List<ModuleInfo> moduleInfos, {
String baseDir = '.',
}) {
final pubspecFile = File(p.join(baseDir, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) return;
final fixWindows = moduleInfos.any((m) => m.windowsIsCpp);
final fixLinux = moduleInfos.any((m) => m.linuxIsCpp);
if (!fixWindows && !fixLinux) return;
final targets = <String>{if (fixWindows) 'windows', if (fixLinux) 'linux'};
final lines = pubspecFile.readAsStringSync().split('\n');
final out = <String>[];
var inPlatforms = false;
var platformsIndent = -1;
var changed = false;
int indentOf(String l) => l.length - l.trimLeft().length;
for (var i = 0; i < lines.length; i++) {
final line = lines[i];
final trimmed = line.trim();
if (trimmed == 'platforms:') {
inPlatforms = true;
platformsIndent = indentOf(line);
out.add(line);
continue;
}
if (inPlatforms && trimmed.isNotEmpty && indentOf(line) <= platformsIndent) {
inPlatforms = false; // left the platforms block
}
final flowMatch = inPlatforms ? RegExp(r'^(\s*)(\w+):\s*\{(.*)\}\s*$').firstMatch(line) : null;
if (flowMatch != null && targets.contains(flowMatch.group(2))) {
// Inline flow map: windows: { pluginClass: Xxx, ffiPlugin: true }
final entries = flowMatch.group(3)!.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList();
if (entries.any((e) => e.startsWith('ffiPlugin:')) && entries.any((e) => e.startsWith('pluginClass:'))) {
// Only strip a DANGLING class — one with no registrant implementation
// in the platform sources. ffiPlugin + a real pluginClass is a
// documented hybrid configuration and user-owned (issue #23).
final cls = entries.firstWhere((e) => e.startsWith('pluginClass:')).substring('pluginClass:'.length).trim();
if (!_desktopPluginClassIsReal(baseDir, flowMatch.group(2)!, cls)) {
entries.removeWhere((e) => e.startsWith('pluginClass:'));
out.add('${flowMatch.group(1)}${flowMatch.group(2)}: { ${entries.join(', ')} }');
changed = true;
continue;
}
}
}
final blockKey = inPlatforms ? RegExp(r'^(\s*)(\w+):\s*$').firstMatch(line) : null;
if (blockKey != null && targets.contains(blockKey.group(2))) {
// Block form: collect the child lines (strictly deeper indent).
final keyIndent = indentOf(line);
var end = i + 1;
while (end < lines.length && (lines[end].trim().isEmpty || indentOf(lines[end]) > keyIndent)) {
end++;
}
final children = lines.sublist(i + 1, end);
final hasFfi = children.any((l) => l.trim().startsWith('ffiPlugin:') && l.contains('true'));
final hasClass = children.any((l) => l.trim().startsWith('pluginClass:'));
// Only strip a DANGLING class — one whose registrant symbol appears
// nowhere in the platform sources. ffiPlugin + a real pluginClass is a
// documented hybrid configuration and user-owned (issue #23).
var classIsDangling = false;
if (hasFfi && hasClass) {
final cls = children.firstWhere((l) => l.trim().startsWith('pluginClass:')).trim().substring('pluginClass:'.length).trim();
classIsDangling = !_desktopPluginClassIsReal(baseDir, blockKey.group(2)!, cls);
}
out.add(line);
for (final child in children) {
if (hasFfi && hasClass && classIsDangling && child.trim().startsWith('pluginClass:')) {
changed = true;
continue;
}
out.add(child);
}
i = end - 1;
continue;
}
out.add(line);
}
if (changed) {
pubspecFile.writeAsStringSync(out.join('\n'));
stdout.writeln(' pubspec.yaml: removed pluginClass from FFI-only desktop platform entries (fixes "No target <plugin>_plugin")');
}
}