Skip to content

Commit 7bd34cc

Browse files
authored
Add prepare for index experimental build argument (#7574)
This will be used by sourcekit-lsp to build the swiftmodule files it needs for indexing. Adds experimental-prepare-for-indexing flag to swift build. Creates a llbuild manifest specific for the prepare build that skips generating object files and linking of those files and calls swiftc to only create the swiftmodule as quickly as it can. ### Motivation: To support background indexing in sourcekit-lsp, it will request a prepare for index build to build the swiftmodule files it needs to do indexing. This build should be minimal to ensure indexing is fast so it can respond to language server requests that require the index as soon as possible. ### Modifications: - Add an experimental-prepare-for-indexing flag to the BuildOptions and pass it around to every that needs it. - Build a custom llbuild manifest so that only the commands necessary are added to the prepare build - Add a target property that also ensures tool builds required for the prepare build are performed as usual. - In SwiftTargetBuildDescription, pass compile options that put the swift compiler in prepare "mode". - Ensure object files and binaries are only generated for tools when in prepare mode. ### Result: Implements prepare build mode without affecting regular build mode.
1 parent d33814b commit 7bd34cc

File tree

13 files changed

+160
-14
lines changed

13 files changed

+160
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ Package.resolved
1818
.docc-build
1919
.vscode
2020
Utilities/InstalledSwiftPMConfiguration/config.json
21+
.devcontainer

Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,17 @@ public final class SwiftTargetBuildDescription {
561561
args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString]
562562
}
563563

564+
if self.buildParameters.prepareForIndexing {
565+
args += [
566+
"-Xfrontend", "-enable-library-evolution",
567+
"-Xfrontend", "-experimental-skip-all-function-bodies",
568+
"-Xfrontend", "-experimental-lazy-typecheck",
569+
"-Xfrontend", "-experimental-skip-non-exportable-decls",
570+
"-Xfrontend", "-experimental-allow-module-with-compiler-errors",
571+
"-Xfrontend", "-empty-abi-descriptor"
572+
]
573+
}
574+
564575
args += self.buildParameters.toolchain.extraFlags.swiftCompilerFlags
565576
// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
566577
args += self.buildParameters.flags.swiftCompilerFlags

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension LLBuildManifestBuilder {
4141
let inputs = try self.computeSwiftCompileCmdInputs(target)
4242

4343
// Outputs.
44-
let objectNodes = try target.objects.map(Node.file)
44+
let objectNodes = target.buildParameters.prepareForIndexing ? [] : try target.objects.map(Node.file)
4545
let moduleNode = Node.file(target.moduleOutputPath)
4646
let cmdOutputs = objectNodes + [moduleNode]
4747

@@ -394,7 +394,8 @@ extension LLBuildManifestBuilder {
394394
fileList: target.sourcesFileListPath,
395395
isLibrary: isLibrary,
396396
wholeModuleOptimization: target.buildParameters.configuration == .release,
397-
outputFileMapPath: try target.writeOutputFileMap() // FIXME: Eliminate side effect.
397+
outputFileMapPath: try target.writeOutputFileMap(), // FIXME: Eliminate side effect.
398+
prepareForIndexing: target.buildParameters.prepareForIndexing
398399
)
399400
}
400401

@@ -419,6 +420,8 @@ extension LLBuildManifestBuilder {
419420
self.manifest.addWriteEmbeddedResourcesCommand(resources: resourceFilesToEmbed, outputPath: resourcesEmbeddingSource)
420421
}
421422

423+
let prepareForIndexing = target.buildParameters.prepareForIndexing
424+
422425
func addStaticTargetInputs(_ target: ResolvedModule) throws {
423426
// Ignore C Modules.
424427
if target.underlying is SystemLibraryTarget { return }
@@ -430,7 +433,7 @@ extension LLBuildManifestBuilder {
430433
if target.underlying is ProvidedLibraryTarget { return }
431434

432435
// Depend on the binary for executable targets.
433-
if target.type == .executable {
436+
if target.type == .executable && !prepareForIndexing {
434437
// FIXME: Optimize.
435438
if let productDescription = try plan.productMap.values.first(where: {
436439
try $0.product.type == .executable && $0.product.executableTarget.id == target.id
@@ -444,8 +447,16 @@ extension LLBuildManifestBuilder {
444447
case .swift(let target)?:
445448
inputs.append(file: target.moduleOutputPath)
446449
case .clang(let target)?:
447-
for object in try target.objects {
448-
inputs.append(file: object)
450+
if prepareForIndexing {
451+
// In preparation, we're only building swiftmodules
452+
// propagate the dependency to the header files in this target
453+
for header in target.clangTarget.headers {
454+
inputs.append(file: header)
455+
}
456+
} else {
457+
for object in try target.objects {
458+
inputs.append(file: object)
459+
}
449460
}
450461
case nil:
451462
throw InternalError("unexpected: target \(target) not in target map \(self.plan.targetMap)")

Sources/Build/BuildManifest/LLBuildManifestBuilder.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,41 @@ public class LLBuildManifestBuilder {
127127
return self.manifest
128128
}
129129

130+
package func generatePrepareManifest(at path: AbsolutePath) throws -> LLBuildManifest {
131+
self.swiftGetVersionFiles.removeAll()
132+
133+
self.manifest.createTarget(TargetKind.main.targetName)
134+
self.manifest.createTarget(TargetKind.test.targetName)
135+
self.manifest.defaultTarget = TargetKind.main.targetName
136+
137+
addPackageStructureCommand()
138+
139+
for (_, description) in self.plan.targetMap {
140+
switch description {
141+
case .swift(let desc):
142+
try self.createSwiftCompileCommand(desc)
143+
case .clang(let desc):
144+
// Need the clang targets for tools
145+
if desc.target.buildTriple == .tools {
146+
try self.createClangCompileCommand(desc)
147+
}
148+
}
149+
}
150+
151+
for (_, description) in self.plan.productMap {
152+
// Need to generate macro products
153+
switch description.product.type {
154+
case .macro, .plugin:
155+
try self.createProductCommand(description)
156+
default:
157+
break
158+
}
159+
}
160+
161+
try LLBuildManifestWriter.write(self.manifest, at: path, fileSystem: self.fileSystem)
162+
return self.manifest
163+
}
164+
130165
func addNode(_ node: Node, toTarget targetKind: TargetKind) {
131166
self.manifest.addNode(node, toTarget: targetKind.targetName)
132167
}

Sources/Build/BuildOperation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,9 @@ extension BuildDescription {
925925
) throws -> (BuildDescription, LLBuildManifest) {
926926
// Generate the llbuild manifest.
927927
let llbuild = LLBuildManifestBuilder(plan, disableSandboxForPluginCommands: disableSandboxForPluginCommands, fileSystem: fileSystem, observabilityScope: observabilityScope)
928-
let buildManifest = try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
928+
let buildManifest = plan.destinationBuildParameters.prepareForIndexing
929+
? try llbuild.generatePrepareManifest(at: plan.destinationBuildParameters.llbuildManifest)
930+
: try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
929931

930932
let swiftCommands = llbuild.manifest.getCmdToolMap(kind: SwiftCompilerTool.self)
931933
let swiftFrontendCommands = llbuild.manifest.getCmdToolMap(kind: SwiftFrontendTool.self)

Sources/CoreCommands/Options.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,9 @@ public struct BuildOptions: ParsableArguments {
437437
@Flag(help: "Enable or disable indexing-while-building feature")
438438
public var indexStoreMode: StoreMode = .autoIndexStore
439439

440+
@Flag(name: .customLong("experimental-prepare-for-indexing"), help: .hidden)
441+
var prepareForIndexing: Bool = false
442+
440443
/// Whether to enable generation of `.swiftinterface`s alongside `.swiftmodule`s.
441444
@Flag(name: .customLong("enable-parseable-module-interfaces"))
442445
public var shouldEnableParseableModuleInterfaces: Bool = false

Sources/CoreCommands/SwiftCommandState.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,8 @@ public final class SwiftCommandState {
727727

728728
private func _buildParams(
729729
toolchain: UserToolchain,
730-
destination: BuildParameters.Destination
730+
destination: BuildParameters.Destination,
731+
prepareForIndexing: Bool? = nil
731732
) throws -> BuildParameters {
732733
let triple = toolchain.targetTriple
733734

@@ -752,6 +753,7 @@ public final class SwiftCommandState {
752753
sanitizers: options.build.enabledSanitizers,
753754
indexStoreMode: options.build.indexStoreMode.buildParameter,
754755
isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode,
756+
prepareForIndexing: prepareForIndexing ?? options.build.prepareForIndexing,
755757
debuggingParameters: .init(
756758
debugInfoFormat: options.build.debugInfoFormat.buildParameter,
757759
triple: triple,
@@ -800,7 +802,7 @@ public final class SwiftCommandState {
800802

801803
private lazy var _toolsBuildParameters: Result<BuildParameters, Swift.Error> = {
802804
Result(catching: {
803-
try _buildParams(toolchain: self.getHostToolchain(), destination: .host)
805+
try _buildParams(toolchain: self.getHostToolchain(), destination: .host, prepareForIndexing: false)
804806
})
805807
}()
806808

Sources/LLBuildManifest/LLBuildManifest.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ public struct LLBuildManifest {
408408
fileList: AbsolutePath,
409409
isLibrary: Bool,
410410
wholeModuleOptimization: Bool,
411-
outputFileMapPath: AbsolutePath
411+
outputFileMapPath: AbsolutePath,
412+
prepareForIndexing: Bool
412413
) {
413414
assert(commands[name] == nil, "already had a command named '\(name)'")
414415
let tool = SwiftCompilerTool(
@@ -426,7 +427,8 @@ public struct LLBuildManifest {
426427
fileList: fileList,
427428
isLibrary: isLibrary,
428429
wholeModuleOptimization: wholeModuleOptimization,
429-
outputFileMapPath: outputFileMapPath
430+
outputFileMapPath: outputFileMapPath,
431+
prepareForIndexing: prepareForIndexing
430432
)
431433
commands[name] = Command(name: name, tool: tool)
432434
}

Sources/LLBuildManifest/Tools.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ public struct SwiftCompilerTool: ToolProtocol {
271271
public var isLibrary: Bool
272272
public var wholeModuleOptimization: Bool
273273
public var outputFileMapPath: AbsolutePath
274+
public var prepareForIndexing: Bool
274275

275276
init(
276277
inputs: [Node],
@@ -287,7 +288,8 @@ public struct SwiftCompilerTool: ToolProtocol {
287288
fileList: AbsolutePath,
288289
isLibrary: Bool,
289290
wholeModuleOptimization: Bool,
290-
outputFileMapPath: AbsolutePath
291+
outputFileMapPath: AbsolutePath,
292+
prepareForIndexing: Bool
291293
) {
292294
self.inputs = inputs
293295
self.outputs = outputs
@@ -304,6 +306,7 @@ public struct SwiftCompilerTool: ToolProtocol {
304306
self.isLibrary = isLibrary
305307
self.wholeModuleOptimization = wholeModuleOptimization
306308
self.outputFileMapPath = outputFileMapPath
309+
self.prepareForIndexing = prepareForIndexing
307310
}
308311

309312
var description: String {
@@ -334,7 +337,10 @@ public struct SwiftCompilerTool: ToolProtocol {
334337
} else {
335338
arguments += ["-incremental"]
336339
}
337-
arguments += ["-c", "@\(self.fileList.pathString)"]
340+
if !prepareForIndexing {
341+
arguments += ["-c"]
342+
}
343+
arguments += ["@\(self.fileList.pathString)"]
338344
arguments += ["-I", importPath.pathString]
339345
arguments += otherArguments
340346
return arguments

Sources/SPMBuildCore/BuildParameters/BuildParameters+Debugging.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ extension BuildParameters {
6363

6464
/// The debugging strategy according to the current build parameters.
6565
public var debuggingStrategy: DebuggingStrategy? {
66-
guard configuration == .debug else {
66+
guard configuration == .debug, !prepareForIndexing else {
6767
return nil
6868
}
6969

Sources/SPMBuildCore/BuildParameters/BuildParameters.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ public struct BuildParameters: Encodable {
114114

115115
public var shouldSkipBuilding: Bool
116116

117+
/// Do minimal build to prepare for indexing
118+
public var prepareForIndexing: Bool
119+
117120
/// Build parameters related to debugging.
118121
public var debuggingParameters: Debugging
119122

@@ -144,6 +147,7 @@ public struct BuildParameters: Encodable {
144147
indexStoreMode: IndexStoreMode = .auto,
145148
isXcodeBuildSystemEnabled: Bool = false,
146149
shouldSkipBuilding: Bool = false,
150+
prepareForIndexing: Bool = false,
147151
debuggingParameters: Debugging? = nil,
148152
driverParameters: Driver = .init(),
149153
linkingParameters: Linking = .init(),
@@ -199,6 +203,7 @@ public struct BuildParameters: Encodable {
199203
self.indexStoreMode = indexStoreMode
200204
self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled
201205
self.shouldSkipBuilding = shouldSkipBuilding
206+
self.prepareForIndexing = prepareForIndexing
202207
self.driverParameters = driverParameters
203208
self.linkingParameters = linkingParameters
204209
self.outputParameters = outputParameters

Sources/SPMTestSupport/MockBuildTestHelper.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ public func mockBuildParameters(
8989
useExplicitModuleBuild: Bool = false,
9090
linkerDeadStrip: Bool = true,
9191
linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil,
92-
omitFramePointers: Bool? = nil
92+
omitFramePointers: Bool? = nil,
93+
prepareForIndexing: Bool = false
9394
) -> BuildParameters {
9495
try! BuildParameters(
9596
destination: destination,
@@ -101,6 +102,7 @@ public func mockBuildParameters(
101102
pkgConfigDirectories: [],
102103
workers: 3,
103104
indexStoreMode: indexStoreMode,
105+
prepareForIndexing: prepareForIndexing,
104106
debuggingParameters: .init(
105107
triple: triple,
106108
shouldEnableDebuggingEntitlement: config == .debug,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2015-2023 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 Build
14+
import Foundation
15+
import LLBuildManifest
16+
@_spi(SwiftPMInternal)
17+
import SPMTestSupport
18+
import TSCBasic
19+
import XCTest
20+
21+
class PrepareForIndexTests: XCTestCase {
22+
func testPrepare() throws {
23+
let (graph, fs, scope) = try macrosPackageGraph()
24+
25+
let plan = try BuildPlan(
26+
destinationBuildParameters: mockBuildParameters(destination: .target, prepareForIndexing: true),
27+
toolsBuildParameters: mockBuildParameters(destination: .host, prepareForIndexing: false),
28+
graph: graph,
29+
fileSystem: fs,
30+
observabilityScope: scope
31+
)
32+
33+
let builder = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: scope)
34+
let manifest = try builder.generatePrepareManifest(at: "/manifest")
35+
36+
// Make sure we're building the swift modules
37+
let outputs = manifest.commands.flatMap(\.value.tool.outputs).map(\.name)
38+
XCTAssertTrue(outputs.contains(where: { $0.hasSuffix(".swiftmodule") }))
39+
40+
// Ensure swiftmodules built with correct arguments
41+
let coreCommands = manifest.commands.values.filter {
42+
$0.tool.outputs.contains(where: {
43+
$0.name.hasSuffix("debug/Core.build/Core.swiftmodule")
44+
})
45+
}
46+
XCTAssertEqual(coreCommands.count, 1)
47+
let coreSwiftc = try XCTUnwrap(coreCommands.first?.tool as? SwiftCompilerTool)
48+
XCTAssertTrue(coreSwiftc.otherArguments.contains("-experimental-skip-all-function-bodies"))
49+
50+
// Ensure tools are built normally
51+
let toolCommands = manifest.commands.values.filter {
52+
$0.tool.outputs.contains(where: {
53+
$0.name.hasSuffix("debug/Modules-tool/SwiftSyntax.swiftmodule")
54+
})
55+
}
56+
XCTAssertEqual(toolCommands.count, 1)
57+
let toolSwiftc = try XCTUnwrap(toolCommands.first?.tool as? SwiftCompilerTool)
58+
XCTAssertFalse(toolSwiftc.otherArguments.contains("-experimental-skip-all-function-bodies"))
59+
60+
// Make sure only object files for tools are built
61+
XCTAssertTrue(
62+
outputs.filter { $0.hasSuffix(".o") }.allSatisfy { $0.contains("-tool.build/") },
63+
"outputs:\n\t\(outputs.filter { $0.hasSuffix(".o") }.joined(separator: "\n\t"))"
64+
)
65+
}
66+
}

0 commit comments

Comments
 (0)