Skip to content

Commit 4f4e853

Browse files
authored
Merge pull request #721 from owenv/api-digester-integration
Add an integration with swift-api-digester
2 parents eb17e10 + 4ec1161 commit 4f4e853

15 files changed

+724
-11
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/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ add_library(SwiftDriver
5555
"IncrementalCompilation/SourceFileDependencyGraph.swift"
5656
"IncrementalCompilation/TwoDMap.swift"
5757

58+
Jobs/APIDigesterJobs.swift
5859
Jobs/AutolinkExtractJob.swift
5960
Jobs/BackendJob.swift
6061
Jobs/CommandLineArguments.swift

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ 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)
@@ -100,6 +102,10 @@ public struct Driver {
100102
return "unable to load output file map '\(path)': no such file or directory"
101103
case .missingExternalDependency(let moduleName):
102104
return "Missing External dependency info for module: \(moduleName)"
105+
case .baselineGenerationRequiresTopLevelModule(let arg):
106+
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"
103109
}
104110
}
105111
}
@@ -288,6 +294,12 @@ public struct Driver {
288294
/// Path to the Swift module source information file.
289295
let moduleSourceInfoPath: VirtualPath.Handle?
290296

297+
/// Path to the module's digester baseline file.
298+
let digesterBaselinePath: VirtualPath.Handle?
299+
300+
/// The mode the API digester should run in.
301+
let digesterMode: DigesterMode
302+
291303
/// Force the driver to emit the module first and then run compile jobs. This could be used to unblock
292304
/// dependencies in parallel builds.
293305
var forceEmitModuleBeforeCompile: Bool = false
@@ -535,6 +547,16 @@ public struct Driver {
535547
self.numThreads = Self.determineNumThreads(&parsedOptions, compilerMode: compilerMode, diagnosticsEngine: diagnosticEngine)
536548
self.numParallelJobs = Self.determineNumParallelJobs(&parsedOptions, diagnosticsEngine: diagnosticEngine, env: env)
537549

550+
var mode = DigesterMode.api
551+
if let modeArg = parsedOptions.getLastArgument(.digesterMode)?.asSingle {
552+
if let digesterMode = DigesterMode(rawValue: modeArg) {
553+
mode = digesterMode
554+
} else {
555+
diagnosticsEngine.emit(Error.invalidArgumentValue(Option.digesterMode.spelling, modeArg))
556+
}
557+
}
558+
self.digesterMode = mode
559+
538560
Self.validateWarningControlArgs(&parsedOptions, diagnosticEngine: diagnosticEngine)
539561
Self.validateProfilingArgs(&parsedOptions,
540562
fileSystem: fileSystem,
@@ -587,7 +609,7 @@ public struct Driver {
587609
diagnosticEngine: diagnosticEngine,
588610
toolchain: toolchain,
589611
targetInfo: frontendTargetInfo)
590-
612+
591613
Self.validateSanitizerAddressUseOdrIndicatorFlag(&parsedOptions, diagnosticEngine: diagnosticsEngine, addressSanitizerEnabled: enabledSanitizers.contains(.address))
592614

593615
Self.validateSanitizerRecoverArgValues(&parsedOptions, diagnosticEngine: diagnosticsEngine, enabledSanitizers: enabledSanitizers)
@@ -663,6 +685,15 @@ public struct Driver {
663685
outputFileMap: self.outputFileMap,
664686
moduleName: moduleOutputInfo.name,
665687
projectDirectory: projectDirectory)
688+
self.digesterBaselinePath = try Self.computeDigesterBaselineOutputPath(
689+
&parsedOptions,
690+
moduleOutputPath: self.moduleOutputInfo.output?.outputPath,
691+
mode: self.digesterMode,
692+
compilerOutputType: compilerOutputType,
693+
compilerMode: compilerMode,
694+
outputFileMap: self.outputFileMap,
695+
moduleName: moduleOutputInfo.name,
696+
projectDirectory: projectDirectory)
666697
self.swiftInterfacePath = try Self.computeSupplementaryOutputPath(
667698
&parsedOptions, type: .swiftInterface, isOutputOptions: [.emitModuleInterface],
668699
outputPath: .emitModuleInterfacePath,
@@ -700,6 +731,12 @@ public struct Driver {
700731
outputFileMap: self.outputFileMap,
701732
moduleName: moduleOutputInfo.name)
702733

734+
Self.validateDigesterArgs(&parsedOptions,
735+
moduleOutputInfo: moduleOutputInfo,
736+
digesterMode: self.digesterMode,
737+
swiftInterfacePath: self.swiftInterfacePath,
738+
diagnosticEngine: diagnosticsEngine)
739+
703740
try verifyOutputOptions()
704741
}
705742

@@ -2196,6 +2233,36 @@ extension Driver {
21962233
}
21972234
}
21982235

2236+
static func validateDigesterArgs(_ parsedOptions: inout ParsedOptions,
2237+
moduleOutputInfo: ModuleOutputInfo,
2238+
digesterMode: DigesterMode,
2239+
swiftInterfacePath: VirtualPath.Handle?,
2240+
diagnosticEngine: DiagnosticsEngine) {
2241+
if moduleOutputInfo.output?.isTopLevel != true {
2242+
for arg in parsedOptions.arguments(for: .emitDigesterBaseline, .emitDigesterBaselinePath, .compareToBaselinePath) {
2243+
diagnosticEngine.emit(Error.baselineGenerationRequiresTopLevelModule(arg.option.spelling))
2244+
}
2245+
}
2246+
2247+
if parsedOptions.hasArgument(.serializeBreakingChangesPath) && !parsedOptions.hasArgument(.compareToBaselinePath) {
2248+
diagnosticEngine.emit(Error.optionRequiresAnother(Option.serializeBreakingChangesPath.spelling,
2249+
Option.compareToBaselinePath.spelling))
2250+
}
2251+
if parsedOptions.hasArgument(.digesterBreakageAllowlistPath) && !parsedOptions.hasArgument(.compareToBaselinePath) {
2252+
diagnosticEngine.emit(Error.optionRequiresAnother(Option.digesterBreakageAllowlistPath.spelling,
2253+
Option.compareToBaselinePath.spelling))
2254+
}
2255+
if digesterMode == .abi && !parsedOptions.hasArgument(.enableLibraryEvolution) {
2256+
diagnosticEngine.emit(Error.optionRequiresAnother("\(Option.digesterMode.spelling) abi",
2257+
Option.enableLibraryEvolution.spelling))
2258+
}
2259+
if digesterMode == .abi && swiftInterfacePath == nil {
2260+
diagnosticEngine.emit(Error.optionRequiresAnother("\(Option.digesterMode.spelling) abi",
2261+
Option.emitModuleInterface.spelling))
2262+
}
2263+
}
2264+
2265+
21992266
static func validateProfilingArgs(_ parsedOptions: inout ParsedOptions,
22002267
fileSystem: FileSystem,
22012268
workingDirectory: AbsolutePath?,
@@ -2650,6 +2717,31 @@ extension Driver {
26502717
projectDirectory: projectDirectory)
26512718
}
26522719

2720+
static func computeDigesterBaselineOutputPath(
2721+
_ parsedOptions: inout ParsedOptions,
2722+
moduleOutputPath: VirtualPath.Handle?,
2723+
mode: DigesterMode,
2724+
compilerOutputType: FileType?,
2725+
compilerMode: CompilerMode,
2726+
outputFileMap: OutputFileMap?,
2727+
moduleName: String,
2728+
projectDirectory: VirtualPath.Handle?
2729+
) throws -> VirtualPath.Handle? {
2730+
// Only emit a baseline if at least of the arguments was provided.
2731+
guard parsedOptions.hasArgument(.emitDigesterBaseline, .emitDigesterBaselinePath) else { return nil }
2732+
return try computeModuleAuxiliaryOutputPath(&parsedOptions,
2733+
moduleOutputPath: moduleOutputPath,
2734+
type: mode.baselineFileType,
2735+
isOutput: .emitDigesterBaseline,
2736+
outputPath: .emitDigesterBaselinePath,
2737+
compilerOutputType: compilerOutputType,
2738+
compilerMode: compilerMode,
2739+
outputFileMap: outputFileMap,
2740+
moduleName: moduleName,
2741+
projectDirectory: projectDirectory)
2742+
}
2743+
2744+
26532745

26542746
/// Determine the output path for a module auxiliary output.
26552747
static func computeModuleAuxiliaryOutputPath(

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ public struct OutputFileMap: Hashable, Codable {
5454
}
5555
return VirtualPath.lookup(path).replacingExtension(with: outputType).intern()
5656

57+
case .jsonAPIBaseline, .jsonABIBaseline:
58+
// Infer paths for these entities using .swiftsourceinfo path.
59+
guard let path = entries[inputFile]?[.swiftSourceInfoFile] else {
60+
return nil
61+
}
62+
return VirtualPath.lookup(path).replacingExtension(with: outputType).intern()
63+
5764
case .object:
5865
// We may generate .o files from bitcode .bc files, but the output file map
5966
// uses .swift file as the key for .o file paths. So we need to dig further.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//===- APIDigesterJobs.swift - Baseline Generation and API/ABI Comparison -===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import TSCBasic
14+
15+
enum DigesterMode: String {
16+
case api, abi
17+
18+
var baselineFileType: FileType {
19+
switch self {
20+
case .api:
21+
return .jsonAPIBaseline
22+
case .abi:
23+
return .jsonABIBaseline
24+
}
25+
}
26+
27+
var baselineGenerationJobKind: Job.Kind {
28+
switch self {
29+
case .api:
30+
return .generateAPIBaseline
31+
case .abi:
32+
return .generateABIBaseline
33+
}
34+
}
35+
36+
var baselineComparisonJobKind: Job.Kind {
37+
switch self {
38+
case .api:
39+
return .compareAPIBaseline
40+
case .abi:
41+
return .compareABIBaseline
42+
}
43+
}
44+
}
45+
46+
extension Driver {
47+
mutating func digesterBaselineGenerationJob(modulePath: VirtualPath.Handle, outputPath: VirtualPath.Handle, mode: DigesterMode) throws -> Job {
48+
var commandLine = [Job.ArgTemplate]()
49+
commandLine.appendFlag("-dump-sdk")
50+
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+
if let arg = parsedOptions.getLastArgument(.serializeBreakingChangesPath)?.asSingle {
79+
let path = try VirtualPath.intern(path: arg)
80+
commandLine.appendFlag("-serialize-diagnostics-path")
81+
commandLine.appendPath(VirtualPath.lookup(path))
82+
serializedDiagnosticsPath = path
83+
}
84+
if let arg = parsedOptions.getLastArgument(.digesterBreakageAllowlistPath)?.asSingle {
85+
let path = try VirtualPath(path: arg)
86+
commandLine.appendFlag("-breakage-allowlist-path")
87+
commandLine.appendPath(path)
88+
}
89+
90+
var inputs: [TypedVirtualPath] = [.init(file: modulePath, type: .swiftModule),
91+
.init(file: baselinePath, type: mode.baselineFileType)]
92+
// If a module interface was emitted, treat it as an input in ABI mode.
93+
if let interfacePath = self.swiftInterfacePath, mode == .abi {
94+
inputs.append(.init(file: interfacePath, type: .swiftInterface))
95+
}
96+
97+
return Job(
98+
moduleName: moduleOutputInfo.name,
99+
kind: mode.baselineComparisonJobKind,
100+
tool: .absolute(try toolchain.getToolPath(.swiftAPIDigester)),
101+
commandLine: commandLine,
102+
inputs: inputs,
103+
primaryInputs: [],
104+
outputs: [.init(file: serializedDiagnosticsPath ?? VirtualPath.Handle.standardOutput, type: .diagnostics)],
105+
supportsResponseFiles: true
106+
)
107+
}
108+
109+
private mutating func addCommonDigesterOptions(_ commandLine: inout [Job.ArgTemplate],
110+
modulePath: VirtualPath.Handle,
111+
mode: DigesterMode) throws {
112+
commandLine.appendFlag("-module")
113+
commandLine.appendFlag(moduleOutputInfo.name)
114+
if mode == .abi {
115+
commandLine.appendFlag("-abi")
116+
commandLine.appendFlag("-use-interface-for-module")
117+
commandLine.appendFlag(moduleOutputInfo.name)
118+
}
119+
120+
// Add a search path for the emitted module, and its module interface if there is one.
121+
let searchPath = VirtualPath.lookup(modulePath).parentDirectory
122+
commandLine.appendFlag(.I)
123+
commandLine.appendPath(searchPath)
124+
if let interfacePath = self.swiftInterfacePath {
125+
let interfaceSearchPath = VirtualPath.lookup(interfacePath).parentDirectory
126+
if interfaceSearchPath != searchPath {
127+
commandLine.appendFlag(.I)
128+
commandLine.appendPath(interfaceSearchPath)
129+
}
130+
}
131+
132+
commandLine.appendFlag(.target)
133+
commandLine.appendFlag(targetTriple.triple)
134+
135+
if let sdkPath = frontendTargetInfo.sdkPath?.path {
136+
commandLine.appendFlag(.sdk)
137+
commandLine.append(.path(VirtualPath.lookup(sdkPath)))
138+
}
139+
140+
commandLine.appendFlag(.resourceDir)
141+
commandLine.appendPath(VirtualPath.lookup(frontendTargetInfo.runtimeResourcePath.path))
142+
143+
try commandLine.appendAll(.I, from: &parsedOptions)
144+
try commandLine.appendAll(.F, from: &parsedOptions)
145+
for systemFramework in parsedOptions.arguments(for: .Fsystem) {
146+
commandLine.appendFlag(.iframework)
147+
commandLine.appendFlag(systemFramework.argument.asSingle)
148+
}
149+
150+
try commandLine.appendLast(.swiftVersion, from: &parsedOptions)
151+
}
152+
}

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ extension Driver {
9393
.privateSwiftInterface, .swiftSourceInfoFile, .diagnostics, .objcHeader, .swiftDeps,
9494
.remap, .tbd, .moduleTrace, .yamlOptimizationRecord, .bitstreamOptimizationRecord, .pcm,
9595
.pch, .clangModuleMap, .jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts,
96-
.indexUnitOutputPath, .modDepCache, nil:
96+
.indexUnitOutputPath, .modDepCache, .jsonAPIBaseline, .jsonABIBaseline, nil:
9797
return false
9898
}
9999
}
@@ -444,7 +444,7 @@ extension FileType {
444444
.diagnostics, .objcHeader, .image, .swiftDeps, .moduleTrace, .tbd,
445445
.yamlOptimizationRecord, .bitstreamOptimizationRecord, .swiftInterface,
446446
.privateSwiftInterface, .swiftSourceInfoFile, .clangModuleMap, .jsonSwiftArtifacts,
447-
.indexUnitOutputPath, .modDepCache:
447+
.indexUnitOutputPath, .modDepCache, .jsonAPIBaseline, .jsonABIBaseline:
448448
fatalError("Output type can never be a primary output")
449449
}
450450
}

0 commit comments

Comments
 (0)