diff --git a/Package.swift b/Package.swift index 67c0614cd..e3415ade8 100644 --- a/Package.swift +++ b/Package.swift @@ -222,7 +222,11 @@ let package = Package( dependencies: ["_TestingInternals",], path: "Sources/_TestingInterop", exclude: ["CMakeLists.txt"], - cxxSettings: .packageSettings(), + cxxSettings: .packageSettings() + [ + .unsafeFlags([ + "-fno-cxx-modules", + ]), + ], swiftSettings: .packageSettings() + .moduleABIName("_TestingInterop") ), diff --git a/Sources/Overlays/_Testing_AppKit/CMakeLists.txt b/Sources/Overlays/_Testing_AppKit/CMakeLists.txt index 7f2425340..41c722b92 100644 --- a/Sources/Overlays/_Testing_AppKit/CMakeLists.txt +++ b/Sources/Overlays/_Testing_AppKit/CMakeLists.txt @@ -21,4 +21,5 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") -emit-module-interface -emit-module-interface-path $/_Testing_AppKit.swiftinterface) _swift_testing_install_target(_Testing_AppKit) + _swift_testing_install_swiftmodule(_Testing_AppKit) endif() diff --git a/Sources/Overlays/_Testing_CoreGraphics/CMakeLists.txt b/Sources/Overlays/_Testing_CoreGraphics/CMakeLists.txt index bb57c5574..aaa376248 100644 --- a/Sources/Overlays/_Testing_CoreGraphics/CMakeLists.txt +++ b/Sources/Overlays/_Testing_CoreGraphics/CMakeLists.txt @@ -23,4 +23,5 @@ if(APPLE) -emit-module-interface -emit-module-interface-path $/_Testing_CoreGraphics.swiftinterface) _swift_testing_install_target(_Testing_CoreGraphics) + _swift_testing_install_swiftmodule(_Testing_CoreGraphics) endif() diff --git a/Sources/Overlays/_Testing_CoreImage/CMakeLists.txt b/Sources/Overlays/_Testing_CoreImage/CMakeLists.txt index 326f13b91..73358c46c 100644 --- a/Sources/Overlays/_Testing_CoreImage/CMakeLists.txt +++ b/Sources/Overlays/_Testing_CoreImage/CMakeLists.txt @@ -21,4 +21,5 @@ if(APPLE) -emit-module-interface -emit-module-interface-path $/_Testing_CoreImage.swiftinterface) _swift_testing_install_target(_Testing_CoreImage) + _swift_testing_install_swiftmodule(_Testing_CoreImage) endif() diff --git a/Sources/Overlays/_Testing_Foundation/CMakeLists.txt b/Sources/Overlays/_Testing_Foundation/CMakeLists.txt index d5922df06..33f6fa093 100644 --- a/Sources/Overlays/_Testing_Foundation/CMakeLists.txt +++ b/Sources/Overlays/_Testing_Foundation/CMakeLists.txt @@ -41,3 +41,4 @@ target_compile_options(_Testing_Foundation PRIVATE -emit-module-interface -emit-module-interface-path $/_Testing_Foundation.swiftinterface) _swift_testing_install_target(_Testing_Foundation) +_swift_testing_install_swiftmodule(_Testing_Foundation) diff --git a/Sources/Overlays/_Testing_UIKit/CMakeLists.txt b/Sources/Overlays/_Testing_UIKit/CMakeLists.txt index e54ef95db..c4f8224a6 100644 --- a/Sources/Overlays/_Testing_UIKit/CMakeLists.txt +++ b/Sources/Overlays/_Testing_UIKit/CMakeLists.txt @@ -21,4 +21,5 @@ if(APPLE) -emit-module-interface -emit-module-interface-path $/_Testing_UIKit.swiftinterface) _swift_testing_install_target(_Testing_UIKit) + _swift_testing_install_swiftmodule(_Testing_UIKit) endif() diff --git a/Sources/Overlays/_Testing_WinSDK/CMakeLists.txt b/Sources/Overlays/_Testing_WinSDK/CMakeLists.txt index e73e818bc..de6b410f7 100644 --- a/Sources/Overlays/_Testing_WinSDK/CMakeLists.txt +++ b/Sources/Overlays/_Testing_WinSDK/CMakeLists.txt @@ -28,4 +28,5 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") -emit-module-interface -emit-module-interface-path $/_Testing_WinSDK.swiftinterface) _swift_testing_install_target(_Testing_WinSDK) + _swift_testing_install_swiftmodule(_Testing_WinSDK) endif() diff --git a/Sources/Testing/CMakeLists.txt b/Sources/Testing/CMakeLists.txt index f10fc18fb..76f841fe0 100644 --- a/Sources/Testing/CMakeLists.txt +++ b/Sources/Testing/CMakeLists.txt @@ -161,6 +161,7 @@ target_compile_options(Testing PRIVATE -emit-module-interface -emit-module-interface-path $/Testing.swiftinterface) _swift_testing_install_target(Testing) +_swift_testing_install_swiftmodule(Testing) # Install the Swift cross-import overlay directory. _swift_testing_install_swiftcrossimport(Testing diff --git a/Sources/Testing/Events/Event+FallbackEventHandler.swift b/Sources/Testing/Events/Event+FallbackEventHandler.swift index ed98d5ad1..c246da2c4 100644 --- a/Sources/Testing/Events/Event+FallbackEventHandler.swift +++ b/Sources/Testing/Events/Event+FallbackEventHandler.swift @@ -8,7 +8,7 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // -private import _TestingInternals +private import _TestingInternals.InteropOnly @_spi(Experimental) @_spi(ForToolsIntegrationOnly) public enum Interop: Sendable {} diff --git a/Sources/_TestDiscovery/CMakeLists.txt b/Sources/_TestDiscovery/CMakeLists.txt index efa20223b..f88595f28 100644 --- a/Sources/_TestDiscovery/CMakeLists.txt +++ b/Sources/_TestDiscovery/CMakeLists.txt @@ -23,6 +23,7 @@ target_compile_options(_TestDiscovery PRIVATE set(CMAKE_STATIC_LIBRARY_PREFIX_Swift "lib") _swift_testing_install_target(_TestDiscovery) +_swift_testing_install_swiftmodule(_TestDiscovery) if(NOT BUILD_SHARED_LIBS) # When building a static library, install the internal library archive diff --git a/Sources/_TestingInternals/CMakeLists.txt b/Sources/_TestingInternals/CMakeLists.txt index e1ca291e2..b2bc5b6b1 100644 --- a/Sources/_TestingInternals/CMakeLists.txt +++ b/Sources/_TestingInternals/CMakeLists.txt @@ -35,9 +35,4 @@ if(NOT BUILD_SHARED_LIBS) # is linked into the main library and does not need to be installed separately. install(TARGETS _TestingInternals ARCHIVE DESTINATION "${SwiftTesting_INSTALL_LIBDIR}") - - # We don't necessarily want to export _TestingInternals, but it is unavoidable - # when building statically since _TestingInterop depends on it - # https://gitlab.kitware.com/cmake/cmake/-/issues/20041 - set_property(GLOBAL APPEND PROPERTY SwiftTesting_EXPORTS _TestingInternals) endif() diff --git a/Sources/_TestingInternals/include/FallbackEventHandler.h b/Sources/_TestingInternals/include/FallbackEventHandler.h new file mode 100644 index 000000000..f54f5f63c --- /dev/null +++ b/Sources/_TestingInternals/include/FallbackEventHandler.h @@ -0,0 +1,57 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#if !defined(SWT_FALLBACK_EVENT_HANDLER_H) +#define SWT_FALLBACK_EVENT_HANDLER_H + +#if !defined(SWT_NO_INTEROP) +#include "Defines.h" +#include "Includes.h" + +SWT_ASSUME_NONNULL_BEGIN + +/// A type describing a fallback event handler that testing API can invoke as an +/// alternate method of reporting test events to the current test runner. +/// +/// - Parameters: +/// - recordJSONSchemaVersionNumber: The JSON schema version used to encode +/// the event record. +/// - recordJSONBaseAddress: A pointer to the first byte of the encoded event. +/// - recordJSONByteCount: The size of the encoded event in bytes. +/// - reserved: Reserved for future use. +typedef void (* SWT_SENDABLE SWTFallbackEventHandler)( + const char *recordJSONSchemaVersionNumber, + const void *recordJSONBaseAddress, + size_t recordJSONByteCount, + const void *_Nullable reserved +); + +/// Set the current fallback event handler if one has not already been set. +/// +/// - Parameters: +/// - handler: The handler function to set. +/// +/// - Returns: Whether or not `handler` was installed. +/// +/// The fallback event handler can only be installed once per process, typically +/// by the first testing library to run. If this function has already been +/// called and the handler set, it does not replace the previous handler. +SWT_EXTERN bool _swift_testing_installFallbackEventHandler(SWTFallbackEventHandler SWT_SENDABLE handler); + +/// Get the current fallback event handler. +/// Shadows the function with the same name in _TestingInterop. +/// +/// - Returns: The currently-set handler function, if any. +SWT_EXTERN SWTFallbackEventHandler SWT_SENDABLE _Nullable _swift_testing_getFallbackEventHandler(void); + +SWT_ASSUME_NONNULL_END + +#endif +#endif diff --git a/Sources/_TestingInternals/include/Stubs.h b/Sources/_TestingInternals/include/Stubs.h index ec7e09706..c62d985c9 100644 --- a/Sources/_TestingInternals/include/Stubs.h +++ b/Sources/_TestingInternals/include/Stubs.h @@ -275,43 +275,6 @@ static const char *_Nullable swt_getExitCodeName(int exitCode) { #undef SWT_SYSEXIT_CODE }; -#if !SWT_NO_INTEROP - -/// A type describing a fallback event handler that testing API can invoke as an -/// alternate method of reporting test events to the current test runner. -/// Shadows the type with the same name in _TestingInterop. -/// -/// - Parameters: -/// - recordJSONSchemaVersionNumber: The JSON schema version used to encode -/// the event record. -/// - recordJSONBaseAddress: A pointer to the first byte of the encoded event. -/// - recordJSONByteCount: The size of the encoded event in bytes. -/// - reserved: Reserved for future use. -typedef void (* SWTFallbackEventHandler)(const char *recordJSONSchemaVersionNumber, - const void *recordJSONBaseAddress, - size_t recordJSONByteCount, - const void *_Nullable reserved); - -/// Set the current fallback event handler if one has not already been set. -/// -/// - Parameters: -/// - handler: The handler function to set. -/// -/// - Returns: Whether or not `handler` was installed. -/// -/// The fallback event handler can only be installed once per process, typically -/// by the first testing library to run. If this function has already been -/// called and the handler set, it does not replace the previous handler. -SWT_EXTERN bool _swift_testing_installFallbackEventHandler(SWTFallbackEventHandler handler); - -/// Get the current fallback event handler. -/// Shadows the function with the same name in _TestingInterop. -/// -/// - Returns: The currently-set handler function, if any. -SWT_EXTERN SWTFallbackEventHandler _Nullable _swift_testing_getFallbackEventHandler(void); - -#endif - SWT_ASSUME_NONNULL_END #endif diff --git a/Sources/_TestingInternals/include/module.modulemap b/Sources/_TestingInternals/include/module.modulemap index c820c7bd8..93b2f1b32 100644 --- a/Sources/_TestingInternals/include/module.modulemap +++ b/Sources/_TestingInternals/include/module.modulemap @@ -16,4 +16,9 @@ module _TestingInternals { header "Stubs.h" export * } + + explicit module InteropOnly { + header "FallbackEventHandler.h" + export * + } } diff --git a/Sources/_TestingInterop/CMakeLists.txt b/Sources/_TestingInterop/CMakeLists.txt index 3269e7fcf..4a4271ae3 100644 --- a/Sources/_TestingInterop/CMakeLists.txt +++ b/Sources/_TestingInterop/CMakeLists.txt @@ -8,19 +8,20 @@ include(ModuleABIName) add_library(_TestingInterop - FallbackEventHandler.swift) - -target_link_libraries(_TestingInterop PRIVATE - _TestingInternals) -if(NOT BUILD_SHARED_LIBS) - # When building a static library, tell clients to autolink the internal - # libraries. + FallbackEventHandler.cpp) +target_include_directories(_TestingInterop PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include) +if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC" OR + "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(_TestingInterop PRIVATE + /EHa-c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + target_compile_options(_TestingInterop PRIVATE + -fno-exceptions -fPIC) +else() target_compile_options(_TestingInterop PRIVATE - "SHELL:-Xfrontend -public-autolink-library -Xfrontend _TestingInternals") + -fno-exceptions) endif() -target_compile_options(_TestingInterop PRIVATE - -enable-library-evolution - -emit-module-interface -emit-module-interface-path $/_TestingInterop.swiftinterface) _swift_testing_install_target(_TestingInterop) diff --git a/Sources/_TestingInterop/FallbackEventHandler.cpp b/Sources/_TestingInterop/FallbackEventHandler.cpp new file mode 100644 index 000000000..9718e1d85 --- /dev/null +++ b/Sources/_TestingInterop/FallbackEventHandler.cpp @@ -0,0 +1,27 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025–2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#if !defined(SWT_NO_INTEROP) +#include "../_TestingInternals/include/FallbackEventHandler.h" + +#include + +/// Storage for the fallback event handler. +static std::atomic fallbackEventHandler { nullptr }; + +bool _swift_testing_installFallbackEventHandler(SWTFallbackEventHandler SWT_SENDABLE handler) { + SWTFallbackEventHandler nullptrValue = nullptr; + return fallbackEventHandler.compare_exchange_strong(nullptrValue, handler, std::memory_order_seq_cst, std::memory_order_relaxed); +} + +SWTFallbackEventHandler SWT_SENDABLE _swift_testing_getFallbackEventHandler(void) { + return fallbackEventHandler.load(std::memory_order_seq_cst); +} +#endif diff --git a/Sources/_TestingInterop/FallbackEventHandler.swift b/Sources/_TestingInterop/FallbackEventHandler.swift deleted file mode 100644 index e83992258..000000000 --- a/Sources/_TestingInterop/FallbackEventHandler.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for Swift project authors -// - -#if !SWT_NO_INTEROP -#if SWT_TARGET_OS_APPLE && !hasFeature(Embedded) -private import _TestingInternals -#else -private import Synchronization -#endif - -/// `Atomic`-compatible storage for ``FallbackEventHandler``. -private final class _FallbackEventHandlerStorage: Sendable, RawRepresentable { - let rawValue: FallbackEventHandler - - init(rawValue: FallbackEventHandler) { - self.rawValue = rawValue - } -} - -/// The installed event handler. -#if SWT_TARGET_OS_APPLE && !hasFeature(Embedded) -private nonisolated(unsafe) let _fallbackEventHandler = { - let result = UnsafeMutablePointer.allocate(capacity: 1) - result.initialize(to: nil) - return result -}() -#else -private let _fallbackEventHandler = AtomicLazyReference<_FallbackEventHandlerStorage>() -#endif - -/// A type describing a fallback event handler that testing API can invoke as an -/// alternate method of reporting test events to the current test runner. -/// -/// For example, an `XCTAssert` failure in the body of a Swift Testing test -/// cannot record issues directly with the Swift Testing runner. Instead, the -/// framework packages the assertion failure as a JSON `Event` and invokes this -/// handler to report the failure. -/// -/// - Parameters: -/// - recordJSONSchemaVersionNumber: The JSON schema version used to encode -/// the event record. -/// - recordJSONBaseAddress: A pointer to the first byte of the encoded event. -/// - recordJSONByteCount: The size of the encoded event in bytes. -/// - reserved: Reserved for future use. -@usableFromInline -package typealias FallbackEventHandler = @Sendable @convention(c) ( - _ recordJSONSchemaVersionNumber: UnsafePointer, - _ recordJSONBaseAddress: UnsafeRawPointer, - _ recordJSONByteCount: Int, - _ reserved: UnsafeRawPointer? -) -> Void - -/// Get the current fallback event handler. -/// -/// - Returns: The currently-set handler function, if any. -@c -@usableFromInline -package func _swift_testing_getFallbackEventHandler() -> FallbackEventHandler? { -#if SWT_TARGET_OS_APPLE && !hasFeature(Embedded) - guard let unmanaged = swt_atomicLoad(_fallbackEventHandler).map(Unmanaged<_FallbackEventHandlerStorage>.fromOpaque) else { - return nil - } - return unmanaged.takeUnretainedValue().rawValue -#else - // If we had a setter, this load would present a race condition because - // another thread could store a new value in between the load and the call to - // `takeUnretainedValue()`, resulting in a use-after-free on this thread. We - // would need a full lock in order to avoid that problem. However, because we - // instead have a one-time installation function, we can be sure that the - // loaded value (if non-nil) will never be replaced with another value. - return _fallbackEventHandler.load()?.rawValue -#endif -} - -/// Set the current fallback event handler if one has not already been set. -/// -/// - Parameters: -/// - handler: The handler function to set. -/// -/// - Returns: Whether or not `handler` was installed. -/// -/// The fallback event handler can only be installed once per process, typically -/// by the first testing library to run. If this function has already been -/// called and the handler set, it does not replace the previous handler. -@c -@usableFromInline -package func _swift_testing_installFallbackEventHandler(_ handler: FallbackEventHandler) -> CBool { - var result = false - - let handler = _FallbackEventHandlerStorage(rawValue: handler) -#if SWT_TARGET_OS_APPLE && !hasFeature(Embedded) - let unmanaged = Unmanaged.passRetained(handler) - var expectedNil: UnsafeRawPointer? - result = swt_atomicCompareExchange(_fallbackEventHandler, &expectedNil, unmanaged.toOpaque()) - if !result { - unmanaged.release() - } -#else - let stored = _fallbackEventHandler.storeIfNil(handler) - result = (handler === stored) -#endif - - return result -} -#endif diff --git a/Sources/_TestingInterop/include/Interop.h b/Sources/_TestingInterop/include/Interop.h new file mode 100644 index 000000000..93ad2c33c --- /dev/null +++ b/Sources/_TestingInterop/include/Interop.h @@ -0,0 +1,19 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#if !defined(SWT_TESTING_INTEROP_H) +#define SWT_TESTING_INTEROP_H + +/// - Note: Because we do not build the `_TestingInterop` library as part of a +/// package build, this header has no content. The module's symbol(s) are +/// instead declared in headers in the `_TestingInternals` target and can be +/// imported into Swift using the `_TestingInternals.InteropOnly` submodule. + +#endif diff --git a/Tests/TestingTests/EventHandlingInteropTests.swift b/Tests/TestingTests/EventHandlingInteropTests.swift index b479dd484..d0a335a3a 100644 --- a/Tests/TestingTests/EventHandlingInteropTests.swift +++ b/Tests/TestingTests/EventHandlingInteropTests.swift @@ -9,7 +9,7 @@ // @testable @_spi(ForToolsIntegrationOnly) import Testing -private import _TestingInternals +private import _TestingInternals.InteropOnly #if canImport(Foundation) import Foundation diff --git a/cmake/modules/SwiftModuleInstallation.cmake b/cmake/modules/SwiftModuleInstallation.cmake index 6947bb1cd..23ad9af68 100644 --- a/cmake/modules/SwiftModuleInstallation.cmake +++ b/cmake/modules/SwiftModuleInstallation.cmake @@ -8,12 +8,27 @@ ## See https://swift.org/CONTRIBUTORS.txt for Swift project authors ## +# Install the specified module's platform build products (executables, +# libraries, etc.) +# +# The files this function installs are sufficient for Swift programs to link to +# the given module at runtime, but not to build against them. To ensure SDK +# content is emitted for the module, you must also call +# _swift_testing_install_swiftmodule(). function(_swift_testing_install_target module) install(TARGETS ${module} ARCHIVE DESTINATION "${SwiftTesting_INSTALL_LIBDIR}" LIBRARY DESTINATION "${SwiftTesting_INSTALL_LIBDIR}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endfunction() +# Install the specified module's .swiftmodule, .swiftdoc, and .swiftinterface +# products into the appropriate directory. +# +# This function does not install the module's platform build products such as +# .dylib or .exe files. Call _swift_testing_install_target() to ensure those +# files are installed. +function(_swift_testing_install_swiftmodule module) get_target_property(type ${module} TYPE) if(type STREQUAL EXECUTABLE) return()