From 0ff56ca9e1b1b7ebe29ad785032fbf3953a01834 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 13 Mar 2026 21:20:58 +0000 Subject: [PATCH] Add Emscripten platform support --- Package.swift | 12 ++++++------ Sources/Testing/ABI/EntryPoints/EntryPoint.swift | 6 +++--- .../Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift | 2 +- Sources/Testing/CMakeLists.txt | 8 +++++--- Sources/Testing/ExitTests/ExitStatus.swift | 2 +- Sources/Testing/SourceAttribution/Backtrace.swift | 2 +- .../Support/Additions/CommandLineAdditions.swift | 2 +- Sources/Testing/Support/Environment.swift | 10 +++++----- Sources/Testing/Support/FileHandle.swift | 10 +++++----- Sources/Testing/Support/Serializer.swift | 2 +- Sources/Testing/Support/Versions.swift | 3 ++- Sources/Testing/Traits/Tags/Tag.Color+Loading.swift | 2 +- Sources/_TestDiscovery/SectionBounds.swift | 2 +- Sources/_TestingInternals/include/Stubs.h | 2 +- cmake/modules/shared/CompilerSettings.cmake | 8 ++++---- 15 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Package.swift b/Package.swift index cdbe7d612..15c81032b 100644 --- a/Package.swift +++ b/Package.swift @@ -510,14 +510,14 @@ extension Array where Element: _LanguageBuildSetting { // building for Embedded. (This is not currently expressible as a build // setting conditional.) let defines: [String: (platforms: [Platform]?, embedded: Bool)] = [ - "SWT_NO_EXIT_TESTS": (platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android], embedded: true), - "SWT_NO_PROCESS_SPAWNING": (platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android], embedded: true), + "SWT_NO_EXIT_TESTS": (platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .custom("emscripten"), .android], embedded: true), + "SWT_NO_PROCESS_SPAWNING": (platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .custom("emscripten"), .android], embedded: true), "SWT_NO_SNAPSHOT_TYPES": (platforms: .nonApplePlatforms, embedded: true), - "SWT_NO_DYNAMIC_LINKING": (platforms: [.wasi], embedded: true), - "SWT_NO_PIPES": (platforms: [.wasi], embedded: true), + "SWT_NO_DYNAMIC_LINKING": (platforms: [.wasi, .custom("emscripten")], embedded: true), + "SWT_NO_PIPES": (platforms: [.wasi, .custom("emscripten")], embedded: true), "SWT_NO_FOUNDATION_FILE_COORDINATION": (platforms: .nonApplePlatforms, embedded: true), - "SWT_NO_IMAGE_ATTACHMENTS": (platforms: [.linux, .custom("freebsd"), .openbsd, .wasi, .android], embedded: true), - "SWT_NO_FILE_CLONING": (platforms: [.openbsd, .wasi, .android], embedded: true), + "SWT_NO_IMAGE_ATTACHMENTS": (platforms: [.linux, .custom("freebsd"), .openbsd, .wasi, .custom("emscripten"), .android], embedded: true), + "SWT_NO_FILE_CLONING": (platforms: [.openbsd, .wasi, .custom("emscripten"), .android], embedded: true), "SWT_NO_ABI_ENTRY_POINT": (platforms: .none, embedded: true), "SWT_NO_ABI_JSON_SCHEMA": (platforms: .none, embedded: true), "SWT_NO_CODABLE": (platforms: .none, embedded: true), diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index ecd2069db..bb53eb827 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -805,7 +805,7 @@ extension Event.ConsoleOutputRecorder.Options { // Windows does not set the "TERM" variable, so assume it supports 16-color // ANSI escape codes. true -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // The "Terminal" under WASI can be assumed to be the browser's JavaScript // console, which we don't expect supports color escape codes. false @@ -827,7 +827,7 @@ extension Event.ConsoleOutputRecorder.Options { // Windows does not set the "TERM" variable, so assume it supports 256-color // ANSI escape codes. true -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // The "Terminal" under WASI can be assumed to be the browser's JavaScript // console, which we don't expect supports color escape codes. false @@ -849,7 +849,7 @@ extension Event.ConsoleOutputRecorder.Options { // Windows does not set the "COLORTERM" variable, so assume it supports // true-color ANSI escape codes. SEE: https://github.com/microsoft/terminal/issues/11057 true -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // The "Terminal" under WASI can be assumed to be the browser's JavaScript // console, which we don't expect supports color escape codes. false diff --git a/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift b/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift index 3c72e9f20..26091d166 100644 --- a/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift @@ -24,7 +24,7 @@ private import _TestingInternals /// /// This constant is not part of the public interface of the testing library. var EXIT_NO_TESTS_FOUND: CInt { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) EX_UNAVAILABLE #elseif os(Windows) CInt(ERROR_NOT_FOUND) diff --git a/Sources/Testing/CMakeLists.txt b/Sources/Testing/CMakeLists.txt index f10fc18fb..e232b883d 100644 --- a/Sources/Testing/CMakeLists.txt +++ b/Sources/Testing/CMakeLists.txt @@ -135,12 +135,14 @@ target_link_libraries(Testing PRIVATE _TestingInternals _TestingInterop) if(NOT APPLE) - if(NOT CMAKE_SYSTEM_NAME STREQUAL WASI) + if(NOT CMAKE_SYSTEM_NAME STREQUAL WASI AND NOT CMAKE_SYSTEM_NAME STREQUAL Emscripten) target_link_libraries(Testing PUBLIC dispatch) endif() - target_link_libraries(Testing PUBLIC - Foundation) + if(NOT CMAKE_SYSTEM_NAME STREQUAL Emscripten) + target_link_libraries(Testing PUBLIC + Foundation) + endif() if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") target_link_libraries(Testing PUBLIC execinfo) diff --git a/Sources/Testing/ExitTests/ExitStatus.swift b/Sources/Testing/ExitTests/ExitStatus.swift index af9575e43..bec8ab30e 100644 --- a/Sources/Testing/ExitTests/ExitStatus.swift +++ b/Sources/Testing/ExitTests/ExitStatus.swift @@ -143,7 +143,7 @@ extension ExitStatus: CustomStringConvertible { #endif #elseif os(Windows) result = windowsSignals[signal] -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // These platforms do not have API to get the programmatic name of a // signal constant. #else diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 97dd9e89c..284af4878 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -75,7 +75,7 @@ public struct Backtrace: Sendable { initializedCount = .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count))) #elseif os(Windows) initializedCount = Int(clamping: RtlCaptureStackBackTrace(0, ULONG(clamping: addresses.count), addresses.baseAddress!, nil)) -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // SEE: https://github.com/WebAssembly/WASI/issues/159 // SEE: https://github.com/swiftlang/swift/pull/31693 #else diff --git a/Sources/Testing/Support/Additions/CommandLineAdditions.swift b/Sources/Testing/Support/Additions/CommandLineAdditions.swift index fa51c9550..9ca820554 100644 --- a/Sources/Testing/Support/Additions/CommandLineAdditions.swift +++ b/Sources/Testing/Support/Additions/CommandLineAdditions.swift @@ -124,7 +124,7 @@ extension CommandLine { } } return result! -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // WASI does not really have the concept of a file system path to the main // executable, so simply return the first argument--presumably the program // name, but as you know this is not guaranteed by the C standard! diff --git a/Sources/Testing/Support/Environment.swift b/Sources/Testing/Support/Environment.swift index bef8b77e9..046e1fa99 100644 --- a/Sources/Testing/Support/Environment.swift +++ b/Sources/Testing/Support/Environment.swift @@ -46,7 +46,7 @@ package enum Environment { } } -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) /// Get all environment variables from a POSIX environment block. /// /// - Parameters: @@ -102,7 +102,7 @@ package enum Environment { nil #elseif SWT_TARGET_OS_APPLE _NSGetEnviron()?.pointee -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(Emscripten) swt_environ() #elseif os(WASI) __wasilibc_get_environ() @@ -120,7 +120,7 @@ package enum Environment { package static func get() -> [String: String] { #if SWT_NO_ENVIRONMENT_VARIABLES simulatedEnvironment.rawValue -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) #if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING _environ_lock_np?() defer { @@ -193,7 +193,7 @@ package enum Environment { } return nil } -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) getenv(name).flatMap { String(validatingCString: $0) } #elseif os(Windows) name.withCString(encodedAs: UTF16.self) { name in @@ -277,7 +277,7 @@ extension Environment { environment[name] = value } return true -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) if let value { return 0 == setenv(name, value, 1) } diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index 338fd064c..111080fe6 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -309,7 +309,7 @@ struct FileHandle: ~Copyable, Sendable { /// descriptor, `nil` is passed to `body`. borrowing func withUnsafePOSIXFileDescriptor(_ body: (CInt?) throws -> R) rethrows -> R { try withUnsafeCFILEHandle { handle in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) let fd = fileno(handle) #elseif os(Windows) let fd = _fileno(handle) @@ -368,7 +368,7 @@ struct FileHandle: ~Copyable, Sendable { /// other threads. borrowing func withLock(_ body: () throws -> R) rethrows -> R { try withUnsafeCFILEHandle { handle in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(Emscripten) flockfile(handle) defer { funlockfile(handle) @@ -403,7 +403,7 @@ extension FileHandle { // If possible, reserve enough space in the resulting buffer to contain // the contents of the file being read. var size: Int? -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) withUnsafePOSIXFileDescriptor { fd in var s = stat() if let fd, 0 == fstat(fd, &s) { @@ -667,7 +667,7 @@ extension FileHandle { extension FileHandle { /// Is this file handle a TTY or PTY? var isTTY: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) // If stderr is a TTY and TERM is set, that's good enough for us. withUnsafePOSIXFileDescriptor { fd in if let fd, 0 != isatty(fd), let term = Environment.variable(named: "TERM"), !term.isEmpty { @@ -769,7 +769,7 @@ func fileExists(atPath path: String) -> Bool { /// resolved, the resulting string may differ slightly but refers to the same /// file system object. If the path could not be resolved, returns `nil`. func canonicalizePath(_ path: String) -> String? { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) path.withCString { path in if let resolvedCPath = realpath(path, nil) { defer { diff --git a/Sources/Testing/Support/Serializer.swift b/Sources/Testing/Support/Serializer.swift index b7bb997f7..3cb0fc663 100644 --- a/Sources/Testing/Support/Serializer.swift +++ b/Sources/Testing/Support/Serializer.swift @@ -20,7 +20,7 @@ private var _cpuCoreCount: Int? { var siInfo = SYSTEM_INFO() GetSystemInfo(&siInfo) return Int(siInfo.dwNumberOfProcessors) -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) return 1 #else #warning("Platform-specific implementation missing: CPU core count unavailable") diff --git a/Sources/Testing/Support/Versions.swift b/Sources/Testing/Support/Versions.swift index 3b4ff2414..3d3290257 100644 --- a/Sources/Testing/Support/Versions.swift +++ b/Sources/Testing/Support/Versions.swift @@ -88,9 +88,10 @@ let operatingSystemVersion: String = { return result } } -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // WASI does not have an API to get the current WASI or Wasm version. // wasi-libc does have uname(3), but it's stubbed out. + // Emscripten's uname(3) is similarly limited. #else #warning("Platform-specific implementation missing: OS version unavailable") #endif diff --git a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift index 7b2d85ad7..ecaa56177 100644 --- a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift +++ b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift @@ -70,7 +70,7 @@ var swiftTestingDirectoryPath: String? { if let appDataDirectoryPath = _appDataDirectoryPath { return appendPathComponent(swiftTestingDirectoryName, to: appDataDirectoryPath) } -#elseif os(WASI) +#elseif os(WASI) || os(Emscripten) // WASI does not support the concept of a home directory. #else #warning("Platform-specific implementation missing: .swift-testing directory location unavailable") diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 6c5ab63bb..1443a4974 100644 --- a/Sources/_TestDiscovery/SectionBounds.swift +++ b/Sources/_TestDiscovery/SectionBounds.swift @@ -346,7 +346,7 @@ private struct _SectionBound: Sendable, ~Copyable { @_silgen_name(raw: "section$start$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound @_silgen_name(raw: "section$end$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound #endif -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) || os(Emscripten) @_silgen_name(raw: "__start_swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound @_silgen_name(raw: "__stop_swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound #if !SWT_NO_LEGACY_TEST_DISCOVERY diff --git a/Sources/_TestingInternals/include/Stubs.h b/Sources/_TestingInternals/include/Stubs.h index ec7e09706..acc112e1a 100644 --- a/Sources/_TestingInternals/include/Stubs.h +++ b/Sources/_TestingInternals/include/Stubs.h @@ -139,7 +139,7 @@ static const IMAGE_SECTION_HEADER *_Null_unspecified swt_IMAGE_FIRST_SECTION(con } #endif -#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__ANDROID__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__ANDROID__) || defined(__EMSCRIPTEN__) /// The environment block. /// /// By POSIX convention, the environment block variable is declared in client diff --git a/cmake/modules/shared/CompilerSettings.cmake b/cmake/modules/shared/CompilerSettings.cmake index 78bd035bf..9cda64395 100644 --- a/cmake/modules/shared/CompilerSettings.cmake +++ b/cmake/modules/shared/CompilerSettings.cmake @@ -30,11 +30,11 @@ add_compile_definitions("SWT_BUILDING_SWIFT_TESTING_CONTENT") if(APPLE) add_compile_definitions("SWT_TARGET_OS_APPLE") endif() -set(SWT_NO_EXIT_TESTS_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI" "Android") +set(SWT_NO_EXIT_TESTS_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI" "Emscripten" "Android") if(CMAKE_SYSTEM_NAME IN_LIST SWT_NO_EXIT_TESTS_LIST) add_compile_definitions("SWT_NO_EXIT_TESTS") endif() -set(SWT_NO_PROCESS_SPAWNING_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI" "Android") +set(SWT_NO_PROCESS_SPAWNING_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI" "Emscripten" "Android") if(CMAKE_SYSTEM_NAME IN_LIST SWT_NO_PROCESS_SPAWNING_LIST) add_compile_definitions("SWT_NO_PROCESS_SPAWNING") endif() @@ -42,14 +42,14 @@ if(NOT APPLE) add_compile_definitions("SWT_NO_SNAPSHOT_TYPES") add_compile_definitions("SWT_NO_FOUNDATION_FILE_COORDINATION") endif() -if(CMAKE_SYSTEM_NAME STREQUAL "WASI") +if(CMAKE_SYSTEM_NAME STREQUAL "WASI" OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten") add_compile_definitions("SWT_NO_DYNAMIC_LINKING") add_compile_definitions("SWT_NO_PIPES") endif() if (NOT (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Windows")) add_compile_definitions("SWT_NO_IMAGE_ATTACHMENTS") endif() -set(SWT_NO_FILE_CLONING_LIST "OpenBSD" "WASI" "Android") +set(SWT_NO_FILE_CLONING_LIST "OpenBSD" "WASI" "Emscripten" "Android") if(CMAKE_SYSTEM_NAME IN_LIST SWT_NO_FILE_CLONING_LIST) add_compile_definitions("SWT_NO_FILE_CLONING") endif()