Skip to content

Native Libraries

Alexandr Esilevich edited this page Apr 8, 2024 · 21 revisions

This article describes how to use native prebuilt libraries with the Swift toolchain for Android. An example of such a library is the sqlite3 library, which is required for using the GRDB.swift and SQLite.swift libraries.

There are two ways to define and use native libraries in SPM manifest:

  1. Define a binary target using the .binaryTarget method
  2. Define a system library target using the .systemLibrary method

The scd build tool from the Swift toolchain for Android supports both methods and automatically embeds all required dynamic libraries into the resulting Android archive. However, using the .systemLibrary method requires passing additional include and library paths to the scd build tool.

Binary targets

The binary target is the preferred method for using prebuilt libraries with the Swift toolchain for Android because it does not require passing additional flags to the scd build tool. This method should be used when developing a new SPM package intended for use on Android.

The .binaryTarget method in the SPM manifest defines a new binary target, which references either a local XCFramework or a remote archive containing an XCFramework. The Swift toolchain for Android and the scd build tool both support Android platforms within XCFrameworks.

Creating XCFramework for Android

A generic XCFramework has the following layout in filesystem:

<name>.xcframework
 ├ Info.plist
 ├ <platform-1>
 │  ├ Headers
 │  │  ├ module.modulemap
 │  │  └ <header files for platform-1>
 │  └ lib<name>.so
 ├ <platform-2>
 │  ├ Headers
 │  │  ├ module.modulemap
 │  │  └ <header files for platform-2>
 │  └ lib<name>.so

The Info.plist manifest describes supported platforms and paths to dynamic libraries and header files for each platform. Here is an example of the Info.plist manifest for the foo library for Android arm64 and x86_64 platforms:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>AvailableLibraries</key>
    <array>
        <dict>
            <key>BinaryPath</key>
            <string>libfoo.so</string>
            <key>HeadersPath</key>
            <string>Headers</string>
            <key>LibraryIdentifier</key>
            <string>android-arm64-v8a</string>
            <key>LibraryPath</key>
            <string>libfoo.so</string>
            <key>SupportedArchitectures</key>
            <array>
                <string>aarch64</string>
            </array>
            <key>SupportedPlatform</key>
            <string>android</string>
        </dict>
        <dict>
            <key>BinaryPath</key>
            <string>libfoo.so</string>
            <key>HeadersPath</key>
            <string>Headers</string>
            <key>LibraryIdentifier</key>
            <string>android-x86_64</string>
            <key>LibraryPath</key>
            <string>libfoo.so</string>
            <key>SupportedArchitectures</key>
            <array>
                <string>x86_64</string>
            </array>
            <key>SupportedPlatform</key>
            <string>android</string>
        </dict>
    </array>
    <key>CFBundlePackageType</key>
    <string>XFWK</string>
    <key>XCFrameworkFormatVersion</key>
    <string>1.0</string>
</dict>
</plist>

The corresponding filesystem layout for this XCFramework should be the following:

foo.xcframework
 ├ Info.plist
 ├ android-arm64-v8a
 │  ├ Headers
 │  │  ├ module.modulemap
 │  │  └ foo.h
 │  └ libfoo.so
 ├ android-x86_64
 │  ├ Headers
 │  │  ├ module.modulemap
 │  │  └ foo.h
 │  └ libfoo.so

Adding XCFramework in SPM project

To use XCFramework in the Package.swift manifest, add the following in the list of targets:

    .binaryTarget(
        name: "foo",
        path: "relative/path/to/foo.xcframework"
    )

After that, you can use the foo target in the list of dependencies for any target in the SPM project.

Read more about XCFramework support in SPM projects: https://github.com/apple/swift-evolution/blob/main/proposals/0272-swiftpm-binary-dependencies.md

Example of using XCFramework in SPM projects: https://github.com/scade-platform/swift-android-toolchain-examples/tree/main/BinaryTargetExample

System libraries

The system library method should be used when you already have an existing SPM package that relies on a system library. In such cases, you can use the Swift toolchain for Android to build the package, providing a prebuilt native library as a replacement for the system library.

Adding system library into SPM project

First you need to create a source directory for a system library inside the Sources subdirectory in an SPM project. This directory should contain a module.modulemap file for a library and an "umbrella" header file that includes all required library headers. Here is the example of modulemap file for a foo system library that should be located in the Sources/foo subdirectory:

module foo [extern_c] {
    header "foo-shims.h"
    link "foo"
    export *
}

This modulemap file instructs SPM to include the foo-shims.h header when compiling Swift sources and to link the foo library in all dependent products. The foo-shims.h header should be located within the source directory for the system library and should contain include directives for actual foo library headers (i.e., #include <foo.h>).

To define a system library, add the following code in the targets section of the SPM project manifest:

    .systemLibrary(
        name: "foo",
        path: "Sources/foo"
    )

After that, you can use the foo target in the list of dependencies for other targets in the SPM project.

Passing library paths to the scd build tool

To make sure the scd build tool can correctly find all required include directories for a system library, and to embed all required dynamic libraries into the resulting package, you need to pass the following flags when executing the scd command. Here is an example of executing the scd command for the libfoo system library with header files located in the libfoo/include subdirectory and prebuilt shared libraries located in the libfoo/build/android-arm64-v8a and libfoo/build/android-x86_64 subdirectories:

scd archive \
        --type android \
        -Xcc -Ilibfoo/include \
        --platform android-arm64-v8a -Xlinker:android-arm64-v8a -Llibfoo/build/android-arm64-v8a --embed-library:android-arm64-v8a=libfoo/build/android-arm64-v8a/libfoo.so \
        --platform x86_64 -Xlinker:android-x86_64 -Llibfoo/build/android-x86_64 --embed-library:android-x86_64=libfoo/build/android-x86_64/libfoo.so

Example of using .systemLibrary fro Android: https://github.com/scade-platform/swift-android-toolchain-examples/tree/main/SystemLibraryExample

Sources

  1. .binaryTarget example: https://github.com/scade-platform/swift-android-toolchain-examples/tree/main/BinaryTargetExample
  2. .systemLibrary example: https://github.com/scade-platform/swift-android-toolchain-examples/tree/main/SystemLibraryExample
  3. SQLite.swift example: https://github.com/scade-platform/swift-android-toolchain-examples/tree/main/SQLiteSwiftExample
  4. GRDB.swift example: https://github.com/scade-platform/swift-android-toolchain-examples/tree/main/GRDBExample