diff --git a/Sources/SWBBuildService/Messages.swift b/Sources/SWBBuildService/Messages.swift index 95e12252c..7b406f484 100644 --- a/Sources/SWBBuildService/Messages.swift +++ b/Sources/SWBBuildService/Messages.swift @@ -29,6 +29,47 @@ private struct GetPlatformsDumpMsg: MessageHandler { } } +private struct BuildCacheInfoMsg: MessageHandler { + func handle(request: Request, message: BuildCacheInfoRequest) throws -> BuildCacheInfoResponse { + let core = try request.session(for: message).core + + let registry = core.toolchainRegistry + let os = core.hostOperatingSystem + let toolchainsToSearch: [Toolchain] = ([registry.defaultToolchain] + registry.toolchains.sorted(by: \.identifier)).compactMap { $0 } + var libclang: Libclang? = nil + for toolchain in toolchainsToSearch { + guard let libclangPath = try? toolchain.lookup(subject: .library(basename: "clang"), operatingSystem: os) else { + continue + } + if let found = core.lookupLibclang(path: libclangPath).libclang { + libclang = found + break + } + } + guard let libclang else { + throw StubError.error("could not load libclang to query the build cache") + } + + let onDiskPath = Path(message.casPath).join(message.pluginEnabled ? "plugin" : "builtin") + + let casOptions = try ClangCASOptions(libclang).setOnDiskPath(onDiskPath.str) + if let pluginPath = message.pluginPath, !pluginPath.isEmpty { + casOptions.setPluginPath(pluginPath) + } else if message.pluginEnabled, case .xcode(let xcodeDeveloperDir) = core.developerPath { + casOptions.setPluginPath(xcodeDeveloperDir.join(Path("usr/lib/libToolchainCASPlugin.dylib")).str) + } + if let remoteServicePath = message.remoteServicePath, !remoteServicePath.isEmpty { + casOptions.setPluginOption(name: "remote-service-path", value: remoteServicePath) + } + + let casDatabases = try ClangCASDatabases(options: casOptions) + guard let onDiskSize = try casDatabases.getOndiskSize() else { + throw StubError.error("the build cache does not support reporting its on-disk size") + } + return BuildCacheInfoResponse(onDiskSize: Int(onDiskSize)) + } +} + private struct GetSDKsDumpMsg: MessageHandler { func handle(request: Request, message: GetSDKsRequest) throws -> StringResponse { return StringResponse(try request.session(for: message).core.getSDKsDump()) @@ -1620,6 +1661,7 @@ package struct ServiceMessageHandlers: ServiceExtension { service.registerMessageHandler(ClearAllCaches.self) service.registerMessageHandler(GetPlatformsDumpMsg.self) + service.registerMessageHandler(BuildCacheInfoMsg.self) service.registerMessageHandler(GetSDKsDumpMsg.self) service.registerMessageHandler(GetToolchainsDumpMsg.self) service.registerMessageHandler(GetSpecsDumpMsg.self) diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index b0a3afe40..c303a2ce4 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -541,7 +541,9 @@ final class WorkspaceSettings: Sendable { table.push(BuiltinMacros.SDK_EXPLICIT_MODULES_OUTPUT_PATH, Static { BuiltinMacros.namespace.parseString("$(DERIVED_DATA_DIR)/SDKExplicitPrecompiledModules") }) // Add default values for the compilation caching plugin (off-by-default). - table.push(BuiltinMacros.COMPILATION_CACHE_PLUGIN_PATH, Static { BuiltinMacros.namespace.parseString("$(DEVELOPER_USR_DIR)/lib/libToolchainCASPlugin.dylib") }) + if case .xcode(_) = core.developerPath { + table.push(BuiltinMacros.COMPILATION_CACHE_PLUGIN_PATH, Static { BuiltinMacros.namespace.parseString("$(DEVELOPER_USR_DIR)/lib/libToolchainCASPlugin.dylib") }) + } // Enable the integrated driver table.push(BuiltinMacros.SWIFT_USE_INTEGRATED_DRIVER, literal: true) diff --git a/Sources/SWBProtocol/Message.swift b/Sources/SWBProtocol/Message.swift index fbc5f0222..726b278df 100644 --- a/Sources/SWBProtocol/Message.swift +++ b/Sources/SWBProtocol/Message.swift @@ -131,6 +131,37 @@ public struct GetPlatformsRequest: SessionMessage, RequestMessage, Equatable { } } +public struct BuildCacheInfoRequest: SessionMessage, RequestMessage, Equatable, SerializableCodable { + public typealias ResponseMessage = BuildCacheInfoResponse + + public static let name = "BUILD_CACHE_INFO" + + public let sessionHandle: String + public let casPath: String + public let pluginPath: String? + public let remoteServicePath: String? + public let pluginEnabled: Bool + + public init(sessionHandle: String, casPath: String, pluginPath: String?, remoteServicePath: String?, pluginEnabled: Bool) { + self.sessionHandle = sessionHandle + self.casPath = casPath + self.pluginPath = pluginPath + self.remoteServicePath = remoteServicePath + self.pluginEnabled = pluginEnabled + } +} + +public struct BuildCacheInfoResponse: Message, Equatable, SerializableCodable { + public static let name = "BUILD_CACHE_INFO_RESPONSE" + + /// The on-disk size of the build cache in bytes. + public let onDiskSize: Int + + public init(onDiskSize: Int) { + self.onDiskSize = onDiskSize + } +} + public struct GetSDKsRequest: SessionMessage, RequestMessage, Equatable { public typealias ResponseMessage = StringResponse @@ -1243,6 +1274,8 @@ public struct IPCMessage: Serializable, Sendable { GetSpecsRequest.self, GetStatisticsRequest.self, GetToolchainsRequest.self, + BuildCacheInfoRequest.self, + BuildCacheInfoResponse.self, GetBuildSettingsDescriptionRequest.self, ExecuteCommandLineToolRequest.self, diff --git a/Sources/SwiftBuild/SWBBuildServiceSession.swift b/Sources/SwiftBuild/SWBBuildServiceSession.swift index 157cc7bed..e09786fd9 100644 --- a/Sources/SwiftBuild/SWBBuildServiceSession.swift +++ b/Sources/SwiftBuild/SWBBuildServiceSession.swift @@ -365,6 +365,11 @@ public final class SWBBuildServiceSession: Sendable { return try msg.output.map { try SWBPreviewTargetDependencyInfo($0) } } + public func buildCacheInfo(casPath: String, pluginPath: String?, remoteServicePath: String?, pluginEnabled: Bool) async throws -> SWBBuildCacheInfo { + let response = try await service.send(request: BuildCacheInfoRequest(sessionHandle: uid, casPath: casPath, pluginPath: pluginPath, remoteServicePath: remoteServicePath, pluginEnabled: pluginEnabled)) + return SWBBuildCacheInfo(onDiskSize: response.onDiskSize) + } + public func describeSchemes(input: [SWBSchemeInput]) async throws -> [SWBSchemeDescription] { let response: DescribeSchemesResponse = try await handleProductPlannerMessage(makeMessage: { DescribeSchemesRequest(sessionHandle: self.uid, responseChannel: $0, input: input.map(SchemeInput.init)) }) return response.schemes.map(SWBSchemeDescription.init) @@ -1039,3 +1044,12 @@ extension SwiftBuildServicePIFObjectType { } } } + +public struct SWBBuildCacheInfo: Sendable { + /// The on-disk size of the build cache in bytes. + public let onDiskSize: Int + + public init(onDiskSize: Int) { + self.onDiskSize = onDiskSize + } +} diff --git a/Tests/SwiftBuildTests/ServiceTests.swift b/Tests/SwiftBuildTests/ServiceTests.swift index ffb69dc2b..833060760 100644 --- a/Tests/SwiftBuildTests/ServiceTests.swift +++ b/Tests/SwiftBuildTests/ServiceTests.swift @@ -227,6 +227,29 @@ fileprivate struct ServiceTests { } } + @Test(.requireDependencyScannerPlusCaching) + func buildCacheInfo() async throws { + try await withAsyncDeferrable { deferrable in + let service = try await SWBBuildService() + await deferrable.addBlock { + await service.close() + } + + let (result, _) = await service.createSession(name: "BUILD_CACHE_INFO", developerPath: nil, cachePath: nil, inferiorProductsPath: nil, environment: nil) + let createdSession = try result.get() + await deferrable.addBlock { + await #expect(throws: Never.self) { + try await createdSession.close() + } + } + + try await withTemporaryDirectory { (tmpDir: Path) in + let info = try await createdSession.buildCacheInfo(casPath: tmpDir.str, pluginPath: nil, remoteServicePath: nil, pluginEnabled: false) + #expect(info.onDiskSize >= 0) + } + } + } + /// Test sending a single large (10K) message. @Test func largeMessages() async throws {