Skip to content

Commit 4e0699f

Browse files
committed
On Darwin, allow XCTest to be missing if we're only building swift-testing tests.
This PR removes the constraint on Darwin that XCTest.framework must be present in order to build tests using swift-testing. On Darwin, XCTest is included as a framework inside Xcode, but if a developer installs the Xcode Command Line Tools instead of the full IDE, XCTest is not included. They then get a diagnostic of the form: > error: XCTest not available: terminated(1): /usr/bin/xcrun --sdk macosx --show-sdk-platform-path output: > xcrun: error: unable to lookup item 'PlatformPath' from command line tools installation > xcrun: error: unable to lookup item 'PlatformPath' in SDK '/Library/Developer/CommandLineTools/SDKs/MacOSX15.0.sdk' Which is a poor experience if they aren't even using XCTest. This change, as a (positive) side effect, suppresses the same diagnostic when running commands that are not usually dependent on the presence of XCTest such as `swift build`. Note that swift-corelibs-xctest is not supported on Darwin, so installing the Xcode Command Line Tools and adding an explicit dependency on swift-corelibs-xctest will not produce a functional test target bundle. Supporting swift-corelibs-xctest on Darwin is a potential future direction. Automated testing for this change is difficult because it relies on a build environment that is not supported in CI (namely the presence of the CL tools but not Xcode nor XCTest.framework.) I have manually tested the change against swift-testing's own test target. A separate PR will be necessary in swift-testing to remove some remaining XCTest dependencies. Those changes are not covered by this PR. Resolves rdar://125372431.
1 parent a809fb5 commit 4e0699f

File tree

3 files changed

+73
-21
lines changed

3 files changed

+73
-21
lines changed

Sources/Commands/SwiftTestCommand.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private enum TestError: Swift.Error {
4040
case testProductNotFound(productName: String)
4141
case productIsNotTest(productName: String)
4242
case multipleTestProducts([String])
43-
case xctestNotAvailable
43+
case xctestNotAvailable(reason: String? = nil)
4444
}
4545

4646
extension TestError: CustomStringConvertible {
@@ -57,8 +57,12 @@ extension TestError: CustomStringConvertible {
5757
return "invalid list test JSON structure, produced by \(context)\(underlying)"
5858
case .multipleTestProducts(let products):
5959
return "found multiple test products: \(products.joined(separator: ", ")); use --test-product to select one"
60-
case .xctestNotAvailable:
61-
return "XCTest not available"
60+
case let .xctestNotAvailable(reason):
61+
if let reason {
62+
return "XCTest not available: \(reason)"
63+
} else {
64+
return "XCTest not available"
65+
}
6266
}
6367
}
6468
}
@@ -203,9 +207,10 @@ package struct SwiftTestCommand: AsyncSwiftCommand {
203207
private func xctestRun(_ swiftCommandState: SwiftCommandState) async throws {
204208
// validate XCTest available on darwin based systems
205209
let toolchain = try swiftCommandState.getTargetToolchain()
206-
let isHostTestingAvailable = try swiftCommandState.getHostToolchain().swiftSDK.supportsTesting
207-
if (toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil) || !isHostTestingAvailable {
208-
throw TestError.xctestNotAvailable
210+
if case let .unsupported(reason) = try swiftCommandState.getHostToolchain().swiftSDK.xctestSupport {
211+
throw TestError.xctestNotAvailable(reason: reason)
212+
} else if toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil {
213+
throw TestError.xctestNotAvailable()
209214
}
210215

211216
let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: .xctest)
@@ -814,7 +819,7 @@ final class TestRunner {
814819
#if os(macOS)
815820
if library == .xctest {
816821
guard let xctestPath = self.toolchain.xctestPath else {
817-
throw TestError.xctestNotAvailable
822+
throw TestError.xctestNotAvailable()
818823
}
819824
args = [xctestPath.pathString]
820825
args += additionalArguments

Sources/Commands/Utilities/TestingSupport.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,12 @@ enum TestingSupport {
178178
#endif
179179
return env
180180
#else
181-
// Add the sdk platform path if we have it. If this is not present, we might always end up failing.
182-
let sdkPlatformFrameworksPath = try SwiftSDK.sdkPlatformFrameworkPaths()
183-
// appending since we prefer the user setting (if set) to the one we inject
184-
env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString)
185-
env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString)
181+
// Add the sdk platform path if we have it.
182+
if let sdkPlatformFrameworksPath = try? SwiftSDK.sdkPlatformFrameworkPaths() {
183+
// appending since we prefer the user setting (if set) to the one we inject
184+
env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString)
185+
env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString)
186+
}
186187

187188
// Fast path when no sanitizers are enabled.
188189
if sanitizers.isEmpty {

Sources/PackageModel/SwiftSDKs/SwiftSDK.swift

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,29 @@ public struct SwiftSDK: Equatable {
147147
public var architectures: [String]? = nil
148148

149149
/// Whether or not the receiver supports testing.
150-
public let supportsTesting: Bool
150+
@available(*, deprecated, message: "Use `xctestSupport` instead")
151+
public var supportsTesting: Bool {
152+
if case .supported = xctestSupport {
153+
return true
154+
}
155+
return false
156+
}
157+
158+
/// Whether or not the receiver supports testing using XCTest.
159+
public enum XCTestSupport: Sendable, Equatable {
160+
/// XCTest is supported.
161+
case supported
162+
163+
/// XCTest is not supported.
164+
///
165+
/// - Parameters:
166+
/// - reason: A string explaining why XCTest is not supported. If
167+
/// `nil`, no additional information is available.
168+
case unsupported(reason: String?)
169+
}
170+
171+
/// Whether or not the receiver supports using XCTest.
172+
public let xctestSupport: XCTestSupport
151173

152174
/// Root directory path of the SDK used to compile for the target triple.
153175
@available(*, deprecated, message: "use `pathsConfiguration.sdkRootPath` instead")
@@ -417,19 +439,44 @@ public struct SwiftSDK: Equatable {
417439
)
418440
}
419441

442+
/// Creates a Swift SDK with the specified properties.
443+
@available(*, deprecated, message: "use `init(hostTriple:targetTriple:toolset:pathsConfiguration:xctestSupport:)` instead")
444+
public init(
445+
hostTriple: Triple? = nil,
446+
targetTriple: Triple? = nil,
447+
toolset: Toolset,
448+
pathsConfiguration: PathsConfiguration,
449+
supportsTesting: Bool
450+
) {
451+
let xctestSupport: XCTestSupport
452+
if supportsTesting {
453+
xctestSupport = .supported
454+
} else {
455+
xctestSupport = .unsupported(reason: nil)
456+
}
457+
458+
self.init(
459+
hostTriple: hostTriple,
460+
targetTriple: targetTriple,
461+
toolset: toolset,
462+
pathsConfiguration: pathsConfiguration,
463+
xctestSupport: xctestSupport
464+
)
465+
}
466+
420467
/// Creates a Swift SDK with the specified properties.
421468
public init(
422469
hostTriple: Triple? = nil,
423470
targetTriple: Triple? = nil,
424471
toolset: Toolset,
425472
pathsConfiguration: PathsConfiguration,
426-
supportsTesting: Bool = true
473+
xctestSupport: XCTestSupport = .supported
427474
) {
428475
self.hostTriple = hostTriple
429476
self.targetTriple = targetTriple
430477
self.toolset = toolset
431478
self.pathsConfiguration = pathsConfiguration
432-
self.supportsTesting = supportsTesting
479+
self.xctestSupport = xctestSupport
433480
}
434481

435482
/// Returns the bin directory for the host.
@@ -496,7 +543,7 @@ public struct SwiftSDK: Equatable {
496543
#endif
497544

498545
// Compute common arguments for clang and swift.
499-
let supportsTesting: Bool
546+
let xctestSupport: XCTestSupport
500547
var extraCCFlags: [String] = []
501548
var extraSwiftCFlags: [String] = []
502549
#if os(macOS)
@@ -506,13 +553,12 @@ public struct SwiftSDK: Equatable {
506553
extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString]
507554
extraSwiftCFlags += ["-I", sdkPaths.lib.pathString]
508555
extraSwiftCFlags += ["-L", sdkPaths.lib.pathString]
509-
supportsTesting = true
556+
xctestSupport = .supported
510557
} catch {
511-
supportsTesting = false
512-
observabilityScope?.emit(warning: "could not determine XCTest paths: \(error)")
558+
xctestSupport = .unsupported(reason: String(describing: error))
513559
}
514560
#else
515-
supportsTesting = true
561+
xctestSupport = .supported
516562
#endif
517563

518564
#if !os(Windows)
@@ -528,7 +574,7 @@ public struct SwiftSDK: Equatable {
528574
rootPaths: [binDir]
529575
),
530576
pathsConfiguration: .init(sdkRootPath: sdkPath),
531-
supportsTesting: supportsTesting
577+
xctestSupport: xctestSupport
532578
)
533579
}
534580

0 commit comments

Comments
 (0)