Skip to content

Commit d470938

Browse files
committed
Adds prepare for index experimental build argument.
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.
1 parent 4dc41b0 commit d470938

File tree

11 files changed

+197
-11
lines changed

11 files changed

+197
-11
lines changed

Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ public final class SwiftTargetBuildDescription {
246246
/// Whether to disable sandboxing (e.g. for macros).
247247
private let shouldDisableSandbox: Bool
248248

249+
/// Whether to build preparing for indexing
250+
public var prepareForIndexing: Bool {
251+
// Do full build for tools
252+
defaultBuildParameters.prepareForIndexing && target.buildTriple != .tools
253+
}
254+
249255
/// Create a new target description with target and build parameters.
250256
init(
251257
package: ResolvedPackage,
@@ -582,6 +588,14 @@ public final class SwiftTargetBuildDescription {
582588
args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString]
583589
}
584590

591+
if self.prepareForIndexing {
592+
args += [
593+
"-Xfrontend", "-experimental-skip-all-function-bodies",
594+
"-Xfrontend", "-experimental-allow-module-with-compiler-errors",
595+
"-Xfrontend", "-empty-abi-descriptor"
596+
]
597+
}
598+
585599
args += self.defaultBuildParameters.toolchain.extraFlags.swiftCompilerFlags
586600
// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
587601
args += self.defaultBuildParameters.flags.swiftCompilerFlags

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ extension LLBuildManifestBuilder {
4343
// Outputs.
4444
let objectNodes = try target.objects.map(Node.file)
4545
let moduleNode = Node.file(target.moduleOutputPath)
46-
let cmdOutputs = objectNodes + [moduleNode]
46+
let cmdOutputs: [Node]
47+
if target.prepareForIndexing {
48+
// Don't include the object nodes on prepare builds
49+
cmdOutputs = [moduleNode]
50+
} else {
51+
cmdOutputs = objectNodes + [moduleNode]
52+
}
4753

4854
if target.defaultBuildParameters.driverParameters.useIntegratedSwiftDriver {
4955
try self.addSwiftCmdsViaIntegratedDriver(
@@ -397,7 +403,8 @@ extension LLBuildManifestBuilder {
397403
fileList: target.sourcesFileListPath,
398404
isLibrary: isLibrary,
399405
wholeModuleOptimization: target.defaultBuildParameters.configuration == .release,
400-
outputFileMapPath: try target.writeOutputFileMap() // FIXME: Eliminate side effect.
406+
outputFileMapPath: try target.writeOutputFileMap(), // FIXME: Eliminate side effect.
407+
prepareForIndexing: target.prepareForIndexing
401408
)
402409
}
403410

@@ -417,6 +424,8 @@ extension LLBuildManifestBuilder {
417424
inputs.append(resourcesNode)
418425
}
419426

427+
let prepareForIndexing = target.prepareForIndexing
428+
420429
func addStaticTargetInputs(_ target: ResolvedModule) throws {
421430
// Ignore C Modules.
422431
if target.underlying is SystemLibraryTarget { return }
@@ -428,7 +437,7 @@ extension LLBuildManifestBuilder {
428437
if target.underlying is ProvidedLibraryTarget { return }
429438

430439
// Depend on the binary for executable targets.
431-
if target.type == .executable {
440+
if target.type == .executable && !prepareForIndexing {
432441
// FIXME: Optimize.
433442
let product = try plan.graph.allProducts.first {
434443
try $0.type == .executable && $0.executableTarget.id == target.id
@@ -446,8 +455,15 @@ extension LLBuildManifestBuilder {
446455
case .swift(let target)?:
447456
inputs.append(file: target.moduleOutputPath)
448457
case .clang(let target)?:
449-
for object in try target.objects {
450-
inputs.append(file: object)
458+
if prepareForIndexing {
459+
// In preparation, we're only building swiftmodules, need to depend on sources
460+
for source in target.clangTarget.sources.paths {
461+
inputs.append(file: source)
462+
}
463+
} else {
464+
for object in try target.objects {
465+
inputs.append(file: object)
466+
}
451467
}
452468
case nil:
453469
throw InternalError("unexpected: target \(target) not in target map \(self.plan.targetMap)")

Sources/Build/BuildManifest/LLBuildManifestBuilder.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,38 @@ 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+
if description.product.type == .macro {
154+
try self.createProductCommand(description)
155+
}
156+
}
157+
158+
try LLBuildManifestWriter.write(self.manifest, at: path, fileSystem: self.fileSystem)
159+
return self.manifest
160+
}
161+
130162
func addNode(_ node: Node, toTarget targetKind: TargetKind) {
131163
self.manifest.addNode(node, toTarget: targetKind.targetName)
132164
}

Sources/Build/BuildOperation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,9 @@ extension BuildDescription {
851851
) throws -> (BuildDescription, LLBuildManifest) {
852852
// Generate the llbuild manifest.
853853
let llbuild = LLBuildManifestBuilder(plan, disableSandboxForPluginCommands: disableSandboxForPluginCommands, fileSystem: fileSystem, observabilityScope: observabilityScope)
854-
let buildManifest = try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
854+
let buildManifest = plan.destinationBuildParameters.prepareForIndexing
855+
? try llbuild.generatePrepareManifest(at: plan.destinationBuildParameters.llbuildManifest)
856+
: try llbuild.generateManifest(at: plan.destinationBuildParameters.llbuildManifest)
855857

856858
let swiftCommands = llbuild.manifest.getCmdToolMap(kind: SwiftCompilerTool.self)
857859
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"))
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ public final class SwiftCommandState {
748748
sanitizers: options.build.enabledSanitizers,
749749
indexStoreMode: options.build.indexStoreMode.buildParameter,
750750
isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode,
751+
prepareForIndexing: options.build.prepareForIndexing,
751752
debuggingParameters: .init(
752753
debugInfoFormat: options.build.debugInfoFormat.buildParameter,
753754
triple: triple,

Sources/LLBuildManifest/LLBuildManifest.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ public struct LLBuildManifest {
368368
fileList: AbsolutePath,
369369
isLibrary: Bool,
370370
wholeModuleOptimization: Bool,
371-
outputFileMapPath: AbsolutePath
371+
outputFileMapPath: AbsolutePath,
372+
prepareForIndexing: Bool
372373
) {
373374
assert(commands[name] == nil, "already had a command named '\(name)'")
374375
let tool = SwiftCompilerTool(
@@ -386,7 +387,8 @@ public struct LLBuildManifest {
386387
fileList: fileList,
387388
isLibrary: isLibrary,
388389
wholeModuleOptimization: wholeModuleOptimization,
389-
outputFileMapPath: outputFileMapPath
390+
outputFileMapPath: outputFileMapPath,
391+
prepareForIndexing: prepareForIndexing
390392
)
391393
commands[name] = Command(name: name, tool: tool)
392394
}

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.swift

Lines changed: 5 additions & 0 deletions
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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public func mockBuildParameters(
8787
useExplicitModuleBuild: Bool = false,
8888
linkerDeadStrip: Bool = true,
8989
linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil,
90-
omitFramePointers: Bool? = nil
90+
omitFramePointers: Bool? = nil,
91+
prepareForIndexing: Bool = false
9192
) -> BuildParameters {
9293
try! BuildParameters(
9394
dataPath: buildPath ?? AbsolutePath("/path/to/build").appending(triple.tripleString),
@@ -98,6 +99,7 @@ public func mockBuildParameters(
9899
pkgConfigDirectories: [],
99100
workers: 3,
100101
indexStoreMode: indexStoreMode,
102+
prepareForIndexing: prepareForIndexing,
101103
debuggingParameters: .init(
102104
triple: triple,
103105
shouldEnableDebuggingEntitlement: config == .debug,
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (C) 2024 Apple Inc. All rights reserved.
2+
//
3+
// This document is the property of Apple Inc.
4+
// It is considered confidential and proprietary.
5+
//
6+
// This document may not be reproduced or transmitted in any form,
7+
// in whole or in part, without the express written permission of
8+
// Apple Inc.
9+
10+
import Build
11+
import Foundation
12+
import LLBuildManifest
13+
@_spi(SwiftPMInternal)
14+
import SPMTestSupport
15+
import XCTest
16+
17+
class PrepareForIndexTests: XCTestCase {
18+
func testPrepare() throws {
19+
let (graph, fs, scope) = try macrosPackageGraph()
20+
21+
let plan = try BuildPlan(
22+
destinationBuildParameters: mockBuildParameters(prepareForIndexing: true),
23+
toolsBuildParameters: mockBuildParameters(prepareForIndexing: true),
24+
graph: graph,
25+
fileSystem: fs,
26+
observabilityScope: scope
27+
)
28+
29+
let builder = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: scope)
30+
let manifest = try builder.generatePrepareManifest(at: "/manifest")
31+
32+
// Make sure we're still building swift modules
33+
XCTAssertNotNil(manifest.commands["<SwiftSyntax-debug.module>"])
34+
// Make sure we're not building things that link
35+
XCTAssertNil(manifest.commands["C.Core-debug.exe"])
36+
37+
let outputs = manifest.commands.flatMap(\.value.tool.outputs).map(\.name)
38+
39+
// Make sure we're building the swift modules
40+
let swiftModules = Set(outputs.filter({ $0.hasSuffix(".swiftmodule")}))
41+
XCTAssertEqual(swiftModules, Set([
42+
"/path/to/build/arm64-apple-macosx15.0/debug/Core.build/Core.swiftmodule",
43+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules/CoreTests.swiftmodule",
44+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules/HAL.swiftmodule",
45+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules/HALTests.swiftmodule",
46+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules/MMIO.swiftmodule",
47+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules/SwiftSyntax.swiftmodule",
48+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules-tool/MMIOMacros.swiftmodule",
49+
"/path/to/build/arm64-apple-macosx15.0/debug/Modules-tool/SwiftSyntax.swiftmodule",
50+
]))
51+
52+
// Ensure swiftmodules built with correct arguments
53+
let coreCommands = manifest.commands.values.filter({
54+
$0.tool.outputs.contains(where: {
55+
$0.name == "/path/to/build/arm64-apple-macosx15.0/debug/Core.build/Core.swiftmodule"
56+
})
57+
})
58+
XCTAssertEqual(coreCommands.count, 1)
59+
let coreSwiftc = try XCTUnwrap(coreCommands.first?.tool as? SwiftCompilerTool)
60+
XCTAssertTrue(coreSwiftc.otherArguments.contains("-experimental-skip-all-function-bodies"))
61+
62+
// Ensure tools are built normally
63+
let toolCommands = manifest.commands.values.filter({
64+
$0.tool.outputs.contains(where: {
65+
$0.name == "/path/to/build/arm64-apple-macosx15.0/debug/Modules-tool/SwiftSyntax.swiftmodule"
66+
})
67+
})
68+
XCTAssertEqual(toolCommands.count, 1)
69+
let toolSwiftc = try XCTUnwrap(toolCommands.first?.tool as? SwiftCompilerTool)
70+
XCTAssertFalse(toolSwiftc.otherArguments.contains("-experimental-skip-all-function-bodies"))
71+
72+
// Make sure only object files for tools are built
73+
let objectFiles = Set(outputs.filter({ $0.hasSuffix(".o") }))
74+
XCTAssertEqual(objectFiles, Set([
75+
"/path/to/build/arm64-apple-macosx15.0/debug/MMIOMacros-tool.build/source.swift.o",
76+
"/path/to/build/arm64-apple-macosx15.0/debug/SwiftSyntax-tool.build/source.swift.o"
77+
]))
78+
79+
// Check diff with regular build plan
80+
let plan0 = try BuildPlan(
81+
destinationBuildParameters: mockBuildParameters(prepareForIndexing: false),
82+
toolsBuildParameters: mockBuildParameters(prepareForIndexing: false),
83+
graph: graph,
84+
fileSystem: fs,
85+
observabilityScope: scope
86+
)
87+
88+
let builder0 = LLBuildManifestBuilder(plan0, fileSystem: fs, observabilityScope: scope)
89+
let manifest0 = try builder0.generateManifest(at: "/manifest")
90+
let outputs0 = manifest0.commands.flatMap(\.value.tool.outputs).map(\.name)
91+
92+
// The prepare shouldn't create any other object files.
93+
let objectFiles0 = Set(outputs0.filter({ $0.hasSuffix(".o") })).subtracting(objectFiles)
94+
XCTAssertEqual(objectFiles0, Set([
95+
"/path/to/build/arm64-apple-macosx15.0/debug/Core.build/source.swift.o",
96+
"/path/to/build/arm64-apple-macosx15.0/debug/CoreTests.build/source.swift.o",
97+
"/path/to/build/arm64-apple-macosx15.0/debug/HAL.build/source.swift.o",
98+
"/path/to/build/arm64-apple-macosx15.0/debug/HALTests.build/source.swift.o",
99+
"/path/to/build/arm64-apple-macosx15.0/debug/MMIO.build/source.swift.o",
100+
"/path/to/build/arm64-apple-macosx15.0/debug/SwiftSyntax.build/source.swift.o",
101+
]))
102+
}
103+
}

0 commit comments

Comments
 (0)