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
68 changes: 46 additions & 22 deletions Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -910,36 +910,51 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible

func cachingBuildEnabled(
_ cbc: CommandBuildContext,
language: GCCCompatibleLanguageDialect,
clangInfo: DiscoveredClangToolSpecInfo?
) -> Bool {
// Disabling compilation caching for index build, for now.
guard !cbc.scope.evaluate(BuiltinMacros.INDEX_ENABLE_BUILD_ARENA) else {
return false
}

let enabledCppModules: Bool = {
guard language.isPlusPlus else {
return false
}
// When response file is used the flag is in the response file, not in the commandLine array,
// so check the build setting.
if cbc.scope.evaluate(BuiltinMacros.OTHER_CPLUSPLUSFLAGS).contains("-fcxx-modules") {
return true
}
return false
}()

guard !enabledCppModules else {
// If this project is on the EBM block list, the project cannot use caching.
if clangInfo?.isClangExplicitModulesBlocked(cbc.producer, cbc.scope) == true {
return false
}

let buildSettingEnabled = cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_COMPILE_CACHE)

// If this project is on the blocklist, override the blocklist default enable for it.
// If this project is on the caching blocklist, override the blocklist default enable for it.
return clangInfo?.isCachingBlocked(cbc.producer, cbc.scope) == true ? false : buildSettingEnabled
}

private func lastCxxModulesFlag(_ flags: [String]) -> Bool? {
guard let flag = flags.last(where: { $0 == "-fcxx-modules" || $0 == "-fno-cxx-modules" }) else {
return nil
}
return flag == "-fcxx-modules"
}

/// Whether clang builds C++ header modules for this TU.
/// Build settings are evaluated rather than scanning `commandLine`: with `CLANG_USE_RESPONSE_FILE`
/// enabled — the default — the flags are written to a response file and are absent from that array.
/// `OTHER_CFLAGS` is not checked because for C++/ObjC++ inputs it is replaced by
/// `OTHER_CPLUSPLUSFLAGS`, which defaults to `$(OTHER_CFLAGS)` anyway.
func cxxHeaderModulesEnabled(_ cbc: CommandBuildContext, _ input: FileToBuild) -> Bool {
guard cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_MODULES) else {
return false
}

if let perFileArgs = input.additionalArgs,
let decision = lastCxxModulesFlag(cbc.scope.evaluate(perFileArgs)) {
return decision
}
if let decision = lastCxxModulesFlag(cbc.scope.evaluate(BuiltinMacros.OTHER_CPLUSPLUSFLAGS)) {
return decision
}
return !cbc.scope.evaluate(BuiltinMacros.CLANG_DISABLE_CXX_MODULES)
}

private func createExplicitModulesActionAndPayload(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, _ compilerLauncher: Path?, _ input: FileToBuild, _ language: GCCCompatibleLanguageDialect?, commandLine: [String], scanningOutputPath: Path, isForPCHTask: Bool, clangInfo: DiscoveredClangToolSpecInfo?) -> (action: (any PlannedTaskAction)?, usesExecutionInputs: Bool, payload: ClangExplicitModulesPayload?, signatureData: String?) {
guard let language else {
// Unknown language.
Expand All @@ -951,21 +966,30 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
return (nil, false, nil, nil)
}

let cachedBuild = cachingBuildEnabled(cbc, language: language, clangInfo: clangInfo)
// The dependency scanner should only be used for C-family languages.
let isCFamily = GCCCompatibleLanguageDialect.allCLanguages.contains(language)

// Whether clang modules are enabled for this translation unit. C++/ObjC++ additionally
// require the project to opt in, since `-fmodules` alone does not enable C++ header modules.
let modulesEnabled = isCFamily
&& cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_MODULES)
&& (!language.isPlusPlus || cxxHeaderModulesEnabled(cbc, input))

// Whether explicitly built modules are in effect. A project on the blocklist is known to fail
// under them. For such a project, EBM is off and the build falls back to implicit modules.
let explicitModules = cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_MODULES)
&& (cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_EXPLICIT_MODULES) || cbc.scope.evaluate(BuiltinMacros._EXPERIMENTAL_CLANG_EXPLICIT_MODULES))
&& clangInfo?.isClangExplicitModulesBlocked(cbc.producer, cbc.scope) != true

let explicitModulesLanguages: Set<GCCCompatibleLanguageDialect> = [
.c, .objectiveC
]
let supportedLanguages = cachedBuild ? GCCCompatibleLanguageDialect.allCLanguages : explicitModulesLanguages
// Whether compile caching is on. Caching requires EBMs.
let cachedBuild = cachingBuildEnabled(cbc, clangInfo: clangInfo)

// Only enable dep scanner if requested by the user and if the language supports it.
EXPLICIT_MODULES: if cachedBuild || explicitModules, supportedLanguages.contains(language) {
EXPLICIT_MODULES: if isCFamily, cachedBuild || (explicitModules && modulesEnabled) {

let usesCompilerLauncher = compilerLauncher != nil

if !explicitModules && explicitModulesLanguages.contains(language) && cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_MODULES) {
if cachedBuild && modulesEnabled && !explicitModules {
delegate.warning("Compile caching is not supported with implicit modules; enable CLANG_ENABLE_EXPLICIT_MODULES")
break EXPLICIT_MODULES
}
Expand Down
17 changes: 16 additions & 1 deletion Sources/SWBCore/ToolInfo/ClangToolInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ public struct ClangBlocklists : Sendable {

var builtinModuleVerify: BuiltinModuleVerifierInfo? = nil

public struct ClangExplicitModulesInfo : ProjectFailuresBlockList, Codable, Sendable {
/// A blocklist of project names that do not support clang explicitly built modules.
/// These project also lose caching.
let KnownFailures: [String]
enum CodingKeys: String, CodingKey {
case KnownFailures
}
}

var clangExplicitModules: ClangExplicitModulesInfo? = nil

/// Helper method for determining if a given functionality is blocklisted for the active scope.
func isBlocked<BlockListT: ProjectFailuresBlockList>(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, info: BlockListT?) -> Bool {
guard let blocklistInfo = info else { return false }
Expand Down Expand Up @@ -101,6 +112,10 @@ public struct DiscoveredClangToolSpecInfo: DiscoveredCommandLineToolSpecInfo {
public func isBuiltinModuleVerifyBlocked(_ producer: any CommandProducer, _ scope: MacroEvaluationScope) -> Bool {
return blocklists.isBlocked(producer, scope, info: blocklists.builtinModuleVerify)
}

public func isClangExplicitModulesBlocked(_ producer: any CommandProducer, _ scope: MacroEvaluationScope) -> Bool {
return blocklists.isBlocked(producer, scope, info: blocklists.clangExplicitModules)
}
}

private let clangVersionRe = RegEx(patternLiteral: #""(?<llvm>[0-9]+(?:\.[0-9]+){0,}) \(clang-(?<clang>[0-9]+(?:\.[0-9]+){0,})\)(?: ((\[.+\])|(\(.+\)))+)?""#)
Expand Down Expand Up @@ -199,7 +214,7 @@ public func discoveredClangToolInfo(
var blocklists = ClangBlocklists()
blocklists.caching = getBlocklist(type: ClangBlocklists.CachingBlocklistInfo.self, toolchainFilename: "clang-caching.json", delegate: delegate)
blocklists.builtinModuleVerify = getBlocklist(type: ClangBlocklists.BuiltinModuleVerifierInfo.self, toolchainFilename: "clang-builtin-module-verify.json", delegate: delegate)

blocklists.clangExplicitModules = getBlocklist(type: ClangBlocklists.ClangExplicitModulesInfo.self, toolchainFilename: "clang-explicit-modules.json", delegate: delegate)

return DiscoveredClangToolSpecInfo(
toolPath: toolPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests {
"PRODUCT_NAME": "$(TARGET_NAME)",
"CLANG_ENABLE_COMPILE_CACHE": "YES",
"COMPILATION_CACHE_CAS_PATH": tmpDirPath.join("CompilationCache").str,
"COMPILATION_CACHE_ENABLE_DIAGNOSTIC_REMARKS": "YES",
])],
targets: [
TestAggregateTarget(
Expand Down Expand Up @@ -157,7 +158,9 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests {
try await tester.checkBuild(runDestination: .macOS, persistent: true) { results in
_ = results.checkTask(.matchTargetName("Textual"), .matchRuleType("ScanDependencies")) { $0 }
_ = results.checkTask(.matchTargetName("TextualWithFMod"), .matchRuleType("ScanDependencies")) { $0 }
results.checkNoTask(.matchTargetName("CppModules"), .matchRuleType("ScanDependencies"))
_ = results.checkTask(.matchTargetName("CppModules"), .matchRuleType("ScanDependencies")) { $0 }
let cppCompile: Task = try results.checkTask(.matchTargetName("CppModules"), .matchRuleType("CompileC")) { $0 }
results.checkCompileCacheMiss(cppCompile)
}
}
}
Expand Down
Loading
Loading