diff --git a/.gitignore b/.gitignore index 316d436..c600db7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,18 @@ *.iml -.gradle +**/.gradle/ /local.properties /.idea .DS_Store -/build +build/ /captures .externalNativeBuild -.cxx +**/.cxx/ local.properties jniLibs + +# C++ build artifacts +**/prebuilt/ + +# Swift build artifacts +**/.build/ +**/Package.resolved diff --git a/README.md b/README.md index 795ccf3..c5e3acb 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,16 @@ This approach is ideal for production Android applications where you want to wri business logic, algorithms, or libraries in Swift, while maintaining a standard Kotlin/Java frontend. +## C++ Integration Example + +The **[hello-cpp-swift](hello-cpp-swift/)** example demonstrates how to integrate +C++ libraries into Android applications through Swift. This example packages a C++ +library as an artifactbundle, imports it as a Swift binary target, and exposes it +to Android using swift-java for automatic JNI generation. + +This pattern is useful when you have existing C++ code and want to leverage Swift's +type safety and swift-java's automatic bridging to create Android-compatible libraries. + ## Other Examples For those who want to explore alternative integration patterns or understand diff --git a/hello-cpp-swift/README.md b/hello-cpp-swift/README.md new file mode 100644 index 0000000..4e5a0cb --- /dev/null +++ b/hello-cpp-swift/README.md @@ -0,0 +1,89 @@ +# C++ to Swift to Android Integration + +This example demonstrates how to call C++ code from Android through Swift. The app uses a C++ library that provides basic calculator functions (add and multiply), wraps them in Swift, and calls them from an Android Kotlin app. + +## Overview + +The project is structured into three main parts: + +1. **`cpp-lib`**: A C++ library with basic calculator functions (`add` and `multiply`). This is built using CMake and packaged as an artifactbundle for consumption by Swift. + +2. **`swift-lib`**: A Swift package that wraps the C++ functions and exposes them to Android using [swift-java](https://github.com/swiftlang/swift-java). The Swift code calls the C++ functions and provides JNI bindings automatically. + +3. **`app`**: A standard Android application written in Kotlin that calls the Swift-wrapped C++ functions and displays the results. + +## Prerequisites + +Before you can build and run this project, you need to have the following installed: + +* **Basic setup**: Follow the Prerequisites and Setup instructions in [hello-swift-java/README.md](../hello-swift-java/README.md) to install JDK, Swiftly, Swift SDK for Android, and publish the swift-java packages locally. +* **Android NDK**: Required to build the C++ library. Set the `ANDROID_NDK_HOME` environment variable to your NDK installation path. + +## Setup and Configuration + +### 1. Build the C++ Library + +Before building the Android app, you need to build the C++ library: + +```bash +cd hello-cpp-swift/cpp-lib +./build-android-static.sh +``` + +This will create the `prebuilt/HelloWorldCpp.artifactbundle` directory containing the compiled C++ static libraries for all Android architectures (arm64-v8a, armeabi-v7a, x86_64). + +## Running the example + +1. Open the `swift-android-examples` project in Android Studio. + +2. Select the `hello-cpp-swift:app` Gradle target. + +3. Run the app on an Android emulator or a physical device. + +4. The app will display the results of C++ calculations (10 + 5 and 10 × 5) called through Swift. + +## Building from command line + +```bash +# From the project root directory +./gradlew :hello-cpp-swift:app:assembleDebug + +# Install on device/emulator +./gradlew :hello-cpp-swift:app:installDebug +``` + +## How it works + +1. **C++ Layer** (`cpp-lib/src/calculator.cpp`): + ```cpp + int add(int a, int b) { + return a + b; + } + ``` + +2. **Swift Layer** (`swift-lib/Sources/HelloCppSwift/Calculator.swift`): + ```swift + import HelloWorldCpp + + public func addNumbers(_ a: Int32, _ b: Int32) -> Int32 { + return add(a, b) + } + ``` + +3. **Android/Kotlin Layer** (`app/MainActivity.kt`): + ```kotlin + val sum = com.example.hellocppswift.HelloCppSwift.addNumbers(10, 5) + ``` + +The Swift code is automatically wrapped with JNI bindings using the `swift-java` JExtract plugin, making it callable from Kotlin/Java code. + +## Rebuilding the C++ Library + +If you make changes to the C++ code, you need to rebuild the artifactbundle: + +```bash +cd hello-cpp-swift/cpp-lib +./build-android-static.sh +``` + +Then rebuild the Android app to pick up the changes. diff --git a/hello-cpp-swift/app/build.gradle.kts b/hello-cpp-swift/app/build.gradle.kts new file mode 100644 index 0000000..ec92bcb --- /dev/null +++ b/hello-cpp-swift/app/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.example.hellocppswift" + compileSdk = 36 + + defaultConfig { + applicationId = "com.example.hellocppswift" + minSdk = 28 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.constraintlayout) + implementation("org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT") + implementation(project(":hello-cpp-swift:swift-lib")) +} diff --git a/hello-cpp-swift/app/src/main/AndroidManifest.xml b/hello-cpp-swift/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7541bda --- /dev/null +++ b/hello-cpp-swift/app/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/hello-cpp-swift/app/src/main/java/com/example/hellocppswift/MainActivity.kt b/hello-cpp-swift/app/src/main/java/com/example/hellocppswift/MainActivity.kt new file mode 100644 index 0000000..63d5356 --- /dev/null +++ b/hello-cpp-swift/app/src/main/java/com/example/hellocppswift/MainActivity.kt @@ -0,0 +1,32 @@ +package com.example.hellocppswift + +import android.os.Bundle +import android.view.Gravity +import android.widget.FrameLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val textView = TextView(this).apply { + textSize = 24f + textAlignment = TextView.TEXT_ALIGNMENT_CENTER + setPadding(32, 32, 32, 32) + gravity = Gravity.CENTER + } + + val sum = com.example.hellocppswift.HelloCppSwift.addNumbers(10, 5) + val product = com.example.hellocppswift.HelloCppSwift.multiplyNumbers(10, 5) + + textView.text = "C++ via Swift Calculations:\n\n10 + 5 = $sum\n10 × 5 = $product" + + val container = FrameLayout(this).apply { + setPadding(0, 200, 0, 0) + addView(textView) + } + + setContentView(container) + } +} diff --git a/hello-cpp-swift/app/src/main/res/values/strings.xml b/hello-cpp-swift/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0aa30d0 --- /dev/null +++ b/hello-cpp-swift/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Hello C++ Swift + diff --git a/hello-cpp-swift/cpp-lib/CMakeLists.txt b/hello-cpp-swift/cpp-lib/CMakeLists.txt new file mode 100644 index 0000000..9eb9864 --- /dev/null +++ b/hello-cpp-swift/cpp-lib/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.18) +project(HelloWorldCpp VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(SOURCES + src/calculator.cpp +) + +set(HEADERS + include/calculator.h +) + +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(${PROJECT_NAME} + PUBLIC + $ + $ +) diff --git a/hello-cpp-swift/cpp-lib/build-android-static.sh b/hello-cpp-swift/cpp-lib/build-android-static.sh new file mode 100755 index 0000000..cb75c4a --- /dev/null +++ b/hello-cpp-swift/cpp-lib/build-android-static.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +if [ -z "$ANDROID_NDK_HOME" ]; then + echo "Error: ANDROID_NDK_HOME environment variable is not set" + echo "Please set it to your Android NDK installation path" + exit 1 +fi + +if [ ! -d "$ANDROID_NDK_HOME" ]; then + echo "Error: ANDROID_NDK_HOME points to non-existent directory: $ANDROID_NDK_HOME" + exit 1 +fi + +ANDROID_TOOLCHAIN="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" + +if [ ! -f "$ANDROID_TOOLCHAIN" ]; then + echo "Error: Android toolchain file not found at: $ANDROID_TOOLCHAIN" + exit 1 +fi + +ANDROID_API=28 + +ABIS=( + "arm64-v8a:android-aarch64" + "armeabi-v7a:android-armv7" + "x86_64:android-x86_64" +) + +echo "Building HelloWorldCpp static libraries for Android..." + +for ABI_ENTRY in "${ABIS[@]}"; do + ABI="${ABI_ENTRY%%:*}" + echo "Building for $ABI..." + + BUILD_DIR="build/android-static/$ABI" + mkdir -p "$BUILD_DIR" + + cmake -S . -B "$BUILD_DIR" \ + -DCMAKE_TOOLCHAIN_FILE="$ANDROID_TOOLCHAIN" \ + -DANDROID_ABI="$ABI" \ + -DANDROID_PLATFORM="android-$ANDROID_API" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=17 + + cmake --build "$BUILD_DIR" --config Release + + echo "✓ Built $ABI" +done + +echo "" +echo "Creating artifactbundle..." + +ARTIFACTBUNDLE_DIR="prebuilt/HelloWorldCpp.artifactbundle" + +for ABI_ENTRY in "${ABIS[@]}"; do + ABI="${ABI_ENTRY%%:*}" + BUNDLE_DIR="${ABI_ENTRY##*:}" + + mkdir -p "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers" + + cp "build/android-static/$ABI/libHelloWorldCpp.a" "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/" + + cp include/calculator.h "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers/" + cp include/module.modulemap "$ARTIFACTBUNDLE_DIR/$BUNDLE_DIR/Headers/" + + echo "✓ Created artifactbundle for $BUNDLE_DIR" +done + +cat > "$ARTIFACTBUNDLE_DIR/info.json" << 'EOF' +{ + "schemaVersion": "1.0", + "artifacts": { + "HelloWorldCpp": { + "version": "1.0.0", + "type": "staticLibrary", + "variants": [ + { + "path": "android-aarch64/libHelloWorldCpp.a", + "supportedTriples": ["aarch64-unknown-linux-android"], + "staticLibraryMetadata": { + "headerPaths": ["android-aarch64/Headers"], + "moduleMapPath": "android-aarch64/Headers/module.modulemap" + } + }, + { + "path": "android-armv7/libHelloWorldCpp.a", + "supportedTriples": ["armv7-unknown-linux-android"], + "staticLibraryMetadata": { + "headerPaths": ["android-armv7/Headers"], + "moduleMapPath": "android-armv7/Headers/module.modulemap" + } + }, + { + "path": "android-x86_64/libHelloWorldCpp.a", + "supportedTriples": ["x86_64-unknown-linux-android"], + "staticLibraryMetadata": { + "headerPaths": ["android-x86_64/Headers"], + "moduleMapPath": "android-x86_64/Headers/module.modulemap" + } + } + ] + } + } +} +EOF + +echo "✓ Generated info.json" +echo "" +echo "All Android static libraries built successfully!" +echo "Artifactbundle created at: $ARTIFACTBUNDLE_DIR" diff --git a/hello-cpp-swift/cpp-lib/include/calculator.h b/hello-cpp-swift/cpp-lib/include/calculator.h new file mode 100644 index 0000000..c84b9ae --- /dev/null +++ b/hello-cpp-swift/cpp-lib/include/calculator.h @@ -0,0 +1,15 @@ +#ifndef CALCULATOR_H +#define CALCULATOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +int add(int a, int b); +int multiply(int a, int b); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hello-cpp-swift/cpp-lib/include/module.modulemap b/hello-cpp-swift/cpp-lib/include/module.modulemap new file mode 100644 index 0000000..b79c670 --- /dev/null +++ b/hello-cpp-swift/cpp-lib/include/module.modulemap @@ -0,0 +1,4 @@ +module HelloWorldCpp { + header "calculator.h" + export * +} diff --git a/hello-cpp-swift/cpp-lib/src/calculator.cpp b/hello-cpp-swift/cpp-lib/src/calculator.cpp new file mode 100644 index 0000000..ec000c3 --- /dev/null +++ b/hello-cpp-swift/cpp-lib/src/calculator.cpp @@ -0,0 +1,9 @@ +#include "calculator.h" + +int add(int a, int b) { + return a + b; +} + +int multiply(int a, int b) { + return a * b; +} diff --git a/hello-cpp-swift/swift-lib/Package.swift b/hello-cpp-swift/swift-lib/Package.swift new file mode 100644 index 0000000..384d60a --- /dev/null +++ b/hello-cpp-swift/swift-lib/Package.swift @@ -0,0 +1,70 @@ +// swift-tools-version: 6.2 + +import CompilerPluginSupport +import PackageDescription + +import class Foundation.FileManager +import class Foundation.ProcessInfo + +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#else + #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#endif + +let package = Package( + name: "HelloCppSwift", + platforms: [.macOS(.v15)], + products: [ + .library( + name: "HelloCppSwift", + type: .dynamic, + targets: ["HelloCppSwift"]) + ], + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-java", branch: "main"), + ], + targets: [ + .binaryTarget( + name: "HelloWorldCpp", + path: "../cpp-lib/prebuilt/HelloWorldCpp.artifactbundle" + ), + .target( + name: "HelloCppSwift", + dependencies: [ + .target(name: "HelloWorldCpp"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) + ], + plugins: [ + .plugin(name: "JExtractSwiftPlugin", package: "swift-java") + ] + ), + ] +) diff --git a/hello-cpp-swift/swift-lib/Sources/HelloCppSwift/Calculator.swift b/hello-cpp-swift/swift-lib/Sources/HelloCppSwift/Calculator.swift new file mode 100644 index 0000000..494ad99 --- /dev/null +++ b/hello-cpp-swift/swift-lib/Sources/HelloCppSwift/Calculator.swift @@ -0,0 +1,14 @@ +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif +import HelloWorldCpp + +public func addNumbers(_ a: Int32, _ b: Int32) -> Int32 { + return add(a, b) +} + +public func multiplyNumbers(_ a: Int32, _ b: Int32) -> Int32 { + return multiply(a, b) +} diff --git a/hello-cpp-swift/swift-lib/Sources/HelloCppSwift/swift-java.config b/hello-cpp-swift/swift-lib/Sources/HelloCppSwift/swift-java.config new file mode 100644 index 0000000..6068c54 --- /dev/null +++ b/hello-cpp-swift/swift-lib/Sources/HelloCppSwift/swift-java.config @@ -0,0 +1,4 @@ +{ + "javaPackage": "com.example.hellocppswift", + "mode": "jni" +} diff --git a/hello-cpp-swift/swift-lib/build.gradle b/hello-cpp-swift/swift-lib/build.gradle new file mode 100644 index 0000000..673a061 --- /dev/null +++ b/hello-cpp-swift/swift-lib/build.gradle @@ -0,0 +1,185 @@ +import java.nio.file.* + +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace "com.example.hellocppswift.lib" + compileSdkVersion 34 + + defaultConfig { + minSdkVersion 28 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + implementation('org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT') +} + +def getSwiftlyPath() { + def fromConfig = project.findProperty("swiftly.path") ?: System.getenv("SWIFTLY_PATH") + if (fromConfig) { + return file(fromConfig) + } + + def homeDir = System.getProperty("user.home") + def possiblePaths = [ + "$homeDir/.swiftly/bin/swiftly", + "$homeDir/.local/share/swiftly/bin/swiftly", + "$homeDir/.local/bin/swiftly", + "/usr/local/bin/swiftly", + "/opt/homebrew/bin/swiftly", + "/root/.local/share/swiftly/bin/swiftly" + ] + + for (path in possiblePaths) { + if (file(path).exists()) { + return path + } + } + + throw new GradleException("Swift SDK path not found. Please set swiftly.path in the gradle.properties file or set SWIFTLY_PATH environment variable.") +} + +def getSwiftSDKPath() { + def fromConfig = project.findProperty("swift.sdk.path") ?: System.getenv("SWIFT_SDK_PATH") + if (fromConfig) { + return file(fromConfig) + } + + def homeDir = System.getProperty("user.home") + def possiblePaths = [ + "${homeDir}/Library/org.swift.swiftpm/swift-sdks/", + "${homeDir}/.config/swiftpm/swift-sdks/", + "${homeDir}/.swiftpm/swift-sdks/", + "/root/.swiftpm/swift-sdks/" + ] + + for (path in possiblePaths) { + if (file(path).exists()) { + return file(path) + } + } + + throw new GradleException("Swift SDK path not found. Please set swift.sdk.path in the gradle.properties file or set SWIFT_SDK_PATH environment variable.") +} + +def swiftRuntimeLibs = [ + "swiftCore", + "swift_Concurrency", + "swift_StringProcessing", + "swift_RegexParser", + "swift_Builtin_float", + "swift_math", + "swiftAndroid", + "dispatch", + "BlocksRuntime", + "swiftSwiftOnoneSupport", + "swiftDispatch", + "Foundation", + "FoundationEssentials", + "FoundationInternationalization", + "_FoundationICU", + "swiftSynchronization" +] + +def sdkName = "swift-DEVELOPMENT-SNAPSHOT-2025-10-16-a-android-0.1.artifactbundle" +def swiftVersion = "main-snapshot-2025-10-16" +def minSdk = android.defaultConfig.minSdkVersion.apiLevel + +def abis = [ + "arm64-v8a" : [triple: "aarch64-unknown-linux-android${minSdk}", androidSdkLibDirectory: "swift-aarch64", ndkDirectory: "aarch64-linux-android"], + "armeabi-v7a" : [triple: "armv7-unknown-linux-android${minSdk}", androidSdkLibDirectory: "swift-armv7", ndkDirectory: "arm-linux-android"], + "x86_64" : [triple: "x86_64-unknown-linux-android${minSdk}", androidSdkLibDirectory: "swift-x86_64", ndkDirectory: "x86_64-linux-android"] +] + +def generatedJniLibsDir = layout.buildDirectory.dir("generated/jniLibs") +def swiftSdkPath = "${getSwiftSDKPath().absolutePath}/${sdkName}" + +def buildSwiftAll = tasks.register("buildSwiftAll") { + group = "build" + description = "Builds the Swift code for all Android ABIs." + + inputs.file(new File(projectDir, "Package.swift")) + inputs.dir(new File(layout.projectDirectory.asFile, "Sources/HelloCppSwift".toString())) + + outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}")) + + File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile + if (!baseSwiftPluginOutputsDir.exists()) { + baseSwiftPluginOutputsDir.mkdirs() + } + Files.walk(layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile.toPath()).each { + if (it.endsWith("JExtractSwiftPlugin/src/generated/java")) { + outputs.dir(it) + } + } +} + +abis.each { abi, info -> + def task = tasks.register("buildSwift${abi.capitalize()}", Exec) { + group = "build" + description = "Builds the Swift code for the ${abi} ABI." + + doFirst { + println("Building Swift for ${abi} (${info.triple})...") + } + + outputs.dir(layout.projectDirectory.dir(".build/${info.triple}/debug")) + + workingDir = layout.projectDirectory + executable(getSwiftlyPath()) + args("run", "swift", "build", "+${swiftVersion}", "--swift-sdk", info.triple) + } + + buildSwiftAll.configure { dependsOn(task) } +} + +def copyJniLibs = tasks.register("copyJniLibs", Copy) { + dependsOn(buildSwiftAll) + + abis.each { abi, info -> + from(layout.projectDirectory.dir(".build/${info.triple}/debug")) { + include("*.so") + into(abi) + } + + from(file("${swiftSdkPath}/swift-android/ndk-sysroot/usr/lib/${info.ndkDirectory}/libc++_shared.so")) { + into(abi) + } + + doFirst { + println("Copying Swift runtime libraries for ${abi}...") + } + + from(swiftRuntimeLibs.collect { libName -> + "${swiftSdkPath}/swift-android/swift-resources/usr/lib/${info.androidSdkLibDirectory}/android/lib${libName}.so" + }) { + into(abi) + } + } + + into(generatedJniLibsDir) +} + +android { + sourceSets { + main { + java { + srcDir(buildSwiftAll) + } + + jniLibs { + srcDir(generatedJniLibsDir) + } + } + } +} + +preBuild.dependsOn(copyJniLibs) diff --git a/settings.gradle.kts b/settings.gradle.kts index 0be2778..0ded51f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,3 +36,7 @@ include(":hello-swift-raw-jni-library") // native-only examples include(":native-activity") + +// cpp-swift example +include(":hello-cpp-swift:swift-lib") +include(":hello-cpp-swift:app")