Skip to content

Commit bfdb2f4

Browse files
committed
Handle multiple llvm-cas execs for validation
While we intentionally wanto to ignore minor differences in CASOptions for validation, the llvm-cas executable path is significant because there could be differences in format version between tools. If the llvm-cas binaries end up using the same format version only one of them will perform the real validation and the rest will skip.
1 parent 2341a33 commit bfdb2f4

File tree

5 files changed

+110
-19
lines changed

5 files changed

+110
-19
lines changed

Sources/SWBBuildSystem/BuildOperation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,13 +846,15 @@ package final class BuildOperation: BuildSystemOperation {
846846
assert(UserDefaults.enableCASValidation)
847847

848848
let casPath = info.options.casPath
849+
let ruleInfo = "ValidateCAS \(casPath.str) \(info.llvmCasExec.str)"
849850

850851
let signatureCtx = InsecureHashContext()
851852
signatureCtx.add(string: "ValidateCAS")
852853
signatureCtx.add(string: casPath.str)
854+
signatureCtx.add(string: info.llvmCasExec.str)
853855
let signature = signatureCtx.signature
854856

855-
let activityId = delegate.beginActivity(self, ruleInfo: "ValidateCAS \(casPath.str)", executionDescription: "Validate CAS contents at \(casPath.str)", signature: signature, target: nil, parentActivity: nil)
857+
let activityId = delegate.beginActivity(self, ruleInfo: ruleInfo, executionDescription: "Validate CAS contents at \(casPath.str)", signature: signature, target: nil, parentActivity: nil)
856858
var status: BuildOperationTaskEnded.Status = .failed
857859
defer {
858860
delegate.endActivity(self, id: activityId, signature: signature, status: status)

Sources/SWBTaskExecution/BuildDescription.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,3 +1511,15 @@ extension BuildDescription.CASValidationInfo: Serializable {
15111511
}
15121512
}
15131513

1514+
// Note: for the purposes of validation we intentionally ignore irrelevant
1515+
// differences in CASOptions. However, we need to keep the llvm-cas executable
1516+
// in case there are multiple cas format versions sharing the path.
1517+
extension BuildDescription.CASValidationInfo: Hashable {
1518+
package func hash(into hasher: inout Hasher) {
1519+
hasher.combine(options.casPath)
1520+
hasher.combine(llvmCasExec)
1521+
}
1522+
static package func ==(lhs: Self, rhs: Self) -> Bool {
1523+
return lhs.options.casPath == rhs.options.casPath && lhs.llvmCasExec == rhs.llvmCasExec
1524+
}
1525+
}

Sources/SWBTaskExecution/BuildDescriptionManager.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ package final class BuildDescriptionManager: Sendable {
180180
var settingsPerTarget = [ConfiguredTarget:Settings]()
181181
var rootPathsPerTarget = [ConfiguredTarget:[Path]]()
182182
var moduleCachePathsPerTarget = [ConfiguredTarget: [Path]]()
183-
var casValidationInfos: [Path: BuildDescription.CASValidationInfo] = [:]
183+
184+
var casValidationInfos: Set<BuildDescription.CASValidationInfo> = []
184185
let buildGraph = planRequest.buildGraph
185186
let shouldValidateCAS = Settings.supportsCompilationCaching(plan.workspaceContext.core) && UserDefaults.enableCASValidation
186187

@@ -204,11 +205,10 @@ package final class BuildDescriptionManager: Sendable {
204205
if shouldValidateCAS, settings.globalScope.evaluate(BuiltinMacros.CLANG_ENABLE_COMPILE_CACHE) || settings.globalScope.evaluate(BuiltinMacros.SWIFT_ENABLE_COMPILE_CACHE) {
205206
// FIXME: currently we only handle the compiler cache here, because the plugin configuration for the generic CAS is not configured by build settings.
206207
for purpose in [CASOptions.Purpose.compiler(.c)] {
207-
if let casOpts = try? CASOptions.create(settings.globalScope, purpose),
208-
!casValidationInfos.contains(casOpts.casPath) {
208+
if let casOpts = try? CASOptions.create(settings.globalScope, purpose) {
209209
let execName = settings.globalScope.evaluate(BuiltinMacros.VALIDATE_CAS_EXEC).nilIfEmpty ?? "llvm-cas"
210210
if let execPath = settings.executableSearchPaths.lookup(Path(execName)) {
211-
casValidationInfos[casOpts.casPath] = .init(options: casOpts, llvmCasExec: execPath)
211+
casValidationInfos.insert(.init(options: casOpts, llvmCasExec: execPath))
212212
}
213213
}
214214
}
@@ -246,7 +246,7 @@ package final class BuildDescriptionManager: Sendable {
246246
}
247247

248248
// Create the build description.
249-
return try await BuildDescription.construct(workspace: buildGraph.workspaceContext.workspace, tasks: plan.tasks, path: path, signature: signature, buildCommand: planRequest.buildRequest.buildCommand, diagnostics: planningDiagnostics, indexingInfo: [], fs: fs, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: buildGraph.targetsBuildInParallel, emitFrontendCommandLines: plan.emitFrontendCommandLines, moduleSessionFilePath: planRequest.workspaceContext.getModuleSessionFilePath(planRequest.buildRequest.parameters), invalidationPaths: plan.invalidationPaths, recursiveSearchPathResults: plan.recursiveSearchPathResults, copiedPathMap: plan.copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, casValidationInfos: casValidationInfos.values.sorted(by: \.options.casPath), staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: delegate, targetDependencies: buildGraph.targetDependenciesByGuid, definingTargetsByModuleName: definingTargetsByModuleName, capturedBuildInfo: capturedBuildInfo, userPreferences: buildGraph.workspaceContext.userPreferences)
249+
return try await BuildDescription.construct(workspace: buildGraph.workspaceContext.workspace, tasks: plan.tasks, path: path, signature: signature, buildCommand: planRequest.buildRequest.buildCommand, diagnostics: planningDiagnostics, indexingInfo: [], fs: fs, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: buildGraph.targetsBuildInParallel, emitFrontendCommandLines: plan.emitFrontendCommandLines, moduleSessionFilePath: planRequest.workspaceContext.getModuleSessionFilePath(planRequest.buildRequest.parameters), invalidationPaths: plan.invalidationPaths, recursiveSearchPathResults: plan.recursiveSearchPathResults, copiedPathMap: plan.copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, casValidationInfos: casValidationInfos.sorted(by: \.options.casPath), staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: delegate, targetDependencies: buildGraph.targetDependenciesByGuid, definingTargetsByModuleName: definingTargetsByModuleName, capturedBuildInfo: capturedBuildInfo, userPreferences: buildGraph.workspaceContext.userPreferences)
250250
}
251251

252252
/// Encapsulates the two ways `getNewOrCachedBuildDescription` can be called, whether we want to retrieve or create a build description based on a plan or whether we have an explicit build description ID that we want to retrieve and we don't need to create a new one.

Tests/SWBBuildSystemTests/ClangCompilationCachingTests.swift

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,17 +1879,20 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests {
18791879
"""
18801880
}
18811881

1882+
let specificCAS = casPath.join(usePlugin ? "plugin" : "builtin")
1883+
let ruleInfo = "ValidateCAS \(specificCAS.str) \(try await ConditionTraitContext.shared.llvmCasToolPath.str)"
1884+
18821885
let checkBuild = { (expectedOutput: ByteString?) in
18831886
try await tester.checkBuild(runDestination: .macOS, persistent: true) { results in
1884-
let specificCAS = casPath.join(usePlugin ? "plugin" : "builtin")
1887+
18851888
if enableCaching {
1886-
results.check(contains: .activityStarted(ruleInfo: "ValidateCAS \(specificCAS.str)"))
1889+
results.check(contains: .activityStarted(ruleInfo: ruleInfo))
18871890
if let expectedOutput {
1888-
results.check(contains: .activityEmittedData(ruleInfo: "ValidateCAS \(specificCAS.str)", expectedOutput.bytes))
1891+
results.check(contains: .activityEmittedData(ruleInfo: ruleInfo, expectedOutput.bytes))
18891892
}
1890-
results.check(contains: .activityEnded(ruleInfo: "ValidateCAS \(specificCAS.str)", status: .succeeded))
1893+
results.check(contains: .activityEnded(ruleInfo: ruleInfo, status: .succeeded))
18911894
} else {
1892-
results.check(notContains: .activityStarted(ruleInfo: "ValidateCAS \(specificCAS.str)"))
1895+
results.check(notContains: .activityStarted(ruleInfo: ruleInfo))
18931896
}
18941897
results.checkNoDiagnostics()
18951898
}
@@ -1947,14 +1950,16 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests {
19471950
"""
19481951
}
19491952

1953+
let specificCAS = casPath.join("builtin")
1954+
let ruleInfo = "ValidateCAS \(specificCAS.str) \(try await ConditionTraitContext.shared.llvmCasToolPath.str)"
1955+
19501956
let checkBuild = { (expectedOutput: ByteString?) in
19511957
try await tester.checkBuild(runDestination: .macOS, persistent: true) { results in
1952-
let specificCAS = casPath.join("builtin")
1953-
results.check(contains: .activityStarted(ruleInfo: "ValidateCAS \(specificCAS.str)"))
1958+
results.check(contains: .activityStarted(ruleInfo: ruleInfo))
19541959
if let expectedOutput {
1955-
results.check(contains: .activityEmittedData(ruleInfo: "ValidateCAS \(specificCAS.str)", expectedOutput.bytes))
1960+
results.check(contains: .activityEmittedData(ruleInfo: ruleInfo, expectedOutput.bytes))
19561961
}
1957-
results.check(contains: .activityEnded(ruleInfo: "ValidateCAS \(specificCAS.str)", status: .succeeded))
1962+
results.check(contains: .activityEnded(ruleInfo: ruleInfo, status: .succeeded))
19581963
results.checkNoDiagnostics()
19591964
}
19601965
}
@@ -1969,6 +1974,76 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests {
19691974
try await checkBuild("recovered from invalid data\n")
19701975
}
19711976
}
1977+
1978+
@Test(.requireCASValidation, .requireSDKs(.macOS))
1979+
func validateCASMultipleExec() async throws {
1980+
try await withTemporaryDirectory { (tmpDirPath: Path) in
1981+
let casPath = tmpDirPath.join("CompilationCache")
1982+
let buildSettings: [String: String] = [
1983+
"PRODUCT_NAME": "$(TARGET_NAME)",
1984+
"CLANG_ENABLE_COMPILE_CACHE": "YES",
1985+
"CLANG_ENABLE_MODULES": "NO",
1986+
"COMPILATION_CACHE_CAS_PATH": casPath.str,
1987+
]
1988+
let llvmCasExec = try await ConditionTraitContext.shared.llvmCasToolPath
1989+
// Create a trivially different path. If we ever canonicalize the path it will be harder to test this.
1990+
let llvmCasExec2 = Path("\(llvmCasExec.dirname.str)\(Path.pathSeparator)\(Path.pathSeparator)\(llvmCasExec.basename)")
1991+
let testWorkspace = TestWorkspace(
1992+
"Test",
1993+
sourceRoot: tmpDirPath.join("Test"),
1994+
projects: [
1995+
TestProject(
1996+
"aProject",
1997+
groupTree: TestGroup(
1998+
"Sources",
1999+
children: [
2000+
TestFile("file.c"),
2001+
]),
2002+
buildConfigurations: [TestBuildConfiguration(
2003+
"Debug",
2004+
buildSettings: buildSettings)],
2005+
targets: [
2006+
TestStandardTarget(
2007+
"Library1",
2008+
type: .staticLibrary,
2009+
buildPhases: [
2010+
TestSourcesBuildPhase(["file.c"]),
2011+
]),
2012+
TestStandardTarget(
2013+
"Library2",
2014+
type: .staticLibrary,
2015+
buildConfigurations: [TestBuildConfiguration(
2016+
"Debug",
2017+
buildSettings: [
2018+
"VALIDATE_CAS_EXEC": llvmCasExec2.str,
2019+
])],
2020+
buildPhases: [
2021+
TestSourcesBuildPhase(["file.c"]),
2022+
]),
2023+
])])
2024+
2025+
let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false)
2026+
try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("aProject/file.c")) { stream in
2027+
stream <<<
2028+
"""
2029+
#include <stdio.h>
2030+
int something = 1;
2031+
"""
2032+
}
2033+
2034+
let specificCAS = casPath.join("builtin")
2035+
let parameters = BuildParameters(configuration: "Debug", activeRunDestination: .macOS)
2036+
let targets = tester.workspace.allTargets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) })
2037+
2038+
try await tester.checkBuild(runDestination: .macOS, buildRequest: BuildRequest(parameters: parameters, buildTargets: targets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false), persistent: true) { results in
2039+
for ruleInfo in ["ValidateCAS \(specificCAS.str) \(llvmCasExec.str)", "ValidateCAS \(specificCAS.str) \(llvmCasExec2.str)"] {
2040+
results.check(contains: .activityStarted(ruleInfo: ruleInfo))
2041+
results.check(contains: .activityEnded(ruleInfo: ruleInfo, status: .succeeded))
2042+
}
2043+
results.checkNoDiagnostics()
2044+
}
2045+
}
2046+
}
19722047
}
19732048

19742049
extension BuildOperationTester.BuildResults {

Tests/SWBBuildSystemTests/SwiftCompilationCachingTests.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,16 @@ fileprivate struct SwiftCompilationCachingTests: CoreBasedTests {
247247
"""
248248
}
249249

250+
let specificCAS = casPath.join("builtin")
251+
let ruleInfo = "ValidateCAS \(specificCAS.str) \(try await ConditionTraitContext.shared.llvmCasToolPath.str)"
252+
250253
let checkBuild = { (expectedOutput: ByteString?) in
251254
try await tester.checkBuild(runDestination: .macOS, persistent: true) { results in
252-
let specificCAS = casPath.join("builtin")
253-
results.check(contains: .activityStarted(ruleInfo: "ValidateCAS \(specificCAS.str)"))
255+
results.check(contains: .activityStarted(ruleInfo: ruleInfo))
254256
if let expectedOutput {
255-
results.check(contains: .activityEmittedData(ruleInfo: "ValidateCAS \(specificCAS.str)", expectedOutput.bytes))
257+
results.check(contains: .activityEmittedData(ruleInfo: ruleInfo, expectedOutput.bytes))
256258
}
257-
results.check(contains: .activityEnded(ruleInfo: "ValidateCAS \(specificCAS.str)", status: .succeeded))
259+
results.check(contains: .activityEnded(ruleInfo: ruleInfo, status: .succeeded))
258260
results.checkNoDiagnostics()
259261
}
260262
}

0 commit comments

Comments
 (0)