Skip to content

Assert that only declared dependencies are used when compiling/linking #466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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
90 changes: 90 additions & 0 deletions Sources/SWBCore/Dependencies.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

public import SWBUtil
public import SWBMacro

import Foundation

// Global/target dependency settings
public struct DependencySettings: Serializable, Sendable, Encodable {
public let dependencies: [String] // lexicographically ordered and uniqued
public let verification: Bool

public init(
dependencies: any Sequence<String>,
verification: Bool
) {
self.dependencies = Array(OrderedSet(dependencies).sorted())
self.verification = verification
}

public func serialize<T: Serializer>(to serializer: T) {
serializer.serializeAggregate(2) {
serializer.serialize(dependencies)
serializer.serialize(verification)
}
}

public init(from deserializer: any Deserializer) throws {
try deserializer.beginAggregate(2)
self.dependencies = try deserializer.deserialize()
self.verification = try deserializer.deserialize()
}
}

extension DependencySettings {
public init(_ scope: MacroEvaluationScope) {
let dependencies = scope.evaluate(BuiltinMacros.DEPENDENCIES)
self.init(
dependencies: dependencies,
verification: scope.evaluate(BuiltinMacros.DEPENDENCIES_VERIFICATION)
.isEnabled(onNotSet: !dependencies.isEmpty),
)
}
}

// Task-specific settings
public struct TaskDependencySettings: Serializable, Sendable, Encodable {

public let traceFile: Path
public let dependencySettings: DependencySettings

init(traceFile: Path, dependencySettings: DependencySettings) {
assert(!traceFile.isEmpty, "traceFile should never be empty")
self.traceFile = traceFile
self.dependencySettings = dependencySettings
}

public func serialize<T: Serializer>(to serializer: T) {
serializer.serializeAggregate(2) {
serializer.serialize(traceFile)
serializer.serialize(dependencySettings)
}
}

public init(from deserializer: any Deserializer) throws {
try deserializer.beginAggregate(2)
self.traceFile = try deserializer.deserialize()
self.dependencySettings = try deserializer.deserialize()
}

func signatureData() -> String {
return "verify:\(dependencySettings.verification),deps:\(dependencySettings.dependencies.joined(separator: ":"))"
}

}

// Protocol for task payloads
public protocol TaskDependencySettingsPayload {
var taskDependencySettings: TaskDependencySettings? { get }
}
2 changes: 2 additions & 0 deletions Sources/SWBCore/PlannedTaskAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,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
Expand All @@ -339,6 +340,7 @@ public protocol TaskActionCreationDelegate
func createSignatureCollectionTaskAction() -> any PlannedTaskAction
func createClangModuleVerifierInputGeneratorTaskAction() -> any PlannedTaskAction
func createProcessSDKImportsTaskAction() -> any PlannedTaskAction
func createLdTaskAction() -> any PlannedTaskAction
}

extension TaskActionCreationDelegate {
Expand Down
25 changes: 25 additions & 0 deletions Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@ public final class BuiltinMacros {
public static let DEFAULT_COMPILER = BuiltinMacros.declareStringMacro("DEFAULT_COMPILER")
public static let DEFAULT_KEXT_INSTALL_PATH = BuiltinMacros.declareStringMacro("DEFAULT_KEXT_INSTALL_PATH")
public static let DEFINES_MODULE = BuiltinMacros.declareBooleanMacro("DEFINES_MODULE")
public static let DEPENDENCIES = BuiltinMacros.declareStringListMacro("DEPENDENCIES")
public static let DEPENDENCIES_VERIFICATION = BuiltinMacros.declareEnumMacro("DEPENDENCIES_VERIFICATION") as EnumMacroDeclaration<DependenciesVerificationSetting>
public static let DEPENDENCY_SCOPE_INCLUDES_DIRECT_DEPENDENCIES = BuiltinMacros.declareBooleanMacro("DEPENDENCY_SCOPE_INCLUDES_DIRECT_DEPENDENCIES")
public static let DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = BuiltinMacros.declareBooleanMacro("DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER")
public static let __DIAGNOSE_DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER_ERROR = BuiltinMacros.declareBooleanMacro("__DIAGNOSE_DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER_ERROR")
Expand Down Expand Up @@ -810,6 +812,7 @@ public final class BuiltinMacros {
public static let LD_RUNPATH_SEARCH_PATHS = BuiltinMacros.declareStringListMacro("LD_RUNPATH_SEARCH_PATHS")
public static let LD_SDK_IMPORTS_FILE = BuiltinMacros.declarePathMacro("LD_SDK_IMPORTS_FILE")
public static let LD_WARN_UNUSED_DYLIBS = BuiltinMacros.declareBooleanMacro("LD_WARN_UNUSED_DYLIBS")
public static let LD_TRACE_FILE = BuiltinMacros.declarePathMacro("LD_TRACE_FILE")
public static let _LD_MULTIARCH = BuiltinMacros.declareBooleanMacro("_LD_MULTIARCH")
public static let _LD_MULTIARCH_PREFIX_MAP = BuiltinMacros.declareStringListMacro("_LD_MULTIARCH_PREFIX_MAP")
public static let LEX = BuiltinMacros.declarePathMacro("LEX")
Expand Down Expand Up @@ -1568,6 +1571,8 @@ public final class BuiltinMacros {
DEFAULT_COMPILER,
DEFAULT_KEXT_INSTALL_PATH,
DEFINES_MODULE,
DEPENDENCIES,
DEPENDENCIES_VERIFICATION,
DEPENDENCY_SCOPE_INCLUDES_DIRECT_DEPENDENCIES,
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER,
__DIAGNOSE_DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER_ERROR,
Expand Down Expand Up @@ -1872,6 +1877,7 @@ public final class BuiltinMacros {
LD_RUNPATH_SEARCH_PATHS,
LD_SDK_IMPORTS_FILE,
LD_WARN_UNUSED_DYLIBS,
LD_TRACE_FILE,
_LD_MULTIARCH,
_LD_MULTIARCH_PREFIX_MAP,
LEGACY_DEVELOPER_DIR,
Expand Down Expand Up @@ -2826,3 +2832,22 @@ extension BuildVersion.Platform {
return dtsn
}
}

public enum DependenciesVerificationSetting: String, Equatable, Hashable, EnumerationMacroType {
public static let defaultValue = DependenciesVerificationSetting.notset

case notset = "NOT_SET"
case enabled = "YES"
case disabled = "NO"

func isEnabled(onNotSet: Bool) -> Bool {
return switch self {
case .notset:
onNotSet
case .enabled:
true
case .disabled:
false
}
}
}
38 changes: 32 additions & 6 deletions Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ struct ClangModuleVerifierPayload: ClangModuleVerifierPayloadType {
}
}

public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEditableTaskPayload, Encodable {
public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEditableTaskPayload, TaskDependencySettingsPayload, Encodable {
let dependencyInfoEditPayload: DependencyInfoEditPayload?

/// The path to the serialized diagnostic output. Every clang task must provide this path.
Expand All @@ -432,7 +432,9 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd

public let fileNameMapPath: Path?

fileprivate init(serializedDiagnosticsPath: Path?, indexingPayload: ClangIndexingPayload?, explicitModulesPayload: ClangExplicitModulesPayload? = nil, outputObjectFilePath: Path? = nil, fileNameMapPath: Path? = nil, developerPathString: String? = nil) {
public let taskDependencySettings: TaskDependencySettings?

fileprivate init(serializedDiagnosticsPath: Path?, indexingPayload: ClangIndexingPayload?, explicitModulesPayload: ClangExplicitModulesPayload? = nil, outputObjectFilePath: Path? = nil, fileNameMapPath: Path? = nil, developerPathString: String? = nil, taskDependencySettings: TaskDependencySettings? = nil) {
if let developerPathString, explicitModulesPayload == nil {
self.dependencyInfoEditPayload = .init(removablePaths: [], removableBasenames: [], developerPath: Path(developerPathString))
} else {
Expand All @@ -443,27 +445,30 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd
self.explicitModulesPayload = explicitModulesPayload
self.outputObjectFilePath = outputObjectFilePath
self.fileNameMapPath = fileNameMapPath
self.taskDependencySettings = taskDependencySettings
}

public func serialize<T: Serializer>(to serializer: T) {
serializer.serializeAggregate(6) {
serializer.serializeAggregate(7) {
serializer.serialize(serializedDiagnosticsPath)
serializer.serialize(indexingPayload)
serializer.serialize(explicitModulesPayload)
serializer.serialize(outputObjectFilePath)
serializer.serialize(fileNameMapPath)
serializer.serialize(dependencyInfoEditPayload)
serializer.serialize(taskDependencySettings)
}
}

public init(from deserializer: any Deserializer) throws {
try deserializer.beginAggregate(6)
try deserializer.beginAggregate(7)
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.taskDependencySettings = try deserializer.deserialize()
}
}

Expand Down Expand Up @@ -1158,6 +1163,24 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
dependencyData = nil
}

let taskDependencySettings = TaskDependencySettings(
traceFile: Path(outputNode.path.str + ".trace.json"),
dependencySettings: DependencySettings(cbc.scope)
)

if taskDependencySettings.dependencySettings.verification {
commandLine += [
"-Xclang",
"-header-include-file",
"-Xclang",
taskDependencySettings.traceFile.str,
"-Xclang",
"-header-include-filtering=only-direct-system",
"-Xclang",
"-header-include-format=json"
]
}

// Add the diagnostics serialization flag. We currently place the diagnostics file right next to the output object file.
let diagFilePath: Path?
if let serializedDiagnosticsOptions = self.serializedDiagnosticsOptions(scope: cbc.scope, outputPath: outputNode.path) {
Expand Down Expand Up @@ -1268,7 +1291,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
explicitModulesPayload: explicitModulesPayload,
outputObjectFilePath: shouldGenerateRemarks ? outputNode.path : nil,
fileNameMapPath: verifierPayload?.fileNameMapPath,
developerPathString: recordSystemHeaderDepsOutsideSysroot ? cbc.scope.evaluate(BuiltinMacros.DEVELOPER_DIR).str : nil
developerPathString: recordSystemHeaderDepsOutsideSysroot ? cbc.scope.evaluate(BuiltinMacros.DEVELOPER_DIR).str : nil,
taskDependencySettings: taskDependencySettings
)

var inputNodes: [any PlannedNode] = inputDeps.map { delegate.createNode($0) }
Expand Down Expand Up @@ -1318,8 +1342,10 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
extraInputs = []
}

additionalSignatureData += taskDependencySettings.signatureData()

// 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], 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], 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 {
Expand Down
Loading