Skip to content

Commit e56d806

Browse files
committed
Add Embedded Swift support to the _TestDiscovery target.
This PR adds preliminary/experimental support for Embedded Swift _to the `_TestDiscovery` target only_ when building Swift Testing as a package. To try it out, you must set the environment variable `SWT_EMBEDDED` to `true` before building. Tested with the following incantation using the 2025-03-28 main-branch toolchain: ```sh SWT_EMBEDDED=1 swift build --target _TestDiscovery --triple arm64-apple-macosx ```
1 parent 1d28fa4 commit e56d806

File tree

4 files changed

+129
-32
lines changed

4 files changed

+129
-32
lines changed

Diff for: Package.swift

+86-18
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,53 @@ let git = Context.gitInformation
2020
/// distribution as a package dependency.
2121
let buildingForDevelopment = (git?.currentTag == nil)
2222

23+
/// Whether or not this package is being built for Embedded Swift.
24+
///
25+
/// This value is `true` if `SWT_EMBEDDED` is set in the environment to `true`
26+
/// when `swift build` is invoked. This inference is experimental and is subject
27+
/// to change in the future.
28+
///
29+
/// - Bug: There is currently no way for us to tell if we are being asked to
30+
/// build for an Embedded Swift target at the package manifest level.
31+
/// ([swift-syntax-#8431](https://github.com/swiftlang/swift-package-manager/issues/8431))
32+
let buildingForEmbedded: Bool = {
33+
guard let envvar = Context.environment["SWT_EMBEDDED"] else {
34+
return false
35+
}
36+
let result = Bool(envvar) ?? ((Int(envvar) ?? 0) != 0)
37+
if result {
38+
print("Building for Embedded Swift...")
39+
}
40+
return result
41+
}()
42+
2343
let package = Package(
2444
name: "swift-testing",
2545

26-
platforms: [
27-
.macOS(.v10_15),
28-
.iOS(.v13),
29-
.watchOS(.v6),
30-
.tvOS(.v13),
31-
.macCatalyst(.v13),
32-
.visionOS(.v1),
33-
],
46+
platforms: {
47+
if !buildingForEmbedded {
48+
[
49+
.macOS(.v10_15),
50+
.iOS(.v13),
51+
.watchOS(.v6),
52+
.tvOS(.v13),
53+
.macCatalyst(.v13),
54+
.visionOS(.v1),
55+
]
56+
} else {
57+
// Open-source main-branch toolchains (currently required to build this
58+
// package for Embedded Swift) have higher Apple platform deployment
59+
// targets than we would otherwise require.
60+
[
61+
.macOS(.v14),
62+
.iOS(.v18),
63+
.watchOS(.v10),
64+
.tvOS(.v18),
65+
.macCatalyst(.v18),
66+
.visionOS(.v1),
67+
]
68+
}
69+
}(),
3470

3571
products: {
3672
var result = [Product]()
@@ -185,6 +221,31 @@ package.targets.append(contentsOf: [
185221
])
186222
#endif
187223

224+
extension BuildSettingCondition {
225+
/// Creates a build setting condition that evaluates to `true` for Embedded
226+
/// Swift.
227+
///
228+
/// - Parameters:
229+
/// - nonEmbeddedCondition: The value to return if the target is not
230+
/// Embedded Swift. If `nil`, the build condition evaluates to `false`.
231+
///
232+
/// - Returns: A build setting condition that evaluates to `true` for Embedded
233+
/// Swift or is equal to `nonEmbeddedCondition` for non-Embedded Swift.
234+
static func whenEmbedded(or nonEmbeddedCondition: @autoclosure () -> Self? = nil) -> Self? {
235+
if !buildingForEmbedded {
236+
if let nonEmbeddedCondition = nonEmbeddedCondition() {
237+
nonEmbeddedCondition
238+
} else {
239+
// The caller did not supply a fallback.
240+
.when(platforms: [])
241+
}
242+
} else {
243+
// Enable unconditionally because the target is Embedded Swift.
244+
nil
245+
}
246+
}
247+
}
248+
188249
extension Array where Element == PackageDescription.SwiftSetting {
189250
/// Settings intended to be applied to every Swift target in this package.
190251
/// Analogous to project-level build settings in an Xcode project.
@@ -197,6 +258,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
197258

198259
result += [
199260
.enableUpcomingFeature("ExistentialAny"),
261+
.enableExperimentalFeature("Embedded", .whenEmbedded()),
200262

201263
.enableExperimentalFeature("AccessLevelOnImport"),
202264
.enableUpcomingFeature("InternalImportsByDefault"),
@@ -214,11 +276,14 @@ extension Array where Element == PackageDescription.SwiftSetting {
214276

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

217-
.define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
218-
.define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
219-
.define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])),
220-
.define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])),
221-
.define("SWT_NO_PIPES", .when(platforms: [.wasi])),
279+
.define("SWT_NO_EXIT_TESTS", .whenEmbedded(or: .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android]))),
280+
.define("SWT_NO_PROCESS_SPAWNING", .whenEmbedded(or: .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android]))),
281+
.define("SWT_NO_SNAPSHOT_TYPES", .whenEmbedded(or: .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android]))),
282+
.define("SWT_NO_DYNAMIC_LINKING", .whenEmbedded(or: .when(platforms: [.wasi]))),
283+
.define("SWT_NO_PIPES", .whenEmbedded(or: .when(platforms: [.wasi]))),
284+
285+
.define("SWT_NO_LEGACY_TEST_DISCOVERY", .whenEmbedded()),
286+
.define("SWT_NO_LIBDISPATCH", .whenEmbedded()),
222287
]
223288

224289
return result
@@ -271,11 +336,14 @@ extension Array where Element == PackageDescription.CXXSetting {
271336
var result = Self()
272337

273338
result += [
274-
.define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
275-
.define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
276-
.define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])),
277-
.define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])),
278-
.define("SWT_NO_PIPES", .when(platforms: [.wasi])),
339+
.define("SWT_NO_EXIT_TESTS", .whenEmbedded(or: .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android]))),
340+
.define("SWT_NO_PROCESS_SPAWNING", .whenEmbedded(or: .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android]))),
341+
.define("SWT_NO_SNAPSHOT_TYPES", .whenEmbedded(or: .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android]))),
342+
.define("SWT_NO_DYNAMIC_LINKING", .whenEmbedded(or: .when(platforms: [.wasi]))),
343+
.define("SWT_NO_PIPES", .whenEmbedded(or: .when(platforms: [.wasi]))),
344+
345+
.define("SWT_NO_LEGACY_TEST_DISCOVERY", .whenEmbedded()),
346+
.define("SWT_NO_LIBDISPATCH", .whenEmbedded()),
279347
]
280348

281349
// Capture the testing library's version as a C++ string constant.

Diff for: Sources/_TestDiscovery/DiscoverableAsTestContent.swift

+2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ public protocol DiscoverableAsTestContent: Sendable, ~Copyable {
4848
#endif
4949
}
5050

51+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
5152
extension DiscoverableAsTestContent where Self: ~Copyable {
5253
public static var _testContentTypeNameHint: String {
5354
"__🟡$"
5455
}
5556
}
57+
#endif

Diff for: Sources/_TestDiscovery/TestContentKind.swift

+2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ extension TestContentKind: Equatable, Hashable {
5252
}
5353
}
5454

55+
#if !hasFeature(Embedded)
5556
// MARK: - Codable
5657

5758
extension TestContentKind: Codable {}
59+
#endif
5860

5961
// MARK: - ExpressibleByStringLiteral, ExpressibleByIntegerLiteral
6062

Diff for: Sources/_TestDiscovery/TestContentRecord.swift

+39-14
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,34 @@ public struct TestContentRecord<T> where T: DiscoverableAsTestContent & ~Copyabl
139139
/// The type of the `hint` argument to ``load(withHint:)``.
140140
public typealias Hint = T.TestContentAccessorHint
141141

142+
/// Invoke an accessor function to load a test content record.
143+
///
144+
/// - Parameters:
145+
/// - accessor: The accessor function to call.
146+
/// - typeAddress: A pointer to the type of test content record.
147+
/// - hint: An optional hint value.
148+
///
149+
/// - Returns: An instance of the test content type `T`, or `nil` if the
150+
/// underlying test content record did not match `hint` or otherwise did not
151+
/// produce a value.
152+
///
153+
/// Do not call this function directly. Instead, call ``load(withHint:)``.
154+
private static func _load(using accessor: _TestContentRecordAccessor, withTypeAt typeAddress: UnsafeRawPointer, withHint hint: Hint? = nil) -> T? {
155+
withUnsafeTemporaryAllocation(of: T.self, capacity: 1) { buffer in
156+
let initialized = if let hint {
157+
withUnsafePointer(to: hint) { hint in
158+
accessor(buffer.baseAddress!, typeAddress, hint, 0)
159+
}
160+
} else {
161+
accessor(buffer.baseAddress!, typeAddress, nil, 0)
162+
}
163+
guard initialized else {
164+
return nil
165+
}
166+
return buffer.baseAddress!.move()
167+
}
168+
}
169+
142170
/// Load the value represented by this record.
143171
///
144172
/// - Parameters:
@@ -157,21 +185,14 @@ public struct TestContentRecord<T> where T: DiscoverableAsTestContent & ~Copyabl
157185
return nil
158186
}
159187

160-
return withUnsafePointer(to: T.self) { type in
161-
withUnsafeTemporaryAllocation(of: T.self, capacity: 1) { buffer in
162-
let initialized = if let hint {
163-
withUnsafePointer(to: hint) { hint in
164-
accessor(buffer.baseAddress!, type, hint, 0)
165-
}
166-
} else {
167-
accessor(buffer.baseAddress!, type, nil, 0)
168-
}
169-
guard initialized else {
170-
return nil
171-
}
172-
return buffer.baseAddress!.move()
173-
}
188+
#if !hasFeature(Embedded)
189+
return withUnsafePointer(to: T.self) { typeAddress in
190+
Self._load(using: accessor, withTypeAt: typeAddress, withHint: hint)
174191
}
192+
#else
193+
let typeAddress = UnsafeRawPointer(bitPattern: UInt(T.testContentKind.rawValue)).unsafelyUnwrapped
194+
return Self._load(using: accessor, withTypeAt: typeAddress, withHint: hint)
195+
#endif
175196
}
176197
}
177198

@@ -188,7 +209,11 @@ extension TestContentRecord: Sendable where Context: Sendable {}
188209

189210
extension TestContentRecord: CustomStringConvertible {
190211
public var description: String {
212+
#if !hasFeature(Embedded)
191213
let typeName = String(describing: Self.self)
214+
#else
215+
let typeName = "TestContentRecord"
216+
#endif
192217
switch _recordStorage {
193218
case let .atAddress(recordAddress):
194219
let recordAddress = imageAddress.map { imageAddress in

0 commit comments

Comments
 (0)