Skip to content

Commit 8063c53

Browse files
committed
Add HEADER_DEPENDENCIES build setting with validation
This is similar to MODULE_DEPENDENCIES but for non-modular header file inclusion dependencies. Our approach to this has changed a bit over the last few months, and the trace output from Clang does not distinguish the modular vs. non-modular includes. We will work on fixing that, but in the meantime, this continues the placeholder approach used for MODULE_DEPENDENCIES where it assumes anything in a framework has a module with the same name as the framework. This also removes moduleDependenciesContext from ClangTaskPayload because the code that needs that now gets it from ValidateDependenciesPayload. rdar://150309197
1 parent 66cdd47 commit 8063c53

24 files changed

+451
-100
lines changed

Sources/SWBCore/Dependencies.swift

Lines changed: 167 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -92,49 +92,21 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable {
9292
self.init(validate: validate, moduleDependencies: settings.moduleDependencies, fixItContext: fixItContext)
9393
}
9494

95-
/// Compute missing module dependencies from Clang imports.
96-
///
97-
/// The compiler tracing information does not provide the import locations or whether they are public imports
98-
/// (which depends on whether the import is in an installed header file).
99-
/// If `files` is nil, the current toolchain does support the feature to trace imports.
100-
public func computeMissingDependencies(files: [Path]?) -> [(ModuleDependency, importLocations: [Diagnostic.Location])]? {
101-
guard validate != .no else { return [] }
102-
guard let files else {
103-
return nil
104-
}
105-
106-
// The following is a provisional/incomplete mechanism for resolving a module dependency from a file path.
107-
// For now, just grab the framework name and assume there is a module with the same name.
108-
func findFrameworkName(_ file: Path) -> String? {
109-
if file.fileExtension == "framework" {
110-
return file.basenameWithoutSuffix
111-
}
112-
return file.dirname.isEmpty || file.dirname.isRoot ? nil : findFrameworkName(file.dirname)
113-
}
114-
115-
let moduleDependencyNames = moduleDependencies.map { $0.name }
116-
let fileNames = files.compactMap { findFrameworkName($0) }
117-
let missingDeps = Set(fileNames.filter {
118-
return !moduleDependencyNames.contains($0)
119-
}.map {
120-
ModuleDependency(name: $0, accessLevel: .Private)
121-
})
122-
123-
return missingDeps.map { ($0, []) }
124-
}
125-
126-
/// Compute missing module dependencies from Swift imports.
95+
/// Compute missing module dependencies.
12796
///
12897
/// If `imports` is nil, the current toolchain does not support the features to gather imports.
129-
public func computeMissingDependencies(imports: [(ModuleDependency, importLocations: [Diagnostic.Location])]?) -> [(ModuleDependency, importLocations: [Diagnostic.Location])]? {
98+
public func computeMissingDependencies(
99+
imports: [(ModuleDependency, importLocations: [Diagnostic.Location])]?,
100+
fromSwift: Bool
101+
) -> [(ModuleDependency, importLocations: [Diagnostic.Location])]? {
130102
guard validate != .no else { return [] }
131103
guard let imports else {
132104
return nil
133105
}
134106

135107
return imports.filter {
136108
// 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
137-
if $0.importLocations.isEmpty { return false }
109+
if fromSwift && $0.importLocations.isEmpty { return false }
138110

139111
// 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
140112
if moduleDependencies.contains($0.0) { return false }
@@ -235,6 +207,166 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable {
235207
}
236208
}
237209

210+
public struct HeaderDependency: Hashable, Sendable, SerializableCodable {
211+
public let name: String
212+
public let accessLevel: AccessLevel
213+
214+
public enum AccessLevel: String, Hashable, Sendable, CaseIterable, Codable, Serializable {
215+
case Private = "private"
216+
case Public = "public"
217+
218+
public init(_ string: String) throws {
219+
guard let accessLevel = AccessLevel(rawValue: string) else {
220+
throw StubError.error("unexpected access modifier '\(string)', expected one of: \(AccessLevel.allCases.map { $0.rawValue }.joined(separator: ", "))")
221+
}
222+
223+
self = accessLevel
224+
}
225+
}
226+
227+
public init(name: String, accessLevel: AccessLevel) {
228+
self.name = name
229+
self.accessLevel = accessLevel
230+
}
231+
232+
public init(entry: String) throws {
233+
var it = entry.split(separator: " ").makeIterator()
234+
switch (it.next(), it.next(), it.next()) {
235+
case (let .some(name), nil, nil):
236+
self.name = String(name)
237+
self.accessLevel = .Private
238+
239+
case (let .some(accessLevel), let .some(name), nil):
240+
self.name = String(name)
241+
self.accessLevel = try AccessLevel(String(accessLevel))
242+
243+
default:
244+
throw StubError.error("expected 1 or 2 space-separated components in: \(entry)")
245+
}
246+
}
247+
248+
public var asBuildSettingEntry: String {
249+
"\(accessLevel == .Private ? "" : "\(accessLevel.rawValue) ")\(name)"
250+
}
251+
252+
public var asBuildSettingEntryQuotedIfNeeded: String {
253+
let e = asBuildSettingEntry
254+
return e.contains(" ") ? "\"\(e)\"" : e
255+
}
256+
}
257+
258+
public struct HeaderDependenciesContext: Sendable, SerializableCodable {
259+
public var validate: BooleanWarningLevel
260+
var headerDependencies: [HeaderDependency]
261+
var fixItContext: FixItContext?
262+
263+
init(validate: BooleanWarningLevel, headerDependencies: [HeaderDependency], fixItContext: FixItContext? = nil) {
264+
self.validate = validate
265+
self.headerDependencies = headerDependencies
266+
self.fixItContext = fixItContext
267+
}
268+
269+
public init?(settings: Settings) {
270+
let validate = settings.globalScope.evaluate(BuiltinMacros.VALIDATE_HEADER_DEPENDENCIES)
271+
guard validate != .no else { return nil }
272+
let fixItContext = HeaderDependenciesContext.FixItContext(settings: settings)
273+
self.init(validate: validate, headerDependencies: settings.headerDependencies, fixItContext: fixItContext)
274+
}
275+
276+
/// Make diagnostics for missing header dependencies.
277+
///
278+
/// The compiler tracing information does not provide the include locations or whether they are public imports
279+
/// (which depends on whether the import is in an installed header file).
280+
/// If `includes` is nil, the current toolchain does support the feature to trace imports.
281+
public func makeDiagnostics(includes: [Path]?) -> [Diagnostic] {
282+
guard validate != .no else { return [] }
283+
guard let includes else {
284+
return [Diagnostic(
285+
behavior: .warning,
286+
location: .unknown,
287+
data: DiagnosticData("The current toolchain does not support \(BuiltinMacros.VALIDATE_HEADER_DEPENDENCIES.name)"))]
288+
}
289+
290+
let headerDependencyNames = headerDependencies.map { $0.name }
291+
let missingDeps = includes.filter { file in
292+
return !headerDependencyNames.contains(where: { file.ends(with: $0) })
293+
}.map {
294+
// TODO: What if the basename doesn't uniquely identify the header?
295+
HeaderDependency(name: $0.basename, accessLevel: .Private)
296+
}
297+
298+
guard !missingDeps.isEmpty else { return [] }
299+
300+
let behavior: Diagnostic.Behavior = validate == .yesError ? .error : .warning
301+
302+
let fixIt = fixItContext?.makeFixIt(newHeaders: missingDeps)
303+
let fixIts = fixIt.map { [$0] } ?? []
304+
305+
let message = "Missing entries in \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(missingDeps.map { $0.asBuildSettingEntryQuotedIfNeeded }.sorted().joined(separator: " "))"
306+
307+
let location: Diagnostic.Location = fixIt.map {
308+
Diagnostic.Location.path($0.sourceRange.path, line: $0.sourceRange.endLine, column: $0.sourceRange.endColumn)
309+
} ?? Diagnostic.Location.buildSetting(BuiltinMacros.HEADER_DEPENDENCIES)
310+
311+
return [Diagnostic(
312+
behavior: behavior,
313+
location: location,
314+
data: DiagnosticData(message),
315+
fixIts: fixIts)]
316+
}
317+
318+
struct FixItContext: Sendable, SerializableCodable {
319+
var sourceRange: Diagnostic.SourceRange
320+
var modificationStyle: ModificationStyle
321+
322+
init(sourceRange: Diagnostic.SourceRange, modificationStyle: ModificationStyle) {
323+
self.sourceRange = sourceRange
324+
self.modificationStyle = modificationStyle
325+
}
326+
327+
init?(settings: Settings) {
328+
guard let target = settings.target else { return nil }
329+
let thisTargetCondition = MacroCondition(parameter: BuiltinMacros.targetNameCondition, valuePattern: target.name)
330+
331+
if let assignment = (settings.globalScope.table.lookupMacro(BuiltinMacros.HEADER_DEPENDENCIES)?.sequence.first {
332+
$0.location != nil && ($0.conditions?.conditions == [thisTargetCondition] || ($0.conditions?.conditions.isEmpty ?? true))
333+
}),
334+
let location = assignment.location
335+
{
336+
self.init(sourceRange: .init(path: location.path, startLine: location.endLine, startColumn: location.endColumn, endLine: location.endLine, endColumn: location.endColumn), modificationStyle: .appendToExistingAssignment)
337+
}
338+
else if let path = settings.constructionComponents.targetXcconfigPath {
339+
self.init(sourceRange: .init(path: path, startLine: .max, startColumn: .max, endLine: .max, endColumn: .max), modificationStyle: .insertNewAssignment(targetNameCondition: nil))
340+
}
341+
else if let path = settings.constructionComponents.projectXcconfigPath {
342+
self.init(sourceRange: .init(path: path, startLine: .max, startColumn: .max, endLine: .max, endColumn: .max), modificationStyle: .insertNewAssignment(targetNameCondition: target.name))
343+
}
344+
else {
345+
return nil
346+
}
347+
}
348+
349+
enum ModificationStyle: Sendable, SerializableCodable, Hashable {
350+
case appendToExistingAssignment
351+
case insertNewAssignment(targetNameCondition: String?)
352+
}
353+
354+
func makeFixIt(newHeaders: [HeaderDependency]) -> Diagnostic.FixIt {
355+
let stringValue = newHeaders.map { $0.asBuildSettingEntryQuotedIfNeeded }.sorted().joined(separator: " ")
356+
let newText: String
357+
switch modificationStyle {
358+
case .appendToExistingAssignment:
359+
newText = " \(stringValue)"
360+
case .insertNewAssignment(let targetNameCondition):
361+
let targetCondition = targetNameCondition.map { "[target=\($0)]" } ?? ""
362+
newText = "\n\(BuiltinMacros.HEADER_DEPENDENCIES.name)\(targetCondition) = $(inherited) \(stringValue)\n"
363+
}
364+
365+
return Diagnostic.FixIt(sourceRange: sourceRange, newText: newText)
366+
}
367+
}
368+
}
369+
238370
public struct DependencyValidationInfo: Hashable, Sendable, Codable {
239371
public struct Import: Hashable, Sendable, Codable {
240372
public let dependency: ModuleDependency
@@ -247,7 +379,7 @@ public struct DependencyValidationInfo: Hashable, Sendable, Codable {
247379
}
248380

249381
public enum Payload: Hashable, Sendable, Codable {
250-
case clangDependencies(files: [String])
382+
case clangDependencies(imports: [Import], includes: [Path])
251383
case swiftDependencies(imports: [Import])
252384
case unsupported
253385
}

Sources/SWBCore/PlannedTaskAction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ public protocol TaskActionCreationDelegate
337337
func createValidateProductTaskAction() -> any PlannedTaskAction
338338
func createConstructStubExecutorInputFileListTaskAction() -> any PlannedTaskAction
339339
func createClangCompileTaskAction() -> any PlannedTaskAction
340+
func createClangNonModularCompileTaskAction() -> any PlannedTaskAction
340341
func createClangScanTaskAction() -> any PlannedTaskAction
341342
func createSwiftDriverTaskAction() -> any PlannedTaskAction
342343
func createSwiftCompilationRequirementTaskAction() -> any PlannedTaskAction

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ public final class BuiltinMacros {
732732
public static let GLOBAL_CFLAGS = BuiltinMacros.declareStringListMacro("GLOBAL_CFLAGS")
733733
public static let HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_TARGETS_NOT_BEING_BUILT = BuiltinMacros.declareBooleanMacro("HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_TARGETS_NOT_BEING_BUILT")
734734
public static let HEADERMAP_USES_VFS = BuiltinMacros.declareBooleanMacro("HEADERMAP_USES_VFS")
735+
public static let HEADER_DEPENDENCIES = BuiltinMacros.declareStringListMacro("HEADER_DEPENDENCIES")
735736
public static let HEADER_OUTPUT_DIR = BuiltinMacros.declareStringMacro("HEADER_OUTPUT_DIR")
736737
public static let HEADER_SEARCH_PATHS = BuiltinMacros.declarePathListMacro("HEADER_SEARCH_PATHS")
737738
public static let IBC_REGIONS_AND_STRINGS_FILES = BuiltinMacros.declareStringListMacro("IBC_REGIONS_AND_STRINGS_FILES")
@@ -1145,6 +1146,7 @@ public final class BuiltinMacros {
11451146
public static let VALIDATE_PRODUCT = BuiltinMacros.declareBooleanMacro("VALIDATE_PRODUCT")
11461147
public static let VALIDATE_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_DEPENDENCIES") as EnumMacroDeclaration<BooleanWarningLevel>
11471148
public static let VALIDATE_DEVELOPMENT_ASSET_PATHS = BuiltinMacros.declareEnumMacro("VALIDATE_DEVELOPMENT_ASSET_PATHS") as EnumMacroDeclaration<BooleanWarningLevel>
1149+
public static let VALIDATE_HEADER_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_HEADER_DEPENDENCIES") as EnumMacroDeclaration<BooleanWarningLevel>
11481150
public static let VALIDATE_MODULE_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_MODULE_DEPENDENCIES") as EnumMacroDeclaration<BooleanWarningLevel>
11491151
public static let VECTOR_SUFFIX = BuiltinMacros.declareStringMacro("VECTOR_SUFFIX")
11501152
public static let VERBOSE_PBXCP = BuiltinMacros.declareBooleanMacro("VERBOSE_PBXCP")
@@ -1798,6 +1800,7 @@ public final class BuiltinMacros {
17981800
GROUP,
17991801
HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_TARGETS_NOT_BEING_BUILT,
18001802
HEADERMAP_USES_VFS,
1803+
HEADER_DEPENDENCIES,
18011804
HEADER_SEARCH_PATHS,
18021805
HEADER_OUTPUT_DIR,
18031806
HOME,
@@ -2373,6 +2376,7 @@ public final class BuiltinMacros {
23732376
VALIDATE_PRODUCT,
23742377
VALIDATE_DEPENDENCIES,
23752378
VALIDATE_DEVELOPMENT_ASSET_PATHS,
2379+
VALIDATE_HEADER_DEPENDENCIES,
23762380
VALIDATE_MODULE_DEPENDENCIES,
23772381
VALID_ARCHS,
23782382
VECTOR_SUFFIX,

Sources/SWBCore/Settings/Settings.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,7 @@ public final class Settings: PlatformBuildContext, Sendable {
756756
}
757757

758758
public let moduleDependencies: [ModuleDependency]
759+
public let headerDependencies: [HeaderDependency]
759760

760761
public static func supportsMacCatalyst(scope: MacroEvaluationScope, core: Core) -> Bool {
761762
@preconcurrency @PluginExtensionSystemActor func sdkVariantInfoExtensions() -> [any SDKVariantInfoExtensionPoint.ExtensionProtocol] {
@@ -904,6 +905,7 @@ public final class Settings: PlatformBuildContext, Sendable {
904905

905906
self.supportedBuildVersionPlatforms = effectiveSupportedPlatforms(sdkRegistry: sdkRegistry)
906907
self.moduleDependencies = builder.moduleDependencies
908+
self.headerDependencies = builder.headerDependencies
907909

908910
self.constructionComponents = builder.constructionComponents
909911
}
@@ -1290,6 +1292,7 @@ private class SettingsBuilder {
12901292
var signingSettings: Settings.SigningSettings? = nil
12911293

12921294
var moduleDependencies: [ModuleDependency] = []
1295+
var headerDependencies: [HeaderDependency] = []
12931296

12941297

12951298
// Mutable state of the builder as we're building up the settings table.
@@ -1631,6 +1634,12 @@ private class SettingsBuilder {
16311634
catch {
16321635
errors.append("Failed to parse \(BuiltinMacros.MODULE_DEPENDENCIES.name): \(error)")
16331636
}
1637+
do {
1638+
self.headerDependencies = try createScope(sdkToUse: boundProperties.sdk).evaluate(BuiltinMacros.HEADER_DEPENDENCIES).map { try HeaderDependency(entry: $0) }
1639+
}
1640+
catch {
1641+
errors.append("Failed to parse \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(error)")
1642+
}
16341643

16351644
// At this point settings construction is finished.
16361645

Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ private final class EnumBuildOptionType : BuildOptionType {
101101
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<PackageResourceTargetKind>
102102
case "VALIDATE_DEPENDENCIES",
103103
"VALIDATE_MODULE_DEPENDENCIES",
104+
"VALIDATE_HEADER_DEPENDENCIES",
104105
"VALIDATE_DEVELOPMENT_ASSET_PATHS":
105106
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<BooleanWarningLevel>
106107
case "STRIP_STYLE":

0 commit comments

Comments
 (0)