diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 31fbf53b..ea94fc70 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -974,6 +974,7 @@ public final class BuiltinMacros { public static let REZ_PREFIX_FILE = BuiltinMacros.declarePathMacro("REZ_PREFIX_FILE") public static let REZ_SEARCH_PATHS = BuiltinMacros.declarePathListMacro("REZ_SEARCH_PATHS") public static let RUN_CLANG_STATIC_ANALYZER = BuiltinMacros.declareBooleanMacro("RUN_CLANG_STATIC_ANALYZER") + public static let SWIFT_API_DIGESTER_MODE = BuiltinMacros.declareEnumMacro("SWIFT_API_DIGESTER_MODE") as EnumMacroDeclaration public static let RUN_SWIFT_ABI_CHECKER_TOOL = BuiltinMacros.declareBooleanMacro("RUN_SWIFT_ABI_CHECKER_TOOL") public static let RUN_SWIFT_ABI_CHECKER_TOOL_DRIVER = BuiltinMacros.declareBooleanMacro("RUN_SWIFT_ABI_CHECKER_TOOL_DRIVER") public static let RUN_SWIFT_ABI_GENERATION_TOOL = BuiltinMacros.declareBooleanMacro("RUN_SWIFT_ABI_GENERATION_TOOL") @@ -2107,6 +2108,7 @@ public final class BuiltinMacros { SKIP_BUILDING_DOCUMENTATION, RUN_SYMBOL_GRAPH_EXTRACT, SYSTEM_EXTENSIONS_FOLDER_PATH, + SWIFT_API_DIGESTER_MODE, RUN_SWIFT_ABI_CHECKER_TOOL, RUN_SWIFT_ABI_CHECKER_TOOL_DRIVER, RUN_SWIFT_ABI_GENERATION_TOOL, @@ -2640,6 +2642,13 @@ public enum SwiftEnableExplicitModulesSetting: String, Equatable, Hashable, Enum case disabled = "NO" } +public enum SwiftAPIDigesterMode: String, Equatable, Hashable, EnumerationMacroType { + public static let defaultValue: SwiftAPIDigesterMode = .abi + + case abi = "abi" + case api = "api" +} + public enum SwiftDependencyRegistrationMode: String, Equatable, Hashable, EnumerationMacroType { public static let defaultValue: SwiftDependencyRegistrationMode = .makeStyleDependenciesSupplementedByScanner diff --git a/Sources/SWBCore/SpecImplementations/ProductTypes.swift b/Sources/SWBCore/SpecImplementations/ProductTypes.swift index 28415e4b..7f26983b 100644 --- a/Sources/SWBCore/SpecImplementations/ProductTypes.swift +++ b/Sources/SWBCore/SpecImplementations/ProductTypes.swift @@ -772,6 +772,10 @@ public class StandaloneExecutableProductTypeSpec : ProductTypeSpec, SpecClassTyp public class var className: String { return "XCStandaloneExecutableProductType" } + + public override var supportsSwiftABIChecker: Bool { + true + } } public class LibraryProductTypeSpec: StandaloneExecutableProductTypeSpec, @unchecked Sendable { diff --git a/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift b/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift index 67997f43..eed3f5e2 100644 --- a/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift +++ b/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift @@ -113,6 +113,8 @@ private final class EnumBuildOptionType : BuildOptionType { return try namespace.declareEnumMacro(name) as EnumMacroDeclaration case "LINKER_DRIVER": return try namespace.declareEnumMacro(name) as EnumMacroDeclaration + case "SWIFT_API_DIGESTER_MODE": + return try namespace.declareEnumMacro(name) as EnumMacroDeclaration default: return try namespace.declareStringMacro(name) } diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftFrameworkABICheckerTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftFrameworkABICheckerTaskProducer.swift index dd829325..6b52df82 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftFrameworkABICheckerTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftFrameworkABICheckerTaskProducer.swift @@ -20,8 +20,8 @@ fileprivate func supportSwiftABIChecking(_ context: TaskProducerContext) -> Bool // swift-api-digester is run only when the "build" component is present. guard scope.evaluate(BuiltinMacros.BUILD_COMPONENTS).contains("build") else { return false } - guard scope.evaluate(BuiltinMacros.SWIFT_EMIT_MODULE_INTERFACE) && - scope.evaluate(BuiltinMacros.SWIFT_ENABLE_LIBRARY_EVOLUTION) else { + guard scope.evaluate(BuiltinMacros.SWIFT_API_DIGESTER_MODE) == .api || + (scope.evaluate(BuiltinMacros.SWIFT_EMIT_MODULE_INTERFACE) && scope.evaluate(BuiltinMacros.SWIFT_ENABLE_LIBRARY_EVOLUTION)) else { // BUILD_LIBRARY_FOR_DISTRIBUTION is the option clients should use (it's also what is exposed in the // Build Settings editor) and is what SWIFT_EMIT_MODULE_INTERFACE uses by default, but they are // configurable independently. @@ -69,6 +69,7 @@ final class SwiftFrameworkABICheckerTaskProducer: PhasedTaskProducer, TaskProduc guard supportSwiftABIChecking(context) else { return [] } // All archs let archs: [String] = scope.evaluate(BuiltinMacros.ARCHS) + let mode = scope.evaluate(BuiltinMacros.SWIFT_API_DIGESTER_MODE) // All variants let buildVariants = scope.evaluate(BuiltinMacros.BUILD_VARIANTS) @@ -83,7 +84,13 @@ final class SwiftFrameworkABICheckerTaskProducer: PhasedTaskProducer, TaskProduc let moduleInput = FileToBuild(absolutePath: moduleDirPath, inferringTypeUsing: context) let interfaceInput = FileToBuild(absolutePath: Path(moduleDirPath.withoutSuffix + ".swiftinterface"), inferringTypeUsing: context) let serializedDiagPath = scope.evaluate(BuiltinMacros.TARGET_TEMP_DIR).join(scope.evaluate(BuiltinMacros.PRODUCT_NAME)).join("SwiftABIChecker").join(variant).join(getBaselineFileName(scope, arch).withoutSuffix + ".dia") - var allInputs = [moduleInput, interfaceInput] + var allInputs: [FileToBuild] + switch mode { + case .abi: + allInputs = [moduleInput, interfaceInput] + case .api: + allInputs = [moduleInput] + } if scope.evaluate(BuiltinMacros.RUN_SWIFT_ABI_GENERATION_TOOL) { // If users also want to generate ABI baseline, we should generate the baseline first. This allows users to update // baseline without re-running the build. @@ -125,6 +132,7 @@ class SwiftABIBaselineGenerationTaskProducer: PhasedTaskProducer, TaskProducer { guard supportSwiftABIChecking(context) else { return [] } // All archs let archs: [String] = scope.evaluate(BuiltinMacros.ARCHS) + let mode = scope.evaluate(BuiltinMacros.SWIFT_API_DIGESTER_MODE) // All variants let buildVariants = scope.evaluate(BuiltinMacros.BUILD_VARIANTS) @@ -140,9 +148,17 @@ class SwiftABIBaselineGenerationTaskProducer: PhasedTaskProducer, TaskProducer { let moduleInput = FileToBuild(absolutePath: moduleDirPath, inferringTypeUsing: context) let interfaceInput = FileToBuild(absolutePath: Path(moduleDirPath.withoutSuffix + ".swiftinterface"), inferringTypeUsing: context) + let allInputs: [FileToBuild] + switch mode { + case .abi: + allInputs = [moduleInput, interfaceInput] + case .api: + allInputs = [moduleInput] + } + let baselinePath = getGeneratedBaselineFilePath(context, arch) - let cbc = CommandBuildContext(producer: context, scope: scope, inputs: [moduleInput, interfaceInput], output: baselinePath) + let cbc = CommandBuildContext(producer: context, scope: scope, inputs: allInputs, output: baselinePath) await appendGeneratedTasks(&tasks) { delegate in // Generate baseline into the baseline directory await context.swiftABIGenerationToolSpec?.constructABIGenerationTask(cbc, delegate, baselinePath) diff --git a/Sources/SWBUniversalPlatform/Specs/Swift.xcspec b/Sources/SWBUniversalPlatform/Specs/Swift.xcspec index ff751506..7d0e5dd3 100644 --- a/Sources/SWBUniversalPlatform/Specs/Swift.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Swift.xcspec @@ -1411,7 +1411,7 @@ RuleName = "CheckSwiftABI $(CURRENT_VARIANT) $(CURRENT_ARCH)"; ExecDescription = "Check ABI stability for $(PRODUCT_MODULE_NAME).swiftinterface"; ProgressDescription = "Checking ABI stability for $(PRODUCT_MODULE_NAME).swiftinterface"; - CommandLine = "swift-api-digester -diagnose-sdk -abort-on-module-fail -abi -compiler-style-diags [options]"; + CommandLine = "swift-api-digester -diagnose-sdk -abort-on-module-fail -compiler-style-diags [options]"; CommandOutputParser = "XCGccCommandOutputParser"; Options = ( { @@ -1440,10 +1440,26 @@ CommandLineArgs = ( "-module", "$(value)", - "-use-interface-for-module", - "$(value)", ); }, + { + Name = "SWIFT_API_DIGESTER_MODE"; + Type = Enumeration; + Values = ( + abi, + api, + ); + DefaultValue = abi; + CommandLineArgs = { + abi = ( + "-abi", + "-use-interface-for-module", + "$(SWIFT_MODULE_NAME)", + ); + api = ( + ); + }; + }, { Name = "OTHER_SWIFT_ABI_CHECKER_FLAGS"; Type = StringList; @@ -1461,7 +1477,7 @@ RuleName = "GenerateSwiftABIBaseline $(CURRENT_VARIANT) $(CURRENT_ARCH)"; ExecDescription = "Generate ABI baseline for $(PRODUCT_MODULE_NAME).swiftinterface"; ProgressDescription = "Generating ABI baseline for $(PRODUCT_MODULE_NAME).swiftinterface"; - CommandLine = "swift-api-digester -dump-sdk -abort-on-module-fail -abi -swift-only -avoid-tool-args [options]"; + CommandLine = "swift-api-digester -dump-sdk -abort-on-module-fail -swift-only -avoid-tool-args [options]"; CommandOutputParser = "XCGccCommandOutputParser"; Options = ( { @@ -1490,10 +1506,26 @@ CommandLineArgs = ( "-module", "$(value)", - "-use-interface-for-module", - "$(value)", ); }, + { + Name = "SWIFT_API_DIGESTER_MODE"; + Type = Enumeration; + Values = ( + abi, + api, + ); + DefaultValue = abi; + CommandLineArgs = { + abi = ( + "-abi", + "-use-interface-for-module", + "$(SWIFT_MODULE_NAME)", + ); + api = ( + ); + }; + }, { Name = "OTHER_SWIFT_ABI_CHECKER_FLAGS"; Type = StringList; diff --git a/Tests/SWBTaskConstructionTests/SwiftABICheckerTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/SwiftABICheckerTaskConstructionTests.swift index 4f47f007..7bae3beb 100644 --- a/Tests/SWBTaskConstructionTests/SwiftABICheckerTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/SwiftABICheckerTaskConstructionTests.swift @@ -138,6 +138,59 @@ fileprivate struct SwiftABICheckerTaskConstructionTests: CoreBasedTests { } } + @Test(.requireSDKs(.iOS)) + func swiftABIBaselineGenerationModes() async throws { + let testProject = try await TestProject( + "aProject", + sourceRoot: Path("/TEST"), + groupTree: TestGroup( + "SomeFiles", path: "Sources", + children: [ + TestFile("Fwk.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "ARCHS": "arm64", + "SDKROOT": "iphoneos", + "PRODUCT_NAME": "$(TARGET_NAME)", + "RUN_SWIFT_ABI_GENERATION_TOOL": "YES", + "SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR": "/tmp/user_given_generated_baseline", + "SWIFT_EXEC": swiftCompilerPath.str, + "SWIFT_VERSION": swiftVersion, + "CODE_SIGNING_ALLOWED": "NO", + "TAPI_EXEC": tapiToolPath.str, + ])], + targets: [ + TestStandardTarget( + "Fwk", + type: .framework, + buildPhases: [ + TestSourcesBuildPhase(["Fwk.swift"]) + ]), + ]) + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug", overrides: ["SWIFT_API_DIGESTER_MODE": "abi"]), runDestination: .anyiOSDevice) { results in + results.checkError(.contains("Swift ABI checker is only functional when BUILD_LIBRARY_FOR_DISTRIBUTION = YES")) + } + + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug", overrides: ["SWIFT_API_DIGESTER_MODE": "abi", "BUILD_LIBRARY_FOR_DISTRIBUTION": "YES"]), runDestination: .anyiOSDevice) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("GenerateSwiftABIBaseline")) { task in + task.checkCommandLineContains(["-abi"]) + task.checkCommandLineContains(["-use-interface-for-module"]) + } + } + + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug", overrides: ["SWIFT_API_DIGESTER_MODE": "api"]), runDestination: .anyiOSDevice) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("GenerateSwiftABIBaseline")) { task in + task.checkCommandLineDoesNotContain("-abi") + task.checkCommandLineDoesNotContain("-use-interface-for-module") + } + } + } + @Test(.requireSDKs(.iOS)) func swiftABICheckerUsingSpecifiedBaseline() async throws { let testProject = try await TestProject( @@ -189,7 +242,6 @@ fileprivate struct SwiftABICheckerTaskConstructionTests: CoreBasedTests { "swift-api-digester", "-diagnose-sdk", "-abort-on-module-fail", - "-abi", "-compiler-style-diags", "-target", "arm64e-apple-ios\(core.loadSDK(.iOS).version)", @@ -201,6 +253,7 @@ fileprivate struct SwiftABICheckerTaskConstructionTests: CoreBasedTests { "\(core.loadSDK(.iOS).path.str)", "-module", "Fwk", + "-abi", "-use-interface-for-module", "-serialize-diagnostics-path", "/TEST/build/aProject.build/Debug-iphoneos/Fwk.build/Fwk/SwiftABIChecker/normal/arm64e-ios.dia", @@ -216,6 +269,64 @@ fileprivate struct SwiftABICheckerTaskConstructionTests: CoreBasedTests { } } + @Test(.requireSDKs(.iOS)) + func swiftABICheckerModes() async throws { + let testProject = try await TestProject( + "aProject", + sourceRoot: Path("/TEST"), + groupTree: TestGroup( + "SomeFiles", path: "Sources", + children: [ + TestFile("Fwk.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "ARCHS": "arm64e", + "SDKROOT": "iphoneos", + "PRODUCT_NAME": "$(TARGET_NAME)", + "RUN_SWIFT_ABI_CHECKER_TOOL": "YES", + "SWIFT_EXEC": swiftCompilerPath.str, + "SWIFT_VERSION": swiftVersion, + "FRAMEWORK_SEARCH_PATHS": "/Target/Framework/Search/Path/A", + "CODE_SIGNING_ALLOWED": "NO", + "BUILD_LIBRARY_FOR_DISTRIBUTION": "YES", + "SWIFT_ABI_CHECKER_BASELINE_DIR": "/tmp/mybaseline", + "SWIFT_ABI_CHECKER_EXCEPTIONS_FILE": "/tmp/allow.txt", + "TAPI_EXEC": tapiToolPath.str, + ])], + targets: [ + TestStandardTarget( + "Fwk", + type: .framework, + buildPhases: [ + TestSourcesBuildPhase(["Fwk.swift"]) + ]), + ]) + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + + let fs = PseudoFS() + try fs.createDirectory(.root.join("tmp")) + try fs.write(.root.join("tmp").join("allow.txt"), contents: "") + try await fs.writeJSON(.root.join("tmp/mybaseline/ABI/arm64e-ios.json"), .plDict([:])) + + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug", overrides: ["SWIFT_API_DIGESTER_MODE": "abi"]), runDestination: .iOS, fs: fs) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("CheckSwiftABI")) { task in + task.checkCommandLineContains(["-abi"]) + task.checkCommandLineContains(["-use-interface-for-module"]) + } + } + + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug", overrides: ["SWIFT_API_DIGESTER_MODE": "api"]), runDestination: .iOS, fs: fs) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("CheckSwiftABI")) { task in + task.checkCommandLineDoesNotContain("-abi") + task.checkCommandLineDoesNotContain("-use-interface-for-module") + } + } + } + @Test(.requireSDKs(.iOS)) func swiftABICheckerTaskSequence() async throws { let testProject = try await TestProject(