Skip to content
Draft
Show file tree
Hide file tree
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
35 changes: 24 additions & 11 deletions Sources/SWBCore/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -302,17 +302,18 @@ public struct HeaderDependenciesContext: Sendable, SerializableCodable {
data: DiagnosticData("The current toolchain does not support \(BuiltinMacros.VALIDATE_HEADER_DEPENDENCIES.name)"))
}

/// Compute missing module dependencies.
public func computeMissingAndUnusedDependencies(includes: [Path]) -> (missing: [HeaderDependency], unused: [HeaderDependency]) {
/// Compute missing header dependencies.
public func computeMissingAndUnusedDependencies(includes: [(Path, includeLocations: [Diagnostic.Location])]) -> (missing: [(HeaderDependency, includeLocations: [Diagnostic.Location])], unused: [HeaderDependency]) {
let declaredNames = Set(declared.map { $0.name })

let missing: [HeaderDependency]
let missing: [(HeaderDependency, includeLocations: [Diagnostic.Location])]
if validate != .no {
missing = includes.filter { file in
return !declaredNames.contains(where: { file.ends(with: $0) })
}.map {
missing = includes.filter { include in
return !declaredNames.contains(where: { include.0.ends(with: $0) })
}.map { include in
// TODO: What if the basename doesn't uniquely identify the header?
HeaderDependency(name: $0.basename, accessLevel: .Private, optional: false)
let dep = HeaderDependency(name: include.0.basename, accessLevel: .Private, optional: false)
return (dep, includeLocations: include.includeLocations)
}
}
else {
Expand All @@ -334,15 +335,26 @@ public struct HeaderDependenciesContext: Sendable, SerializableCodable {
///
/// 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).
public func makeDiagnostics(missingDependencies: [HeaderDependency], unusedDependencies: [HeaderDependency]) -> [Diagnostic] {
public func makeDiagnostics(missingDependencies: [(HeaderDependency, includeLocations: [Diagnostic.Location])], unusedDependencies: [HeaderDependency]) -> [Diagnostic] {
let missingDiagnostics: [Diagnostic]
if !missingDependencies.isEmpty {
let behavior: Diagnostic.Behavior = validate == .yesError ? .error : .warning

let fixIt = fixItContext?.makeFixIt(newHeaders: missingDependencies)
let fixIt = fixItContext?.makeFixIt(newHeaders: missingDependencies.map { $0.0 })
let fixIts = fixIt.map { [$0] } ?? []

let message = "Missing entries in \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(missingDependencies.map { $0.asBuildSettingEntryQuotedIfNeeded }.sorted().joined(separator: " "))"
let importDiags: [Diagnostic] = missingDependencies
.flatMap { dep in
dep.1.map {
return Diagnostic(
behavior: behavior,
location: $0,
data: DiagnosticData("Missing entry in \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(dep.0.asBuildSettingEntryQuotedIfNeeded)"),
fixIts: fixIts)
}
}

let message = "Missing entries in \(BuiltinMacros.HEADER_DEPENDENCIES.name): \(missingDependencies.map { $0.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)
Expand All @@ -352,7 +364,8 @@ public struct HeaderDependenciesContext: Sendable, SerializableCodable {
behavior: behavior,
location: location,
data: DiagnosticData(message),
fixIts: fixIts)]
fixIts: fixIts,
childDiagnostics: importDiags)]
}
else {
missingDiagnostics = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public final class ValidateDependenciesTaskAction: TaskAction {
if unsupported {
diagnostics.append(contentsOf: [headerContext.makeUnsupportedToolchainDiagnostic()])
} else {
let (missingDeps, unusedDeps) = headerContext.computeMissingAndUnusedDependencies(includes: allClangIncludes.map { $0.path })
let (missingDeps, unusedDeps) = headerContext.computeMissingAndUnusedDependencies(includes: allClangIncludes.map { ($0.path, $0.includeLocations) })
outputDelegate.incrementCounter(.headerDependenciesMissing, by: missingDeps.count)
outputDelegate.incrementCounter(.headerDependenciesUnused, by: unusedDeps.count)

Expand Down
42 changes: 38 additions & 4 deletions Tests/SWBBuildSystemTests/DependencyValidationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -660,17 +660,18 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {
groupTree: TestGroup(
"Sources", path: "Sources",
children: [
TestFile("CoreFoo.c")
TestFile("CoreFoo.c"),
TestFile("Project.xcconfig"),
]),
buildConfigurations: [
TestBuildConfiguration(
"Debug",
baseConfig: "Project.xcconfig",
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)",
Expand All @@ -691,9 +692,10 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {

let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false)
let SRCROOT = testWorkspace.sourceRoot.join("aProject")
let srcFile = SRCROOT.join("Sources/CoreFoo.c")

// Write the source files.
try await tester.fs.writeFileContents(SRCROOT.join("Sources/CoreFoo.c")) { contents in
try await tester.fs.writeFileContents(srcFile) { contents in
contents <<< """
#include <stdio.h>
#include <stdlib.h>
Expand All @@ -702,9 +704,41 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {
"""
}

let projectXCConfigPath = SRCROOT.join("Sources/Project.xcconfig")
try await tester.fs.writeFileContents(projectXCConfigPath) { stream in
stream <<<
"""
HEADER_DEPENDENCIES = stdio.h
"""
}

// 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"))
results.checkError(.contains("Missing entries in HEADER_DEPENDENCIES: stdlib.h")) {
diag in
let expectedDiag = Diagnostic(
behavior: .error,
location: Diagnostic.Location.path(projectXCConfigPath, line: 1, column: 30),
data: DiagnosticData("Missing entries in HEADER_DEPENDENCIES: stdlib.h"),
fixIts: [
Diagnostic.FixIt(
sourceRange: Diagnostic.SourceRange(path: projectXCConfigPath, startLine: 1, startColumn: 30, endLine: 1, endColumn: 30),
newText: " \\\n stdlib.h"),
],
childDiagnostics: [
Diagnostic(
behavior: .error,
location: Diagnostic.Location.path(srcFile, line: 2, column: 8),
data: DiagnosticData("Missing entry in HEADER_DEPENDENCIES: stdlib.h"),
fixIts: [Diagnostic.FixIt(
sourceRange: Diagnostic.SourceRange(path: projectXCConfigPath, startLine: 1, startColumn: 30, endLine: 1, endColumn: 30),
newText: " \\\n stdlib.h")],
),
]
)
#expect(expectedDiag == diag)
return true
}
}

// Declaring dependencies resolves the problem
Expand Down
Loading