Skip to content

Commit

Permalink
On Darwin, allow XCTest to be missing if we're only building swift-te…
Browse files Browse the repository at this point in the history
…sting 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/MacOSX14.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.
  • Loading branch information
grynspan committed Mar 25, 2024
1 parent a809fb5 commit a335fa6
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 21 deletions.
19 changes: 12 additions & 7 deletions Sources/Commands/SwiftTestCommand.swift
Expand Up @@ -40,7 +40,7 @@ private enum TestError: Swift.Error {
case testProductNotFound(productName: String)
case productIsNotTest(productName: String)
case multipleTestProducts([String])
case xctestNotAvailable
case xctestNotAvailable(reason: String? = nil)
}

extension TestError: CustomStringConvertible {
Expand All @@ -57,8 +57,12 @@ extension TestError: CustomStringConvertible {
return "invalid list test JSON structure, produced by \(context)\(underlying)"
case .multipleTestProducts(let products):
return "found multiple test products: \(products.joined(separator: ", ")); use --test-product to select one"
case .xctestNotAvailable:
return "XCTest not available"
case let .xctestNotAvailable(reason):
if let reason {
return "XCTest not available: \(reason)"
} else {
return "XCTest not available"
}
}
}
}
Expand Down Expand Up @@ -203,9 +207,10 @@ package struct SwiftTestCommand: AsyncSwiftCommand {
private func xctestRun(_ swiftCommandState: SwiftCommandState) async throws {
// validate XCTest available on darwin based systems
let toolchain = try swiftCommandState.getTargetToolchain()
let isHostTestingAvailable = try swiftCommandState.getHostToolchain().swiftSDK.supportsTesting
if (toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil) || !isHostTestingAvailable {
throw TestError.xctestNotAvailable
if case let .unsupported(reason) = try swiftCommandState.getHostToolchain().swiftSDK.xctestSupport {
throw TestError.xctestNotAvailable(reason: reason)
} else if toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil {
throw TestError.xctestNotAvailable()
}

let buildParameters = try swiftCommandState.buildParametersForTest(options: self.options, library: .xctest)
Expand Down Expand Up @@ -814,7 +819,7 @@ final class TestRunner {
#if os(macOS)
if library == .xctest {
guard let xctestPath = self.toolchain.xctestPath else {
throw TestError.xctestNotAvailable
throw TestError.xctestNotAvailable()
}
args = [xctestPath.pathString]
args += additionalArguments
Expand Down
11 changes: 6 additions & 5 deletions Sources/Commands/Utilities/TestingSupport.swift
Expand Up @@ -178,11 +178,12 @@ enum TestingSupport {
#endif
return env
#else
// Add the sdk platform path if we have it. If this is not present, we might always end up failing.
let sdkPlatformFrameworksPath = try SwiftSDK.sdkPlatformFrameworkPaths()
// appending since we prefer the user setting (if set) to the one we inject
env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString)
env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString)
// Add the sdk platform path if we have it.
if let sdkPlatformFrameworksPath = try? SwiftSDK.sdkPlatformFrameworkPaths() {
// appending since we prefer the user setting (if set) to the one we inject
env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString)
env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString)
}

// Fast path when no sanitizers are enabled.
if sanitizers.isEmpty {
Expand Down
64 changes: 55 additions & 9 deletions Sources/PackageModel/SwiftSDKs/SwiftSDK.swift
Expand Up @@ -147,7 +147,29 @@ public struct SwiftSDK: Equatable {
public var architectures: [String]? = nil

/// Whether or not the receiver supports testing.
public let supportsTesting: Bool
@available(*, deprecated, message: "Use `xctestSupport` instead")
public var supportsTesting: Bool {
if case .supported = xctestSupport {
return true
}
return false
}

/// Whether or not the receiver supports testing using XCTest.
public enum XCTestSupport: Sendable, Equatable {
/// XCTest is supported.
case supported

/// XCTest is not supported.
///
/// - Parameters:
/// - reason: A string explaining why XCTest is not supported. If
/// `nil`, no additional information is available.
case unsupported(reason: String?)
}

/// Whether or not the receiver supports using XCTest.
public let xctestSupport: XCTestSupport

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

/// Creates a Swift SDK with the specified properties.
@available(*, deprecated, message: "use `init(hostTriple:targetTriple:toolset:pathsConfiguration:xctestSupport:)` instead")
public init(
hostTriple: Triple? = nil,
targetTriple: Triple? = nil,
toolset: Toolset,
pathsConfiguration: PathsConfiguration,
supportsTesting: Bool
) {
let xctestSupport: XCTestSupport
if supportsTesting {
xctestSupport = .supported
} else {
xctestSupport = .unsupported(reason: nil)
}

self.init(
hostTriple: hostTriple,
targetTriple: targetTriple,
toolset: toolset,
pathsConfiguration: pathsConfiguration,
xctestSupport: xctestSupport
)
}

/// Creates a Swift SDK with the specified properties.
public init(
hostTriple: Triple? = nil,
targetTriple: Triple? = nil,
toolset: Toolset,
pathsConfiguration: PathsConfiguration,
supportsTesting: Bool = true
xctestSupport: XCTestSupport = .supported
) {
self.hostTriple = hostTriple
self.targetTriple = targetTriple
self.toolset = toolset
self.pathsConfiguration = pathsConfiguration
self.supportsTesting = supportsTesting
self.xctestSupport = xctestSupport
}

/// Returns the bin directory for the host.
Expand Down Expand Up @@ -496,7 +543,7 @@ public struct SwiftSDK: Equatable {
#endif

// Compute common arguments for clang and swift.
let supportsTesting: Bool
let xctestSupport: XCTestSupport
var extraCCFlags: [String] = []
var extraSwiftCFlags: [String] = []
#if os(macOS)
Expand All @@ -506,13 +553,12 @@ public struct SwiftSDK: Equatable {
extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString]
extraSwiftCFlags += ["-I", sdkPaths.lib.pathString]
extraSwiftCFlags += ["-L", sdkPaths.lib.pathString]
supportsTesting = true
xctestSupport = .supported
} catch {
supportsTesting = false
observabilityScope?.emit(warning: "could not determine XCTest paths: \(error)")
xctestSupport = .unsupported(reason: String(describing: error))
}
#else
supportsTesting = true
xctestSupport = .supported
#endif

#if !os(Windows)
Expand All @@ -528,7 +574,7 @@ public struct SwiftSDK: Equatable {
rootPaths: [binDir]
),
pathsConfiguration: .init(sdkRootPath: sdkPath),
supportsTesting: supportsTesting
xctestSupport: xctestSupport
)
}

Expand Down

0 comments on commit a335fa6

Please sign in to comment.