diff --git a/Sources/SWBCore/Dependencies.swift b/Sources/SWBCore/Dependencies.swift index 0675792a..5888602a 100644 --- a/Sources/SWBCore/Dependencies.swift +++ b/Sources/SWBCore/Dependencies.swift @@ -92,41 +92,13 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable { self.init(validate: validate, moduleDependencies: settings.moduleDependencies, fixItContext: fixItContext) } - /// Compute missing module dependencies from Clang imports. - /// - /// The compiler tracing information does not provide the import locations or whether they are public imports - /// (which depends on whether the import is in an installed header file). - /// If `files` is nil, the current toolchain does support the feature to trace imports. - public func computeMissingDependencies(files: [Path]?) -> [(ModuleDependency, importLocations: [Diagnostic.Location])]? { - guard validate != .no else { return [] } - guard let files else { - return nil - } - - // The following is a provisional/incomplete mechanism for resolving a module dependency from a file path. - // For now, just grab the framework name and assume there is a module with the same name. - func findFrameworkName(_ file: Path) -> String? { - if file.fileExtension == "framework" { - return file.basenameWithoutSuffix - } - return file.dirname.isEmpty || file.dirname.isRoot ? nil : findFrameworkName(file.dirname) - } - - let moduleDependencyNames = moduleDependencies.map { $0.name } - let fileNames = files.compactMap { findFrameworkName($0) } - let missingDeps = Set(fileNames.filter { - return !moduleDependencyNames.contains($0) - }.map { - ModuleDependency(name: $0, accessLevel: .Private) - }) - - return missingDeps.map { ($0, []) } - } - - /// Compute missing module dependencies from Swift imports. + /// Compute missing module dependencies. /// /// If `imports` is nil, the current toolchain does not support the features to gather imports. - public func computeMissingDependencies(imports: [(ModuleDependency, importLocations: [Diagnostic.Location])]?) -> [(ModuleDependency, importLocations: [Diagnostic.Location])]? { + public func computeMissingDependencies( + imports: [(ModuleDependency, importLocations: [Diagnostic.Location])]?, + fromSwift: Bool + ) -> [(ModuleDependency, importLocations: [Diagnostic.Location])]? { guard validate != .no else { return [] } guard let imports else { return nil @@ -134,7 +106,7 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable { return imports.filter { // ignore module deps without source locations, these are inserted by swift / swift-build and we should treat them as implementation details which we can track without needing the user to declare them - if $0.importLocations.isEmpty { return false } + if fromSwift && $0.importLocations.isEmpty { return false } // TODO: if the difference is just the access modifier, we emit a new entry, but ultimately our fixit should update the existing entry or emit an error about a conflict if moduleDependencies.contains($0.0) { return false } @@ -235,6 +207,166 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable { } } +public struct HeaderDependency: Hashable, Sendable, SerializableCodable { + public let name: String + public let accessLevel: AccessLevel + + public enum AccessLevel: String, Hashable, Sendable, CaseIterable, Codable, Serializable { + case Private = "private" + case Public = "public" + + public init(_ string: String) throws { + guard let accessLevel = AccessLevel(rawValue: string) else { + throw StubError.error("unexpected access modifier '\(string)', expected one of: \(AccessLevel.allCases.map { $0.rawValue }.joined(separator: ", "))") + } + + self = accessLevel + } + } + + public init(name: String, accessLevel: AccessLevel) { + self.name = name + self.accessLevel = accessLevel + } + + public init(entry: String) throws { + var it = entry.split(separator: " ").makeIterator() + switch (it.next(), it.next(), it.next()) { + case (let .some(name), nil, nil): + self.name = String(name) + self.accessLevel = .Private + + case (let .some(accessLevel), let .some(name), nil): + self.name = String(name) + self.accessLevel = try AccessLevel(String(accessLevel)) + + default: + throw StubError.error("expected 1 or 2 space-separated components in: \(entry)") + } + } + + public var asBuildSettingEntry: String { + "\(accessLevel == .Private ? "" : "\(accessLevel.rawValue) ")\(name)" + } + + public var asBuildSettingEntryQuotedIfNeeded: String { + let e = asBuildSettingEntry + return e.contains(" ") ? "\"\(e)\"" : e + } +} + +public struct HeaderDependenciesContext: Sendable, SerializableCodable { + public var validate: BooleanWarningLevel + var headerDependencies: [HeaderDependency] + var fixItContext: FixItContext? + + init(validate: BooleanWarningLevel, headerDependencies: [HeaderDependency], fixItContext: FixItContext? = nil) { + self.validate = validate + self.headerDependencies = headerDependencies + self.fixItContext = fixItContext + } + + public init?(settings: Settings) { + let validate = settings.globalScope.evaluate(BuiltinMacros.VALIDATE_HEADER_DEPENDENCIES) + guard validate != .no else { return nil } + let fixItContext = HeaderDependenciesContext.FixItContext(settings: settings) + self.init(validate: validate, headerDependencies: settings.headerDependencies, fixItContext: fixItContext) + } + + /// Make diagnostics for missing header dependencies. + /// + /// The compiler tracing information does not provide the include locations or whether they are public imports + /// (which depends on whether the import is in an installed header file). + /// If `includes` is nil, the current toolchain does support the feature to trace imports. + public func makeDiagnostics(includes: [Path]?) -> [Diagnostic] { + guard validate != .no else { return [] } + guard let includes else { + return [Diagnostic( + behavior: .warning, + location: .unknown, + data: DiagnosticData("The current toolchain does not support \(BuiltinMacros.VALIDATE_HEADER_DEPENDENCIES.name)"))] + } + + let headerDependencyNames = headerDependencies.map { $0.name } + let missingDeps = includes.filter { file in + return !headerDependencyNames.contains(where: { file.ends(with: $0) }) + }.map { + // TODO: What if the basename doesn't uniquely identify the header? + HeaderDependency(name: $0.basename, accessLevel: .Private) + } + + guard !missingDeps.isEmpty else { return [] } + + let behavior: Diagnostic.Behavior = validate == .yesError ? .error : .warning + + let fixIt = fixItContext?.makeFixIt(newHeaders: missingDeps) + let fixIts = fixIt.map { [$0] } ?? [] + + let message = "Missing entries in \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(missingDeps.map { $0.asBuildSettingEntryQuotedIfNeeded }.sorted().joined(separator: " "))" + + let location: Diagnostic.Location = fixIt.map { + Diagnostic.Location.path($0.sourceRange.path, line: $0.sourceRange.endLine, column: $0.sourceRange.endColumn) + } ?? Diagnostic.Location.buildSetting(BuiltinMacros.HEADER_DEPENDENCIES) + + return [Diagnostic( + behavior: behavior, + location: location, + data: DiagnosticData(message), + fixIts: fixIts)] + } + + struct FixItContext: Sendable, SerializableCodable { + var sourceRange: Diagnostic.SourceRange + var modificationStyle: ModificationStyle + + init(sourceRange: Diagnostic.SourceRange, modificationStyle: ModificationStyle) { + self.sourceRange = sourceRange + self.modificationStyle = modificationStyle + } + + init?(settings: Settings) { + guard let target = settings.target else { return nil } + let thisTargetCondition = MacroCondition(parameter: BuiltinMacros.targetNameCondition, valuePattern: target.name) + + if let assignment = (settings.globalScope.table.lookupMacro(BuiltinMacros.HEADER_DEPENDENCIES)?.sequence.first { + $0.location != nil && ($0.conditions?.conditions == [thisTargetCondition] || ($0.conditions?.conditions.isEmpty ?? true)) + }), + let location = assignment.location + { + self.init(sourceRange: .init(path: location.path, startLine: location.endLine, startColumn: location.endColumn, endLine: location.endLine, endColumn: location.endColumn), modificationStyle: .appendToExistingAssignment) + } + else if let path = settings.constructionComponents.targetXcconfigPath { + self.init(sourceRange: .init(path: path, startLine: .max, startColumn: .max, endLine: .max, endColumn: .max), modificationStyle: .insertNewAssignment(targetNameCondition: nil)) + } + else if let path = settings.constructionComponents.projectXcconfigPath { + self.init(sourceRange: .init(path: path, startLine: .max, startColumn: .max, endLine: .max, endColumn: .max), modificationStyle: .insertNewAssignment(targetNameCondition: target.name)) + } + else { + return nil + } + } + + enum ModificationStyle: Sendable, SerializableCodable, Hashable { + case appendToExistingAssignment + case insertNewAssignment(targetNameCondition: String?) + } + + func makeFixIt(newHeaders: [HeaderDependency]) -> Diagnostic.FixIt { + let stringValue = newHeaders.map { $0.asBuildSettingEntryQuotedIfNeeded }.sorted().joined(separator: " ") + let newText: String + switch modificationStyle { + case .appendToExistingAssignment: + newText = " \(stringValue)" + case .insertNewAssignment(let targetNameCondition): + let targetCondition = targetNameCondition.map { "[target=\($0)]" } ?? "" + newText = "\n\(BuiltinMacros.HEADER_DEPENDENCIES.name)\(targetCondition) = $(inherited) \(stringValue)\n" + } + + return Diagnostic.FixIt(sourceRange: sourceRange, newText: newText) + } + } +} + public struct DependencyValidationInfo: Hashable, Sendable, Codable { public struct Import: Hashable, Sendable, Codable { public let dependency: ModuleDependency @@ -247,7 +379,7 @@ public struct DependencyValidationInfo: Hashable, Sendable, Codable { } public enum Payload: Hashable, Sendable, Codable { - case clangDependencies(files: [String]) + case clangDependencies(imports: [Import], includes: [Path]) case swiftDependencies(imports: [Import]) case unsupported } diff --git a/Sources/SWBCore/PlannedTaskAction.swift b/Sources/SWBCore/PlannedTaskAction.swift index 4844d09c..4ffc6484 100644 --- a/Sources/SWBCore/PlannedTaskAction.swift +++ b/Sources/SWBCore/PlannedTaskAction.swift @@ -337,6 +337,7 @@ public protocol TaskActionCreationDelegate func createValidateProductTaskAction() -> any PlannedTaskAction func createConstructStubExecutorInputFileListTaskAction() -> any PlannedTaskAction func createClangCompileTaskAction() -> any PlannedTaskAction + func createClangNonModularCompileTaskAction() -> any PlannedTaskAction func createClangScanTaskAction() -> any PlannedTaskAction func createSwiftDriverTaskAction() -> any PlannedTaskAction func createSwiftCompilationRequirementTaskAction() -> any PlannedTaskAction diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 5a90b641..d0f5dbd5 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -732,6 +732,7 @@ public final class BuiltinMacros { public static let GLOBAL_CFLAGS = BuiltinMacros.declareStringListMacro("GLOBAL_CFLAGS") public static let HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_TARGETS_NOT_BEING_BUILT = BuiltinMacros.declareBooleanMacro("HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_TARGETS_NOT_BEING_BUILT") public static let HEADERMAP_USES_VFS = BuiltinMacros.declareBooleanMacro("HEADERMAP_USES_VFS") + public static let HEADER_DEPENDENCIES = BuiltinMacros.declareStringListMacro("HEADER_DEPENDENCIES") public static let HEADER_OUTPUT_DIR = BuiltinMacros.declareStringMacro("HEADER_OUTPUT_DIR") public static let HEADER_SEARCH_PATHS = BuiltinMacros.declarePathListMacro("HEADER_SEARCH_PATHS") public static let IBC_REGIONS_AND_STRINGS_FILES = BuiltinMacros.declareStringListMacro("IBC_REGIONS_AND_STRINGS_FILES") @@ -1145,6 +1146,7 @@ public final class BuiltinMacros { public static let VALIDATE_PRODUCT = BuiltinMacros.declareBooleanMacro("VALIDATE_PRODUCT") public static let VALIDATE_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_DEPENDENCIES") as EnumMacroDeclaration public static let VALIDATE_DEVELOPMENT_ASSET_PATHS = BuiltinMacros.declareEnumMacro("VALIDATE_DEVELOPMENT_ASSET_PATHS") as EnumMacroDeclaration + public static let VALIDATE_HEADER_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_HEADER_DEPENDENCIES") as EnumMacroDeclaration public static let VALIDATE_MODULE_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_MODULE_DEPENDENCIES") as EnumMacroDeclaration public static let VECTOR_SUFFIX = BuiltinMacros.declareStringMacro("VECTOR_SUFFIX") public static let VERBOSE_PBXCP = BuiltinMacros.declareBooleanMacro("VERBOSE_PBXCP") @@ -1798,6 +1800,7 @@ public final class BuiltinMacros { GROUP, HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_TARGETS_NOT_BEING_BUILT, HEADERMAP_USES_VFS, + HEADER_DEPENDENCIES, HEADER_SEARCH_PATHS, HEADER_OUTPUT_DIR, HOME, @@ -2373,6 +2376,7 @@ public final class BuiltinMacros { VALIDATE_PRODUCT, VALIDATE_DEPENDENCIES, VALIDATE_DEVELOPMENT_ASSET_PATHS, + VALIDATE_HEADER_DEPENDENCIES, VALIDATE_MODULE_DEPENDENCIES, VALID_ARCHS, VECTOR_SUFFIX, diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index bf5923f6..fc4b362d 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -756,6 +756,7 @@ public final class Settings: PlatformBuildContext, Sendable { } public let moduleDependencies: [ModuleDependency] + public let headerDependencies: [HeaderDependency] public static func supportsMacCatalyst(scope: MacroEvaluationScope, core: Core) -> Bool { @preconcurrency @PluginExtensionSystemActor func sdkVariantInfoExtensions() -> [any SDKVariantInfoExtensionPoint.ExtensionProtocol] { @@ -904,6 +905,7 @@ public final class Settings: PlatformBuildContext, Sendable { self.supportedBuildVersionPlatforms = effectiveSupportedPlatforms(sdkRegistry: sdkRegistry) self.moduleDependencies = builder.moduleDependencies + self.headerDependencies = builder.headerDependencies self.constructionComponents = builder.constructionComponents } @@ -1290,6 +1292,7 @@ private class SettingsBuilder { var signingSettings: Settings.SigningSettings? = nil var moduleDependencies: [ModuleDependency] = [] + var headerDependencies: [HeaderDependency] = [] // Mutable state of the builder as we're building up the settings table. @@ -1631,6 +1634,12 @@ private class SettingsBuilder { catch { errors.append("Failed to parse \(BuiltinMacros.MODULE_DEPENDENCIES.name): \(error)") } + do { + self.headerDependencies = try createScope(sdkToUse: boundProperties.sdk).evaluate(BuiltinMacros.HEADER_DEPENDENCIES).map { try HeaderDependency(entry: $0) } + } + catch { + errors.append("Failed to parse \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(error)") + } // At this point settings construction is finished. diff --git a/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift b/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift index fe284753..0738bac6 100644 --- a/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift +++ b/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift @@ -101,6 +101,7 @@ private final class EnumBuildOptionType : BuildOptionType { return try namespace.declareEnumMacro(name) as EnumMacroDeclaration case "VALIDATE_DEPENDENCIES", "VALIDATE_MODULE_DEPENDENCIES", + "VALIDATE_HEADER_DEPENDENCIES", "VALIDATE_DEVELOPMENT_ASSET_PATHS": return try namespace.declareEnumMacro(name) as EnumMacroDeclaration case "STRIP_STYLE": diff --git a/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift index 5cba1b9c..d09d9119 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift @@ -432,11 +432,10 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd public let fileNameMapPath: Path? - public let moduleDependenciesContext: ModuleDependenciesContext? public let traceFilePath: Path? public let dependencyValidationOutputPath: Path? - fileprivate init(serializedDiagnosticsPath: Path?, indexingPayload: ClangIndexingPayload?, explicitModulesPayload: ClangExplicitModulesPayload? = nil, outputObjectFilePath: Path? = nil, fileNameMapPath: Path? = nil, developerPathString: String? = nil, moduleDependenciesContext: ModuleDependenciesContext? = nil, traceFilePath: Path? = nil, dependencyValidationOutputPath: Path? = nil) { + fileprivate init(serializedDiagnosticsPath: Path?, indexingPayload: ClangIndexingPayload?, explicitModulesPayload: ClangExplicitModulesPayload? = nil, outputObjectFilePath: Path? = nil, fileNameMapPath: Path? = nil, developerPathString: String? = nil, traceFilePath: Path? = nil, dependencyValidationOutputPath: Path? = nil) { if let developerPathString, explicitModulesPayload == nil { self.dependencyInfoEditPayload = .init(removablePaths: [], removableBasenames: [], developerPath: Path(developerPathString)) } else { @@ -447,34 +446,31 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd self.explicitModulesPayload = explicitModulesPayload self.outputObjectFilePath = outputObjectFilePath self.fileNameMapPath = fileNameMapPath - self.moduleDependenciesContext = moduleDependenciesContext self.traceFilePath = traceFilePath self.dependencyValidationOutputPath = dependencyValidationOutputPath } public func serialize(to serializer: T) { - serializer.serializeAggregate(9) { + serializer.serializeAggregate(8) { serializer.serialize(serializedDiagnosticsPath) serializer.serialize(indexingPayload) serializer.serialize(explicitModulesPayload) serializer.serialize(outputObjectFilePath) serializer.serialize(fileNameMapPath) serializer.serialize(dependencyInfoEditPayload) - serializer.serialize(moduleDependenciesContext) serializer.serialize(traceFilePath) serializer.serialize(dependencyValidationOutputPath) } } public init(from deserializer: any Deserializer) throws { - try deserializer.beginAggregate(9) + try deserializer.beginAggregate(8) self.serializedDiagnosticsPath = try deserializer.deserialize() self.indexingPayload = try deserializer.deserialize() self.explicitModulesPayload = try deserializer.deserialize() self.outputObjectFilePath = try deserializer.deserialize() self.fileNameMapPath = try deserializer.deserialize() self.dependencyInfoEditPayload = try deserializer.deserialize() - self.moduleDependenciesContext = try deserializer.deserialize() self.traceFilePath = try deserializer.deserialize() self.dependencyValidationOutputPath = try deserializer.deserialize() } @@ -1183,12 +1179,14 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible let extraOutputs: [any PlannedNode] let moduleDependenciesContext = cbc.producer.moduleDependenciesContext + let headerDependenciesContext = cbc.producer.headerDependenciesContext let dependencyValidationOutputPath: Path? let traceFilePath: Path? if clangInfo?.hasFeature("print-headers-direct-per-file") ?? false, - (moduleDependenciesContext?.validate ?? .defaultValue) != .no { - dependencyValidationOutputPath = Path(outputNode.path.str + ".dependencies") + ((moduleDependenciesContext?.validate ?? .defaultValue) != .no || + (headerDependenciesContext?.validate ?? .defaultValue) != .no) { + dependencyValidationOutputPath = Path(outputNode.path.str + ".dependencies") let file = Path(outputNode.path.str + ".trace.json") commandLine += [ "-Xclang", "-header-include-file", @@ -1316,7 +1314,6 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible outputObjectFilePath: shouldGenerateRemarks ? outputNode.path : nil, fileNameMapPath: verifierPayload?.fileNameMapPath, developerPathString: recordSystemHeaderDepsOutsideSysroot ? cbc.scope.evaluate(BuiltinMacros.DEVELOPER_DIR).str : nil, - moduleDependenciesContext: moduleDependenciesContext, traceFilePath: traceFilePath, dependencyValidationOutputPath: dependencyValidationOutputPath ) @@ -1368,21 +1365,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible extraInputs = [] } - if let moduleDependenciesContext { - do { - let jsonData = try JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(moduleDependenciesContext) - guard let signature = String(data: jsonData, encoding: .utf8) else { - throw StubError.error("non-UTF-8 data") - } - additionalSignatureData += "|\(signature)" - } catch { - delegate.error("failed to serialize 'MODULE_DEPENDENCIES' context information: \(error)") - return - } - } - // Finally, create the task. - delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo, additionalSignatureData: additionalSignatureData, commandLine: commandLine, additionalOutput: additionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: inputNodes + extraInputs, outputs: [outputNode] + extraOutputs + additionalOutputs.outputs.map { delegate.createNode($0.path) }, action: action ?? delegate.taskActionCreationDelegate.createDeferredExecutionTaskActionIfRequested(userPreferences: cbc.producer.userPreferences), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilationForIndexableSourceFile], usesExecutionInputs: usesExecutionInputs, showEnvironment: true, priority: .preferred) + delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo, additionalSignatureData: additionalSignatureData, commandLine: commandLine, additionalOutput: additionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: inputNodes + extraInputs, outputs: [outputNode] + extraOutputs + additionalOutputs.outputs.map { delegate.createNode($0.path) }, action: action ?? delegate.taskActionCreationDelegate.createClangNonModularCompileTaskAction(), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilationForIndexableSourceFile], usesExecutionInputs: usesExecutionInputs, showEnvironment: true, priority: .preferred) // If the object file verifier is enabled and we are building with explicit modules, also create a job to produce adjacent objects using implicit modules, then compare the results. if cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_EXPLICIT_MODULES_OBJECT_FILE_VERIFIER) && action != nil { diff --git a/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift b/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift index 43021306..9407cd11 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/ValidateDependencies.swift @@ -55,9 +55,11 @@ public final class ValidateDependenciesSpec: CommandLineToolSpec, SpecImplementa } public struct ValidateDependenciesPayload: TaskPayload, Sendable, SerializableCodable { - public let moduleDependenciesContext: ModuleDependenciesContext + public let moduleDependenciesContext: ModuleDependenciesContext? + public let headerDependenciesContext: HeaderDependenciesContext? - public init(moduleDependenciesContext: ModuleDependenciesContext) { + public init(moduleDependenciesContext: ModuleDependenciesContext?, headerDependenciesContext: HeaderDependenciesContext?) { self.moduleDependenciesContext = moduleDependenciesContext + self.headerDependenciesContext = headerDependenciesContext } } diff --git a/Sources/SWBCore/Specs/CoreBuildSystem.xcspec b/Sources/SWBCore/Specs/CoreBuildSystem.xcspec index fe11ab15..e2c35411 100644 --- a/Sources/SWBCore/Specs/CoreBuildSystem.xcspec +++ b/Sources/SWBCore/Specs/CoreBuildSystem.xcspec @@ -1611,6 +1611,16 @@ When `GENERATE_INFOPLIST_FILE` is enabled, sets the value of the [CFBundleIdenti sdk, ); }, + { + Name = "HEADER_DEPENDENCIES"; + Type = StringList; + Category = BuildOptions; + DefaultValue = ""; + ConditionFlavors = ( + arch, + sdk, + ); + }, { Name = "GENERATE_PRELINK_OBJECT_FILE"; Type = Boolean; diff --git a/Sources/SWBCore/Specs/en.lproj/CoreBuildSystem.strings b/Sources/SWBCore/Specs/en.lproj/CoreBuildSystem.strings index fef09bcc..769350c7 100644 --- a/Sources/SWBCore/Specs/en.lproj/CoreBuildSystem.strings +++ b/Sources/SWBCore/Specs/en.lproj/CoreBuildSystem.strings @@ -400,6 +400,9 @@ Generally you should not specify an order file in Debug or Development configura "[MODULE_DEPENDENCIES]-name" = "Module Dependencies"; "[MODULE_DEPENDENCIES]-description" = "Other modules this target depends on."; +"[HEADER_DEPENDENCIES]-name" = "Header Dependencies"; +"[HEADER_DEPENDENCIES]-description" = "Header files this target depends on."; + "[OTHER_LIBTOOLFLAGS]-name" = "Other Librarian Flags"; "[OTHER_LIBTOOLFLAGS]-description" = "Options defined in this setting are passed to all invocations of the archive librarian, which is used to generate static libraries."; diff --git a/Sources/SWBCore/TaskGeneration.swift b/Sources/SWBCore/TaskGeneration.swift index 6bba61ec..7ef96613 100644 --- a/Sources/SWBCore/TaskGeneration.swift +++ b/Sources/SWBCore/TaskGeneration.swift @@ -273,6 +273,7 @@ public protocol CommandProducer: PlatformBuildContext, SpecLookupContext, Refere var hostOperatingSystem: OperatingSystem { get } var moduleDependenciesContext: ModuleDependenciesContext? { get } + var headerDependenciesContext: HeaderDependenciesContext? { get } } extension CommandProducer { diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index 8a4ff447..c02f73e2 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -1574,14 +1574,16 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F } // Create a task to validate dependencies if that feature is enabled. - if let moduleDependenciesContext = context.moduleDependenciesContext, moduleDependenciesContext.validate != .no { + let validateModuleDeps = (context.moduleDependenciesContext?.validate ?? .no) != .no + let validateHeaderDeps = (context.headerDependenciesContext?.validate ?? .no) != .no + if validateModuleDeps || validateHeaderDeps { var validateDepsTasks = [any PlannedTask]() await appendGeneratedTasks(&validateDepsTasks, usePhasedOrdering: true) { delegate in await context.validateDependenciesSpec.createTasks( CommandBuildContext(producer: context, scope: scope, inputs: []), delegate, dependencyInfos: dependencyDataFiles, - payload: .init(moduleDependenciesContext: moduleDependenciesContext) + payload: .init(moduleDependenciesContext: context.moduleDependenciesContext, headerDependenciesContext: context.headerDependenciesContext) ) } tasks.append(contentsOf: validateDepsTasks) diff --git a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift index b24fbc1c..d5397dac 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift @@ -122,6 +122,7 @@ public class TaskProducerContext: StaleFileRemovalContext, BuildFileResolution var emitFrontendCommandLines: Bool public let moduleDependenciesContext: ModuleDependenciesContext? + public let headerDependenciesContext: HeaderDependenciesContext? private struct State: Sendable { fileprivate var onDemandResourcesAssetPacks: [ODRTagSet: ODRAssetPackInfo] = [:] @@ -439,6 +440,7 @@ public class TaskProducerContext: StaleFileRemovalContext, BuildFileResolution } self.moduleDependenciesContext = ModuleDependenciesContext(settings: settings) + self.headerDependenciesContext = HeaderDependenciesContext(settings: settings) } /// The set of all known deployment target macro names, even if the platforms that use those settings are not installed. diff --git a/Sources/SWBTaskExecution/BuildDescriptionManager.swift b/Sources/SWBTaskExecution/BuildDescriptionManager.swift index 05e43c74..ecdfbbbd 100644 --- a/Sources/SWBTaskExecution/BuildDescriptionManager.swift +++ b/Sources/SWBTaskExecution/BuildDescriptionManager.swift @@ -856,6 +856,10 @@ extension BuildSystemTaskPlanningDelegate: TaskActionCreationDelegate { return ClangCompileTaskAction() } + func createClangNonModularCompileTaskAction() -> any PlannedTaskAction { + return ClangNonModularCompileTaskAction() + } + func createClangScanTaskAction() -> any PlannedTaskAction { return ClangScanTaskAction() } diff --git a/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift b/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift index cd19b016..1b2e6312 100644 --- a/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift +++ b/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift @@ -53,6 +53,7 @@ public struct BuiltinTaskActionsExtension: TaskActionExtension { 38: GenericCachingTaskAction.self, 39: ProcessSDKImportsTaskAction.self, 40: ValidateDependenciesTaskAction.self, + 41: ClangNonModularCompileTaskAction.self, 42: ObjectLibraryAssemblerTaskAction.self, 43: LinkerTaskAction.self, ] diff --git a/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift index 88062d09..10941936 100644 --- a/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift @@ -250,15 +250,12 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA // Check if verifying dependencies from trace data is enabled. let traceFilePath: Path? - let moduleDependenciesContext: ModuleDependenciesContext? let dependencyValidationOutputPath: Path? if let payload = task.payload as? ClangTaskPayload { traceFilePath = payload.traceFilePath - moduleDependenciesContext = payload.moduleDependenciesContext dependencyValidationOutputPath = payload.dependencyValidationOutputPath } else { traceFilePath = nil - moduleDependenciesContext = nil dependencyValidationOutputPath = nil } if let traceFilePath { @@ -334,7 +331,8 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA var allFiles = Set() traceData.forEach { allFiles.formUnion(Set($0.includes)) } - payload = .clangDependencies(files: allFiles.map { $0.str }) + let (imports, includes) = separateImportsFromIncludes(allFiles) + payload = .clangDependencies(imports: imports, includes: includes) } else { payload = .unsupported } @@ -357,6 +355,31 @@ 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) -> ([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) } + 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 /// a `ClangCachingMaterializeKeyTaskAction` as task dependency. @@ -478,6 +501,83 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA } } +public final class ClangNonModularCompileTaskAction: TaskAction { + public override class var toolIdentifier: String { + return "ccompile-nonmodular" + } + + override public func performTaskAction( + _ task: any ExecutableTask, + dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, + executionDelegate: any TaskExecutionDelegate, + clientDelegate: any TaskExecutionClientDelegate, + outputDelegate: any TaskOutputDelegate, + ) async -> CommandResult { + do { + // Check if verifying dependencies from trace data is enabled. + let traceFilePath: Path? + let dependencyValidationOutputPath: Path? + if let payload = task.payload as? ClangTaskPayload { + traceFilePath = payload.traceFilePath + dependencyValidationOutputPath = payload.dependencyValidationOutputPath + } else { + traceFilePath = nil + dependencyValidationOutputPath = nil + } + if let traceFilePath { + // Remove the trace output file if it already exists. + if executionDelegate.fs.exists(traceFilePath) { + try executionDelegate.fs.remove(traceFilePath) + } + } + + let processDelegate = TaskProcessDelegate(outputDelegate: outputDelegate) + try await spawn( + commandLine: Array(task.commandLineAsStrings), + environment: task.environment.bindingsDictionary, + workingDirectory: task.workingDirectory, + dynamicExecutionDelegate: dynamicExecutionDelegate, + clientDelegate: clientDelegate, + processDelegate: processDelegate, + ) + if let error = processDelegate.executionError { + outputDelegate.error(error) + return .failed + } + 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.self, from: fs.readMemoryMapped(traceFilePath)) + + var allFiles = Set() + 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) + ) + ) + } + } + + return execResult + } catch { + outputDelegate.error(error.localizedDescription) + return .failed + } + } +} + // Results from tracing header includes with "direct-per-file" filtering. // This is used to validate dependencies. fileprivate struct TraceData: Decodable { diff --git a/Sources/SWBTaskExecution/TaskActions/ValidateDependenciesTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ValidateDependenciesTaskAction.swift index 06f67771..9d463104 100644 --- a/Sources/SWBTaskExecution/TaskActions/ValidateDependenciesTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ValidateDependenciesTaskAction.swift @@ -26,7 +26,7 @@ public final class ValidateDependenciesTaskAction: TaskAction { return .failed } - guard let context = (task.payload as? ValidateDependenciesPayload)?.moduleDependenciesContext else { + guard let payload = (task.payload as? ValidateDependenciesPayload) else { if let payload = task.payload { outputDelegate.emitError("invalid task payload: \(payload)") } else { @@ -36,7 +36,8 @@ public final class ValidateDependenciesTaskAction: TaskAction { } do { - var allClangFiles = Set() + var allClangIncludes = Set() + var allClangImports = Set() var allSwiftImports = Set() var unsupported = false @@ -45,9 +46,12 @@ public final class ValidateDependenciesTaskAction: TaskAction { let info = try JSONDecoder().decode(DependencyValidationInfo.self, from: inputData) switch info.payload { - case .clangDependencies(let files): - files.forEach { - allClangFiles.insert($0) + case .clangDependencies(let imports, let includes): + imports.forEach { + allClangImports.insert($0) + } + includes.forEach { + allClangIncludes.insert($0) } case .swiftDependencies(let imports): imports.forEach { @@ -60,33 +64,42 @@ public final class ValidateDependenciesTaskAction: TaskAction { var diagnostics: [Diagnostic] = [] - if unsupported { - diagnostics.append(contentsOf: context.makeDiagnostics(missingDependencies: nil)) - } else { - let clangMissingDeps = context.computeMissingDependencies(files: allClangFiles.map { Path($0) }) - let swiftMissingDeps = context.computeMissingDependencies(imports: allSwiftImports.map { ($0.dependency, $0.importLocations) }) + if let moduleContext = payload.moduleDependenciesContext { + if unsupported { + diagnostics.append(contentsOf: moduleContext.makeDiagnostics(missingDependencies: nil)) + } else { + let clangMissingDeps = moduleContext.computeMissingDependencies(imports: allClangImports.map { ($0.dependency, $0.importLocations) }, fromSwift: false) + let swiftMissingDeps = moduleContext.computeMissingDependencies(imports: allSwiftImports.map { ($0.dependency, $0.importLocations) }, fromSwift: true) - // Update Swift dependencies with information from Clang dependencies on the same module. - var clangMissingDepsByName = [String: (ModuleDependency, importLocations: [Diagnostic.Location])]() - clangMissingDeps?.forEach { - clangMissingDepsByName[$0.0.name] = $0 - } - let updatedSwiftMissingDeps: [(ModuleDependency, importLocations: [Diagnostic.Location])] = swiftMissingDeps?.map { - if let clangMissingDep = clangMissingDepsByName[$0.0.name] { - return ( - ModuleDependency(name: $0.0.name, accessLevel: max($0.0.accessLevel, clangMissingDep.0.accessLevel)), - $0.importLocations + clangMissingDep.importLocations - ) - } else { - return $0 + // Update Swift dependencies with information from Clang dependencies on the same module. + var clangMissingDepsByName = [String: (ModuleDependency, importLocations: [Diagnostic.Location])]() + clangMissingDeps?.forEach { + clangMissingDepsByName[$0.0.name] = $0 } - } ?? [] + let updatedSwiftMissingDeps: [(ModuleDependency, importLocations: [Diagnostic.Location])] = swiftMissingDeps?.map { + if let clangMissingDep = clangMissingDepsByName[$0.0.name] { + return ( + ModuleDependency(name: $0.0.name, accessLevel: max($0.0.accessLevel, clangMissingDep.0.accessLevel)), + $0.importLocations + clangMissingDep.importLocations + ) + } else { + return $0 + } + } ?? [] - // Filter missing C dependencies by known Swift dependencies to avoid duplicate diagnostics. - let swiftImports = Set(allSwiftImports.map { $0.dependency.name }) - let uniqueClangMissingDeps = clangMissingDeps?.filter { !swiftImports.contains($0.0.name) } ?? [] + // Filter missing C dependencies by known Swift dependencies to avoid duplicate diagnostics. + let swiftImports = Set(allSwiftImports.map { $0.dependency.name }) + let uniqueClangMissingDeps = clangMissingDeps?.filter { !swiftImports.contains($0.0.name) } ?? [] - diagnostics.append(contentsOf: context.makeDiagnostics(missingDependencies: uniqueClangMissingDeps + updatedSwiftMissingDeps)) + diagnostics.append(contentsOf: moduleContext.makeDiagnostics(missingDependencies: uniqueClangMissingDeps + updatedSwiftMissingDeps)) + } + } + if let headerContext = payload.headerDependenciesContext { + if unsupported { + diagnostics.append(contentsOf: headerContext.makeDiagnostics(includes: nil)) + } else { + diagnostics.append(contentsOf: headerContext.makeDiagnostics(includes: Array(allClangIncludes))) + } } for diagnostic in diagnostics { diff --git a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift index 979e947f..91f6937a 100644 --- a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift +++ b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift @@ -204,6 +204,10 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { return ClangCompileTaskAction() } + package func createClangNonModularCompileTaskAction() -> any PlannedTaskAction { + return ClangNonModularCompileTaskAction() + } + package func createClangScanTaskAction() -> any PlannedTaskAction { return ClangScanTaskAction() } diff --git a/Sources/SWBTestSupport/DummyCommandProducer.swift b/Sources/SWBTestSupport/DummyCommandProducer.swift index 9d00f0b5..1b0e9e9a 100644 --- a/Sources/SWBTestSupport/DummyCommandProducer.swift +++ b/Sources/SWBTestSupport/DummyCommandProducer.swift @@ -244,4 +244,8 @@ package struct MockCommandProducer: CommandProducer, Sendable { package var moduleDependenciesContext: SWBCore.ModuleDependenciesContext? { nil } + + package var headerDependenciesContext: SWBCore.HeaderDependenciesContext? { + nil + } } diff --git a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift index cc6505df..9ba7c3d9 100644 --- a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift +++ b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift @@ -432,6 +432,10 @@ extension TestTaskPlanningDelegate: TaskActionCreationDelegate { return ClangCompileTaskAction() } + package func createClangNonModularCompileTaskAction() -> any PlannedTaskAction { + return ClangNonModularCompileTaskAction() + } + package func createClangScanTaskAction() -> any PlannedTaskAction { return ClangScanTaskAction() } diff --git a/Sources/SWBUniversalPlatform/Plugin.swift b/Sources/SWBUniversalPlatform/Plugin.swift index e6c44e50..4fffa083 100644 --- a/Sources/SWBUniversalPlatform/Plugin.swift +++ b/Sources/SWBUniversalPlatform/Plugin.swift @@ -81,6 +81,6 @@ struct UniversalPlatformTaskProducerExtension: TaskProducerExtension { struct UniversalPlatformTaskActionExtension: TaskActionExtension { var taskActionImplementations: [SWBUtil.SerializableTypeCode : any SWBUtil.PolymorphicSerializable.Type] { - [41: TestEntryPointGenerationTaskAction.self] + [44: TestEntryPointGenerationTaskAction.self] } } diff --git a/Tests/SWBBuildSystemTests/DependencyValidationTests.swift b/Tests/SWBBuildSystemTests/DependencyValidationTests.swift index 3fd4b2cd..9a5955e9 100644 --- a/Tests/SWBBuildSystemTests/DependencyValidationTests.swift +++ b/Tests/SWBBuildSystemTests/DependencyValidationTests.swift @@ -620,4 +620,69 @@ fileprivate struct DependencyValidationTests: CoreBasedTests { } } + @Test(.requireSDKs(.host), .requireClangFeatures(.printHeadersDirectPerFile), arguments: ["YES", "NO"]) + func validateHeaderDependencies(enableModules: String) async throws { + try await withTemporaryDirectory { tmpDir async throws -> Void in + let testWorkspace = TestWorkspace( + "Test", + sourceRoot: tmpDir.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", path: "Sources", + children: [ + TestFile("CoreFoo.c") + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "CLANG_ENABLE_MODULES": enableModules, + "CLANG_ENABLE_EXPLICIT_MODULES": enableModules, + "GENERATE_INFOPLIST_FILE": "YES", + "HEADER_DEPENDENCIES": "stdio.h", + "VALIDATE_HEADER_DEPENDENCIES": "YES_ERROR", + "SDKROOT": "$(HOST_PLATFORM)", + "SUPPORTED_PLATFORMS": "$(HOST_PLATFORM)", + "DSTROOT": tmpDir.join("dstroot").str, + ] + ) + ], + targets: [ + TestStandardTarget( + "CoreFoo", type: .framework, + buildPhases: [ + TestSourcesBuildPhase(["CoreFoo.c"]), + TestFrameworksBuildPhase() + ]) + ]) + ] + ) + + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + let SRCROOT = testWorkspace.sourceRoot.join("aProject") + + // Write the source files. + try await tester.fs.writeFileContents(SRCROOT.join("Sources/CoreFoo.c")) { contents in + contents <<< """ + #include + #include + + void f0(void) { }; + """ + } + + // Expect complaint about undeclared dependency + try await tester.checkBuild(parameters: BuildParameters(configuration: "Debug"), runDestination: .host, persistent: true) { results in + results.checkError(.contains("Missing entries in HEADER_DEPENDENCIES: stdlib.h")) + } + + // Declaring dependencies resolves the problem + try await tester.checkBuild(parameters: BuildParameters(configuration: "Debug", overrides: ["HEADER_DEPENDENCIES": "stdio.h stdlib.h"]), runDestination: .host, persistent: true) { results in + results.checkNoErrors() + } + } + } } diff --git a/Tests/SWBBuildSystemTests/WatchBuildOperationTests.swift b/Tests/SWBBuildSystemTests/WatchBuildOperationTests.swift index be5d8045..f1206cfb 100644 --- a/Tests/SWBBuildSystemTests/WatchBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/WatchBuildOperationTests.swift @@ -157,6 +157,7 @@ fileprivate struct WatchBuildOperationTests: CoreBasedTests { } results.checkWarning(.contains("Could not read serialized diagnostics file")) results.checkError(.equal("[targetIntegrity] Watch-Only Application Stubs are not available when building for watchOS. (in target 'Watchable' from project 'aProject')")) + results.checkWarning(.contains("Unable to perform dependency info modifications")) results.checkNoDiagnostics() } } diff --git a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift index 75c1d8ea..a9e2004d 100644 --- a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift +++ b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift @@ -198,6 +198,10 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { return ClangCompileTaskAction() } + public func createClangNonModularCompileTaskAction() -> any PlannedTaskAction { + return ClangNonModularCompileTaskAction() + } + public func createClangScanTaskAction() -> any PlannedTaskAction { return ClangScanTaskAction() } diff --git a/Tests/SWBTaskConstructionTests/DependencyVerificationTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/DependencyVerificationTaskConstructionTests.swift index e1767d5a..fa9ed476 100644 --- a/Tests/SWBTaskConstructionTests/DependencyVerificationTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/DependencyVerificationTaskConstructionTests.swift @@ -29,11 +29,11 @@ fileprivate struct DependencyVerificationTaskConstructionTests: CoreBasedTests { return "\(srcroot.str)/build/\(project).build/Debug/\(target).build/Objects-normal/x86_64/\(filename)" } - @Test(.requireSDKs(.macOS), .requireClangFeatures(.printHeadersDirectPerFile)) - func addsTraceArgsWhenValidationEnabled() async throws { + @Test(.requireSDKs(.macOS), .requireClangFeatures(.printHeadersDirectPerFile), arguments: ["MODULE", "HEADER"]) + func addsTraceArgsWhenValidationEnabled(depKind: String) async throws { try await testWith([ - "MODULE_DEPENDENCIES": "Foo", - "VALIDATE_MODULE_DEPENDENCIES": "YES_ERROR" + "\(depKind)_DEPENDENCIES": "Foo", + "VALIDATE_\(depKind)_DEPENDENCIES": "YES_ERROR" ]) { tester, srcroot in await tester.checkBuild(runDestination: .macOS, fs: localFS) { results in results.checkTask(.compileC(target, fileName: source)) { task in