Skip to content

Commit 416f00f

Browse files
committed
[Build] Add support for indexing-while-building
This add supports for indexing while building which will be useful for LSP users. The indexing-while-building feature will be enabled by default when building in debug mode but can be disabled by passing the flag --disable-index-store. The feature is never enabled in release. This feature is supported by Swift compiler on all platforms but clang has some nuances. We can expect this feature to be available on Apple's clang but not OSS or other distributions of clang. So, the feature is selectively enabled for clang targets. <rdar://problem/45689037> Add support for indexing-while-building
1 parent 270de45 commit 416f00f

File tree

7 files changed

+147
-4
lines changed

7 files changed

+147
-4
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ import func POSIX.getenv
1717

1818
public struct BuildParameters {
1919

20+
/// Mode for the indexing-while-building feature.
21+
public enum IndexStoreMode: Equatable {
22+
/// Index store should be enabled.
23+
case on
24+
/// Index store should be disabled.
25+
case off
26+
/// Index store should be enabled in debug configuration.
27+
case auto
28+
}
29+
2030
// FIXME: Error handling.
2131
//
2232
/// Path to the module cache directory to use for SwiftPM's own tests.
@@ -46,6 +56,12 @@ public struct BuildParameters {
4656
return dataPath.appending(component: configuration.dirname)
4757
}
4858

59+
/// The path to the index store directory.
60+
public var indexStore: AbsolutePath {
61+
assert(indexStoreMode != .off, "index store is disabled")
62+
return buildPath.appending(components: "index", "store")
63+
}
64+
4965
/// The path to the code coverage directory.
5066
public var codeCovPath: AbsolutePath {
5167
return buildPath.appending(component: "codecov")
@@ -90,6 +106,9 @@ public struct BuildParameters {
90106
/// If should enable llbuild manifest caching.
91107
public let shouldEnableManifestCaching: Bool
92108

109+
/// The mode to use for indexing-while-building feature.
110+
public let indexStoreMode: IndexStoreMode
111+
93112
/// Whether to enable code coverage.
94113
public let enableCodeCoverage: Bool
95114

@@ -119,7 +138,8 @@ public struct BuildParameters {
119138
shouldLinkStaticSwiftStdlib: Bool = false,
120139
shouldEnableManifestCaching: Bool = false,
121140
sanitizers: EnabledSanitizers = EnabledSanitizers(),
122-
enableCodeCoverage: Bool = false
141+
enableCodeCoverage: Bool = false,
142+
indexStoreMode: IndexStoreMode = .auto
123143
) {
124144
self.dataPath = dataPath
125145
self.configuration = configuration
@@ -131,6 +151,25 @@ public struct BuildParameters {
131151
self.shouldEnableManifestCaching = shouldEnableManifestCaching
132152
self.sanitizers = sanitizers
133153
self.enableCodeCoverage = enableCodeCoverage
154+
self.indexStoreMode = indexStoreMode
155+
}
156+
157+
/// Returns the compiler arguments for the index store, if enabled.
158+
fileprivate var indexStoreArguments: [String] {
159+
let addIndexStoreArguments: Bool
160+
switch indexStoreMode {
161+
case .on:
162+
addIndexStoreArguments = true
163+
case .off:
164+
addIndexStoreArguments = false
165+
case .auto:
166+
addIndexStoreArguments = configuration == .debug
167+
}
168+
169+
if addIndexStoreArguments {
170+
return ["-index-store-path", indexStore.asString]
171+
}
172+
return []
134173
}
135174
}
136175

@@ -229,6 +268,16 @@ public final class ClangTargetBuildDescription {
229268
}
230269
args += ["-fblocks"]
231270

271+
// Enable index store, if appropriate.
272+
//
273+
// This feature is not widely available in OSS clang. So, we only enable
274+
// index store for Apple's clang or if explicitly asked to.
275+
if Process.env.keys.contains("SWIFTPM_ENABLE_CLANG_INDEX_STORE") {
276+
args += buildParameters.indexStoreArguments
277+
} else if buildParameters.triple.isDarwin(), (try? buildParameters.toolchain._isClangCompilerVendorApple()) == true {
278+
args += buildParameters.indexStoreArguments
279+
}
280+
232281
if !buildParameters.triple.isWindows() {
233282
// Using modules currently conflicts with the Windows SDKs.
234283
args += ["-fmodules", "-fmodule-name=" + target.c99name]
@@ -361,6 +410,7 @@ public final class SwiftTargetBuildDescription {
361410
case .release: break
362411
}
363412

413+
args += buildParameters.indexStoreArguments
364414
args += buildParameters.toolchain.extraSwiftCFlags
365415
args += optimizationArguments
366416
args += ["-j\(SwiftCompilerTool.numThreads)"]

Sources/Build/Toolchain.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public protocol Toolchain {
1818
/// Path of the `clang` compiler.
1919
func getClangCompiler() throws -> AbsolutePath
2020

21+
// FIXME: This is a temporary API until index store is widely available in
22+
// the OSS clang compiler. This API should not used for any other purpose.
23+
/// Returns true if clang compiler's vendor is Apple and nil if unknown.
24+
func _isClangCompilerVendorApple() throws -> Bool?
25+
2126
/// Additional flags to be passed to the C compiler.
2227
var extraCCFlags: [String] { get }
2328

@@ -30,3 +35,9 @@ public protocol Toolchain {
3035
/// The dynamic library extension, for e.g. dylib, so.
3136
var dynamicLibraryExtension: String { get }
3237
}
38+
39+
extension Toolchain {
40+
public func _isClangCompilerVendorApple() throws -> Bool? {
41+
return nil
42+
}
43+
}

Sources/Commands/Options.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,8 @@ public class ToolOptions {
6464
/// Use Package.resolved file for resolving dependencies.
6565
public var forceResolvedVersions = false
6666

67+
/// The mode to use for indexing-while-building feature.
68+
public var indexStoreMode: BuildParameters.IndexStoreMode = .auto
69+
6770
public required init() {}
6871
}

Sources/Commands/SwiftTool.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,16 @@ public class SwiftTool<Options: ToolOptions> {
356356
usage: "Force resolve to versions recorded in the Package.resolved file"),
357357
to: { $0.forceResolvedVersions = $1 })
358358

359+
binder.bind(
360+
option: parser.add(option: "--enable-index-store", kind: Bool.self,
361+
usage: "Enable indexing-while-building feature"),
362+
to: { if $1 { $0.indexStoreMode = .on } })
363+
364+
binder.bind(
365+
option: parser.add(option: "--disable-index-store", kind: Bool.self,
366+
usage: "Disable indexing-while-building feature"),
367+
to: { if $1 { $0.indexStoreMode = .off } })
368+
359369
// Let subclasses bind arguments.
360370
type(of: self).defineArguments(parser: parser, binder: binder)
361371

@@ -694,7 +704,8 @@ public class SwiftTool<Options: ToolOptions> {
694704
flags: options.buildFlags,
695705
shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib,
696706
sanitizers: options.sanitizers,
697-
enableCodeCoverage: options.shouldEnableCodeCoverage
707+
enableCodeCoverage: options.shouldEnableCodeCoverage,
708+
indexStoreMode: options.indexStoreMode
698709
)
699710
})
700711
}()

Sources/Workspace/UserToolchain.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,23 @@ public final class UserToolchain: Toolchain {
160160
}
161161
let toolPath = try AbsolutePath(validating: foundPath)
162162

163+
// If we found clang using xcrun, assume the vendor is Apple.
164+
// FIXME: This might not be the best way to determine this.
165+
#if os(macOS)
166+
__isClangCompilerVendorApple = true
167+
#endif
168+
163169
_clangCompiler = toolPath
164170
return toolPath
165171
}
166172
private var _clangCompiler: AbsolutePath?
173+
private var __isClangCompilerVendorApple: Bool?
174+
175+
public func _isClangCompilerVendorApple() throws -> Bool? {
176+
// The boolean gets computed as a side-effect of lookup for clang compiler.
177+
_ = try getClangCompiler()
178+
return __isClangCompilerVendorApple
179+
}
167180

168181
/// Returns the path to llvm-cov tool.
169182
public func getLLVMCov() throws -> AbsolutePath {

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ private struct MockToolchain: Toolchain {
3434
func getClangCompiler() throws -> AbsolutePath {
3535
return AbsolutePath("/fake/path/to/clang")
3636
}
37+
38+
func _isClangCompilerVendorApple() throws -> Bool? {
39+
#if os(macOS)
40+
return true
41+
#else
42+
return false
43+
#endif
44+
}
3745
}
3846

3947
final class BuildPlanTests: XCTestCase {
@@ -47,15 +55,18 @@ final class BuildPlanTests: XCTestCase {
4755
buildPath: AbsolutePath = AbsolutePath("/path/to/build"),
4856
config: Build.Configuration = .debug,
4957
shouldLinkStaticSwiftStdlib: Bool = false,
50-
destinationTriple: Triple = Triple.hostTriple
58+
destinationTriple: Triple = Triple.hostTriple,
59+
indexStoreMode: BuildParameters.IndexStoreMode = .off
5160
) -> BuildParameters {
5261
return BuildParameters(
5362
dataPath: buildPath,
5463
configuration: config,
5564
toolchain: MockToolchain(),
5665
destinationTriple: destinationTriple,
5766
flags: BuildFlags(),
58-
shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib)
67+
shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib,
68+
indexStoreMode: indexStoreMode
69+
)
5970
}
6071

6172
func testBasicSwiftPackage() throws {
@@ -991,6 +1002,49 @@ final class BuildPlanTests: XCTestCase {
9911002
"@/path/to/build/debug/exe.product/Objects.LinkFileList",
9921003
])
9931004
}
1005+
1006+
func testIndexStore() throws {
1007+
let fs = InMemoryFileSystem(emptyFiles:
1008+
"/Pkg/Sources/exe/main.swift",
1009+
"/Pkg/Sources/lib/lib.c",
1010+
"/Pkg/Sources/lib/include/lib.h"
1011+
)
1012+
1013+
let diagnostics = DiagnosticsEngine()
1014+
let graph = loadPackageGraph(root: "/Pkg", fs: fs, diagnostics: diagnostics,
1015+
manifests: [
1016+
Manifest.createV4Manifest(
1017+
name: "Pkg",
1018+
path: "/Pkg",
1019+
url: "/Pkg",
1020+
targets: [
1021+
TargetDescription(name: "exe", dependencies: ["lib"]),
1022+
TargetDescription(name: "lib", dependencies: []),
1023+
]),
1024+
]
1025+
)
1026+
XCTAssertNoDiagnostics(diagnostics)
1027+
1028+
func check(for mode: BuildParameters.IndexStoreMode, config: Build.Configuration) throws {
1029+
let result = BuildPlanResult(plan: try BuildPlan(buildParameters: mockBuildParameters(config: config, indexStoreMode: mode), graph: graph, diagnostics: diagnostics, fileSystem: fs))
1030+
1031+
let lib = try result.target(for: "lib").clangTarget()
1032+
let path = StringPattern.equal(result.plan.buildParameters.indexStore.asString)
1033+
1034+
#if os(macOS)
1035+
XCTAssertMatch(lib.basicArguments(), [.anySequence, "-index-store-path", path, .anySequence])
1036+
#else
1037+
XCTAssertNoMatch(lib.basicArguments(), [.anySequence, "-index-store-path", path, .anySequence])
1038+
#endif
1039+
1040+
let exe = try result.target(for: "exe").swiftTarget().compileArguments()
1041+
XCTAssertMatch(exe, [.anySequence, "-index-store-path", path, .anySequence])
1042+
}
1043+
1044+
try check(for: .auto, config: .debug)
1045+
try check(for: .on, config: .debug)
1046+
try check(for: .on, config: .release)
1047+
}
9941048
}
9951049

9961050
// MARK:- Test Helpers

Tests/BuildTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extension BuildPlanTests {
1616
("testCppModule", testCppModule),
1717
("testDynamicProducts", testDynamicProducts),
1818
("testExecAsDependency", testExecAsDependency),
19+
("testIndexStore", testIndexStore),
1920
("testNonReachableProductsAndTargets", testNonReachableProductsAndTargets),
2021
("testPkgConfigGenericDiagnostic", testPkgConfigGenericDiagnostic),
2122
("testPkgConfigHintDiagnostic", testPkgConfigHintDiagnostic),

0 commit comments

Comments
 (0)