Skip to content
Merged
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
9 changes: 9 additions & 0 deletions Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<SwiftAPIDigesterMode>
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")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions Sources/SWBCore/SpecImplementations/ProductTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private final class EnumBuildOptionType : BuildOptionType {
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<SwiftEnableExplicitModulesSetting>
case "LINKER_DRIVER":
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<LinkerDriverChoice>
case "SWIFT_API_DIGESTER_MODE":
return try namespace.declareEnumMacro(name) as EnumMacroDeclaration<SwiftAPIDigesterMode>
default:
return try namespace.declareStringMacro(name)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
44 changes: 38 additions & 6 deletions Sources/SWBUniversalPlatform/Specs/Swift.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
{
Expand Down Expand Up @@ -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;
Expand All @@ -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 = (
{
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)",
Expand All @@ -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",
Expand All @@ -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(
Expand Down
Loading