diff --git a/CMakeLists.txt b/CMakeLists.txt index 516f1840..ed60c850 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,6 @@ add_compile_definitions(USE_STATIC_PLUGIN_INITIALIZATION) find_package(ArgumentParser) find_package(LLBuild) find_package(SwiftDriver) -find_package(SwiftToolsProtocols) find_package(SwiftSystem) find_package(TSC) # NOTE: these two are required for LLBuild dependencies diff --git a/Package.swift b/Package.swift index c22f317e..63989112 100644 --- a/Package.swift +++ b/Package.swift @@ -106,16 +106,7 @@ let package = Package( // Libraries .target( name: "SwiftBuild", - dependencies: [ - "SWBCSupport", - "SWBCore", - "SWBProtocol", - "SWBUtil", - "SWBProjectModel", - .product(name: "BuildServerProtocol", package: "swift-tools-protocols", condition: .when(platforms: [.macOS, .linux, .windows, .android, .openbsd, .custom("freebsd")])), - .product(name: "LanguageServerProtocol", package: "swift-tools-protocols", condition: .when(platforms: [.macOS, .linux, .windows, .android, .openbsd, .custom("freebsd")])), - .product(name: "LanguageServerProtocolTransport", package: "swift-tools-protocols", condition: .when(platforms: [.macOS, .linux, .windows, .android, .openbsd, .custom("freebsd")])) - ], + dependencies: ["SWBCSupport", "SWBCore", "SWBProtocol", "SWBUtil", "SWBProjectModel"], exclude: ["CMakeLists.txt"], swiftSettings: swiftSettings(languageMode: .v5)), .target( @@ -474,7 +465,6 @@ if useLocalDependencies { .package(path: "../swift-driver"), .package(path: "../swift-system"), .package(path: "../swift-argument-parser"), - .package(path: "../swift-tools-protocols"), ] if !useLLBuildFramework { package.dependencies += [.package(path: "../llbuild"),] @@ -484,7 +474,6 @@ if useLocalDependencies { .package(url: "https://github.com/swiftlang/swift-driver.git", branch: "main"), .package(url: "https://github.com/apple/swift-system.git", .upToNextMajor(from: "1.5.0")), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.3"), - .package(url: "https://github.com/swiftlang/swift-tools-protocols.git", .upToNextMinor(from: "0.0.9")), ] if !useLLBuildFramework { package.dependencies += [.package(url: "https://github.com/swiftlang/swift-llbuild.git", branch: "main"),] diff --git a/Plugins/cmake-smoke-test/cmake-smoke-test.swift b/Plugins/cmake-smoke-test/cmake-smoke-test.swift index 88556fc7..fcba09f3 100644 --- a/Plugins/cmake-smoke-test/cmake-smoke-test.swift +++ b/Plugins/cmake-smoke-test/cmake-smoke-test.swift @@ -58,10 +58,7 @@ struct CMakeSmokeTest: CommandPlugin { let swiftDriverURL = try findDependency("swift-driver", pluginContext: context) let swiftDriverBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-driver") - let swiftToolsProtocolsURL = try findDependency("swift-tools-protocols", pluginContext: context) - let swiftToolsProtocolsBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-tools-protocols") - - for url in [swiftToolsSupportCoreBuildURL, swiftSystemBuildURL, llbuildBuildURL, swiftArgumentParserBuildURL, swiftDriverBuildURL, swiftToolsProtocolsBuildURL, swiftBuildBuildURL] { + for url in [swiftToolsSupportCoreBuildURL, swiftSystemBuildURL, llbuildBuildURL, swiftArgumentParserBuildURL, swiftDriverBuildURL, swiftBuildBuildURL] { try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) } @@ -78,8 +75,7 @@ struct CMakeSmokeTest: CommandPlugin { "-DLLBuild_DIR=\(llbuildBuildURL.appending(components: "cmake", "modules").filePath)", "-DTSC_DIR=\(swiftToolsSupportCoreBuildURL.appending(components: "cmake", "modules").filePath)", "-DSwiftDriver_DIR=\(swiftDriverBuildURL.appending(components: "cmake", "modules").filePath)", - "-DSwiftSystem_DIR=\(swiftSystemBuildURL.appending(components: "cmake", "modules").filePath)", - "-DSwiftToolsProtocols_DIR=\(swiftToolsProtocolsBuildURL.appending(components: "cmake", "modules").filePath)" + "-DSwiftSystem_DIR=\(swiftSystemBuildURL.appending(components: "cmake", "modules").filePath)" ] let sharedCMakeArgs = [ @@ -116,11 +112,6 @@ struct CMakeSmokeTest: CommandPlugin { try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftDriverBuildURL) Diagnostics.progress("Built swift-driver") - Diagnostics.progress("Building swift-tools-protocols") - try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftToolsProtocolsURL.filePath], workingDirectory: swiftToolsProtocolsBuildURL) - try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftToolsProtocolsBuildURL) - Diagnostics.progress("Built swift-tools-protocols") - Diagnostics.progress("Building swift-build in \(swiftBuildBuildURL)") try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftBuildURL.filePath], workingDirectory: swiftBuildBuildURL) try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftBuildBuildURL) diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index b8224891..5570cc92 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -483,7 +483,6 @@ public final class BuiltinMacros { public static let BUILD_DIR = BuiltinMacros.declarePathMacro("BUILD_DIR") public static let BUILD_LIBRARY_FOR_DISTRIBUTION = BuiltinMacros.declareBooleanMacro("BUILD_LIBRARY_FOR_DISTRIBUTION") public static let BUILD_PACKAGE_FOR_DISTRIBUTION = BuiltinMacros.declareBooleanMacro("BUILD_PACKAGE_FOR_DISTRIBUTION") - public static let BUILD_SERVER_PROTOCOL_TARGET_TAGS = BuiltinMacros.declareBooleanMacro("BUILD_SERVER_PROTOCOL_TARGET_TAGS") public static let BUILD_VARIANTS = BuiltinMacros.declareStringListMacro("BUILD_VARIANTS") public static let BuiltBinaryPath = BuiltinMacros.declareStringMacro("BuiltBinaryPath") public static let BUNDLE_FORMAT = BuiltinMacros.declareStringMacro("BUNDLE_FORMAT") @@ -1485,7 +1484,6 @@ public final class BuiltinMacros { BUILD_DIR, BUILD_LIBRARY_FOR_DISTRIBUTION, BUILD_PACKAGE_FOR_DISTRIBUTION, - BUILD_SERVER_PROTOCOL_TARGET_TAGS, BUILD_STYLE, BUILD_VARIANTS, BUILT_PRODUCTS_DIR, diff --git a/Sources/SwiftBuild/CMakeLists.txt b/Sources/SwiftBuild/CMakeLists.txt index adb71ca2..18da3458 100644 --- a/Sources/SwiftBuild/CMakeLists.txt +++ b/Sources/SwiftBuild/CMakeLists.txt @@ -36,7 +36,6 @@ add_library(SwiftBuild SWBBuildOperationBacktraceFrame.swift SWBBuildParameters.swift SWBBuildRequest.swift - SWBBuildServer.swift SWBBuildService.swift SWBBuildServiceConnection.swift SWBBuildServiceConsole.swift @@ -71,12 +70,7 @@ target_link_libraries(SwiftBuild PUBLIC SWBCore SWBProtocol SWBUtil - SWBProjectModel - SwiftToolsProtocols::SKLogging - SwiftToolsProtocols::ToolsProtocolsSwiftExtensions - SwiftToolsProtocols::BuildServerProtocol - SwiftToolsProtocols::LanguageServerProtocol - SwiftToolsProtocols::LanguageServerProtocolTransport) + SWBProjectModel) set_target_properties(SwiftBuild PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) diff --git a/Sources/SwiftBuild/SWBBuildServer.swift b/Sources/SwiftBuild/SWBBuildServer.swift deleted file mode 100644 index 06d4a476..00000000 --- a/Sources/SwiftBuild/SWBBuildServer.swift +++ /dev/null @@ -1,521 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// -#if !os(iOS) -import BuildServerProtocol -public import LanguageServerProtocol -public import LanguageServerProtocolTransport -public import ToolsProtocolsSwiftExtensions -import SWBProtocol -import SWBUtil -import Foundation - -/// Wraps a `SWBBuildServiceSession` to expose Build Server Protocol functionality. -public actor SWBBuildServer: QueueBasedMessageHandler { - /// The session used for underlying build system functionality. - private let session: SWBBuildServiceSession - enum PIFSource { - // PIF should be loaded from the container at the given path - case container(String) - // PIF will be transferred to the session externally - case session - } - /// The source of PIF describing the workspace for this build server. - private let pifSource: PIFSource - /// The build request representing preparation. - private let buildRequest: SWBBuildRequest - /// The currently planned build description used to fulfill requests. - private var buildDescriptionID: SWBBuildDescriptionID? = nil - - private var indexStorePath: String? { - buildRequest.parameters.arenaInfo?.indexDataStoreFolderPath.map { - Path($0).dirname.join("index-store").str - } - } - private var indexDatabasePath: String? { - buildRequest.parameters.arenaInfo?.indexDataStoreFolderPath - } - - public let messageHandlingHelper = QueueBasedMessageHandlerHelper( - signpostLoggingCategory: "build-server-message-handling", - createLoggingScope: false - ) - public let messageHandlingQueue = AsyncQueue() - /// Used to serialize workspace loading. - private let workspaceLoadingQueue = AsyncQueue() - /// Used to serialize preparation builds, which cannot run concurrently. - private let preparationQueue = AsyncQueue() - /// Connection used to send messages to the client of the build server (an LSP or higher-level BSP implementation). - private let connectionToClient: any Connection - - /// Represents the lifetime of the build server implementation.. - enum ServerState: CustomStringConvertible { - case waitingForInitializeRequest - case waitingForInitializedNotification - case running - case shutdown - - var description: String { - switch self { - case .waitingForInitializeRequest: - "waiting for initialization request" - case .waitingForInitializedNotification: - "waiting for initialization notification" - case .running: - "running" - case .shutdown: - "shutdown" - } - } - } - var state: ServerState = .waitingForInitializeRequest - /// Allows customization of server exit behavior. - var exitHandler: (Int) async -> Void - - public static let sessionPIFURI = DocumentURI(.init(string: "swift-build://session-pif")!) - - public init(session: SWBBuildServiceSession, containerPath: String, buildRequest: SWBBuildRequest, connectionToClient: any Connection, exitHandler: @escaping (Int) async -> Void) { - self.init(session: session, pifSource: .container(containerPath), buildRequest: buildRequest, connectionToClient: connectionToClient, exitHandler: exitHandler) - } - - public init(session: SWBBuildServiceSession, buildRequest: SWBBuildRequest, connectionToClient: any Connection, exitHandler: @escaping (Int) async -> Void) { - self.init(session: session, pifSource: .session, buildRequest: buildRequest, connectionToClient: connectionToClient, exitHandler: exitHandler) - } - - private init(session: SWBBuildServiceSession, pifSource: PIFSource, buildRequest: SWBBuildRequest, connectionToClient: any Connection, exitHandler: @escaping (Int) async -> Void) { - self.session = session - self.pifSource = pifSource - self.buildRequest = Self.preparationRequest(for: buildRequest) - self.connectionToClient = connectionToClient - self.exitHandler = exitHandler - } - - /// Derive a request suitable from preparation from one suitable for a normal build. - private static func preparationRequest(for buildRequest: SWBBuildRequest) -> SWBBuildRequest { - var updatedBuildRequest = buildRequest - updatedBuildRequest.buildCommand = .prepareForIndexing( - buildOnlyTheseTargets: nil, - enableIndexBuildArena: true - ) - updatedBuildRequest.enableIndexBuildArena = true - updatedBuildRequest.continueBuildingAfterErrors = true - - updatedBuildRequest.parameters.action = "indexbuild" - var overridesTable = buildRequest.parameters.overrides.commandLine ?? SWBSettingsTable() - overridesTable.set(value: "YES", for: "ONLY_ACTIVE_ARCH") - updatedBuildRequest.parameters.overrides.commandLine = overridesTable - for targetIndex in updatedBuildRequest.configuredTargets.indices { - updatedBuildRequest.configuredTargets[targetIndex].parameters?.action = "indexbuild" - var overridesTable = updatedBuildRequest.configuredTargets[targetIndex].parameters?.overrides.commandLine ?? SWBSettingsTable() - overridesTable.set(value: "YES", for: "ONLY_ACTIVE_ARCH") - updatedBuildRequest.configuredTargets[targetIndex].parameters?.overrides.commandLine = overridesTable - } - - return updatedBuildRequest - } - - public func handle(notification: some NotificationType) async { - switch notification { - case is OnBuildExitNotification: - if state == .shutdown { - await exitHandler(0) - } else { - await exitHandler(1) - } - case is OnBuildInitializedNotification: - guard state == .waitingForInitializedNotification else { - logToClient(.error, "Build initialized notification received while the build server is \(state.description)") - break - } - state = .running - case let notification as OnWatchedFilesDidChangeNotification: - if state != .running { - logToClient(.error, "Watched files changed notification received while the build server is \(state.description)") - } - for change in notification.changes { - switch pifSource { - case .container(let containerPath): - if change.uri == DocumentURI(.init(filePath: containerPath)) { - scheduleRegeneratingBuildDescription() - return - } - case .session: - if change.uri == Self.sessionPIFURI { - scheduleRegeneratingBuildDescription() - return - } - } - } - default: - logToClient(.error, "Unknown notification type received") - break - } - } - - public func handle( - request: Request, - id: RequestID, - reply: @Sendable @escaping (LSPResult) -> Void - ) async { - let request = RequestAndReply(request, reply: reply) - if !(request.params is InitializeBuildRequest) { - let state = self.state - guard state == .running else { - await request.reply { throw ResponseError.unknown("Request received while the build server is \(state.description)") } - return - } - } - switch request { - case let request as RequestAndReply: - await request.reply { await shutdown() } - case let request as RequestAndReply: - await request.reply { try await prepare(request: request.params) } - case let request as RequestAndReply: - await request.reply { try await buildTargetSources(request: request.params) } - case let request as RequestAndReply: - await request.reply { try await self.initialize(request: request.params) } - case let request as RequestAndReply: - await request.reply { try await sourceKitOptions(request: request.params) } - case let request as RequestAndReply: - await request.reply { try await buildTargets(request: request.params) } - case let request as RequestAndReply: - await request.reply { await waitForBuildSystemUpdates(request: request.params) } - default: - await request.reply { throw ResponseError.methodNotFound(Request.method) } - } - } - - private func initialize(request: InitializeBuildRequest) throws -> InitializeBuildResponse { - guard state == .waitingForInitializeRequest else { - throw ResponseError.unknown("Received initialization request while the build server is \(state)") - } - state = .waitingForInitializedNotification - scheduleRegeneratingBuildDescription() - return InitializeBuildResponse( - displayName: "Swift Build Server (Session: \(session.uid))", - version: "", - bspVersion: "2.2.0", - capabilities: BuildServerCapabilities(), - dataKind: .sourceKit, - data: SourceKitInitializeBuildResponseData( - indexDatabasePath: indexDatabasePath, - indexStorePath: indexStorePath, - outputPathsProvider: true, - prepareProvider: true, - sourceKitOptionsProvider: true, - watchers: [] - ).encodeToLSPAny() - ) - } - - private func shutdown() -> LanguageServerProtocol.VoidResponse { - state = .shutdown - return VoidResponse() - } - - private func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> LanguageServerProtocol.VoidResponse { - await workspaceLoadingQueue.async {}.valuePropagatingCancellation - return VoidResponse() - } - - private func scheduleRegeneratingBuildDescription() { - workspaceLoadingQueue.async { - do { - try await self.logTaskToClient(name: "Generating build description") { log in - switch self.pifSource { - case .container(let containerPath): - try await self.session.loadWorkspace(containerPath: containerPath) - case .session: - break - } - try await self.session.setSystemInfo(.default()) - let buildDescriptionOperation = try await self.session.createBuildOperationForBuildDescriptionOnly( - request: self.buildRequest, - delegate: PlanningOperationDelegate() - ) - var buildDescriptionID: BuildDescriptionID? - for try await event in try await buildDescriptionOperation.start() { - guard case .reportBuildDescription(let info) = event else { - continue - } - guard buildDescriptionID == nil else { - throw ResponseError.unknown("Unexpectedly reported multiple build descriptions") - } - buildDescriptionID = BuildDescriptionID(info.buildDescriptionID) - } - guard let buildDescriptionID else { - throw ResponseError.unknown("Failed to get build description ID") - } - self.buildDescriptionID = SWBBuildDescriptionID(buildDescriptionID) - } - } catch { - self.logToClient(.error, "Error generating build description: \(error)") - } - } - } - - private func buildTargets(request: WorkspaceBuildTargetsRequest) async throws -> WorkspaceBuildTargetsResponse { - try await logTaskToClient(name: "Computing targets list") { _ in - guard let buildDescriptionID else { - throw ResponseError.unknown("No build description") - } - let targets = try await session.configuredTargets( - buildDescription: buildDescriptionID, - buildRequest: buildRequest - ).asyncMap { targetInfo in - let tags = try await session.evaluateMacroAsStringList( - "BUILD_SERVER_PROTOCOL_TARGET_TAGS", - level: .target(targetInfo.identifier.targetGUID.rawValue), - buildParameters: buildRequest.parameters, - overrides: nil - ).filter { - !$0.isEmpty - }.map { - BuildTargetTag(rawValue: $0) - } - let toolchain: DocumentURI? = - if let toolchain = targetInfo.toolchain { - DocumentURI(filePath: toolchain.pathString, isDirectory: true) - } else { - nil - } - - return BuildTarget( - id: try BuildTargetIdentifier(configuredTargetIdentifier: targetInfo.identifier), - displayName: targetInfo.name, - baseDirectory: nil, - tags: tags, - capabilities: BuildTargetCapabilities(), - languageIds: [.c, .cpp, .objective_c, .objective_cpp, .swift], - dependencies: try targetInfo.dependencies.map { - try BuildTargetIdentifier(configuredTargetIdentifier: $0) - }, - dataKind: .sourceKit, - data: SourceKitBuildTarget(toolchain: toolchain).encodeToLSPAny() - ) - } - - return WorkspaceBuildTargetsResponse(targets: targets) - } - } - - private func buildTargetSources(request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse { - try await logTaskToClient(name: "Computing sources list") { _ in - guard let buildDescriptionID else { - throw ResponseError.unknown("No build description") - } - let response = try await session.sources( - of: request.targets.map { try $0.configuredTargetIdentifier }, - buildDescription: buildDescriptionID, - buildRequest: buildRequest - ) - let sourcesItems = try response.compactMap { (swbSourcesItem) -> SourcesItem? in - let sources = swbSourcesItem.sourceFiles.map { sourceFile in - SourceItem( - uri: DocumentURI(URL(filePath: sourceFile.path.pathString)), - kind: .file, - // Should `generated` check if the file path is a descendant of OBJROOT/DERIVED_SOURCES_DIR? - // SourceKit-LSP doesn't use this currently. - generated: false, - dataKind: .sourceKit, - data: SourceKitSourceItemData( - language: Language(sourceFile.language), - outputPath: sourceFile.indexOutputPath - ).encodeToLSPAny() - ) - } - return SourcesItem( - target: try BuildTargetIdentifier(configuredTargetIdentifier: swbSourcesItem.configuredTarget), - sources: sources - ) - } - return BuildTargetSourcesResponse(items: sourcesItems) - } - } - - private func sourceKitOptions(request: TextDocumentSourceKitOptionsRequest) async throws -> TextDocumentSourceKitOptionsResponse? { - try await logTaskToClient(name: "Computing compiler options") { _ in - guard let buildDescriptionID else { - throw ResponseError.unknown("No build description") - } - guard let fileURL = request.textDocument.uri.fileURL else { - throw ResponseError.unknown("Text document is not a file") - } - let response = try await session.indexCompilerArguments( - of: AbsolutePath(validating: fileURL.filePath.str), - in: request.target.configuredTargetIdentifier, - buildDescription: buildDescriptionID, - buildRequest: buildRequest - ) - return TextDocumentSourceKitOptionsResponse(compilerArguments: response) - } - } - - private func prepare(request: BuildTargetPrepareRequest) async throws -> LanguageServerProtocol.VoidResponse { - try await preparationQueue.asyncThrowing { - var updatedBuildRequest = self.buildRequest - let targetGUIDs = try request.targets.map { - try $0.configuredTargetIdentifier.targetGUID.rawValue - } - updatedBuildRequest.buildCommand = .prepareForIndexing( - buildOnlyTheseTargets: targetGUIDs, - enableIndexBuildArena: true - ) - let buildOperation = try await self.session.createBuildOperation( - request: updatedBuildRequest, - delegate: PlanningOperationDelegate() - ) - try await self.logTaskToClient(name: "Preparing targets") { taskID in - let events = try await buildOperation.start() - await self.reportEventStream(events) - await buildOperation.waitForCompletion() - } - }.valuePropagatingCancellation - return VoidResponse() - } - - private func reportEventStream(_ events: AsyncStream) async { - for try await event in events { - switch event { - case .planningOperationStarted(_): - logToClient(.log, "Planning Build", .begin(.init(title: "Planning Build"))) - case .planningOperationCompleted(_): - logToClient(.info, "Build Planning Complete", .end(.init())) - case .buildStarted(_): - logToClient(.log, "Building", .begin(.init(title: "Building"))) - case .buildDiagnostic(let info): - logToClient(.log, info.message, .report(.init())) - case .buildCompleted(let info): - switch info.result { - case .ok: - logToClient(.log, "Build Complete", .end(.init())) - case .failed: - logToClient(.log, "Build Failed", .end(.init())) - case .cancelled: - logToClient(.log, "Build Cancelled", .end(.init())) - case .aborted: - logToClient(.log, "Build Aborted", .end(.init())) - } - case .preparationComplete(_): - logToClient(.log, "Build Preparation Complete", .end(.init())) - case .didUpdateProgress(_): - break - case .taskStarted(let info): - logToClient(.log, info.executionDescription, .begin(.init(title: info.executionDescription))) - case .taskDiagnostic(let info): - logToClient(.log, info.message, .report(.init())) - case .taskComplete(_): - break - case .targetDiagnostic(let info): - logToClient(.log, info.message, .report(.init())) - case .diagnostic(let info): - logToClient(.log, info.message, .report(.init())) - case .backtraceFrame, .reportPathMap, .reportBuildDescription, .preparedForIndex, .buildOutput, .targetStarted, .targetComplete, .targetOutput, .targetUpToDate, .taskUpToDate, .taskOutput, .output: - break - } - } - } - - private func logToClient(_ kind: BuildServerProtocol.MessageType, _ message: String, _ structure: BuildServerProtocol.StructuredLogKind? = nil) { - connectionToClient.send( - OnBuildLogMessageNotification(type: .log, message: "\(message)", structure: structure) - ) - } - - private func logTaskToClient(name: String, _ perform: (String) async throws -> T) async throws -> T { - let taskID = UUID().uuidString - logToClient(.log, name, .begin(.init(title: name))) - defer { - logToClient(.log, name, .end(.init())) - } - return try await perform(taskID) - } -} - -extension BuildTargetIdentifier { - static let swiftBuildBuildServerTargetScheme = "swift-build" - - init(configuredTargetIdentifier: SWBConfiguredTargetIdentifier) throws { - var components = URLComponents() - components.scheme = Self.swiftBuildBuildServerTargetScheme - components.host = "configured-target" - components.queryItems = [ - URLQueryItem(name: "configuredTargetGUID", value: configuredTargetIdentifier.rawGUID), - URLQueryItem(name: "targetGUID", value: configuredTargetIdentifier.targetGUID.rawValue), - ] - - struct FailedToConvertSwiftBuildTargetToUrlError: Swift.Error, CustomStringConvertible { - var configuredTargetIdentifier: SWBConfiguredTargetIdentifier - - var description: String { - return "Failed to generate URL for configured target '\(configuredTargetIdentifier.rawGUID)'" - } - } - - guard let url = components.url else { - throw FailedToConvertSwiftBuildTargetToUrlError(configuredTargetIdentifier: configuredTargetIdentifier) - } - - self.init(uri: URI(url)) - } - - var isSwiftBuildBuildServerTargetID: Bool { - uri.scheme == Self.swiftBuildBuildServerTargetScheme - } - - var configuredTargetIdentifier: SWBConfiguredTargetIdentifier { - get throws { - struct InvalidTargetIdentifierError: Swift.Error, CustomStringConvertible { - var target: BuildTargetIdentifier - - var description: String { - return "Invalid target identifier \(target)" - } - } - guard let components = URLComponents(url: self.uri.arbitrarySchemeURL, resolvingAgainstBaseURL: false) else { - throw InvalidTargetIdentifierError(target: self) - } - guard let configuredTargetGUID = components.queryItems?.last(where: { $0.name == "configuredTargetGUID" })?.value else { - throw InvalidTargetIdentifierError(target: self) - } - guard let targetGUID = components.queryItems?.last(where: { $0.name == "targetGUID" })?.value else { - throw InvalidTargetIdentifierError(target: self) - } - - return SWBConfiguredTargetIdentifier(rawGUID: configuredTargetGUID, targetGUID: SWBTargetGUID(TargetGUID(rawValue: targetGUID))) - } - } -} - -private final class PlanningOperationDelegate: SWBPlanningOperationDelegate, Sendable { - func provisioningTaskInputs(targetGUID: String, provisioningSourceData: SWBProvisioningTaskInputsSourceData) async -> SWBProvisioningTaskInputs { - return SWBProvisioningTaskInputs() - } - - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> SWBExternalToolResult { - .deferred - } -} - -fileprivate extension Language { - init?(_ language: SWBSourceLanguage?) { - switch language { - case nil: return nil - case .c: self = .c - case .cpp: self = .cpp - case .metal: return nil - case .objectiveC: self = .objective_c - case .objectiveCpp: self = .objective_cpp - case .swift: self = .swift - } - } -} -#endif diff --git a/Sources/SwiftBuild/SWBBuildServiceConnection.swift b/Sources/SwiftBuild/SWBBuildServiceConnection.swift index d27d23da..19efb51c 100644 --- a/Sources/SwiftBuild/SWBBuildServiceConnection.swift +++ b/Sources/SwiftBuild/SWBBuildServiceConnection.swift @@ -203,7 +203,7 @@ typealias swb_build_service_connection_message_handler_t = @Sendable (UInt64, SW // Now we look for the rpath the linker added. We expect it to end in "lib/darwin". Additionally we expect it to either contain "/Applications/Xcode.app/", or be a path into DEVELOPER_DIR (for unit testing, since the main bundle will be the test runner). if rpath.hasSuffix("lib/darwin") { var xcodeRelativeRpath: String? = nil - if rpath.hasPrefix(currentXcodeApp + "/") { + if rpath.starts(with: currentXcodeApp + "/") { xcodeRelativeRpath = "\(rpath.dropFirst((currentXcodeApp + "/").count))" } else if rpath.contains("/Applications/Xcode.app/") { diff --git a/Tests/SwiftBuildTests/BuildServerTests.swift b/Tests/SwiftBuildTests/BuildServerTests.swift deleted file mode 100644 index b0702d06..00000000 --- a/Tests/SwiftBuildTests/BuildServerTests.swift +++ /dev/null @@ -1,498 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// -#if !os(iOS) -import Foundation -import Testing -@_spi(Testing) import SwiftBuild -import SwiftBuildTestSupport -import SWBBuildService -import SWBCore -import SWBUtil -import SWBTestSupport -import SWBProtocol -import BuildServerProtocol -import LanguageServerProtocol -import LanguageServerProtocolTransport -import Synchronization -import SKLogging - -final fileprivate class CollectingMessageHandler: MessageHandler { - - let notifications: SWBMutex<[any NotificationType]> = .init([]) - - func handle(_ notification: some NotificationType) { - notifications.withLock { - $0.append(notification) - } - } - - func handle(_ request: Request, id: RequestID, reply: @escaping @Sendable (LSPResult) -> Void) where Request : RequestType {} -} - -extension Connection { - fileprivate func send(_ request: Request) async throws -> Request.Response { - return try await withCheckedThrowingContinuation { continuation in - _ = send(request, reply: { response in - switch response { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - }) - } - } -} - -fileprivate func withBuildServerConnection(setup: (Path) async throws -> (TestWorkspace, SWBBuildRequest), body: (any Connection, CollectingMessageHandler, Path) async throws -> Void) async throws { - try await withTemporaryDirectory { (temporaryDirectory: NamedTemporaryDirectory) in - try await withAsyncDeferrable { deferrable in - let tmpDir = temporaryDirectory.path - let testSession = try await TestSWBSession(temporaryDirectory: temporaryDirectory) - await deferrable.addBlock { - await #expect(throws: Never.self) { - try await testSession.close() - } - } - - let (workspace, request) = try await setup(tmpDir) - try await testSession.sendPIF(workspace) - - let connectionToServer = LocalConnection(receiverName: "server") - let connectionToClient = LocalConnection(receiverName: "client") - let buildServer = SWBBuildServer(session: testSession.session, buildRequest: request, connectionToClient: connectionToClient, exitHandler: { _ in }) - let collectingMessageHandler = CollectingMessageHandler() - - connectionToServer.start(handler: buildServer) - connectionToClient.start(handler: collectingMessageHandler) - _ = try await connectionToServer.send( - InitializeBuildRequest( - displayName: "test-bsp-client", - version: "1.0.0", - bspVersion: "2.2.0", - rootUri: URI(URL(filePath: tmpDir.str)), - capabilities: .init(languageIds: [.swift, .c, .objective_c, .cpp, .objective_cpp]) - ) - ) - connectionToServer.send(OnBuildInitializedNotification()) - _ = try await connectionToServer.send(WorkspaceWaitForBuildSystemUpdatesRequest()) - - try await body(connectionToServer, collectingMessageHandler, tmpDir) - - _ = try await connectionToServer.send(BuildShutdownRequest()) - connectionToServer.send(OnBuildExitNotification()) - connectionToServer.close() - } - } -} - -@Suite -fileprivate struct BuildServerTests: CoreBasedTests { - init() { - LoggingScope.configureDefaultLoggingSubsystem("org.swift.swift-build-tests") - } - - @Test(.requireSDKs(.host)) - func workspaceTargets() async throws { - try await withBuildServerConnection(setup: { tmpDir in - let testWorkspace = TestWorkspace( - "aWorkspace", - sourceRoot: tmpDir.join("Test"), - projects: [ - TestProject( - "aProject", - defaultConfigurationName: "Debug", - groupTree: TestGroup( - "Foo", - children: [ - TestFile("a.swift"), - TestFile("b.swift"), - TestFile("c.swift") - ] - ), - targets: [ - TestStandardTarget( - "Target", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "a.swift" - ]) - ] - ), - TestStandardTarget( - "Target2", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [ - "BUILD_SERVER_PROTOCOL_TARGET_TAGS": "dependency" - ]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "b.swift" - ]) - ] - ), - TestStandardTarget( - "Tests", - type: .unitTest, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [ - "BUILD_SERVER_PROTOCOL_TARGET_TAGS": "test" - ]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "c.swift" - ]) - ], - dependencies: [ - "Target", - "Target2" - ] - ) - ] - ) - ]) - - var request = SWBBuildRequest() - request.parameters = SWBBuildParameters() - request.parameters.action = "build" - request.parameters.configurationName = "Debug" - for target in testWorkspace.projects.flatMap({ $0.targets }) { - request.add(target: SWBConfiguredTarget(guid: target.guid)) - } - - return (testWorkspace, request) - }) { connection, _, _ in - let targetsResponse = try await connection.send(WorkspaceBuildTargetsRequest()) - let firstLibrary = try #require(targetsResponse.targets.filter { $0.displayName == "Target" }.only) - let secondLibrary = try #require(targetsResponse.targets.filter { $0.displayName == "Target2" }.only) - let tests = try #require(targetsResponse.targets.filter { $0.displayName == "Tests" }.only) - - #expect(firstLibrary.dependencies == []) - #expect(secondLibrary.dependencies == []) - #expect(Set(tests.dependencies) == Set([firstLibrary.id, secondLibrary.id])) - - #expect(firstLibrary.tags == []) - #expect(secondLibrary.tags == [.dependency]) - #expect(Set(tests.tags) == Set([.test])) - } - } - - @Test(.requireSDKs(.host)) - func targetSources() async throws { - try await withBuildServerConnection(setup: { tmpDir in - let testWorkspace = TestWorkspace( - "aWorkspace", - sourceRoot: tmpDir.join("Test"), - projects: [ - TestProject( - "aProject", - defaultConfigurationName: "Debug", - groupTree: TestGroup( - "Foo", - children: [ - TestFile("a.swift"), - TestFile("b.c"), - ] - ), - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - targets: [ - TestStandardTarget( - "Target", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "a.swift", - "b.c" - ]) - ] - ), - ] - ) - ]) - - var request = SWBBuildRequest() - request.parameters = SWBBuildParameters() - request.parameters.action = "build" - request.parameters.configurationName = "Debug" - for target in testWorkspace.projects.flatMap({ $0.targets }) { - request.add(target: SWBConfiguredTarget(guid: target.guid)) - } - request.parameters.activeRunDestination = .host - - return (testWorkspace, request) - }) { connection, _, tmpDir in - let targetsResponse = try await connection.send(WorkspaceBuildTargetsRequest()) - let target = try #require(targetsResponse.targets.only) - let sourcesResponse = try await connection.send(BuildTargetSourcesRequest(targets: [target.id])) - - do { - let sourceA = try #require(sourcesResponse.items.only?.sources.filter { $0.uri.fileURL?.lastPathComponent == "a.swift" }.only) - #expect(sourceA.uri == DocumentURI(URL(filePath: tmpDir.join("Test/aProject/a.swift").str))) - #expect(sourceA.kind == .file) - #expect(!sourceA.generated) - #expect(sourceA.dataKind == .sourceKit) - let data = try #require(SourceKitSourceItemData(fromLSPAny: sourceA.data)) - #expect(data.language == .swift) - #expect(data.outputPath?.hasSuffix("a.o") == true) - } - - do { - let sourceB = try #require(sourcesResponse.items.only?.sources.filter { $0.uri.fileURL?.lastPathComponent == "b.c" }.only) - #expect(sourceB.uri == DocumentURI(URL(filePath: tmpDir.join("Test/aProject/b.c").str))) - #expect(sourceB.kind == .file) - #expect(!sourceB.generated) - #expect(sourceB.dataKind == .sourceKit) - let data = try #require(SourceKitSourceItemData(fromLSPAny: sourceB.data)) - #expect(data.language == .c) - #expect(data.outputPath?.hasSuffix("b.o") == true) - } - } - } - - @Test(.requireSDKs(.host), .skipHostOS(.windows)) - func basicPreparationAndCompilerArgs() async throws { - try await withBuildServerConnection(setup: { tmpDir in - let testWorkspace = TestWorkspace( - "aWorkspace", - sourceRoot: tmpDir.join("Test"), - projects: [ - TestProject( - "aProject", - defaultConfigurationName: "Debug", - groupTree: TestGroup( - "Foo", - children: [ - TestFile("a.swift"), - TestFile("b.swift"), - ] - ), - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [ - "PRODUCT_NAME": "$(TARGET_NAME)", - "CODE_SIGNING_ALLOWED": "NO", - "SWIFT_VERSION": "5.0", - ]) - ], - targets: [ - TestStandardTarget( - "Target", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "b.swift", - ]) - ] - ), - TestStandardTarget( - "Target2", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "a.swift", - ]) - ], - dependencies: ["Target"] - ), - ] - ) - ]) - - var request = SWBBuildRequest() - request.parameters = SWBBuildParameters() - request.parameters.action = "build" - request.parameters.configurationName = "Debug" - for target in testWorkspace.projects.flatMap({ $0.targets }) { - request.add(target: SWBConfiguredTarget(guid: target.guid)) - } - request.parameters.activeRunDestination = .host - - try localFS.createDirectory(tmpDir.join("Test/aProject"), recursive: true) - try localFS.write(tmpDir.join("Test/aProject/b.swift"), contents: "public let x = 42") - try localFS.write(tmpDir.join("Test/aProject/a.swift"), contents: """ - import Target - public func foo() { - print(x) - } - """) - - return (testWorkspace, request) - }) { connection, collector, tmpDir in - let targetsResponse = try await connection.send(WorkspaceBuildTargetsRequest()) - let target = try #require(targetsResponse.targets.filter { $0.displayName == "Target2" }.only) - let sourcesResponse = try await connection.send(BuildTargetSourcesRequest(targets: [target.id])) - let sourceA = try #require(sourcesResponse.items.only?.sources.filter { $0.uri.fileURL?.lastPathComponent == "a.swift" }.only) - // Prepare, request compiler args for a source file, and then ensure those args work. - _ = try await connection.send(BuildTargetPrepareRequest(targets: [target.id])) - let logs = collector.notifications.withLock { notifications in - notifications.compactMap { notification in - (notification as? OnBuildLogMessageNotification)?.message - } - } - #expect(logs.contains("Build Complete")) - let optionsResponse = try #require(try await connection.send(TextDocumentSourceKitOptionsRequest(textDocument: .init(sourceA.uri), target: target.id, language: .swift))) - try await runProcess([swiftCompilerPath.str] + optionsResponse.compilerArguments + ["-typecheck"], workingDirectory: optionsResponse.workingDirectory.map { Path($0) }) - } - } - - @Test(.requireSDKs(.host)) - func pifUpdate() async throws { - try await withTemporaryDirectory { (temporaryDirectory: NamedTemporaryDirectory) in - try await withAsyncDeferrable { deferrable in - let tmpDir = temporaryDirectory.path - let testSession = try await TestSWBSession(temporaryDirectory: temporaryDirectory) - await deferrable.addBlock { - await #expect(throws: Never.self) { - try await testSession.close() - } - } - - let workspace = TestWorkspace( - "aWorkspace", - sourceRoot: tmpDir.join("Test"), - projects: [ - TestProject( - "aProject", - defaultConfigurationName: "Debug", - groupTree: TestGroup( - "Foo", - children: [ - TestFile("a.swift"), - ] - ), - targets: [ - TestStandardTarget( - "Target", - guid: "TargetGUID", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "a.swift" - ]) - ] - ), - ] - ) - ]) - var request = SWBBuildRequest() - request.parameters = SWBBuildParameters() - request.parameters.action = "build" - request.parameters.configurationName = "Debug" - for target in workspace.projects.flatMap({ $0.targets }) { - request.add(target: SWBConfiguredTarget(guid: target.guid)) - } - try await testSession.sendPIF(workspace) - - let connectionToServer = LocalConnection(receiverName: "server") - let connectionToClient = LocalConnection(receiverName: "client") - let buildServer = SWBBuildServer(session: testSession.session, buildRequest: request, connectionToClient: connectionToClient, exitHandler: { _ in }) - let collectingMessageHandler = CollectingMessageHandler() - - connectionToServer.start(handler: buildServer) - connectionToClient.start(handler: collectingMessageHandler) - _ = try await connectionToServer.send( - InitializeBuildRequest( - displayName: "test-bsp-client", - version: "1.0.0", - bspVersion: "2.2.0", - rootUri: URI(URL(filePath: tmpDir.str)), - capabilities: .init(languageIds: [.swift, .c, .objective_c, .cpp, .objective_cpp]) - ) - ) - connectionToServer.send(OnBuildInitializedNotification()) - _ = try await connectionToServer.send(WorkspaceWaitForBuildSystemUpdatesRequest()) - - let targetsResponse = try await connectionToServer.send(WorkspaceBuildTargetsRequest()) - #expect(targetsResponse.targets.map(\.displayName).sorted() == ["Target"]) - - let updatedWorkspace = TestWorkspace( - "aWorkspace", - sourceRoot: tmpDir.join("Test"), - projects: [ - TestProject( - "aProject", - defaultConfigurationName: "Debug", - groupTree: TestGroup( - "Foo", - children: [ - TestFile("a.swift"), - TestFile("b.swift"), - ] - ), - targets: [ - TestStandardTarget( - "Target2", - guid: "Target2GUID", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "b.swift" - ]) - ] - ), - TestStandardTarget( - "Target", - guid: "TargetGUID", - type: .dynamicLibrary, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]) - ], - buildPhases: [ - TestSourcesBuildPhase([ - "a.swift" - ]) - ], - dependencies: ["Target2"] - ), - ] - ) - ]) - try await testSession.sendPIF(updatedWorkspace) - connectionToServer.send(OnWatchedFilesDidChangeNotification(changes: [ - .init(uri: SWBBuildServer.sessionPIFURI, type: .changed) - ])) - _ = try await connectionToServer.send(WorkspaceWaitForBuildSystemUpdatesRequest()) - - let updatedTargetsResponse = try await connectionToServer.send(WorkspaceBuildTargetsRequest()) - #expect(updatedTargetsResponse.targets.map(\.displayName).sorted() == ["Target", "Target2"]) - - _ = try await connectionToServer.send(BuildShutdownRequest()) - connectionToServer.send(OnBuildExitNotification()) - connectionToServer.close() - } - } - } -} -#endif