Skip to content

Commit f8c87c9

Browse files
dschaefer2bnbarham
authored andcommitted
Add prepare for index experimental build argument (swiftlang#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. (cherry picked from commit 7bd34cc)
1 parent 9afe748 commit f8c87c9

13 files changed

+162
-14
lines changed

.gitignore

+1
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

+11
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,17 @@ public final class SwiftTargetBuildDescription {
551551
args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString]
552552
}
553553

554+
if self.buildParameters.prepareForIndexing {
555+
args += [
556+
"-Xfrontend", "-enable-library-evolution",
557+
"-Xfrontend", "-experimental-skip-all-function-bodies",
558+
"-Xfrontend", "-experimental-lazy-typecheck",
559+
"-Xfrontend", "-experimental-skip-non-exportable-decls",
560+
"-Xfrontend", "-experimental-allow-module-with-compiler-errors",
561+
"-Xfrontend", "-empty-abi-descriptor"
562+
]
563+
}
564+
554565
args += self.buildParameters.toolchain.extraFlags.swiftCompilerFlags
555566
// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
556567
args += self.buildParameters.flags.swiftCompilerFlags

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

+16-5
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

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

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

424+
let prepareForIndexing = target.buildParameters.prepareForIndexing
425+
423426
func addStaticTargetInputs(_ target: ResolvedModule) throws {
424427
// Ignore C Modules.
425428
if target.underlying is SystemLibraryTarget { return }
@@ -429,7 +432,7 @@ extension LLBuildManifestBuilder {
429432
if target.underlying is PluginTarget { return }
430433

431434
// Depend on the binary for executable targets.
432-
if target.type == .executable {
435+
if target.type == .executable && !prepareForIndexing {
433436
// FIXME: Optimize.
434437
let product = try plan.graph.allProducts.first {
435438
try $0.type == .executable && $0.executableTarget.id == target.id
@@ -447,8 +450,16 @@ extension LLBuildManifestBuilder {
447450
case .swift(let target)?:
448451
inputs.append(file: target.moduleOutputPath)
449452
case .clang(let target)?:
450-
for object in try target.objects {
451-
inputs.append(file: object)
453+
if prepareForIndexing {
454+
// In preparation, we're only building swiftmodules
455+
// propagate the dependency to the header files in this target
456+
for header in target.clangTarget.headers {
457+
inputs.append(file: header)
458+
}
459+
} else {
460+
for object in try target.objects {
461+
inputs.append(file: object)
462+
}
452463
}
453464
case nil:
454465
throw InternalError("unexpected: target \(target) not in target map \(self.plan.targetMap)")

Sources/Build/BuildManifest/LLBuildManifestBuilder.swift

+35
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

+3-1
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,9 @@ extension BuildDescription {
847847
) throws -> (BuildDescription, LLBuildManifest) {
848848
// Generate the llbuild manifest.
849849
let llbuild = LLBuildManifestBuilder(plan, disableSandboxForPluginCommands: disableSandboxForPluginCommands, fileSystem: fileSystem, observabilityScope: observabilityScope)
850-
let buildManifest = try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
850+
let buildManifest = plan.destinationBuildParameters.prepareForIndexing
851+
? try llbuild.generatePrepareManifest(at: plan.destinationBuildParameters.llbuildManifest)
852+
: try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
851853

852854
let swiftCommands = llbuild.manifest.getCmdToolMap(kind: SwiftCompilerTool.self)
853855
let swiftFrontendCommands = llbuild.manifest.getCmdToolMap(kind: SwiftFrontendTool.self)

Sources/CoreCommands/Options.swift

+3
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,9 @@ public struct BuildOptions: ParsableArguments {
438438
@Flag(help: "Enable or disable indexing-while-building feature")
439439
public var indexStoreMode: StoreMode = .autoIndexStore
440440

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

Sources/CoreCommands/SwiftCommandState.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,10 @@ public final class SwiftCommandState {
726726
when building on macOS.
727727
"""
728728

729-
private func _buildParams(toolchain: UserToolchain) throws -> BuildParameters {
729+
private func _buildParams(
730+
toolchain: UserToolchain,
731+
prepareForIndexing: Bool? = nil
732+
) throws -> BuildParameters {
730733
let triple = toolchain.targetTriple
731734

732735
let dataPath = self.scratchDirectory.appending(
@@ -749,6 +752,7 @@ public final class SwiftCommandState {
749752
sanitizers: options.build.enabledSanitizers,
750753
indexStoreMode: options.build.indexStoreMode.buildParameter,
751754
isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode,
755+
prepareForIndexing: prepareForIndexing ?? options.build.prepareForIndexing,
752756
debuggingParameters: .init(
753757
debugInfoFormat: options.build.debugInfoFormat.buildParameter,
754758
triple: triple,
@@ -797,7 +801,7 @@ public final class SwiftCommandState {
797801

798802
private lazy var _toolsBuildParameters: Result<BuildParameters, Swift.Error> = {
799803
Result(catching: {
800-
try _buildParams(toolchain: self.getHostToolchain())
804+
try _buildParams(toolchain: self.getHostToolchain(), prepareForIndexing: false)
801805
})
802806
}()
803807

Sources/LLBuildManifest/LLBuildManifest.swift

+4-2
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

+8-2
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

+1-1
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

+5
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ public struct BuildParameters: Encodable {
102102

103103
public var shouldSkipBuilding: Bool
104104

105+
/// Do minimal build to prepare for indexing
106+
public var prepareForIndexing: Bool
107+
105108
/// Build parameters related to debugging.
106109
public var debuggingParameters: Debugging
107110

@@ -131,6 +134,7 @@ public struct BuildParameters: Encodable {
131134
indexStoreMode: IndexStoreMode = .auto,
132135
isXcodeBuildSystemEnabled: Bool = false,
133136
shouldSkipBuilding: Bool = false,
137+
prepareForIndexing: Bool = false,
134138
debuggingParameters: Debugging? = nil,
135139
driverParameters: Driver = .init(),
136140
linkingParameters: Linking = .init(),
@@ -185,6 +189,7 @@ public struct BuildParameters: Encodable {
185189
self.indexStoreMode = indexStoreMode
186190
self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled
187191
self.shouldSkipBuilding = shouldSkipBuilding
192+
self.prepareForIndexing = prepareForIndexing
188193
self.driverParameters = driverParameters
189194
self.linkingParameters = linkingParameters
190195
self.outputParameters = outputParameters

Sources/SPMTestSupport/MockBuildTestHelper.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ public func mockBuildParameters(
8383
useExplicitModuleBuild: Bool = false,
8484
linkerDeadStrip: Bool = true,
8585
linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil,
86-
omitFramePointers: Bool? = nil
86+
omitFramePointers: Bool? = nil,
87+
prepareForIndexing: Bool = false
8788
) -> BuildParameters {
8889
try! BuildParameters(
8990
dataPath: buildPath,
@@ -94,6 +95,7 @@ public func mockBuildParameters(
9495
pkgConfigDirectories: [],
9596
workers: 3,
9697
indexStoreMode: indexStoreMode,
98+
prepareForIndexing: prepareForIndexing,
9799
debuggingParameters: .init(
98100
triple: targetTriple,
99101
shouldEnableDebuggingEntitlement: config == .debug,
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)