Skip to content

Commit f10fb2d

Browse files
benlangmuirLuke Daley
authored and
Luke Daley
committed
[cas] Add ValidateCAS action to ensure data coherence (swiftlang#460)
Use llvm-cas's new validate-if-needed action to ensure the correctness of CAS data in the case of power failure or similar situations. This action is added to prepareForBuilding to ensure that it is run before any other other CAS accesses. Note that validate-if-needed internally avoids performing unnecessary work - in particular, it only validates data once for every machine boot. rdar://150295950
1 parent faef2b5 commit f10fb2d

12 files changed

+434
-13
lines changed

Sources/SWBBuildSystem/BuildOperation.swift

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ package final class BuildOperation: BuildSystemOperation {
419419
}
420420

421421
// Perform any needed steps before we kick off the build.
422-
if let (warnings, errors) = prepareForBuilding() {
422+
if let (warnings, errors) = await prepareForBuilding() {
423423
// Emit any warnings and errors. If there were any errors, then bail out.
424424
for message in warnings { buildOutputDelegate.warning(message) }
425425
for message in errors { buildOutputDelegate.error(message) }
@@ -809,7 +809,7 @@ package final class BuildOperation: BuildSystemOperation {
809809
return delegate.buildComplete(self, status: effectiveStatus, delegate: buildOutputDelegate, metrics: .init(counters: aggregatedCounters))
810810
}
811811

812-
func prepareForBuilding() -> ([String], [String])? {
812+
func prepareForBuilding() async -> ([String], [String])? {
813813
let warnings = [String]() // Not presently used
814814
var errors = [String]()
815815

@@ -829,9 +829,61 @@ package final class BuildOperation: BuildSystemOperation {
829829
}
830830
}
831831

832+
if UserDefaults.enableCASValidation {
833+
for info in buildDescription.casValidationInfos {
834+
do {
835+
try await validateCAS(info)
836+
} catch {
837+
errors.append("cas validation failed for \(info.options.casPath.str)")
838+
}
839+
}
840+
}
841+
832842
return (warnings.count > 0 || errors.count > 0) ? (warnings, errors) : nil
833843
}
834844

845+
func validateCAS(_ info: BuildDescription.CASValidationInfo) async throws {
846+
assert(UserDefaults.enableCASValidation)
847+
848+
let casPath = info.options.casPath
849+
let ruleInfo = "ValidateCAS \(casPath.str) \(info.llvmCasExec.str)"
850+
851+
let signatureCtx = InsecureHashContext()
852+
signatureCtx.add(string: "ValidateCAS")
853+
signatureCtx.add(string: casPath.str)
854+
signatureCtx.add(string: info.llvmCasExec.str)
855+
let signature = signatureCtx.signature
856+
857+
let activityId = delegate.beginActivity(self, ruleInfo: ruleInfo, executionDescription: "Validate CAS contents at \(casPath.str)", signature: signature, target: nil, parentActivity: nil)
858+
var status: BuildOperationTaskEnded.Status = .failed
859+
defer {
860+
delegate.endActivity(self, id: activityId, signature: signature, status: status)
861+
}
862+
863+
var commandLine = [
864+
info.llvmCasExec.str,
865+
"-cas", casPath.str,
866+
"-validate-if-needed",
867+
"-check-hash",
868+
"-allow-recovery",
869+
]
870+
if let pluginPath = info.options.pluginPath {
871+
commandLine.append(contentsOf: [
872+
"-fcas-plugin-path", pluginPath.str
873+
])
874+
}
875+
let result: Processes.ExecutionResult = try await clientDelegate.executeExternalTool(commandLine: commandLine)
876+
// In a task we might use a discovered tool info to detect if the tool supports validation, but without that scaffolding, just check the specific error.
877+
if result.exitStatus == .exit(1) && result.stderr.contains(ByteString("Unknown command line argument '-validate-if-needed'")) {
878+
delegate.emit(data: ByteString("validation not supported").bytes, for: activityId, signature: signature)
879+
status = .succeeded
880+
} else {
881+
delegate.emit(data: ByteString(result.stderr).bytes, for: activityId, signature: signature)
882+
delegate.emit(data: ByteString(result.stdout).bytes, for: activityId, signature: signature)
883+
status = result.exitStatus.isSuccess ? .succeeded : result.exitStatus.wasCanceled ? .cancelled : .failed
884+
}
885+
}
886+
835887
/// Cancel the executing build operation.
836888
package func cancel() {
837889
queue.blocking_sync() {

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ public final class BuiltinMacros {
11251125
public static let TAPI_HEADER_SEARCH_PATHS = BuiltinMacros.declarePathListMacro("TAPI_HEADER_SEARCH_PATHS")
11261126
public static let USE_HEADER_SYMLINKS = BuiltinMacros.declareBooleanMacro("USE_HEADER_SYMLINKS")
11271127
public static let USE_HIERARCHICAL_LAYOUT_FOR_COPIED_ASIDE_PRODUCTS = BuiltinMacros.declareBooleanMacro("USE_HIERARCHICAL_LAYOUT_FOR_COPIED_ASIDE_PRODUCTS")
1128+
public static let VALIDATE_CAS_EXEC = BuiltinMacros.declareStringMacro("VALIDATE_CAS_EXEC")
11281129
public static let VALIDATE_PLIST_FILES_WHILE_COPYING = BuiltinMacros.declareBooleanMacro("VALIDATE_PLIST_FILES_WHILE_COPYING")
11291130
public static let VALIDATE_PRODUCT = BuiltinMacros.declareBooleanMacro("VALIDATE_PRODUCT")
11301131
public static let VALIDATE_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_DEPENDENCIES") as EnumMacroDeclaration<BooleanWarningLevel>
@@ -2325,6 +2326,7 @@ public final class BuiltinMacros {
23252326
USE_HEADER_SYMLINKS,
23262327
USE_HIERARCHICAL_LAYOUT_FOR_COPIED_ASIDE_PRODUCTS,
23272328
VALIDATE_PLIST_FILES_WHILE_COPYING,
2329+
VALIDATE_CAS_EXEC,
23282330
VALIDATE_PRODUCT,
23292331
VALIDATE_DEPENDENCIES,
23302332
VALIDATE_DEVELOPMENT_ASSET_PATHS,

Sources/SWBCore/TaskGeneration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ extension CoreClientTargetDiagnosticProducingDelegate {
768768
private let externalToolExecutionQueue = AsyncOperationQueue(concurrentTasks: ProcessInfo.processInfo.activeProcessorCount)
769769

770770
extension CoreClientDelegate {
771-
func executeExternalTool(commandLine: [String], workingDirectory: String? = nil, environment: [String: String] = [:]) async throws -> Processes.ExecutionResult {
771+
package func executeExternalTool(commandLine: [String], workingDirectory: String? = nil, environment: [String: String] = [:]) async throws -> Processes.ExecutionResult {
772772
switch try await executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: environment) {
773773
case .deferred:
774774
guard let url = commandLine.first.map(URL.init(fileURLWithPath:)) else {

0 commit comments

Comments
 (0)