Skip to content

Commit 7f310d2

Browse files
committed
bsp
1 parent 015c647 commit 7f310d2

File tree

13 files changed

+1701
-8
lines changed

13 files changed

+1701
-8
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public func foo() {
2+
{}()
3+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// swift-tools-version:6.0
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "Foo",
6+
products: [
7+
.library(name: "Foo", targets: ["Foo"]),
8+
],
9+
targets: [
10+
.target(name: "Foo", path: "./"),
11+
]
12+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// swift-tools-version:5.2
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "Foo",
6+
products: [
7+
.library(name: "Foo", targets: ["Foo"]),
8+
],
9+
targets: [
10+
.target(name: "Foo", path: "./"),
11+
]
12+
)

Package.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,23 @@ let package = Package(
575575
]
576576
),
577577

578+
// MARK: BSP
579+
.target(
580+
name: "SwiftPMBuildServer",
581+
dependencies: [
582+
"Basics",
583+
"Build",
584+
"PackageGraph",
585+
"PackageLoading",
586+
"PackageModel",
587+
"SPMBuildCore",
588+
"SourceControl",
589+
"SourceKitLSPAPI",
590+
"SwiftBuildSupport",
591+
.product(name: "SWBBuildServerProtocol", package: "swift-build"),
592+
] + swiftTSCBasicsDeps
593+
),
594+
578595
// MARK: Commands
579596

580597
.target(
@@ -613,6 +630,7 @@ let package = Package(
613630
"XCBuildSupport",
614631
"SwiftBuildSupport",
615632
"SwiftFixIt",
633+
"SwiftPMBuildServer",
616634
] + swiftSyntaxDependencies(["SwiftIDEUtils", "SwiftRefactor"]),
617635
exclude: ["CMakeLists.txt", "README.md"],
618636
swiftSettings: swift6CompatibleExperimentalFeatures + [
@@ -1053,6 +1071,14 @@ if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] ==
10531071
"dummy-swiftc",
10541072
]
10551073
),
1074+
.testTarget(
1075+
name: "SwiftPMBuildServerTests",
1076+
dependencies: [
1077+
"SwiftPMBuildServer",
1078+
"_InternalTestSupport",
1079+
.product(name: "SWBBuildServerProtocol", package: "swift-build"),
1080+
]
1081+
),
10561082
])
10571083
}
10581084

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import TSCBasic
15+
import SWBBuildServerProtocol
16+
import CoreCommands
17+
import Foundation
18+
import PackageGraph
19+
import SwiftPMBuildServer
20+
import SPMBuildCore
21+
import SwiftBuildSupport
22+
23+
struct BuildServer: AsyncSwiftCommand {
24+
static let configuration = CommandConfiguration(
25+
commandName: "build-server",
26+
abstract: "Launch a build server for Swift Packages"
27+
)
28+
29+
@OptionGroup(visibility: .hidden)
30+
var globalOptions: GlobalOptions
31+
32+
func run(_ swiftCommandState: SwiftCommandState) async throws {
33+
// Dup stdout and redirect the fd to stderr so that a careless print()
34+
// will not break our connection stream.
35+
let realStdout = dup(STDOUT_FILENO)
36+
if realStdout == -1 {
37+
fatalError("failed to dup stdout: \(strerror(errno)!)")
38+
}
39+
if dup2(STDERR_FILENO, STDOUT_FILENO) == -1 {
40+
fatalError("failed to redirect stdout -> stderr: \(strerror(errno)!)")
41+
}
42+
43+
let realStdoutHandle = FileHandle(fileDescriptor: realStdout, closeOnDealloc: false)
44+
45+
let clientConnection = JSONRPCConnection(
46+
name: "client",
47+
protocol: bspRegistry,
48+
inFD: FileHandle.standardInput,
49+
outFD: realStdoutHandle,
50+
inputMirrorFile: nil,
51+
outputMirrorFile: nil
52+
)
53+
54+
guard let buildSystem = try await swiftCommandState.createBuildSystem() as? SwiftBuildSystem else {
55+
print("Build server requires --build-system swiftbuild")
56+
Self.exit()
57+
}
58+
59+
guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else {
60+
throw StringError("unknown package")
61+
}
62+
63+
let server = try await SwiftPMBuildServer(packageRoot: packagePath, buildSystem: buildSystem, workspace: swiftCommandState.getActiveWorkspace(), connectionToClient: clientConnection, exitHandler: {_ in Self.exit() })
64+
clientConnection.start(
65+
receiveHandler: server,
66+
closeHandler: {
67+
Self.exit()
68+
}
69+
)
70+
71+
// Park the main function by sleeping for 10 years.
72+
while true {
73+
try? await Task.sleep(for: .seconds(60 * 60 * 24 * 365 * 10))
74+
}
75+
}
76+
}

Sources/Commands/PackageCommands/SwiftPackageCommand.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand {
3939
AddTargetDependency.self,
4040
AddSetting.self,
4141
AuditBinaryArtifact.self,
42+
BuildServer.self,
4243
Clean.self,
4344
PurgeCache.self,
4445
Reset.self,

Sources/SwiftBuildSupport/SwiftBuildSystem.swift

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ func withSession(
109109
}
110110
}
111111

112-
private final class PlanningOperationDelegate: SWBPlanningOperationDelegate, Sendable {
112+
package final class SwiftBuildSystemPlanningOperationDelegate: SWBPlanningOperationDelegate, SWBIndexingDelegate, Sendable {
113+
package init() {}
114+
113115
public func provisioningTaskInputs(
114116
targetGUID: String,
115117
provisioningSourceData: SWBProvisioningTaskInputsSourceData
@@ -194,7 +196,7 @@ public struct PluginConfiguration {
194196
}
195197

196198
public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
197-
private let buildParameters: BuildParameters
199+
package let buildParameters: BuildParameters
198200
private let packageGraphLoader: () async throws -> ModulesGraph
199201
private let packageManagerResourcesDirectory: Basics.AbsolutePath?
200202
private let logLevel: Basics.Diagnostic.Severity
@@ -349,7 +351,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
349351
)
350352
}
351353

352-
try await writePIF(buildParameters: buildParameters)
354+
try await writePIF()
353355

354356
return try await startSWBuildOperation(
355357
pifTargetName: subset.pifTargetName,
@@ -545,7 +547,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
545547

546548
let operation = try await session.createBuildOperation(
547549
request: request,
548-
delegate: PlanningOperationDelegate()
550+
delegate: SwiftBuildSystemPlanningOperationDelegate()
549551
)
550552

551553
var buildState = BuildState()
@@ -631,7 +633,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
631633
)
632634
}
633635

634-
private func makeBuildParameters(session: SWBBuildServiceSession, genSymbolGraph: Bool) async throws -> SwiftBuild.SWBBuildParameters {
636+
package func makeBuildParameters(session: SWBBuildServiceSession, genSymbolGraph: Bool) async throws -> SwiftBuild.SWBBuildParameters {
635637
// Generate the run destination parameters.
636638
let runDestination = makeRunDestination()
637639

@@ -767,8 +769,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
767769
buildProductsPath: ddPathPrefix + "/Products",
768770
buildIntermediatesPath: ddPathPrefix + "/Intermediates.noindex",
769771
pchPath: ddPathPrefix + "/PCH",
770-
indexRegularBuildProductsPath: nil,
771-
indexRegularBuildIntermediatesPath: nil,
772+
indexRegularBuildProductsPath: ddPathPrefix + "/Index/Products",
773+
indexRegularBuildIntermediatesPath: ddPathPrefix + "/Index/Intermediates.noindex",
772774
indexPCHPath: ddPathPrefix,
773775
indexDataStoreFolderPath: ddPathPrefix,
774776
indexEnableDataStore: request.parameters.arenaInfo?.indexEnableDataStore ?? false
@@ -902,7 +904,9 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
902904
}
903905
}
904906

905-
public func writePIF(buildParameters: BuildParameters) async throws {
907+
public func writePIF() async throws {
908+
pifBuilder = .init()
909+
packageGraph = .init()
906910
let pifBuilder = try await getPIFBuilder()
907911
let pif = try await pifBuilder.generatePIF(
908912
printPIFManifestGraphviz: buildParameters.printPIFManifestGraphviz,
@@ -912,6 +916,27 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem {
912916
try self.fileSystem.writeIfChanged(path: buildParameters.pifManifest, string: pif)
913917
}
914918

919+
package struct LongLivedBuildServiceSession {
920+
package var session: SWBBuildServiceSession
921+
package var diagnostics: [SwiftBuildMessage.DiagnosticInfo]
922+
package var teardownHandler: () async throws -> Void
923+
}
924+
925+
package func createLongLivedSession(name: String) async throws -> LongLivedBuildServiceSession {
926+
let service = try await SWBBuildService(connectionMode: .inProcessStatic(swiftbuildServiceEntryPoint))
927+
do {
928+
let (session, diagnostics) = try await createSession(service: service, name: name, toolchainPath: buildParameters.toolchain.toolchainDir, packageManagerResourcesDirectory: packageManagerResourcesDirectory)
929+
let teardownHandler = {
930+
try await session.close()
931+
await service.close()
932+
}
933+
return LongLivedBuildServiceSession(session: session, diagnostics: diagnostics, teardownHandler: teardownHandler)
934+
} catch {
935+
await service.close()
936+
throw error
937+
}
938+
}
939+
915940
public func cancel(deadline: DispatchTime) throws {}
916941

917942
/// Returns the package graph using the graph loader closure.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if canImport(Glibc)
14+
import Glibc
15+
#elseif canImport(Musl)
16+
import Musl
17+
#elseif canImport(Android)
18+
import Android
19+
#endif
20+
21+
#if canImport(Glibc) || canImport(Musl) || canImport(Android)
22+
// This is a lazily initialised global variable that when read for the first time, will ignore SIGPIPE.
23+
private let globallyIgnoredSIGPIPE: Bool = {
24+
/* no F_SETNOSIGPIPE on Linux :( */
25+
_ = signal(SIGPIPE, SIG_IGN)
26+
return true
27+
}()
28+
#endif
29+
30+
/// We receive a `SIGPIPE` if we write to a pipe that points to a crashed process. This in particular happens if the
31+
/// target of a `JSONRPCConnection` has crashed and we try to send it a message or if swift-format crashes and we try
32+
/// to send the source file to it.
33+
///
34+
/// On Darwin, `DispatchIO` ignores `SIGPIPE` for the pipes handled by it and swift-tools-support-core offers
35+
/// `LocalFileOutputByteStream.disableSigpipe`, but that features is not available on Linux.
36+
///
37+
/// Instead, globally ignore `SIGPIPE` on Linux to prevent us from crashing if the `JSONRPCConnection`'s target crashes.
38+
///
39+
/// On Darwin platforms and on Windows this is a no-op.
40+
package func globallyDisableSigpipeIfNeeded() {
41+
#if !canImport(Darwin) && !os(Windows)
42+
let haveWeIgnoredSIGPIEThisIsHereToTriggerIgnoringIt = globallyIgnoredSIGPIPE
43+
guard haveWeIgnoredSIGPIEThisIsHereToTriggerIgnoringIt else {
44+
fatalError("globallyIgnoredSIGPIPE should always be true")
45+
}
46+
#endif
47+
}

0 commit comments

Comments
 (0)