Skip to content

Commit 1ddb804

Browse files
committed
Refactor macOS/iOS native bindings to SwiftPM
Migrates macOS and iOS native bindings from CocoaPods to Swift Package Manager, removing podspecs and Classes/ sources. Adds SwiftPM Package.swift and reorganizes sources under platform-specific directories. Updates codegen.py to support new structure, modifies Dart bindings to use DynamicLibrary.process(), and removes deprecated C API (run_example_app). Updates example projects and build configs to reflect the new integration method.
1 parent d2c3f82 commit 1ddb804

33 files changed

Lines changed: 583 additions & 572 deletions

File tree

packages/cnativeapi/codegen.py

Lines changed: 157 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
"""
33
Script to regenerate bindings by:
44
1. Pulling latest cxx_impl submodule
5-
2. Updating macos/Classes/cnativeapi.mm include statements
6-
3. Updating ios/Classes/cnativeapi.mm include statements
7-
4. Updating ffigen.yaml with all C API header files
8-
5. Generating bindings with ffigen
5+
2. Updating macos/cnativeapi/Sources/cnativeapi/cnativeapi.mm include statements
6+
3. Updating ios/cnativeapi/Sources/cnativeapi/cnativeapi.mm include statements
7+
4. Updating macos/cnativeapi/Sources/cnativeapi/include/cnativeapi.h
8+
5. Updating ios/cnativeapi/Sources/cnativeapi/include/cnativeapi.h
9+
6. Updating ffigen.yaml with all C API header files
10+
7. Generating bindings with ffigen
911
"""
1012

1113
import os
@@ -67,9 +69,95 @@ def find_capi_headers(cxx_impl_dir):
6769
return sorted(headers)
6870

6971

72+
def find_cpp_headers(cxx_impl_dir):
73+
"""Find all C++ API header files (*.h) in cxx_impl/src, excluding capi/ and platform/."""
74+
src_dir = cxx_impl_dir / "src"
75+
if not src_dir.exists():
76+
return []
77+
78+
headers = []
79+
# Find all .h files in src/ and subdirectories, excluding capi/ and platform/
80+
for header_file in src_dir.rglob("*.h"):
81+
file_str = str(header_file)
82+
# Skip capi/ and platform/ directories
83+
if "/capi/" in file_str or "/platform/" in file_str:
84+
continue
85+
# Calculate relative path from cnativeapi.h location
86+
# cnativeapi.h is at packages/cnativeapi/{platform}/cnativeapi/Sources/cnativeapi/include/
87+
# cxx_impl is at packages/cnativeapi/cxx_impl/
88+
# So relative path should be ../../../../../cxx_impl/src/...
89+
rel_path = os.path.relpath(header_file, cxx_impl_dir.parent)
90+
headers.append((rel_path, header_file))
91+
92+
# Sort by path for consistent ordering
93+
headers.sort(key=lambda x: x[0])
94+
return [h[0] for h in headers]
95+
96+
97+
def update_cnativeapi_h(header_path, cxx_impl_dir):
98+
"""Update cnativeapi.h file with C++ and C API header includes."""
99+
print(f"Updating {header_path.name}...")
100+
101+
# Find all C++ API headers (returns paths relative to cxx_impl_dir.parent)
102+
cpp_headers = find_cpp_headers(cxx_impl_dir)
103+
print(f" Found {len(cpp_headers)} C++ API headers")
104+
105+
# Find all C API headers (returns paths relative to cxx_impl_dir.parent)
106+
capi_headers = find_capi_headers(cxx_impl_dir)
107+
print(f" Found {len(capi_headers)} C API headers")
108+
109+
# Calculate relative path from header_path to cxx_impl_dir
110+
# header_path is at packages/cnativeapi/{platform}/cnativeapi/Sources/cnativeapi/include/
111+
# cxx_impl_dir is at packages/cnativeapi/cxx_impl/
112+
cxx_impl_rel = os.path.relpath(cxx_impl_dir, header_path.parent)
113+
114+
# Generate C++ includes
115+
cpp_includes = []
116+
for header in cpp_headers:
117+
# header is like "cxx_impl/src/accessibility_manager.h"
118+
# Need to convert to relative path from header_path
119+
# Join cxx_impl_rel with the part after "cxx_impl/"
120+
if header.startswith("cxx_impl/"):
121+
rel_path = os.path.join(cxx_impl_rel, header[len("cxx_impl/"):])
122+
else:
123+
rel_path = os.path.join(cxx_impl_rel, "src", header)
124+
# Normalize path separators
125+
rel_path = rel_path.replace("\\", "/")
126+
cpp_includes.append(f'#include "{rel_path}"')
127+
128+
# Generate C API includes
129+
capi_includes = []
130+
for header in capi_headers:
131+
# header is like "cxx_impl/src/capi/accessibility_manager_c.h"
132+
# Need to convert to relative path from header_path
133+
if header.startswith("cxx_impl/"):
134+
rel_path = os.path.join(cxx_impl_rel, header[len("cxx_impl/"):])
135+
else:
136+
rel_path = os.path.join(cxx_impl_rel, "src", "capi", header)
137+
# Normalize path separators
138+
rel_path = rel_path.replace("\\", "/")
139+
capi_includes.append(f'#include "{rel_path}"')
140+
141+
# Generate header file content
142+
content = "#pragma once\n\n"
143+
content += "#ifdef __cplusplus\n"
144+
content += "// C++ API headers\n"
145+
content += "\n".join(cpp_includes) + "\n"
146+
content += "#endif\n\n"
147+
content += "// C API headers (available for both C and C++)\n"
148+
content += "\n".join(capi_includes) + "\n"
149+
150+
# Write header file
151+
with open(header_path, "w", encoding="utf-8") as f:
152+
f.write(content)
153+
154+
print(f" Updated {header_path.name} successfully")
155+
return True
156+
157+
70158
def update_ffigen_yaml(ffigen_path, capi_headers):
71159
"""Update ffigen.yaml with all C API header files."""
72-
print("\nStep 4/5: Updating ffigen configuration")
160+
print("\nStep 6/7: Updating ffigen configuration")
73161
print(f"Found {len(capi_headers)} C API header files to process...")
74162

75163
with open(ffigen_path, "r", encoding="utf-8") as f:
@@ -214,9 +302,9 @@ def update_nativeapi_mm(nativeapi_path, cxx_impl_dir, platform):
214302
file_str = str(file_path)
215303

216304
# Calculate relative path from cnativeapi.mm location
217-
# cnativeapi.mm is at packages/cnativeapi/{platform}/Classes/
305+
# cnativeapi.mm is at packages/cnativeapi/{platform}/cnativeapi/Sources/cnativeapi/
218306
# cxx_impl is at packages/cnativeapi/cxx_impl/
219-
# So relative path should be ../../cxx_impl/src/...
307+
# So relative path should be ../../../../cxx_impl/src/...
220308
rel_path = os.path.relpath(file_path, nativeapi_path.parent)
221309

222310
if "/capi/" in file_str:
@@ -243,12 +331,16 @@ def update_nativeapi_mm(nativeapi_path, cxx_impl_dir, platform):
243331
print(f" - Foundation files: {len(foundation_files)}")
244332
print(f" - Core files: {len(core_files)}")
245333

246-
# Build the new include section (no trailing newline, match original format)
334+
# Build the new include section
247335
new_include_section = match.group(1) + "\n".join(new_includes)
248336

249337
# Replace the old include section with the new one
250338
new_content = content[: match.start()] + new_include_section
251339

340+
# Ensure file ends with a newline
341+
if not new_content.endswith("\n"):
342+
new_content += "\n"
343+
252344
# Write back to file
253345
with open(nativeapi_path, "w", encoding="utf-8") as f:
254346
f.write(new_content)
@@ -257,10 +349,10 @@ def update_nativeapi_mm(nativeapi_path, cxx_impl_dir, platform):
257349

258350

259351
def update_macos_mm(cnativeapi_dir, cxx_impl_dir):
260-
"""Update macos/Classes/cnativeapi.mm."""
261-
print("\nStep 2/5: Updating macOS platform bindings")
352+
"""Update macos/cnativeapi/Sources/cnativeapi/cnativeapi.mm."""
353+
print("\nStep 2/7: Updating macOS platform bindings")
262354

263-
macos_mm_path = cnativeapi_dir / "macos/Classes/cnativeapi.mm"
355+
macos_mm_path = cnativeapi_dir / "macos/cnativeapi/Sources/cnativeapi/cnativeapi.mm"
264356

265357
if not macos_mm_path.exists():
266358
print(f"Error: {macos_mm_path} not found")
@@ -275,10 +367,10 @@ def update_macos_mm(cnativeapi_dir, cxx_impl_dir):
275367

276368

277369
def update_ios_mm(cnativeapi_dir, cxx_impl_dir):
278-
"""Update ios/Classes/cnativeapi.mm."""
279-
print("\nStep 3/5: Updating iOS platform bindings")
370+
"""Update ios/cnativeapi/Sources/cnativeapi/cnativeapi.mm."""
371+
print("\nStep 3/7: Updating iOS platform bindings")
280372

281-
ios_mm_path = cnativeapi_dir / "ios/Classes/cnativeapi.mm"
373+
ios_mm_path = cnativeapi_dir / "ios/cnativeapi/Sources/cnativeapi/cnativeapi.mm"
282374

283375
if not ios_mm_path.exists():
284376
print(f"Error: {ios_mm_path} not found")
@@ -292,6 +384,42 @@ def update_ios_mm(cnativeapi_dir, cxx_impl_dir):
292384
return False
293385

294386

387+
def update_macos_h(cnativeapi_dir, cxx_impl_dir):
388+
"""Update macos/cnativeapi/Sources/cnativeapi/include/cnativeapi.h."""
389+
print("\nStep 4/7: Updating macOS header file")
390+
391+
macos_h_path = cnativeapi_dir / "macos/cnativeapi/Sources/cnativeapi/include/cnativeapi.h"
392+
393+
if not macos_h_path.exists():
394+
print(f"Error: {macos_h_path} not found")
395+
return False
396+
397+
if update_cnativeapi_h(macos_h_path, cxx_impl_dir):
398+
print("macOS header updated successfully")
399+
return True
400+
else:
401+
print("Failed to update macOS header")
402+
return False
403+
404+
405+
def update_ios_h(cnativeapi_dir, cxx_impl_dir):
406+
"""Update ios/cnativeapi/Sources/cnativeapi/include/cnativeapi.h."""
407+
print("\nStep 5/7: Updating iOS header file")
408+
409+
ios_h_path = cnativeapi_dir / "ios/cnativeapi/Sources/cnativeapi/include/cnativeapi.h"
410+
411+
if not ios_h_path.exists():
412+
print(f"Error: {ios_h_path} not found")
413+
return False
414+
415+
if update_cnativeapi_h(ios_h_path, cxx_impl_dir):
416+
print("iOS header updated successfully")
417+
return True
418+
else:
419+
print("Failed to update iOS header")
420+
return False
421+
422+
295423
def main():
296424
"""Main function to regenerate bindings."""
297425
cnativeapi_dir = Path(__file__).parent
@@ -310,17 +438,27 @@ def main():
310438
print(f"\nError: cxx_impl directory not found: {cxx_impl_dir}")
311439
return 1
312440

313-
# Step 2: Update macos/Classes/cnativeapi.mm
441+
# Step 2: Update macos/cnativeapi/Sources/cnativeapi/cnativeapi.mm
314442
if not update_macos_mm(cnativeapi_dir, cxx_impl_dir):
315443
print("\nError: macOS bindings update failed")
316444
return 1
317445

318-
# Step 3: Update ios/Classes/cnativeapi.mm
446+
# Step 3: Update ios/cnativeapi/Sources/cnativeapi/cnativeapi.mm
319447
if not update_ios_mm(cnativeapi_dir, cxx_impl_dir):
320448
print("\nError: iOS bindings update failed")
321449
return 1
322450

323-
# Step 4: Update ffigen.yaml
451+
# Step 4: Update macos/cnativeapi/Sources/cnativeapi/include/cnativeapi.h
452+
if not update_macos_h(cnativeapi_dir, cxx_impl_dir):
453+
print("\nError: macOS header update failed")
454+
return 1
455+
456+
# Step 5: Update ios/cnativeapi/Sources/cnativeapi/include/cnativeapi.h
457+
if not update_ios_h(cnativeapi_dir, cxx_impl_dir):
458+
print("\nError: iOS header update failed")
459+
return 1
460+
461+
# Step 6: Update ffigen.yaml
324462
capi_headers = find_capi_headers(cxx_impl_dir)
325463
if not capi_headers:
326464
print("\nWarning: No C API headers found in cxx_impl")
@@ -329,8 +467,8 @@ def main():
329467
print("\nError: Failed to update ffigen configuration")
330468
return 1
331469

332-
# Step 5: Generate bindings using ffigen
333-
print("\nStep 5/5: Generating Dart bindings")
470+
# Step 7: Generate bindings using ffigen
471+
print("\nStep 7/7: Generating Dart bindings")
334472
print("Running ffigen to generate Dart bindings from C headers...")
335473

336474
ffigen_cmd = ["dart", "run", "ffigen", "--config", "ffigen.yaml"]

packages/cnativeapi/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
1212
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
1313
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
14+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
1415
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
1516
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
1617
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -47,6 +48,7 @@
4748
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4849
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
4950
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
51+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
5052
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
5153
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
5254
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
@@ -62,6 +64,7 @@
6264
isa = PBXFrameworksBuildPhase;
6365
buildActionMask = 2147483647;
6466
files = (
67+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
6568
);
6669
runOnlyForDeploymentPostprocessing = 0;
6770
};
@@ -79,6 +82,7 @@
7982
9740EEB11CF90186004384FC /* Flutter */ = {
8083
isa = PBXGroup;
8184
children = (
85+
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
8286
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
8387
9740EEB21CF90195004384FC /* Debug.xcconfig */,
8488
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -157,6 +161,9 @@
157161
dependencies = (
158162
);
159163
name = Runner;
164+
packageProductDependencies = (
165+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
166+
);
160167
productName = Runner;
161168
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
162169
productType = "com.apple.product-type.application";
@@ -190,6 +197,9 @@
190197
Base,
191198
);
192199
mainGroup = 97C146E51CF9000F007C117D;
200+
packageReferences = (
201+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
202+
);
193203
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
194204
projectDirPath = "";
195205
projectRoot = "";
@@ -611,6 +621,20 @@
611621
defaultConfigurationName = Release;
612622
};
613623
/* End XCConfigurationList section */
624+
625+
/* Begin XCLocalSwiftPackageReference section */
626+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
627+
isa = XCLocalSwiftPackageReference;
628+
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
629+
};
630+
/* End XCLocalSwiftPackageReference section */
631+
632+
/* Begin XCSwiftPackageProductDependency section */
633+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
634+
isa = XCSwiftPackageProductDependency;
635+
productName = FlutterGeneratedPluginSwiftPackage;
636+
};
637+
/* End XCSwiftPackageProductDependency section */
614638
};
615639
rootObject = 97C146E61CF9000F007C117D /* Project object */;
616640
}

packages/cnativeapi/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
8+
<PreActions>
9+
<ExecutionAction
10+
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
11+
<ActionContent
12+
title = "Run Prepare Flutter Framework Script"
13+
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
14+
<EnvironmentBuildable>
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
18+
BuildableName = "Runner.app"
19+
BlueprintName = "Runner"
20+
ReferencedContainer = "container:Runner.xcodeproj">
21+
</BuildableReference>
22+
</EnvironmentBuildable>
23+
</ActionContent>
24+
</ExecutionAction>
25+
</PreActions>
826
<BuildActionEntries>
927
<BuildActionEntry
1028
buildForTesting = "YES"

packages/cnativeapi/example/linux/flutter/generated_plugins.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
66
)
77

88
list(APPEND FLUTTER_FFI_PLUGIN_LIST
9+
cnativeapi
910
)
1011

1112
set(PLUGIN_BUNDLED_LIBRARIES)

packages/cnativeapi/example/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: cnativeapi_example
22
description: "A new Flutter project."
33
# The following line prevents the package from being accidentally published to
44
# pub.dev using `flutter pub publish`. This is preferred for private packages.
5-
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
5+
publish_to: "none" # Remove this line if you wish to publish to pub.dev
66

77
# The following defines the version and build number for your application.
88
# A version number is three numbers separated by dots, like 1.2.43
@@ -35,6 +35,8 @@ dependencies:
3535
# The following adds the Cupertino Icons font to your application.
3636
# Use with the CupertinoIcons class for iOS style icons.
3737
cupertino_icons: ^1.0.8
38+
cnativeapi:
39+
path: ../
3840

3941
dev_dependencies:
4042
flutter_test:
@@ -52,7 +54,6 @@ dev_dependencies:
5254

5355
# The following section is specific to Flutter packages.
5456
flutter:
55-
5657
# The following line ensures that the Material Icons font is
5758
# included with your application, so that you can use the icons in
5859
# the material Icons class.

packages/cnativeapi/example/windows/flutter/generated_plugins.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
66
)
77

88
list(APPEND FLUTTER_FFI_PLUGIN_LIST
9+
cnativeapi
910
)
1011

1112
set(PLUGIN_BUNDLED_LIBRARIES)

0 commit comments

Comments
 (0)