From 913ed0d9898b62b8540a85637b4609c1c6421cf1 Mon Sep 17 00:00:00 2001 From: Boris Buegling Date: Mon, 22 Jan 2024 17:30:56 -0800 Subject: [PATCH] Enable index store based on Clang feature detection Today, the index store is only enabled on Darwin by default and needs a manual opt-in on other platforms. We can instead switch this to enabling it based on whether the used clang supports `-index-store-path`. rdar://117744039 --- .../ClangTargetBuildDescription.swift | 13 ++-- Sources/Build/CMakeLists.txt | 1 + Sources/Build/ClangSupport.swift | 41 ++++++++++++ Tests/BuildTests/BuildPlanTests.swift | 13 ++-- .../ClangTargetBuildDescriptionTests.swift | 63 +++++++++++++++++++ 5 files changed, 114 insertions(+), 17 deletions(-) create mode 100644 Sources/Build/ClangSupport.swift create mode 100644 Tests/BuildTests/ClangTargetBuildDescriptionTests.swift diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index 3533104400e..69412b64fb3 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -225,16 +225,11 @@ public final class ClangTargetBuildDescription { args += activeCompilationConditions args += ["-fblocks"] - let buildTriple = self.buildParameters.triple // Enable index store, if appropriate. - // - // This feature is not widely available in OSS clang. So, we only enable - // index store for Apple's clang or if explicitly asked to. - if ProcessEnv.vars.keys.contains("SWIFTPM_ENABLE_CLANG_INDEX_STORE") { - args += self.buildParameters.indexStoreArguments(for: target) - } else if buildTriple.isDarwin(), - (try? self.buildParameters.toolchain._isClangCompilerVendorApple()) == true - { + if let supported = try? ClangSupport.supportsFeature( + name: "index-unit-output-path", + toolchain: self.buildParameters.toolchain + ), supported { args += self.buildParameters.indexStoreArguments(for: target) } diff --git a/Sources/Build/CMakeLists.txt b/Sources/Build/CMakeLists.txt index 6f933c69f93..fcc51aed76d 100644 --- a/Sources/Build/CMakeLists.txt +++ b/Sources/Build/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(Build BuildPlan/BuildPlan+Product.swift BuildPlan/BuildPlan+Swift.swift BuildPlan/BuildPlan+Test.swift + ClangSupport.swift SwiftCompilerOutputParser.swift TestObservation.swift) target_link_libraries(Build PUBLIC diff --git a/Sources/Build/ClangSupport.swift b/Sources/Build/ClangSupport.swift new file mode 100644 index 00000000000..af5fd8581a5 --- /dev/null +++ b/Sources/Build/ClangSupport.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import Foundation +import PackageModel + +public enum ClangSupport { + private struct Feature: Decodable { + let name: String + let value: [String]? + } + + private struct Features: Decodable { + let features: [Feature] + } + + private static var cachedFeatures = ThreadSafeBox() + + public static func supportsFeature(name: String, toolchain: PackageModel.Toolchain) throws -> Bool { + let features = try cachedFeatures.memoize { + let clangPath = try toolchain.getClangCompiler() + let featuresPath = clangPath.parentDirectory.parentDirectory.appending(components: ["share", "clang", "features.json"]) + return try JSONDecoder.makeWithDefaults().decode( + path: featuresPath, + fileSystem: localFileSystem, + as: Features.self + ) + } + return features.features.first(where: { $0.name == name }) != nil + } +} diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 4909f43d701..2055e433f4b 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -3613,7 +3613,11 @@ final class BuildPlanTests: XCTestCase { func check(for mode: BuildParameters.IndexStoreMode, config: BuildConfiguration) throws { let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(config: config, indexStoreMode: mode), + buildParameters: mockBuildParameters( + config: config, + toolchain: try UserToolchain.default, + indexStoreMode: mode + ), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -3622,17 +3626,10 @@ final class BuildPlanTests: XCTestCase { let lib = try result.target(for: "lib").clangTarget() let path = StringPattern.equal(result.plan.destinationBuildParameters.indexStore.pathString) - #if os(macOS) XCTAssertMatch( try lib.basicArguments(isCXX: false), [.anySequence, "-index-store-path", path, .anySequence] ) - #else - XCTAssertNoMatch( - try lib.basicArguments(isCXX: false), - [.anySequence, "-index-store-path", path, .anySequence] - ) - #endif let exe = try result.target(for: "exe").swiftTarget().compileArguments() XCTAssertMatch(exe, [.anySequence, "-index-store-path", path, .anySequence]) diff --git a/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift b/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift new file mode 100644 index 00000000000..cc052cfe146 --- /dev/null +++ b/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +@testable import Build +import PackageGraph +import PackageModel +import SPMTestSupport +import XCTest + +final class ClangTargetBuildDescriptionTests: XCTestCase { + func testClangIndexStorePath() throws { + let targetDescription = try makeTargetBuildDescription() + XCTAssertTrue(try targetDescription.basicArguments().contains("-index-store-path")) + } + + private func makeClangTarget() throws -> ClangTarget { + try ClangTarget( + name: "dummy", + cLanguageStandard: nil, + cxxLanguageStandard: nil, + includeDir: .root, + moduleMapType: .none, + type: .library, + path: .root, + sources: .init(paths: [.root.appending(component: "foo.c")], root: .root), + usesUnsafeFlags: false + ) + } + + private func makeResolvedTarget() throws -> ResolvedTarget { + ResolvedTarget( + packageIdentity: .plain("dummy"), + underlying: try makeClangTarget(), + dependencies: [], + supportedPlatforms: [], + platformVersionProvider: .init(implementation: .minimumDeploymentTargetDefault) + ) + } + + private func makeTargetBuildDescription() throws -> ClangTargetBuildDescription { + let observability = ObservabilitySystem.makeForTesting(verbose: false) + return try ClangTargetBuildDescription( + target: try makeResolvedTarget(), + toolsVersion: .current, + buildParameters: mockBuildParameters( + toolchain: try UserToolchain.default, + indexStoreMode: .on + ), + fileSystem: localFileSystem, + observabilityScope: observability.topScope + ) + } +}