Skip to content

Commit f2c8ee1

Browse files
grynspancompnerd
andauthored
Add basic platform support for Android. (#653)
This PR seeks out all the nooks and crannies where we have platform-specific code or logic and adds Android. In most cases, it's as simple as changing `os(Linux)` to `os(Linux) || os(Android)` but there are a few spots where they diverge. The PR should be _mostly_ self-explanatory. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated. Co-authored-by: Saleem Abdulrasool <[email protected]>
1 parent 4c1286e commit f2c8ee1

19 files changed

+70
-31
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
130130

131131
.define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])),
132132

133-
.define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi])),
133+
.define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
134134
.define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .windows, .wasi])),
135135
.define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])),
136136
]

Sources/Testing/ABI/EntryPoints/EntryPoint.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ extension Event.ConsoleOutputRecorder.Options {
651651
/// Whether or not the system terminal claims to support 16-color ANSI escape
652652
/// codes.
653653
private static var _terminalSupports16ColorANSIEscapeCodes: Bool {
654-
#if SWT_TARGET_OS_APPLE || os(Linux)
654+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android)
655655
if let termVariable = Environment.variable(named: "TERM") {
656656
return termVariable != "dumb"
657657
}
@@ -673,7 +673,7 @@ extension Event.ConsoleOutputRecorder.Options {
673673
/// Whether or not the system terminal claims to support 256-color ANSI escape
674674
/// codes.
675675
private static var _terminalSupports256ColorANSIEscapeCodes: Bool {
676-
#if SWT_TARGET_OS_APPLE || os(Linux)
676+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android)
677677
if let termVariable = Environment.variable(named: "TERM") {
678678
return strstr(termVariable, "256") != nil
679679
}
@@ -695,7 +695,7 @@ extension Event.ConsoleOutputRecorder.Options {
695695
/// Whether or not the system terminal claims to support true-color ANSI
696696
/// escape codes.
697697
private static var _terminalSupportsTrueColorANSIEscapeCodes: Bool {
698-
#if SWT_TARGET_OS_APPLE || os(Linux)
698+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android)
699699
if let colortermVariable = Environment.variable(named: "COLORTERM") {
700700
return strstr(colortermVariable, "truecolor") != nil
701701
}

Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private import _TestingInternals
2424
///
2525
/// This constant is not part of the public interface of the testing library.
2626
var EXIT_NO_TESTS_FOUND: CInt {
27-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
27+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
2828
EX_UNAVAILABLE
2929
#elseif os(Windows)
3030
CInt(ERROR_NOT_FOUND)

Sources/Testing/Events/Recorder/Event.Symbol.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ extension Event.Symbol {
100100
/// be used to represent it in text-based output. The value of this property
101101
/// is platform-dependent.
102102
public var unicodeCharacter: Character {
103-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
103+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
104104
switch self {
105105
case .default:
106106
// Unicode: WHITE DIAMOND

Sources/Testing/SourceAttribution/Backtrace.swift

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ public struct Backtrace: Sendable {
7171
}
7272
#elseif os(Linux)
7373
initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count)))
74+
#elseif os(Android)
75+
addresses.withMemoryRebound(to: UnsafeMutableRawPointer.self) { addresses in
76+
initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count)))
77+
}
7478
#elseif os(Windows)
7579
initializedCount = Int(RtlCaptureStackBackTrace(0, ULONG(addresses.count), addresses.baseAddress!, nil))
7680
#elseif os(WASI)

Sources/Testing/Support/Additions/CommandLineAdditions.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ extension CommandLine {
2929
}
3030
}
3131
return result!
32-
#elseif os(Linux)
32+
#elseif os(Linux) || os(Android)
3333
return try withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(PATH_MAX) * 2) { buffer in
34-
let readCount = readlink("/proc/\(getpid())/exe", buffer.baseAddress!, buffer.count - 1)
34+
let readCount = readlink("/proc/self/exe", buffer.baseAddress!, buffer.count - 1)
3535
guard readCount >= 0 else {
3636
throw CError(rawValue: swt_errno())
3737
}

Sources/Testing/Support/Environment.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ enum Environment {
4242
}
4343
}
4444

45-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
45+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
4646
/// Get all environment variables from a POSIX environment block.
4747
///
4848
/// - Parameters:
@@ -103,7 +103,7 @@ enum Environment {
103103
}
104104
#endif
105105
return _get(fromEnviron: _NSGetEnviron()!.pointee!)
106-
#elseif os(Linux)
106+
#elseif os(Linux) || os(Android)
107107
_get(fromEnviron: swt_environ())
108108
#elseif os(WASI)
109109
_get(fromEnviron: __wasilibc_get_environ())
@@ -170,7 +170,7 @@ enum Environment {
170170
}
171171
return nil
172172
}
173-
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
173+
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
174174
getenv(name).flatMap { String(validatingCString: $0) }
175175
#elseif os(Windows)
176176
name.withCString(encodedAs: UTF16.self) { name in

Sources/Testing/Support/FileHandle.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ struct FileHandle: ~Copyable, Sendable {
156156
/// descriptor, `nil` is passed to `body`.
157157
borrowing func withUnsafePOSIXFileDescriptor<R>(_ body: (CInt?) throws -> R) rethrows -> R {
158158
try withUnsafeCFILEHandle { handle in
159-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
159+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
160160
let fd = fileno(handle)
161161
#elseif os(Windows)
162162
let fd = _fileno(handle)
@@ -215,7 +215,7 @@ struct FileHandle: ~Copyable, Sendable {
215215
/// other threads.
216216
borrowing func withLock<R>(_ body: () throws -> R) rethrows -> R {
217217
try withUnsafeCFILEHandle { handle in
218-
#if SWT_TARGET_OS_APPLE || os(Linux)
218+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android)
219219
flockfile(handle)
220220
defer {
221221
funlockfile(handle)
@@ -250,7 +250,7 @@ extension FileHandle {
250250
// If possible, reserve enough space in the resulting buffer to contain
251251
// the contents of the file being read.
252252
var size: Int?
253-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
253+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
254254
withUnsafePOSIXFileDescriptor { fd in
255255
var s = stat()
256256
if let fd, 0 == fstat(fd, &s) {
@@ -388,7 +388,7 @@ extension FileHandle {
388388
extension FileHandle {
389389
/// Is this file handle a TTY or PTY?
390390
var isTTY: Bool {
391-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
391+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
392392
// If stderr is a TTY and TERM is set, that's good enough for us.
393393
withUnsafePOSIXFileDescriptor { fd in
394394
if let fd, 0 != isatty(fd), let term = Environment.variable(named: "TERM"), !term.isEmpty {
@@ -414,7 +414,7 @@ extension FileHandle {
414414

415415
/// Is this file handle a pipe or FIFO?
416416
var isPipe: Bool {
417-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
417+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
418418
withUnsafePOSIXFileDescriptor { fd in
419419
guard let fd else {
420420
return false

Sources/Testing/Support/GetSymbol.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal import _TestingInternals
1313
#if !SWT_NO_DYNAMIC_LINKING
1414

1515
/// The platform-specific type of a loaded image handle.
16-
#if SWT_TARGET_OS_APPLE || os(Linux)
16+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android)
1717
typealias ImageAddress = UnsafeMutableRawPointer
1818
#elseif os(Windows)
1919
typealias ImageAddress = HMODULE
@@ -30,7 +30,9 @@ typealias ImageAddress = Never
3030
/// declare a wrapper function in the internal module's Stubs.h file.
3131
#if SWT_TARGET_OS_APPLE
3232
private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: -2)
33-
#elseif os(Linux)
33+
#elseif os(Android) && _pointerBitWidth(_32)
34+
private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0xFFFFFFFF)
35+
#elseif os(Linux) || os(Android)
3436
private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0)
3537
#endif
3638

@@ -57,7 +59,7 @@ private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0)
5759
/// calling `EnumProcessModules()` and iterating over the returned handles
5860
/// looking for one containing the given function.
5961
func symbol(in handle: ImageAddress? = nil, named symbolName: String) -> UnsafeRawPointer? {
60-
#if SWT_TARGET_OS_APPLE || os(Linux)
62+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android)
6163
dlsym(handle ?? RTLD_DEFAULT, symbolName).map(UnsafeRawPointer.init)
6264
#elseif os(Windows)
6365
symbolName.withCString { symbolName in

Sources/Testing/Support/Locked.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
3636
/// To keep the implementation of this type as simple as possible,
3737
/// `pthread_mutex_t` is used on Apple platforms instead of `os_unfair_lock`
3838
/// or `OSAllocatedUnfairLock`.
39-
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
39+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
4040
private typealias _Lock = pthread_mutex_t
4141
#elseif os(Windows)
4242
private typealias _Lock = SRWLOCK
@@ -52,7 +52,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
5252
private final class _Storage: ManagedBuffer<T, _Lock> {
5353
deinit {
5454
withUnsafeMutablePointerToElements { lock in
55-
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
55+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
5656
_ = pthread_mutex_destroy(lock)
5757
#elseif os(Windows)
5858
// No deinitialization needed.
@@ -71,7 +71,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
7171
init(rawValue: T) {
7272
_storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue })
7373
_storage.withUnsafeMutablePointerToElements { lock in
74-
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
74+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
7575
_ = pthread_mutex_init(lock, nil)
7676
#elseif os(Windows)
7777
InitializeSRWLock(lock)
@@ -101,7 +101,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
101101
/// concurrency tools.
102102
nonmutating func withLock<R>(_ body: (inout T) throws -> R) rethrows -> R {
103103
try _storage.withUnsafeMutablePointers { rawValue, lock in
104-
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
104+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
105105
_ = pthread_mutex_lock(lock)
106106
defer {
107107
_ = pthread_mutex_unlock(lock)
@@ -121,7 +121,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
121121
}
122122
}
123123

124-
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
124+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
125125
/// Acquire the lock and invoke a function while it is held, yielding both the
126126
/// protected value and a reference to the lock itself.
127127
///

Sources/Testing/Support/Versions.swift

+23
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ let operatingSystemVersion: String = {
5555
return "\(release) (\(version))"
5656
}
5757
}
58+
#elseif os(Android)
59+
if let version = systemProperty(named: "ro.build.version.release") {
60+
return "Android \(version)"
61+
}
5862
#elseif os(Windows)
5963
// See if we can query the kernel directly, bypassing the fake-out logic added
6064
// in Windows 10 and later that misreports the OS version. GetVersionExW()
@@ -170,3 +174,22 @@ func sysctlbyname(_ name: String, as _: String.Type) -> String? {
170174
}
171175
}
172176
#endif
177+
178+
#if os(Android)
179+
/// Get the Android system property with the given name.
180+
///
181+
/// - Parameters:
182+
/// - name: The name of the system property to get.
183+
///
184+
/// - Returns: The value of the requested system property, or `nil` if it could
185+
/// not be read or could not be converted to a string.
186+
func systemProperty(named name: String) -> String? {
187+
withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(PROP_VALUE_MAX)) { buffer in
188+
let length = __system_property_get(name, buffer.baseAddress!)
189+
if length > 0 {
190+
return String(validatingCString: buffer.baseAddress!)
191+
}
192+
return nil
193+
}
194+
}
195+
#endif

Sources/Testing/Traits/Tags/Tag.Color+Loading.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ var swiftTestingDirectoryPath: String? {
6161
if let homeDirectoryPath = _homeDirectoryPath {
6262
return appendPathComponent(swiftTestingDirectoryName, to: homeDirectoryPath)
6363
}
64-
#elseif SWT_TARGET_OS_APPLE
64+
#elseif SWT_TARGET_OS_APPLE || os(Android)
6565
// Other Apple/Darwin platforms do not support the concept of a home
6666
// directory. One exists for the current user, but it's not something that
6767
// actually contains user-configurable data like a .swift-testing directory.
68+
// Android also does not support per-user home directories (does it?)
6869
#elseif os(Windows)
6970
if let appDataDirectoryPath = _appDataDirectoryPath {
7071
return appendPathComponent(swiftTestingDirectoryName, to: appDataDirectoryPath)

Sources/_TestingInternals/Discovery.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
306306
}
307307
}
308308

309-
#elif defined(__linux__) || defined(_WIN32) || defined(__wasi__)
309+
#elif defined(__linux__) || defined(_WIN32) || defined(__wasi__) || defined(__ANDROID__)
310310
#pragma mark - Linux/Windows implementation
311311

312312
/// Specifies the address range corresponding to a section.

Sources/_TestingInternals/include/Includes.h

+5
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,9 @@
120120
#include <Psapi.h>
121121
#endif
122122

123+
#if defined(__ANDROID__)
124+
#pragma clang module import posix_filesystem.linux_stat
125+
#include <sys/system_properties.h>
126+
#endif
127+
123128
#endif

Sources/_TestingInternals/include/Stubs.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static LANGID swt_MAKELANGID(int p, int s) {
7474
}
7575
#endif
7676

77-
#if defined(__linux__)
77+
#if defined(__linux__) || defined(__ANDROID__)
7878
/// The environment block.
7979
///
8080
/// By POSIX convention, the environment block variable is declared in client
@@ -97,6 +97,7 @@ static char *_Nullable *_Null_unspecified swt_environ(void) {
9797
SWT_IMPORT_FROM_STDLIB int pthread_setname_np(pthread_t, const char *);
9898
#endif
9999

100+
#if !defined(__ANDROID__)
100101
#if __has_include(<signal.h>) && defined(si_pid)
101102
/// Get the value of the `si_pid` field of a `siginfo_t` structure.
102103
///
@@ -120,6 +121,7 @@ static int swt_siginfo_t_si_status(const siginfo_t *siginfo) {
120121
return siginfo->si_status;
121122
}
122123
#endif
124+
#endif
123125

124126
#if defined(__wasi__)
125127
/// Get the version of the C standard library and runtime used by WASI, if

Tests/TestingTests/ABIEntryPointTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ struct ABIEntryPointTests {
127127
passing arguments: __CommandLineArguments_v0,
128128
recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void = { _ in }
129129
) async throws -> Bool {
130-
#if !os(Linux) && !SWT_NO_DYNAMIC_LINKING
130+
#if !os(Linux) && !os(Android) && !SWT_NO_DYNAMIC_LINKING
131131
// Get the ABI entry point by dynamically looking it up at runtime.
132132
//
133133
// NOTE: The standard Linux linker does not allow exporting symbols from

Tests/TestingTests/Support/EnvironmentTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ extension Environment {
9090
environment[name] = value
9191
}
9292
return true
93-
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
93+
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI)
9494
if let value {
9595
return 0 == setenv(name, value, 1)
9696
}

Tests/TestingTests/Support/FileHandleTests.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private import _TestingInternals
1414
#if !SWT_NO_FILE_IO
1515
// NOTE: we don't run these tests on iOS (etc.) because processes on those
1616
// platforms are sandboxed and do not have arbitrary filesystem access.
17-
#if os(macOS) || os(Linux) || os(Windows)
17+
#if os(macOS) || os(Linux) || os(Android) || os(Windows)
1818
@Suite("FileHandle Tests")
1919
struct FileHandleTests {
2020
// FileHandle is non-copyable, so it cannot yet be used as a test parameter.
@@ -226,6 +226,8 @@ func temporaryDirectory() throws -> String {
226226
}
227227
#elseif os(Linux)
228228
"/tmp"
229+
#elseif os(Android)
230+
Environment.variable(named: "TMPDIR") ?? "/data/local/tmp"
229231
#elseif os(Windows)
230232
try withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: Int(MAX_PATH + 1)) { buffer in
231233
// NOTE: GetTempPath2W() was introduced in Windows 10 Build 20348.

cmake/modules/shared/CompilerSettings.cmake

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ add_compile_options(
2020
if(APPLE)
2121
add_compile_definitions("SWT_TARGET_OS_APPLE")
2222
endif()
23-
set(SWT_NO_EXIT_TESTS_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI")
23+
set(SWT_NO_EXIT_TESTS_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI" "Android")
2424
if(CMAKE_SYSTEM_NAME IN_LIST SWT_NO_EXIT_TESTS_LIST)
2525
add_compile_definitions("SWT_NO_EXIT_TESTS")
2626
endif()

0 commit comments

Comments
 (0)