diff --git a/Package.swift b/Package.swift index 2d87941e..65cc2d98 100644 --- a/Package.swift +++ b/Package.swift @@ -80,10 +80,12 @@ var targets: [Target] = [ "SwiftExtensions", "ToolchainRegistry", "TSCExtensions", - .product(name: "SwiftPM-auto", package: "swift-package-manager"), - .product(name: "SwiftPMDataModel-auto", package: "swift-package-manager"), .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), - ], + ] + + swiftPMDependency([ + .product(name: "SwiftPM-auto", package: "swift-package-manager"), + .product(name: "SwiftPMDataModel-auto", package: "swift-package-manager"), + ]), exclude: ["CMakeLists.txt"], swiftSettings: globalSwiftSettings ), @@ -387,8 +389,10 @@ var targets: [Target] = [ .product(name: "IndexStoreDB", package: "indexstore-db"), .product(name: "Crypto", package: "swift-crypto"), .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), - .product(name: "SwiftPM-auto", package: "swift-package-manager"), ] + + swiftPMDependency([ + .product(name: "SwiftPM-auto", package: "swift-package-manager") + ]) + swiftSyntaxDependencies([ "SwiftBasicFormat", "SwiftDiagnostics", "SwiftIDEUtils", "SwiftParser", "SwiftParserDiagnostics", "SwiftRefactor", "SwiftSyntax", @@ -449,8 +453,6 @@ var targets: [Target] = [ "SKUtilities", "SwiftExtensions", "TSCExtensions", - .product(name: "SwiftPMDataModel-auto", package: "swift-package-manager"), - .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), ], exclude: ["CMakeLists.txt"], swiftSettings: globalSwiftSettings @@ -461,8 +463,6 @@ var targets: [Target] = [ dependencies: [ "SKTestSupport", "ToolchainRegistry", - .product(name: "SwiftPMDataModel-auto", package: "swift-package-manager"), - .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), ], swiftSettings: globalSwiftSettings ), @@ -524,6 +524,13 @@ func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] { } } +func swiftPMDependency(_ values: [T]) -> [T] { + if noSwiftPMDependency { + return [] + } + return values +} + // MARK: - Parse build arguments func hasEnvironmentVariable(_ name: String) -> Bool { @@ -558,6 +565,9 @@ var buildDynamicSwiftSyntaxLibrary: Bool { hasEnvironmentVariable("SWIFTSYNTAX_B /// the `swift test` invocation so that all pre-built modules can be found. var buildOnlyTests: Bool { hasEnvironmentVariable("SOURCEKIT_LSP_BUILD_ONLY_TESTS") } +/// Build SourceKit-LSP without a dependency on SwiftPM, ie. without support for SwiftPM projects. +var noSwiftPMDependency: Bool { hasEnvironmentVariable("SOURCEKIT_LSP_NO_SWIFTPM_DEPENDENCY") } + // MARK: - Dependencies // When building with the swift build-script, use local dependencies whose contents are controlled @@ -570,18 +580,16 @@ var dependencies: [Package.Dependency] { } else if useLocalDependencies { return [ .package(path: "../indexstore-db"), - .package(name: "swift-package-manager", path: "../swiftpm"), .package(path: "../swift-tools-support-core"), .package(path: "../swift-argument-parser"), .package(path: "../swift-syntax"), .package(path: "../swift-crypto"), - ] + ] + swiftPMDependency([.package(name: "swift-package-manager", path: "../swiftpm")]) } else { let relatedDependenciesBranch = "main" return [ .package(url: "https://github.com/swiftlang/indexstore-db.git", branch: relatedDependenciesBranch), - .package(url: "https://github.com/swiftlang/swift-package-manager.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/apple/swift-tools-support-core.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.4.0"), .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: relatedDependenciesBranch), @@ -589,6 +597,9 @@ var dependencies: [Package.Dependency] { // Not a build dependency. Used so the "Format Source Code" command plugin can be used to format sourcekit-lsp .package(url: "https://github.com/swiftlang/swift-format.git", branch: relatedDependenciesBranch), ] + + swiftPMDependency([ + .package(url: "https://github.com/swiftlang/swift-package-manager.git", branch: relatedDependenciesBranch) + ]) } } diff --git a/Sources/BuildSystemIntegration/BuildSystemManager.swift b/Sources/BuildSystemIntegration/BuildSystemManager.swift index 8dff5ea1..6f764203 100644 --- a/Sources/BuildSystemIntegration/BuildSystemManager.swift +++ b/Sources/BuildSystemIntegration/BuildSystemManager.swift @@ -210,6 +210,7 @@ private extension BuildSystemSpec { ) } case .swiftPM: + #if canImport(PackageModel) return await Self.createBuiltInBuildSystemAdapter( projectRoot: projectRoot, messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler, @@ -223,6 +224,9 @@ private extension BuildSystemSpec { testHooks: testHooks.swiftPMTestHooks ) } + #else + return nil + #endif case .testBuildSystem: return await Self.createBuiltInBuildSystemAdapter( projectRoot: projectRoot, diff --git a/Sources/BuildSystemIntegration/BuildSystemTestHooks.swift b/Sources/BuildSystemIntegration/BuildSystemTestHooks.swift index aa59f033..4b469906 100644 --- a/Sources/BuildSystemIntegration/BuildSystemTestHooks.swift +++ b/Sources/BuildSystemIntegration/BuildSystemTestHooks.swift @@ -16,6 +16,19 @@ package import LanguageServerProtocol import LanguageServerProtocol #endif +package struct SwiftPMTestHooks: Sendable { + package var reloadPackageDidStart: (@Sendable () async -> Void)? + package var reloadPackageDidFinish: (@Sendable () async -> Void)? + + package init( + reloadPackageDidStart: (@Sendable () async -> Void)? = nil, + reloadPackageDidFinish: (@Sendable () async -> Void)? = nil + ) { + self.reloadPackageDidStart = reloadPackageDidStart + self.reloadPackageDidFinish = reloadPackageDidFinish + } +} + package struct BuildSystemTestHooks: Sendable { package var swiftPMTestHooks: SwiftPMTestHooks diff --git a/Sources/BuildSystemIntegration/BuildTargetIdentifierExtensions.swift b/Sources/BuildSystemIntegration/BuildTargetIdentifierExtensions.swift index 11f897e1..a6ac0c4f 100644 --- a/Sources/BuildSystemIntegration/BuildTargetIdentifierExtensions.swift +++ b/Sources/BuildSystemIntegration/BuildTargetIdentifierExtensions.swift @@ -10,20 +10,92 @@ // //===----------------------------------------------------------------------===// +import Foundation +import LanguageServerProtocol +import SKLogging + #if compiler(>=6) package import BuildServerProtocol -import LanguageServerProtocol -import SKLogging #else import BuildServerProtocol -import LanguageServerProtocol -import SKLogging #endif extension BuildTargetIdentifier { package static let dummy: BuildTargetIdentifier = BuildTargetIdentifier(uri: try! URI(string: "dummy://dummy")) } +package enum BuildDestinationIdentifier { + case host + case target + + /// A string that can be used to identify the build triple in a `BuildTargetIdentifier`. + /// + /// `BuildSystemManager.canonicalBuildTargetIdentifier` picks the canonical target based on alphabetical + /// ordering. We rely on the string "destination" being ordered before "tools" so that we prefer a + /// `destination` (or "target") target over a `tools` (or "host") target. + var id: String { + switch self { + case .host: + return "tools" + case .target: + return "destination" + } + } +} + +extension BuildTargetIdentifier { + /// - Important: *For testing only* + package init(target: String, destination: BuildDestinationIdentifier) throws { + var components = URLComponents() + components.scheme = "swiftpm" + components.host = "target" + components.queryItems = [ + URLQueryItem(name: "target", value: target), + URLQueryItem(name: "destination", value: destination.id), + ] + + struct FailedToConvertSwiftBuildTargetToUrlError: Swift.Error, CustomStringConvertible { + var target: String + var destination: String + + var description: String { + return "Failed to generate URL for target: \(target), destination: \(destination)" + } + } + + guard let url = components.url else { + throw FailedToConvertSwiftBuildTargetToUrlError(target: target, destination: destination.id) + } + + self.init(uri: URI(url)) + } + + fileprivate static let forPackageManifest = BuildTargetIdentifier(uri: try! URI(string: "swiftpm://package-manifest")) + + fileprivate var targetProperties: (target: String, runDestination: String) { + 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) + } + let target = components.queryItems?.last(where: { $0.name == "target" })?.value + let runDestination = components.queryItems?.last(where: { $0.name == "destination" })?.value + + guard let target, let runDestination else { + throw InvalidTargetIdentifierError(target: self) + } + + return (target, runDestination) + } + } +} + #if compiler(>=6) extension BuildTargetIdentifier: CustomLogStringConvertible { package var description: String { diff --git a/Sources/BuildSystemIntegration/DetermineBuildSystem.swift b/Sources/BuildSystemIntegration/DetermineBuildSystem.swift index fabf3378..7dd35706 100644 --- a/Sources/BuildSystemIntegration/DetermineBuildSystem.swift +++ b/Sources/BuildSystemIntegration/DetermineBuildSystem.swift @@ -59,9 +59,13 @@ package func determineBuildSystem( return BuildSystemSpec(kind: .compilationDatabase, projectRoot: projectRoot) } case .swiftPM: + #if canImport(PackageModel) if let projectRoot = SwiftPMBuildSystem.projectRoot(for: workspaceFolderUrl, options: options) { return BuildSystemSpec(kind: .swiftPM, projectRoot: projectRoot) } + #else + return nil + #endif } } diff --git a/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift b/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift index 50a8e963..c8e92edc 100644 --- a/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift +++ b/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift @@ -10,53 +10,41 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=6) +#if canImport(PackageModel) import Basics @preconcurrency import Build -package import BuildServerProtocol import Dispatch -package import Foundation -package import LanguageServerProtocol import LanguageServerProtocolExtensions @preconcurrency import PackageGraph import PackageLoading import PackageModel import SKLogging -package import SKOptions @preconcurrency package import SPMBuildCore import SourceControl -package import SourceKitLSPAPI import SwiftExtensions -package import ToolchainRegistry import TSCExtensions @preconcurrency import Workspace import struct TSCBasic.AbsolutePath import class TSCBasic.Process + +#if compiler(>=6) +package import BuildServerProtocol +package import BuildSystemIntegration +package import Foundation +package import LanguageServerProtocol +package import SKOptions +package import SourceKitLSPAPI +package import ToolchainRegistry package import class ToolchainRegistry.Toolchain #else -import Basics -@preconcurrency import Build import BuildServerProtocol -import Dispatch +import BuildSystemIntegration import Foundation import LanguageServerProtocol -import LanguageServerProtocolExtensions -@preconcurrency import PackageGraph -import PackageLoading -import PackageModel -import SKLogging import SKOptions -@preconcurrency import SPMBuildCore -import SourceControl import SourceKitLSPAPI -import SwiftExtensions import ToolchainRegistry -import TSCExtensions -@preconcurrency import Workspace - -import struct TSCBasic.AbsolutePath -import class TSCBasic.Process import class ToolchainRegistry.Toolchain #endif @@ -78,51 +66,18 @@ fileprivate extension Basics.Diagnostic.Severity { } } -fileprivate extension BuildDestination { - /// A string that can be used to identify the build triple in a `BuildTargetIdentifier`. - /// - /// `BuildSystemManager.canonicalBuildTargetIdentifier` picks the canonical target based on alphabetical - /// ordering. We rely on the string "destination" being ordered before "tools" so that we prefer a - /// `destination` (or "target") target over a `tools` (or "host") target. - var id: String { - switch self { - case .host: - return "tools" - case .target: - return "destination" +extension BuildDestinationIdentifier { + init(_ destination: BuildDestination) { + switch destination { + case .target: self = .target + case .host: self = .host } } } extension BuildTargetIdentifier { fileprivate init(_ buildTarget: any SwiftBuildTarget) throws { - try self.init(target: buildTarget.name, destination: buildTarget.destination) - } - - /// - Important: *For testing only* - package init(target: String, destination: BuildDestination) throws { - var components = URLComponents() - components.scheme = "swiftpm" - components.host = "target" - components.queryItems = [ - URLQueryItem(name: "target", value: target), - URLQueryItem(name: "destination", value: destination.id), - ] - - struct FailedToConvertSwiftBuildTargetToUrlError: Swift.Error, CustomStringConvertible { - var target: String - var destination: String - - var description: String { - return "Failed to generate URL for target: \(target), destination: \(destination)" - } - } - - guard let url = components.url else { - throw FailedToConvertSwiftBuildTargetToUrlError(target: target, destination: destination.id) - } - - self.init(uri: URI(url)) + try self.init(target: buildTarget.name, destination: BuildDestinationIdentifier(buildTarget.destination)) } fileprivate static let forPackageManifest = BuildTargetIdentifier(uri: try! URI(string: "swiftpm://package-manifest")) @@ -159,19 +114,6 @@ fileprivate extension TSCBasic.AbsolutePath { fileprivate let preparationTaskID: AtomicUInt32 = AtomicUInt32(initialValue: 0) -package struct SwiftPMTestHooks: Sendable { - package var reloadPackageDidStart: (@Sendable () async -> Void)? - package var reloadPackageDidFinish: (@Sendable () async -> Void)? - - package init( - reloadPackageDidStart: (@Sendable () async -> Void)? = nil, - reloadPackageDidFinish: (@Sendable () async -> Void)? = nil - ) { - self.reloadPackageDidStart = reloadPackageDidStart - self.reloadPackageDidFinish = reloadPackageDidFinish - } -} - /// Swift Package Manager build system and workspace support. /// /// This class implements the `BuiltInBuildSystem` interface to provide the build settings for a Swift @@ -793,3 +735,5 @@ fileprivate extension URL { (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true } } + +#endif diff --git a/Sources/SourceKitLSP/Swift/CodeActions/PackageManifestEdits.swift b/Sources/SourceKitLSP/Swift/CodeActions/PackageManifestEdits.swift index d2b047e6..6e6419cf 100644 --- a/Sources/SourceKitLSP/Swift/CodeActions/PackageManifestEdits.swift +++ b/Sources/SourceKitLSP/Swift/CodeActions/PackageManifestEdits.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +#if canImport(PackageModel) + import Foundation import LanguageServerProtocol import PackageModel @@ -298,3 +300,5 @@ fileprivate extension FunctionCallExprSyntax { return memberAccess.declName.baseName.text } } + +#endif diff --git a/Sources/SourceKitLSP/Swift/CodeActions/SyntaxCodeActions.swift b/Sources/SourceKitLSP/Swift/CodeActions/SyntaxCodeActions.swift index fd50f528..d8424725 100644 --- a/Sources/SourceKitLSP/Swift/CodeActions/SyntaxCodeActions.swift +++ b/Sources/SourceKitLSP/Swift/CodeActions/SyntaxCodeActions.swift @@ -14,15 +14,20 @@ import SwiftRefactor /// List of all of the syntactic code action providers, which can be used /// to produce code actions using only the swift-syntax tree of a file. -let allSyntaxCodeActions: [SyntaxCodeActionProvider.Type] = [ - AddDocumentation.self, - AddSeparatorsToIntegerLiteral.self, - ConvertIntegerLiteral.self, - ConvertJSONToCodableStruct.self, - ConvertStringConcatenationToStringInterpolation.self, - FormatRawStringLiteral.self, - MigrateToNewIfLetSyntax.self, - OpaqueParameterToGeneric.self, - PackageManifestEdits.self, - RemoveSeparatorsFromIntegerLiteral.self, -] +let allSyntaxCodeActions: [SyntaxCodeActionProvider.Type] = { + var result: [SyntaxCodeActionProvider.Type] = [ + AddDocumentation.self, + AddSeparatorsToIntegerLiteral.self, + ConvertIntegerLiteral.self, + ConvertJSONToCodableStruct.self, + ConvertStringConcatenationToStringInterpolation.self, + FormatRawStringLiteral.self, + MigrateToNewIfLetSyntax.self, + OpaqueParameterToGeneric.self, + RemoveSeparatorsFromIntegerLiteral.self, + ] + #if canImport(PackageModel) + result.append(PackageManifestEdits.self) + #endif + return result +}() diff --git a/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift b/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift index 3944dbba..213450c7 100644 --- a/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift +++ b/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import Build +#if canImport(PackageModel) import BuildServerProtocol @_spi(Testing) import BuildSystemIntegration import LanguageServerProtocol @@ -19,6 +19,7 @@ import PackageModel import SKOptions import SKTestSupport import SourceKitLSP +@preconcurrency import SPMBuildCore import SwiftExtensions import TSCBasic import TSCExtensions @@ -27,11 +28,6 @@ import XCTest import struct Basics.AbsolutePath import struct Basics.Triple -import struct PackageModel.BuildFlags - -#if canImport(SPMBuildCore) -@preconcurrency import SPMBuildCore -#endif private var hostTriple: Triple { get async throws { @@ -1243,3 +1239,4 @@ fileprivate extension URL { return result } } +#endif diff --git a/Tests/SourceKitLSPTests/ExpectedIndexTaskTracker.swift b/Tests/SourceKitLSPTests/ExpectedIndexTaskTracker.swift index e082052b..92cf4610 100644 --- a/Tests/SourceKitLSPTests/ExpectedIndexTaskTracker.swift +++ b/Tests/SourceKitLSPTests/ExpectedIndexTaskTracker.swift @@ -15,9 +15,55 @@ import BuildSystemIntegration import LanguageServerProtocol import SKLogging import SemanticIndex -import SourceKitLSPAPI import XCTest +enum BuildDestination { + case host + case target + + /// A string that can be used to identify the build triple in a `BuildTargetIdentifier`. + /// + /// `BuildSystemManager.canonicalBuildTargetIdentifier` picks the canonical target based on alphabetical + /// ordering. We rely on the string "destination" being ordered before "tools" so that we prefer a + /// `destination` (or "target") target over a `tools` (or "host") target. + var id: String { + switch self { + case .host: + return "tools" + case .target: + return "destination" + } + } +} + +extension BuildTargetIdentifier { + /// - Important: *For testing only* + init(target: String, destination: BuildDestination) throws { + var components = URLComponents() + components.scheme = "swiftpm" + components.host = "target" + components.queryItems = [ + URLQueryItem(name: "target", value: target), + URLQueryItem(name: "destination", value: destination.id), + ] + + struct FailedToConvertSwiftBuildTargetToUrlError: Swift.Error, CustomStringConvertible { + var target: String + var destination: String + + var description: String { + return "Failed to generate URL for target: \(target), destination: \(destination)" + } + } + + guard let url = components.url else { + throw FailedToConvertSwiftBuildTargetToUrlError(target: target, destination: destination.id) + } + + self.init(uri: URI(url)) + } +} + struct ExpectedPreparation { let target: BuildTargetIdentifier