Skip to content

Android NDK version needs to be visible in Swift #81402

Open
@marcprux

Description

@marcprux

The proposed Android SDK bundle (#80788) will depend on an external NDK being configured. The NDK changes various headers in between their releases, especially around nullability annotations being aded or removed, which affects Swift's view of the C functions and structures.

This means that in practice, some packages that rely on the definitions in the NDK headers will fail to build with earlier or later versions. For example, swift-nio builds successfully for Android with NDK 27, but fails for NDK 26 and 28:

swift-nio failure for NDK 26
[1618/1660] Compiling NIOPosix Linux.swift
/Users/runner/work/swift-package-builds/swift-package-builds/vapor/.build/checkouts/swift-nio/Sources/NIOPosix/System.swift:130:96: error: C function pointer signature '@Sendable (Int32, UnsafeMutablePointer<msghdr>?, Int32) -> Int' is not compatible with expected type '@convention(c) (CInt, UnsafeMutablePointer<msghdr>, CInt) -> ssize_t' (aka '@convention(c) (Int32, UnsafeMutablePointer<msghdr>, Int32) -> Int')
 128 | #endif
 129 | #if canImport(Android)
 130 | private let sysRecvMsg: @convention(c) (CInt, UnsafeMutablePointer<msghdr>, CInt) -> ssize_t = recvmsg
     |                                                                                                `- error: C function pointer signature '@Sendable (Int32, UnsafeMutablePointer<msghdr>?, Int32) -> Int' is not compatible with expected type '@convention(c) (CInt, UnsafeMutablePointer<msghdr>, CInt) -> ssize_t' (aka '@convention(c) (Int32, UnsafeMutablePointer<msghdr>, Int32) -> Int')
 131 | private let sysSendMsg: @convention(c) (CInt, UnsafePointer<msghdr>, CInt) -> ssize_t = sendmsg
 132 | #elseif !os(Windows)

/Users/runner/work/swift-package-builds/swift-package-builds/vapor/.build/checkouts/swift-nio/Sources/NIOPosix/System.swift:131:89: error: C function pointer signature '@Sendable (Int32, UnsafePointer<msghdr>?, Int32) -> Int' is not compatible with expected type '@convention(c) (CInt, UnsafePointer<msghdr>, CInt) -> ssize_t' (aka '@convention(c) (Int32, UnsafePointer<msghdr>, Int32) -> Int')
 129 | #if canImport(Android)
 130 | private let sysRecvMsg: @convention(c) (CInt, UnsafeMutablePointer<msghdr>, CInt) -> ssize_t = recvmsg
 131 | private let sysSendMsg: @convention(c) (CInt, UnsafePointer<msghdr>, CInt) -> ssize_t = sendmsg
     |                                                                                         `- error: C function pointer signature '@Sendable (Int32, UnsafePointer<msghdr>?, Int32) -> Int' is not compatible with expected type '@convention(c) (CInt, UnsafePointer<msghdr>, CInt) -> ssize_t' (aka '@convention(c) (Int32, UnsafePointer<msghdr>, Int32) -> Int')
 132 | #elseif !os(Windows)
 133 | private let sysRecvMsg: @convention(c) (CInt, UnsafeMutablePointer<msghdr>?, CInt) -> ssize_t = recvmsg

/Users/runner/work/swift-package-builds/swift-package-builds/vapor/.build/checkouts/swift-nio/Sources/NIOPosix/System.swift:139:102: error: C function pointer signature '@Sendable (Int32, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> Int32' (aka '@Sendable (Int32, Optional<UnsafeMutablePointer<sockaddr>>, Optional<UnsafeMutablePointer<UInt32>>) -> Int32') is not compatible with expected type '@convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt' (aka '@convention(c) (Int32, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<UInt32>) -> Int32')
 137 | #if canImport(Android)
 138 | private let sysGetpeername:
 139 |     @convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt = getpeername
     |                                                                                                      `- error: C function pointer signature '@Sendable (Int32, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> Int32' (aka '@Sendable (Int32, Optional<UnsafeMutablePointer<sockaddr>>, Optional<UnsafeMutablePointer<UInt32>>) -> Int32') is not compatible with expected type '@convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt' (aka '@convention(c) (Int32, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<UInt32>) -> Int32')
 140 | private let sysGetsockname:
 141 |     @convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt = getsockname

/Users/runner/work/swift-package-builds/swift-package-builds/vapor/.build/checkouts/swift-nio/Sources/NIOPosix/System.swift:141:102: error: C function pointer signature '@Sendable (Int32, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> Int32' (aka '@Sendable (Int32, Optional<UnsafeMutablePointer<sockaddr>>, Optional<UnsafeMutablePointer<UInt32>>) -> Int32') is not compatible with expected type '@convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt' (aka '@convention(c) (Int32, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<UInt32>) -> Int32')
 139 |     @convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt = getpeername
 140 | private let sysGetsockname:
 141 |     @convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt = getsockname
     |                                                                                                      `- error: C function pointer signature '@Sendable (Int32, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> Int32' (aka '@Sendable (Int32, Optional<UnsafeMutablePointer<sockaddr>>, Optional<UnsafeMutablePointer<UInt32>>) -> Int32') is not compatible with expected type '@convention(c) (CInt, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<socklen_t>) -> CInt' (aka '@convention(c) (Int32, UnsafeMutablePointer<sockaddr>, UnsafeMutablePointer<UInt32>) -> Int32')
 142 | #elseif !os(Windows)
 143 | private let sysGetpeername:
swift-nio failure for NDK 28
[333/417] Compiling NIOPosix VsockAddress.swift
/Users/runner/work/swift-package-builds/swift-package-builds/swift-nio/Sources/NIOPosix/ThreadPosix.swift:66:9: error: cannot convert value of type 'ThreadDestructor' (aka '@convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer') to expected argument type '@convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?'
 64 |         &handleLinux,
 65 |         nil,
 66 |         destructor,
    |         `- error: cannot convert value of type 'ThreadDestructor' (aka '@convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer') to expected argument type '@convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?'
 67 |         args
 68 |     )

This is similar to needing to be able to check the Android API level described at #76671, except it is a build-time constant indicated by __NDK_MAJOR__ that wouldn't affect anything at runtime. Android API levels and NDK versions are unrelated (e.g., you can build for API 34+NDK 26 or API 33+NDK 27 or API 29+NDK 28), and so the Android API level cannot be used as a proxy for the NDK version.

The "just do it in C" solution might look like this:

#if __has_include(<android/ndk-version.h>)
#  include <android/ndk-version.h>
#if __NDK_MAJOR__ >= 27
struct adapted_struct = (compatible cast)some_ndk_struct_changed_in_27
#else
struct adapted_struct = (compatible cast)some_ndk_struct_in_26
#endif
#endif

A crude solution might be to have the Android SDK post-install script add per-NDK build variables for everything up to the current NDK, so that when building against NDK 26, swiftc would look like:

swiftc -DANDROID_NDK25 -DANDROID_NDK26 File.swift

and when building against NDK 27, it would look like:

swiftc -DANDROID_NDK25 -DANDROID_NDK26 -DANDROID_NDK27 File.swift

That way the Swift code could perform checks like:

#if os(Android)
#if ANDROID_NDK28
typealias adapted_func_signature = (Int) -> (Int)
#elseif ANDROID_NDK27
// NDK 27 changed nullability for this signature
typealias adapted_func_signature = (Int?) -> (Int)
#elseif ANDROID_NDK26
// NDK 28 changed nullability *again* for this signature!
typealias adapted_func_signature = (Int?) -> (Int?)
#else
#error("This package needs NDK 26+")
#endif
let param: adapted_func_signature = {  }
let result = call_ndk_func(param)
#endif

A solution built into Swift might look like:

#if _ndkVersion(>=27)
…
#else
…
#endif

Metadata

Metadata

Assignees

No one assigned

    Labels

    AndroidPlatform: Androidcross-compilationArea → utils: Cross-compilation of project sources

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions