From b860b49a99f7b2a91777bd7cfffb6d71f3a463a4 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Sun, 29 Jun 2025 13:42:32 -0700 Subject: [PATCH] Allow setting LINKER_DRIVER=auto to choose an appropriate value for the given target's sources --- Sources/SWBCore/PlannedTaskAction.swift | 9 +- Sources/SWBCore/Settings/BuiltinMacros.swift | 1 + .../SpecImplementations/ProductTypes.swift | 24 +++-- .../Tools/LinkerTools.swift | 102 +++++++++++++----- Tests/SWBCoreTests/CommandLineSpecTests.swift | 6 +- .../LinkerTaskConstructionTests.swift | 97 +++++++++++++++++ 6 files changed, 196 insertions(+), 43 deletions(-) create mode 100644 Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift diff --git a/Sources/SWBCore/PlannedTaskAction.swift b/Sources/SWBCore/PlannedTaskAction.swift index 999660e5..3518ddc3 100644 --- a/Sources/SWBCore/PlannedTaskAction.swift +++ b/Sources/SWBCore/PlannedTaskAction.swift @@ -264,7 +264,14 @@ public struct FileCopyTaskActionContext { extension FileCopyTaskActionContext { public init(_ cbc: CommandBuildContext) { let compilerPath = cbc.producer.clangSpec.resolveExecutablePath(cbc, forLanguageOfFileType: cbc.producer.lookupFileType(languageDialect: .c)) - let linkerPath = cbc.producer.ldLinkerSpec.resolveExecutablePath(cbc.producer, Path(cbc.producer.ldLinkerSpec.computeExecutablePath(cbc))) + let linkerPath = cbc.producer.ldLinkerSpec.resolveExecutablePath(cbc.producer, cbc.producer.ldLinkerSpec.computeLinkerPath(cbc, usedCXX: false, lookup: { macro in + switch macro { + case BuiltinMacros.LINKER_DRIVER: + return cbc.scope.namespace.parseString("clang") + default: + return nil + } + })) let lipoPath = cbc.producer.lipoSpec.resolveExecutablePath(cbc.producer, Path(cbc.producer.lipoSpec.computeExecutablePath(cbc))) // If we couldn't find clang, skip the special stub binary handling. We may be using an Open Source toolchain which only has Swift. Also skip it for installLoc builds. diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 618b3ca5..541cbcad 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -2701,6 +2701,7 @@ public enum LinkerDriverChoice: String, Equatable, Hashable, EnumerationMacroTyp case clang case swiftc + case auto } /// Enumeration macro type for the value of the `INFOPLIST_KEY_LSApplicationCategoryType` build setting. diff --git a/Sources/SWBCore/SpecImplementations/ProductTypes.swift b/Sources/SWBCore/SpecImplementations/ProductTypes.swift index 7f26983b..345c0d85 100644 --- a/Sources/SWBCore/SpecImplementations/ProductTypes.swift +++ b/Sources/SWBCore/SpecImplementations/ProductTypes.swift @@ -260,31 +260,35 @@ public class ProductTypeSpec : Spec, SpecType, @unchecked Sendable { } /// Computes and returns additional arguments to pass to the linker appropriate for the product type. Also returns a list of additional paths to treat as inputs to the link command, if appropriate. - func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) { + func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> (args: [String], inputs: [Path]) { return ([], []) } - fileprivate func computeDylibArgs(_ producer: any CommandProducer, _ scope: MacroEvaluationScope) -> [String] { + fileprivate func computeDylibArgs(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> [String] { var args = [String]() if producer.isApplePlatform { - let compatibilityVersion = scope.evaluate(BuiltinMacros.DYLIB_COMPATIBILITY_VERSION) + let compatibilityVersion = scope.evaluate(BuiltinMacros.DYLIB_COMPATIBILITY_VERSION, lookup: lookup) if !compatibilityVersion.isEmpty { - switch scope.evaluate(BuiltinMacros.LINKER_DRIVER) { + switch scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) { case .clang: args += ["-compatibility_version", compatibilityVersion] case .swiftc: args += ["-Xlinker", "-compatibility_version", "-Xlinker", compatibilityVersion] + case .auto: + preconditionFailure("Expected LINKER_DRIVER to be bound to a concrete value") } } - let currentVersion = scope.evaluate(BuiltinMacros.DYLIB_CURRENT_VERSION) + let currentVersion = scope.evaluate(BuiltinMacros.DYLIB_CURRENT_VERSION, lookup: lookup) if !currentVersion.isEmpty { - switch scope.evaluate(BuiltinMacros.LINKER_DRIVER) { + switch scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) { case .clang: args += ["-current_version", currentVersion] case .swiftc: args += ["-Xlinker", "-current_version", "-Xlinker", currentVersion] + case .auto: + preconditionFailure("Expected LINKER_DRIVER to be bound to a concrete value") } } } @@ -563,9 +567,9 @@ public class FrameworkProductTypeSpec : BundleProductTypeSpec, @unchecked Sendab ]) */ - override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) { + override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> (args: [String], inputs: [Path]) { if scope.evaluate(BuiltinMacros.MACH_O_TYPE) != "staticlib" { - return (computeDylibArgs(producer, scope), []) + return (computeDylibArgs(producer, scope, lookup: lookup), []) } return ([], []) } @@ -801,9 +805,9 @@ public final class DynamicLibraryProductTypeSpec : LibraryProductTypeSpec, @unch return true } - override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope) -> (args: [String], inputs: [Path]) { + override func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> (args: [String], inputs: [Path]) { if scope.evaluate(BuiltinMacros.MACH_O_TYPE) != "staticlib" { - return (computeDylibArgs(producer, scope), []) + return (computeDylibArgs(producer, scope, lookup: lookup), []) } return ([], []) } diff --git a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift index d00cb7da..9c36a2c1 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -233,16 +233,6 @@ public struct DiscoveredLdLinkerToolSpecInfo: DiscoveredCommandLineToolSpecInfo public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchecked Sendable { public static let identifier = "com.apple.pbx.linkers.ld" - public override func computeExecutablePath(_ cbc: CommandBuildContext) -> String { - // TODO: We should also provide an "auto" option which chooses based on the source files in the target - switch cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER) { - case .clang: - return cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang") - case .swiftc: - return cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "swiftc") - } - } - override public var toolBasenameAliases: [String] { // We use clang as our linker, so return ld and libtool in aliases in // order to parse the errors from the actual linker. @@ -281,7 +271,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec } // FIXME: Is there a better way to figure out if we are linking Swift? - private func isUsingSwift(_ usedTools: [CommandLineToolSpec: Set]) -> Bool { + private static func isUsingSwift(_ usedTools: [CommandLineToolSpec: Set]) -> Bool { return usedTools.keys.map({ type(of: $0) }).contains(where: { $0 == SwiftCompilerSpec.self }) } @@ -304,10 +294,35 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec return runpathSearchPaths } + static func resolveLinkerDriver(_ cbc: CommandBuildContext, usedTools: [CommandLineToolSpec: Set]) -> LinkerDriverChoice { + switch cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER) { + case .clang: + return .clang + case .swiftc: + return.swiftc + case .auto: + if Self.isUsingSwift(usedTools) { + return .swiftc + } else { + return .clang + } + } + } + override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set]) async { + let resolvedLinkerDriver = Self.resolveLinkerDriver(cbc, usedTools: usedTools) + let linkerDriverLookup: ((MacroDeclaration) -> MacroStringExpression?) = { macro in + switch macro { + case BuiltinMacros.LINKER_DRIVER: + return cbc.scope.namespace.parseString(resolvedLinkerDriver.rawValue) + default: + return nil + } + } + // Validate that OTHER_LDFLAGS doesn't contain flags for constructs which we have dedicated settings for. This should be expanded over time. let dyldEnvDiagnosticBehavior: Diagnostic.Behavior = SWBFeatureFlag.useStrictLdEnvironmentBuildSetting.value ? .error : .warning - let originalLdFlags = cbc.scope.evaluate(BuiltinMacros.OTHER_LDFLAGS) + let originalLdFlags = cbc.scope.evaluate(BuiltinMacros.OTHER_LDFLAGS, lookup: linkerDriverLookup) enumerateLinkerCommandLine(arguments: originalLdFlags) { arg, value in switch arg { case "-dyld_env": @@ -354,7 +369,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec specialArgs.append(contentsOf: sparseSDKSearchPathArguments(cbc)) // Define the linker file list. - let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__) + let fileListPath = cbc.scope.evaluate(BuiltinMacros.__INPUT_FILE_LIST_PATH__, lookup: linkerDriverLookup) if !fileListPath.isEmpty { let contents = OutputByteStream() for input in cbc.inputs { @@ -385,7 +400,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec } // Add linker flags desired by the product type. - let productTypeArgs = cbc.producer.productType?.computeAdditionalLinkerArgs(cbc.producer, scope: cbc.scope) + let productTypeArgs = cbc.producer.productType?.computeAdditionalLinkerArgs(cbc.producer, scope: cbc.scope, lookup: linkerDriverLookup) specialArgs += productTypeArgs?.args ?? [] inputPaths += productTypeArgs?.inputs ?? [] @@ -425,7 +440,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec inputPaths.append(contentsOf: inputs) } - let isLinkUsingSwift = isUsingSwift(usedTools) + let isLinkUsingSwift = Self.isUsingSwift(usedTools) if !isLinkUsingSwift { // Check if we need to link with Swift's standard library // when linking a pure Objective-C/C++ target. This might be needed @@ -483,6 +498,9 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec let frameworkSearchPathsExpr = cbc.scope.namespace.parseStringList(frameworkSearchPaths) func lookup(_ macro: MacroDeclaration) -> MacroExpression? { + if let result = linkerDriverLookup(macro) { + return result + } switch macro { case BuiltinMacros.LD_RUNPATH_SEARCH_PATHS: return runpathSearchPathsExpr @@ -589,7 +607,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // Select the driver to use based on the input file types, replacing the value computed by commandLineFromTemplate(). let usedCXX = usedTools.values.contains(where: { $0.contains(where: { $0.languageDialect?.isPlusPlus ?? false }) }) - commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX), delegate: delegate).str + commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX, lookup: linkerDriverLookup), delegate: delegate).str let entitlementsSection = cbc.scope.evaluate(BuiltinMacros.LD_ENTITLEMENTS_SECTION) if !entitlementsSection.isEmpty { @@ -763,6 +781,15 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec } public func constructPreviewShimLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set], rpaths: [String], ldflags: [String]?) async { + let resolvedLinkerDriver = Self.resolveLinkerDriver(cbc, usedTools: usedTools) + let linkerDriverLookup: ((MacroDeclaration) -> MacroStringExpression?) = { macro in + switch macro { + case BuiltinMacros.LINKER_DRIVER: + return cbc.scope.namespace.parseString(resolvedLinkerDriver.rawValue) + default: + return nil + } + } // Construct the "special args". var specialArgs = [String]() var inputPaths = cbc.inputs.map({ $0.absolutePath }) @@ -782,6 +809,9 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec } func lookup(_ macro: MacroDeclaration) -> MacroExpression? { + if let result = linkerDriverLookup(macro) { + return result + } switch macro { case BuiltinMacros.LD_ENTRY_POINT where cbc.scope.previewStyle == .xojit: return cbc.scope.namespace.parseLiteralString("___debug_blank_executor_main") @@ -835,7 +865,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // Select the driver to use based on the input file types, replacing the value computed by commandLineFromTemplate(). let usedCXX = usedTools.values.contains(where: { $0.contains(where: { $0.languageDialect?.isPlusPlus ?? false }) }) - commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX), delegate: delegate).str + commandLine[0] = await resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX, lookup: linkerDriverLookup), delegate: delegate).str let entitlementsSection = cbc.scope.evaluate(BuiltinMacros.LD_ENTITLEMENTS_SECTION) if !entitlementsSection.isEmpty { @@ -1105,31 +1135,45 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec ] } - private func computeLinkerPath(_ cbc: CommandBuildContext, usedCXX: Bool) -> Path { + public override func computeExecutablePath(_ cbc: CommandBuildContext) -> String { + // Placeholder fallback + return cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang") + } + + public func computeLinkerPath(_ cbc: CommandBuildContext, usedCXX: Bool, lookup: @escaping ((MacroDeclaration) -> MacroStringExpression?)) -> Path { if usedCXX { - let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LDPLUSPLUS) + let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LDPLUSPLUS, lookup: lookup) if !perArchValue.isEmpty { - return Path(perArchValue) + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: perArchValue)) } - let value = cbc.scope.evaluate(BuiltinMacros.LDPLUSPLUS) + let value = cbc.scope.evaluate(BuiltinMacros.LDPLUSPLUS, lookup: lookup) if !value.isEmpty { - return Path(value) + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: value)) } - - return Path("clang++") } else { - let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LD) + let perArchValue = cbc.scope.evaluate(BuiltinMacros.PER_ARCH_LD, lookup: lookup) if !perArchValue.isEmpty { - return Path(perArchValue) + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: perArchValue)) } - let value = cbc.scope.evaluate(BuiltinMacros.LD) + let value = cbc.scope.evaluate(BuiltinMacros.LD, lookup: lookup) if !value.isEmpty { - return Path(value) + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: value)) } + } - return Path(computeExecutablePath(cbc)) + switch cbc.scope.evaluate(BuiltinMacros.LINKER_DRIVER, lookup: lookup) { + case .clang: + if usedCXX { + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang++")) + } else { + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "clang")) + } + case .swiftc: + return Path(cbc.producer.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) + case .auto: + preconditionFailure("LINKER_DRIVER was expected to be bound to a concrete value") } } diff --git a/Tests/SWBCoreTests/CommandLineSpecTests.swift b/Tests/SWBCoreTests/CommandLineSpecTests.swift index 69419765..826e08ab 100644 --- a/Tests/SWBCoreTests/CommandLineSpecTests.swift +++ b/Tests/SWBCoreTests/CommandLineSpecTests.swift @@ -1560,7 +1560,7 @@ import SWBMacro } // Check with just LD. - for (name, expected) in [("file.c", "SomeCLinker"), ("file.cpp", "clang++")] { + for (name, expected) in [("file.c", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeCLinker")), ("file.cpp", core.hostOperatingSystem.imageFormat.executableName(basename:"clang++"))] { try await check(name: name, expectedLinker: expected, macros: [ BuiltinMacros.LD: "SomeCLinker" // NOTE: One wonders whether this shouldn't change the C++ linker. @@ -1568,7 +1568,7 @@ import SWBMacro } // Check with LD & LDPLUSPLUS. - for (name, expected) in [("file.c", "SomeCLinker"), ("file.cpp", "SomeC++Linker")] { + for (name, expected) in [("file.c", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeCLinker")), ("file.cpp", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeC++Linker"))] { try await check(name: name, expectedLinker: expected, macros: [ BuiltinMacros.LD: "SomeCLinker", BuiltinMacros.LDPLUSPLUS: "SomeC++Linker" @@ -1576,7 +1576,7 @@ import SWBMacro } // Check with arch specific LD. - for (name, expected) in [("file.c", "SomeCLinker_x86_64"), ("file.cpp", "SomeC++Linker_x86_64")] { + for (name, expected) in [("file.c", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeCLinker_x86_64")), ("file.cpp", core.hostOperatingSystem.imageFormat.executableName(basename:"SomeC++Linker_x86_64"))] { try await check(name: name, expectedLinker: expected, macros: [ BuiltinMacros.CURRENT_ARCH: "x86_64", try core.specRegistry.internalMacroNamespace.declareStringMacro("LD_x86_64"): "SomeCLinker_x86_64", diff --git a/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift new file mode 100644 index 00000000..48b02778 --- /dev/null +++ b/Tests/SWBTaskConstructionTests/LinkerTaskConstructionTests.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Testing + +import SWBCore +import SWBTaskConstruction +import SWBTestSupport +import SWBUtil + +@Suite +fileprivate struct LinkerTaskConstructionTests: CoreBasedTests { + @Test(.requireSDKs(.host)) + func linkerDriverSelection() async throws { + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("c.c"), + TestFile("cxx.cpp"), + TestFile("s.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_EXEC": try await swiftCompilerPath.str, + "SWIFT_VERSION": try await swiftVersion + ]), + ], + targets: [ + TestStandardTarget( + "Library", + type: .dynamicLibrary, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]), + ], + buildPhases: [ + TestSourcesBuildPhase(["c.c", "cxx.cpp", "s.swift"]), + ] + ), + ]) + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: [:]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.contains("clang++"), .anySequence]) + } + } + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["EXCLUDED_SOURCE_FILE_NAMES": "cxx.cpp"]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.contains("clang"), .anySequence]) + } + } + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["LINKER_DRIVER": "swiftc"]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.contains("swiftc"), .anySequence]) + } + } + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["LINKER_DRIVER": "auto"]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.contains("swiftc"), .anySequence]) + } + } + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["LINKER_DRIVER": "auto", "EXCLUDED_SOURCE_FILE_NAMES": "s.swift"]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.contains("clang++"), .anySequence]) + } + } + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: ["LINKER_DRIVER": "auto", "EXCLUDED_SOURCE_FILE_NAMES": "s.swift cxx.cpp"]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.contains("clang"), .anySequence]) + } + } + } +}