Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 86 additions & 67 deletions Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,28 +337,12 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA

if lastResult == .succeeded {
// Verify the dependencies from the trace data.
let payload: DependencyValidationInfo.Payload
if let traceFilePath {
let fs = executionDelegate.fs
let traceData = try JSONDecoder().decode(Array<TraceData>.self, from: Data(fs.read(traceFilePath)))

var allFiles = Set<Path>()
traceData.forEach { allFiles.formUnion(Set($0.includes)) }
let (imports, includes) = separateImportsFromIncludes(allFiles)
payload = .clangDependencies(imports: imports, includes: includes)
} else {
payload = .unsupported
}

if let dependencyValidationOutputPath {
let validationInfo = DependencyValidationInfo(payload: payload)
_ = try executionDelegate.fs.writeIfChanged(
dependencyValidationOutputPath,
contents: ByteString(
JSONEncoder(outputFormatting: .sortedKeys).encode(validationInfo)
)
)
}
try Self.handleDependencyValidation(
traceFilePath: traceFilePath,
dependencyValidationOutputPath: dependencyValidationOutputPath,
fileSystem: executionDelegate.fs,
isModular: true
)
}

return lastResult ?? .failed
Expand All @@ -368,30 +352,7 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
}
}

// Clang's dependency tracing does not currently clearly distinguish modular imports from non-modular includes.
// Until that gets fixed, just guess that if the file is contained in a framework, it comes from a module with
// the same name. That is obviously not going to be reliable but it unblocks us from continuing experiments with
// dependency specifications.
private func separateImportsFromIncludes(_ files: Set<Path>) -> ([DependencyValidationInfo.Import], [Path]) {
func findFrameworkName(_ file: Path) -> String? {
if file.fileExtension == "framework" {
return file.basenameWithoutSuffix
}
return file.dirname.isEmpty || file.dirname.isRoot ? nil : findFrameworkName(file.dirname)
}
var moduleNames: [String] = []
var includeFiles: [Path] = []
for file in files {
if let frameworkName = findFrameworkName(file) {
moduleNames.append(frameworkName)
} else {
includeFiles.append(file)
}
}
let moduleDependencies = moduleNames.map { ModuleDependency(name: $0, accessLevel: .Private, optional: false) }
let moduleImports = moduleDependencies.map { DependencyValidationInfo.Import(dependency: $0, importLocations: []) }
return (moduleImports, includeFiles)
}


/// Intended to be called during task dependency setup.
/// If remote caching is enabled along with integrated cache queries, it will request
Expand Down Expand Up @@ -512,6 +473,79 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
activityReporter: dynamicExecutionDelegate
)
}

/// Handles dependency validation by reading trace data and writing out DependencyValidationInfo.
/// This is shared between modular and non-modular compilation tasks.
fileprivate static func handleDependencyValidation(
traceFilePath: Path?,
dependencyValidationOutputPath: Path?,
fileSystem: any FSProxy,
isModular: Bool
) throws {
let payload: DependencyValidationInfo.Payload
if let traceFilePath {
let traceFileContent = try fileSystem.read(traceFilePath)

let traceData: Array<TraceData>
// clang will emit an empty file instead of an empty array when there's nothing to trace
if traceFileContent.isEmpty {
traceData = []
} else {
do {
traceData = try JSONDecoder().decode(Array<TraceData>.self, from: Data(traceFileContent))
} catch {
throw StubError.error("Failed to decode json trace at \(traceFilePath.str): \(error)")
}
}

var allFiles = Set<Path>()
traceData.forEach { allFiles.formUnion(Set($0.includes)) }

if isModular {
let (imports, includes) = separateImportsFromIncludes(allFiles)
payload = .clangDependencies(imports: imports, includes: includes)
} else {
payload = .clangDependencies(imports: [], includes: Array(allFiles))
}
} else {
payload = .unsupported
}

if let dependencyValidationOutputPath {
let validationInfo = DependencyValidationInfo(payload: payload)
_ = try fileSystem.writeIfChanged(
dependencyValidationOutputPath,
contents: ByteString(
JSONEncoder(outputFormatting: .sortedKeys).encode(validationInfo)
)
)
}
}

// Clang's dependency tracing does not currently clearly distinguish modular imports from non-modular includes.
// Until that gets fixed, just guess that if the file is contained in a framework, it comes from a module with
// the same name. That is obviously not going to be reliable but it unblocks us from continuing experiments with
// dependency specifications.
private static func separateImportsFromIncludes(_ files: Set<Path>) -> ([DependencyValidationInfo.Import], [Path]) {
func findFrameworkName(_ file: Path) -> String? {
if file.fileExtension == "framework" {
return file.basenameWithoutSuffix
}
return file.dirname.isEmpty || file.dirname.isRoot ? nil : findFrameworkName(file.dirname)
}
var moduleNames: [String] = []
var includeFiles: [Path] = []
for file in files {
if let frameworkName = findFrameworkName(file) {
moduleNames.append(frameworkName)
} else {
includeFiles.append(file)
}
}
let moduleDependencies = moduleNames.map { ModuleDependency(name: $0, accessLevel: .Private, optional: false) }
let moduleImports = moduleDependencies.map { DependencyValidationInfo.Import(dependency: $0, importLocations: []) }
return (moduleImports, includeFiles)
}
}

public final class ClangNonModularCompileTaskAction: TaskAction {
Expand Down Expand Up @@ -560,27 +594,12 @@ public final class ClangNonModularCompileTaskAction: TaskAction {
let execResult = processDelegate.commandResult ?? .failed

if execResult == .succeeded {
let payload: DependencyValidationInfo.Payload
if let traceFilePath {
let fs = executionDelegate.fs
let traceData = try JSONDecoder().decode(Array<TraceData>.self, from: fs.readMemoryMapped(traceFilePath))

var allFiles = Set<Path>()
traceData.forEach { allFiles.formUnion(Set($0.includes)) }
payload = .clangDependencies(imports: [], includes: Array(allFiles))
} else {
payload = .unsupported
}

if let dependencyValidationOutputPath {
let validationInfo = DependencyValidationInfo(payload: payload)
_ = try executionDelegate.fs.writeIfChanged(
dependencyValidationOutputPath,
contents: ByteString(
JSONEncoder(outputFormatting: .sortedKeys).encode(validationInfo)
)
)
}
try ClangCompileTaskAction.handleDependencyValidation(
traceFilePath: traceFilePath,
dependencyValidationOutputPath: dependencyValidationOutputPath,
fileSystem: executionDelegate.fs,
isModular: false
)
}

return execResult
Expand Down