Skip to content

Commit c52bfba

Browse files
committed
Add support for API digester baseline comparison jobs
1 parent f26cb05 commit c52bfba

File tree

7 files changed

+253
-21
lines changed

7 files changed

+253
-21
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ public struct Driver {
3434
case missingProfilingData(String)
3535
case conditionalCompilationFlagHasRedundantPrefix(String)
3636
case conditionalCompilationFlagIsNotValidIdentifier(String)
37+
case baselineGenerationRequiresTopLevelModule(String)
38+
case optionRequiresAnother(String, String)
3739
// Explicit Module Build Failures
3840
case malformedModuleDependency(String, String)
3941
case missingPCMArguments(String)
4042
case missingModuleDependency(String)
4143
case missingContextHashOnSwiftDependency(String)
4244
case dependencyScanningFailure(Int, String)
4345
case missingExternalDependency(String)
44-
case baselineGenerationRequiresTopLevelModule(String)
4546

4647
public var description: String {
4748
switch self {
@@ -103,6 +104,8 @@ public struct Driver {
103104
return "Missing External dependency info for module: \(moduleName)"
104105
case .baselineGenerationRequiresTopLevelModule(let arg):
105106
return "generating a baseline with '\(arg)' is only supported with '-emit-module' or '-emit-module-path'"
107+
case .optionRequiresAnother(let first, let second):
108+
return "'\(first)' cannot be specified if '\(second)' is not present"
106109
}
107110
}
108111
}
@@ -2234,10 +2237,28 @@ extension Driver {
22342237
diagnosticEngine: DiagnosticsEngine) {
22352238
if moduleOutputInfo.output?.isTopLevel != true {
22362239
for arg in parsedOptions.arguments(for: .emitAPIBaseline, .emitAPIBaselinePath,
2237-
.emitABIBaseline, .emitABIBaselinePath) {
2240+
.emitABIBaseline, .emitABIBaselinePath,
2241+
.compareToAPIBaselinePath, .compareToABIBaselinePath) {
22382242
diagnosticEngine.emit(Error.baselineGenerationRequiresTopLevelModule(arg.option.spelling))
22392243
}
22402244
}
2245+
2246+
if parsedOptions.hasArgument(.serializeAPIBreakingChangesPath) && !parsedOptions.hasArgument(.compareToAPIBaselinePath) {
2247+
diagnosticEngine.emit(Error.optionRequiresAnother(Option.serializeAPIBreakingChangesPath.spelling,
2248+
Option.compareToAPIBaselinePath.spelling))
2249+
}
2250+
if parsedOptions.hasArgument(.serializeABIBreakingChangesPath) && !parsedOptions.hasArgument(.compareToABIBaselinePath) {
2251+
diagnosticEngine.emit(Error.optionRequiresAnother(Option.serializeABIBreakingChangesPath.spelling,
2252+
Option.compareToABIBaselinePath.spelling))
2253+
}
2254+
if parsedOptions.hasArgument(.apiBreakageAllowlistPath) && !parsedOptions.hasArgument(.compareToAPIBaselinePath) {
2255+
diagnosticEngine.emit(Error.optionRequiresAnother(Option.apiBreakageAllowlistPath.spelling,
2256+
Option.compareToAPIBaselinePath.spelling))
2257+
}
2258+
if parsedOptions.hasArgument(.abiBreakageAllowlistPath) && !parsedOptions.hasArgument(.compareToABIBaselinePath) {
2259+
diagnosticEngine.emit(Error.optionRequiresAnother(Option.abiBreakageAllowlistPath.spelling,
2260+
Option.compareToABIBaselinePath.spelling))
2261+
}
22412262
}
22422263

22432264

Sources/SwiftDriver/Jobs/APIDigesterJobs.swift

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,92 @@ enum DigesterMode {
3232
return .generateABIBaseline
3333
}
3434
}
35+
36+
var baselineComparisonJobKind: Job.Kind {
37+
switch self {
38+
case .api:
39+
return .compareAPIBaseline
40+
case .abi:
41+
return .compareABIBaseline
42+
}
43+
}
3544
}
3645

3746
extension Driver {
3847
mutating func digesterBaselineGenerationJob(modulePath: VirtualPath.Handle, outputPath: VirtualPath.Handle, mode: DigesterMode) throws -> Job {
3948
var commandLine = [Job.ArgTemplate]()
4049
commandLine.appendFlag("-dump-sdk")
4150

51+
try addCommonDigesterOptions(&commandLine, modulePath: modulePath, mode: mode)
52+
53+
commandLine.appendFlag(.o)
54+
commandLine.appendPath(VirtualPath.lookup(outputPath))
55+
56+
return Job(
57+
moduleName: moduleOutputInfo.name,
58+
kind: mode.baselineGenerationJobKind,
59+
tool: .absolute(try toolchain.getToolPath(.swiftAPIDigester)),
60+
commandLine: commandLine,
61+
inputs: [.init(file: modulePath, type: .swiftModule)],
62+
primaryInputs: [],
63+
outputs: [.init(file: outputPath, type: mode.baselineFileType)],
64+
supportsResponseFiles: true
65+
)
66+
}
67+
68+
mutating func digesterCompareToBaselineJob(modulePath: VirtualPath.Handle, baselinePath: VirtualPath.Handle, mode: DigesterMode) throws -> Job {
69+
var commandLine = [Job.ArgTemplate]()
70+
commandLine.appendFlag("-diagnose-sdk")
71+
commandLine.appendFlag("-disable-fail-on-error")
72+
commandLine.appendFlag("-baseline-path")
73+
commandLine.appendPath(VirtualPath.lookup(baselinePath))
74+
75+
try addCommonDigesterOptions(&commandLine, modulePath: modulePath, mode: mode)
76+
77+
var serializedDiagnosticsPath: VirtualPath.Handle?
78+
var breakageAllowlistPath: VirtualPath.Handle?
79+
switch mode {
80+
case .api :
81+
if let arg = parsedOptions.getLastArgument(.serializeAPIBreakingChangesPath)?.asSingle {
82+
serializedDiagnosticsPath = try VirtualPath.intern(path: arg)
83+
}
84+
if let arg = parsedOptions.getLastArgument(.apiBreakageAllowlistPath)?.asSingle {
85+
breakageAllowlistPath = try VirtualPath.intern(path: arg)
86+
}
87+
case .abi:
88+
if let arg = parsedOptions.getLastArgument(.serializeABIBreakingChangesPath)?.asSingle {
89+
serializedDiagnosticsPath = try VirtualPath.intern(path: arg)
90+
}
91+
if let arg = parsedOptions.getLastArgument(.abiBreakageAllowlistPath)?.asSingle {
92+
breakageAllowlistPath = try VirtualPath.intern(path: arg)
93+
}
94+
}
95+
96+
if let serializedDiagnosticsPath = serializedDiagnosticsPath {
97+
commandLine.appendFlag("-serialize-diagnostics-path")
98+
commandLine.appendPath(VirtualPath.lookup(serializedDiagnosticsPath))
99+
}
100+
101+
if let breakageAllowlistPath = breakageAllowlistPath {
102+
commandLine.appendFlag("-breakage-allowlist-path")
103+
commandLine.appendPath(VirtualPath.lookup(breakageAllowlistPath))
104+
}
105+
106+
return Job(
107+
moduleName: moduleOutputInfo.name,
108+
kind: mode.baselineComparisonJobKind,
109+
tool: .absolute(try toolchain.getToolPath(.swiftAPIDigester)),
110+
commandLine: commandLine,
111+
inputs: [.init(file: modulePath, type: .swiftModule), .init(file: baselinePath, type: mode.baselineFileType)],
112+
primaryInputs: [],
113+
outputs: [.init(file: serializedDiagnosticsPath ?? VirtualPath.Handle.standardOutput, type: .diagnostics)],
114+
supportsResponseFiles: true
115+
)
116+
}
117+
118+
private mutating func addCommonDigesterOptions(_ commandLine: inout [Job.ArgTemplate],
119+
modulePath: VirtualPath.Handle,
120+
mode: DigesterMode) throws {
42121
if mode == .abi {
43122
commandLine.appendFlag("-abi")
44123
}
@@ -69,19 +148,5 @@ extension Driver {
69148
}
70149

71150
try commandLine.appendLast(.swiftVersion, from: &parsedOptions)
72-
73-
commandLine.appendFlag(.o)
74-
commandLine.appendPath(VirtualPath.lookup(outputPath))
75-
76-
return Job(
77-
moduleName: moduleOutputInfo.name,
78-
kind: mode.baselineGenerationJobKind,
79-
tool: .absolute(try toolchain.getToolPath(.swiftAPIDigester)),
80-
commandLine: commandLine,
81-
inputs: [.init(file: modulePath, type: .swiftModule)],
82-
primaryInputs: [],
83-
outputs: [.init(file: outputPath, type: mode.baselineFileType)],
84-
supportsResponseFiles: true
85-
)
86151
}
87152
}

Sources/SwiftDriver/Jobs/Job.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public struct Job: Codable, Equatable, Hashable {
3939
case help
4040
case generateAPIBaseline = "generate-api-baseline"
4141
case generateABIBaseline = "generate-abi-baseline"
42+
case compareAPIBaseline = "compare-api-baseline"
43+
case compareABIBaseline = "compare-abi-baseline"
4244
}
4345

4446
public enum ArgTemplate: Equatable, Hashable {
@@ -228,6 +230,12 @@ extension Job : CustomStringConvertible {
228230

229231
case .generateABIBaseline:
230232
return "Generating ABI baseline file for module \(moduleName)"
233+
234+
case .compareAPIBaseline:
235+
return "Comparing API of \(moduleName) to baseline"
236+
237+
case .compareABIBaseline:
238+
return "Comparing ABI of \(moduleName) to baseline"
231239
}
232240
}
233241

@@ -251,7 +259,7 @@ extension Job.Kind {
251259
return true
252260

253261
case .autolinkExtract, .generateDSYM, .help, .link, .verifyDebugInfo, .moduleWrap,
254-
.generateAPIBaseline, .generateABIBaseline:
262+
.generateAPIBaseline, .generateABIBaseline, .compareAPIBaseline, .compareABIBaseline:
255263
return false
256264
}
257265
}
@@ -266,7 +274,8 @@ extension Job.Kind {
266274
.versionRequest, .autolinkExtract, .generateDSYM,
267275
.help, .link, .verifyDebugInfo, .scanDependencies,
268276
.emitSupportedFeatures, .moduleWrap, .verifyModuleInterface,
269-
.generateAPIBaseline, .generateABIBaseline:
277+
.generateAPIBaseline, .generateABIBaseline, .compareAPIBaseline,
278+
.compareABIBaseline:
270279
return false
271280
}
272281
}

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,14 @@ extension Driver {
471471
if let abiBaselinePath = self.abiBaselinePath {
472472
try addJob(digesterBaselineGenerationJob(modulePath: moduleOutputPath, outputPath: abiBaselinePath, mode: .abi))
473473
}
474+
if let baselineArg = parsedOptions.getLastArgument(.compareToAPIBaselinePath)?.asSingle,
475+
let baselinePath = try? VirtualPath.intern(path: baselineArg) {
476+
addJob(try digesterCompareToBaselineJob(modulePath: moduleOutputPath, baselinePath: baselinePath, mode: .api))
477+
}
478+
if let baselineArg = parsedOptions.getLastArgument(.compareToABIBaselinePath)?.asSingle,
479+
let baselinePath = try? VirtualPath.intern(path: baselineArg) {
480+
addJob(try digesterCompareToBaselineJob(modulePath: moduleOutputPath, baselinePath: baselinePath, mode: .abi))
481+
}
474482
}
475483

476484
private mutating func addLinkAndPostLinkJobs(

Sources/SwiftOptions/ExtraOptions.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@ extension Option {
1717
public static let emitModuleSeparately: Option = Option("-experimental-emit-module-separately", .flag, attributes: [.helpHidden], helpText: "Emit module files as a distinct job")
1818
public static let noEmitModuleSeparately: Option = Option("-no-emit-module-separately", .flag, attributes: [.helpHidden], helpText: "Force using merge-module as the incremental build mode")
1919
public static let useFrontendParseableOutput: Option = Option("-use-frontend-parseable-output", .flag, attributes: [.helpHidden], helpText: "Emit parseable-output from swift-frontend jobs instead of from the driver")
20+
21+
// API digester operations
2022
public static let emitAPIBaseline: Option = Option("-emit-api-baseline", .flag, attributes: [.noInteractive, .supplementaryOutput], helpText: "Emit an API baseline file for the module")
2123
public static let emitAPIBaselinePath: Option = Option("-emit-api-baseline-path", .separate, attributes: [.noInteractive, .supplementaryOutput, .argumentIsPath], metaVar: "<path>", helpText: "Emit an API baseline file for the module to <path>")
2224
public static let emitABIBaseline: Option = Option("-emit-abi-baseline", .flag, attributes: [.noInteractive, .supplementaryOutput], helpText: "Emit an ABI baseline file for the module")
2325
public static let emitABIBaselinePath: Option = Option("-emit-abi-baseline-path", .separate, attributes: [.noInteractive, .supplementaryOutput, .argumentIsPath], metaVar: "<path>", helpText: "Emit an ABI baseline file for the module to <path>")
26+
public static let compareToAPIBaselinePath: Option = Option("-compare-to-api-baseline-path", .separate, attributes: [.noInteractive, .argumentIsPath], metaVar: "<path>", helpText: "Compare the built module's API to the baseline at <path> and diagnose breaking changes")
27+
public static let compareToABIBaselinePath: Option = Option("-compare-to-abi-baseline-path", .separate, attributes: [.noInteractive, .argumentIsPath], metaVar: "<path>", helpText: "Compare the built module's ABI to the baseline at <path> and diagnose breaking changes")
28+
public static let serializeAPIBreakingChangesPath: Option = Option("-serialize-api-breaking-changes-path", .separate, attributes: [.noInteractive, .argumentIsPath], metaVar: "<path>", helpText: "Serialize API-breaking changes found using -compare-to-api-baseline-path to <path>")
29+
public static let serializeABIBreakingChangesPath: Option = Option("-serialize-abi-breaking-changes-path", .separate, attributes: [.noInteractive, .argumentIsPath], metaVar: "<path>", helpText: "Serialize ABI-breaking changes found using -compare-to-abi-baseline-path to <path>")
30+
public static let apiBreakageAllowlistPath: Option = Option("-api-breakage-allowlist-path", .separate, attributes: [.noInteractive, .argumentIsPath], metaVar: "<path>", helpText: "The path to a list of permitted API-breaking changes to ignore")
31+
public static let abiBreakageAllowlistPath: Option = Option("-abi-breakage-allowlist-path", .separate, attributes: [.noInteractive, .argumentIsPath], metaVar: "<path>", helpText: "The path to a list of permitted ABI-breaking changes to ignore")
2432

2533
public static var extraOptions: [Option] {
2634
return [
@@ -34,7 +42,13 @@ extension Option {
3442
Option.emitAPIBaseline,
3543
Option.emitAPIBaselinePath,
3644
Option.emitABIBaseline,
37-
Option.emitABIBaselinePath
45+
Option.emitABIBaselinePath,
46+
Option.compareToAPIBaselinePath,
47+
Option.compareToABIBaselinePath,
48+
Option.serializeAPIBreakingChangesPath,
49+
Option.serializeABIBreakingChangesPath,
50+
Option.apiBreakageAllowlistPath,
51+
Option.abiBreakageAllowlistPath
3852
]
3953
}
4054
}

0 commit comments

Comments
 (0)