diff --git a/Sources/SWBBuildService/Messages.swift b/Sources/SWBBuildService/Messages.swift index ab5b43b8..3c2321b6 100644 --- a/Sources/SWBBuildService/Messages.swift +++ b/Sources/SWBBuildService/Messages.swift @@ -302,6 +302,14 @@ private struct SetSessionUserPreferencesMsg: MessageHandler { } } +private struct LookupToolchainMsg: MessageHandler { + func handle(request: Request, message: LookupToolchainRequest) throws -> LookupToolchainResponse { + let session = try request.session(for: message) + let toolchain = try session.core.toolchainRegistry.lookup(path: message.path) + return LookupToolchainResponse(toolchainIdentifier: toolchain?.identifier) + } +} + /// Start a PIF transfer from the client. /// /// This will establish a workspace context in the relevant session by exchanging a PIF from the client to the service incrementally, only transferring subobjects as necessary. @@ -1543,6 +1551,7 @@ public struct ServiceSessionMessageHandlers: ServiceExtension { service.registerMessageHandler(SetSessionUserInfoMsg.self) service.registerMessageHandler(SetSessionUserPreferencesMsg.self) service.registerMessageHandler(DeveloperPathHandler.self) + service.registerMessageHandler(LookupToolchainMsg.self) } } diff --git a/Sources/SWBCore/ToolchainRegistry.swift b/Sources/SWBCore/ToolchainRegistry.swift index 28afaf07..1279156e 100644 --- a/Sources/SWBCore/ToolchainRegistry.swift +++ b/Sources/SWBCore/ToolchainRegistry.swift @@ -540,6 +540,16 @@ public final class ToolchainRegistry: @unchecked Sendable { } } + public func lookup(path: Path) throws -> Toolchain? { + let path = try self.fs.realpath(path) + for toolchain in toolchains { + if try self.fs.realpath(toolchain.path) == path { + return toolchain + } + } + return nil + } + public var defaultToolchain: Toolchain? { return self.lookup("default") } diff --git a/Sources/SWBProtocol/Message.swift b/Sources/SWBProtocol/Message.swift index 1c16ff18..8299c71c 100644 --- a/Sources/SWBProtocol/Message.swift +++ b/Sources/SWBProtocol/Message.swift @@ -632,6 +632,31 @@ public struct SetSessionUserPreferencesRequest: SessionMessage, RequestMessage, } } +public struct LookupToolchainRequest: SessionMessage, RequestMessage, Equatable, SerializableCodable { + public typealias ResponseMessage = LookupToolchainResponse + + public static let name = "LOOKUP_TOOLCHAIN" + + public let sessionHandle: String + + public let path: Path + + public init(sessionHandle: String, path: Path) { + self.sessionHandle = sessionHandle + self.path = path + } +} + +public struct LookupToolchainResponse: Message, Equatable, SerializableCodable { + public static let name = "LOOKUP_TOOLCHAIN_RESPONSE" + + public let toolchainIdentifier: String? + + public init(toolchainIdentifier: String?) { + self.toolchainIdentifier = toolchainIdentifier + } +} + public struct ListSessionsRequest: RequestMessage, Equatable { public typealias ResponseMessage = ListSessionsResponse @@ -1176,6 +1201,8 @@ public struct IPCMessage: Serializable, Sendable { SetSessionSystemInfoRequest.self, SetSessionUserInfoRequest.self, SetSessionUserPreferencesRequest.self, + LookupToolchainRequest.self, + LookupToolchainResponse.self, ListSessionsRequest.self, ListSessionsResponse.self, WaitForQuiescenceRequest.self, diff --git a/Sources/SwiftBuild/SWBBuildServiceSession.swift b/Sources/SwiftBuild/SWBBuildServiceSession.swift index c96d6c7e..ae3baad4 100644 --- a/Sources/SwiftBuild/SWBBuildServiceSession.swift +++ b/Sources/SwiftBuild/SWBBuildServiceSession.swift @@ -629,6 +629,14 @@ public final class SWBBuildServiceSession: Sendable { public func setUserPreferences(enableDebugActivityLogs: Bool, enableBuildDebugging: Bool, enableBuildSystemCaching: Bool, activityTextShorteningLevel: Int, usePerConfigurationBuildLocations: Bool?, allowsExternalToolExecution: Bool) async throws { _ = try await service.send(request: SetSessionUserPreferencesRequest(sessionHandle: self.uid, enableDebugActivityLogs: enableDebugActivityLogs, enableBuildDebugging: enableBuildDebugging, enableBuildSystemCaching: enableBuildSystemCaching, activityTextShorteningLevel: ActivityTextShorteningLevel(rawValue: activityTextShorteningLevel) ?? .default, usePerConfigurationBuildLocations: usePerConfigurationBuildLocations, allowsExternalToolExecution: allowsExternalToolExecution)) } + + public func lookupToolchain(at path: String) async throws -> SWBToolchainIdentifier? { + return try await service.send(request: LookupToolchainRequest(sessionHandle: self.uid, path: Path(path))).toolchainIdentifier.map { SWBToolchainIdentifier(rawValue: $0) } + } +} + +public struct SWBToolchainIdentifier { + public var rawValue: String } extension SWBBuildServiceSession { diff --git a/Sources/SwiftBuildTestSupport/TestUtilities.swift b/Sources/SwiftBuildTestSupport/TestUtilities.swift index cde69e6f..f644a308 100644 --- a/Sources/SwiftBuildTestSupport/TestUtilities.swift +++ b/Sources/SwiftBuildTestSupport/TestUtilities.swift @@ -37,11 +37,11 @@ package actor TestSWBSession { package nonisolated let sessionDiagnostics: [SwiftBuildMessage.DiagnosticInfo] private var closed = false - package init(connectionMode: SWBBuildServiceConnectionMode = .default, variant: SWBBuildServiceVariant = .default, temporaryDirectory: NamedTemporaryDirectory?) async throws { + package init(connectionMode: SWBBuildServiceConnectionMode = .default, variant: SWBBuildServiceVariant = .default, temporaryDirectory: NamedTemporaryDirectory?, environment: [String: String] = [:]) async throws { self.tmpDir = try temporaryDirectory ?? NamedTemporaryDirectory() // Construct the test session. self.service = try await SWBBuildService(connectionMode: connectionMode, variant: variant) - let (result, sessionDiagnostics) = await service.createSession(name: #function, cachePath: tmpDir.path.str) + let (result, sessionDiagnostics) = await service.createSession(name: #function, cachePath: tmpDir.path.str, environment: environment) self.sessionDiagnostics = sessionDiagnostics do { self.session = try result.get() @@ -203,8 +203,8 @@ extension SWBRunDestinationInfo: _RunDestinationInfo { extension SWBBuildService { /// Overload of `createSession` which supplies an inferior products path. - package func createSession(name: String, developerPath: String? = nil, cachePath: String?) async -> (Result, [SwiftBuildMessage.DiagnosticInfo]) { - return await createSession(name: name, developerPath: developerPath, cachePath: cachePath, inferiorProductsPath: Core.inferiorProductsPath()?.str, environment: [:]) + package func createSession(name: String, developerPath: String? = nil, cachePath: String?, environment: [String:String] = [:]) async -> (Result, [SwiftBuildMessage.DiagnosticInfo]) { + return await createSession(name: name, developerPath: developerPath, cachePath: cachePath, inferiorProductsPath: Core.inferiorProductsPath()?.str, environment: environment) } } diff --git a/Tests/SwiftBuildTests/ToolchainTests.swift b/Tests/SwiftBuildTests/ToolchainTests.swift new file mode 100644 index 00000000..87e0ecd1 --- /dev/null +++ b/Tests/SwiftBuildTests/ToolchainTests.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Foundation.UUID + +import SwiftBuild +import SwiftBuildTestSupport + +import SWBCore +import SWBTestSupport +@_spi(Testing) import SWBUtil + +import Testing + +@Suite +fileprivate struct ToolchainTests: CoreBasedTests { + @Test(.skipIfEnvironmentVariableSet(key: .externalToolchainsDir)) + func toolchainLookupByPath() async throws { + try await withTemporaryDirectory { (temporaryDirectory: NamedTemporaryDirectory) in + let tmpDir = temporaryDirectory.path + try await withAsyncDeferrable { deferrable in + try localFS.createDirectory(tmpDir.join("toolchain.xctoolchain")) + try await localFS.writePlist(tmpDir.join("toolchain.xctoolchain/Info.plist"), .plDict(["Identifier": "com.foo.bar"])) + let testSession = try await TestSWBSession(temporaryDirectory: temporaryDirectory, environment: ["EXTERNAL_TOOLCHAINS_DIR": tmpDir.str]) + await deferrable.addBlock { + await #expect(throws: Never.self) { + try await testSession.close() + } + } + + let id = try await testSession.session.lookupToolchain(at: tmpDir.join("toolchain.xctoolchain").str) + #expect(id?.rawValue == "com.foo.bar") + } + } + } +}