Skip to content

Commit 1aece47

Browse files
authored
Merge pull request #621 from swiftlang/owenv/digester-downgrade
Allow downgrading API breakage errors to warnings
2 parents 8888752 + 675cd2b commit 1aece47

File tree

3 files changed

+151
-5
lines changed

3 files changed

+151
-5
lines changed

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,7 @@ public final class BuiltinMacros {
998998
public static let RPATH_ORIGIN = BuiltinMacros.declareStringMacro("RPATH_ORIGIN")
999999
public static let PLATFORM_USES_DSYMS = BuiltinMacros.declareBooleanMacro("PLATFORM_USES_DSYMS")
10001000
public static let SWIFT_ABI_CHECKER_BASELINE_DIR = BuiltinMacros.declareStringMacro("SWIFT_ABI_CHECKER_BASELINE_DIR")
1001+
public static let SWIFT_ABI_CHECKER_DOWNGRADE_ERRORS = BuiltinMacros.declareBooleanMacro("SWIFT_ABI_CHECKER_DOWNGRADE_ERRORS")
10011002
public static let SWIFT_ABI_CHECKER_EXCEPTIONS_FILE = BuiltinMacros.declareStringMacro("SWIFT_ABI_CHECKER_EXCEPTIONS_FILE")
10021003
public static let SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR = BuiltinMacros.declareStringMacro("SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR")
10031004
public static let SWIFT_ACCESS_NOTES_PATH = BuiltinMacros.declareStringMacro("SWIFT_ACCESS_NOTES_PATH")
@@ -2174,6 +2175,7 @@ public final class BuiltinMacros {
21742175
RPATH_ORIGIN,
21752176
PLATFORM_USES_DSYMS,
21762177
SWIFT_ABI_CHECKER_BASELINE_DIR,
2178+
SWIFT_ABI_CHECKER_DOWNGRADE_ERRORS,
21772179
SWIFT_ABI_CHECKER_EXCEPTIONS_FILE,
21782180
SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR,
21792181
SWIFT_ACCESS_NOTES_PATH,

Sources/SWBCore/SpecImplementations/Tools/SwiftABICheckerTool.swift

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,22 @@ public final class SwiftABICheckerToolSpec : GenericCommandLineToolSpec, SpecIde
4242
/// The path to the serialized diagnostic output. Every clang task must provide this path.
4343
let serializedDiagnosticsPath: Path
4444

45-
init(serializedDiagnosticsPath: Path) {
45+
let downgradeErrors: Bool
46+
47+
init(serializedDiagnosticsPath: Path, downgradeErrors: Bool) {
4648
self.serializedDiagnosticsPath = serializedDiagnosticsPath
49+
self.downgradeErrors = downgradeErrors
4750
}
4851
public func serialize<T: Serializer>(to serializer: T) {
49-
serializer.serializeAggregate(1) {
52+
serializer.serializeAggregate(2) {
5053
serializer.serialize(serializedDiagnosticsPath)
54+
serializer.serialize(downgradeErrors)
5155
}
5256
}
5357
public init(from deserializer: any Deserializer) throws {
54-
try deserializer.beginAggregate(1)
58+
try deserializer.beginAggregate(2)
5559
self.serializedDiagnosticsPath = try deserializer.deserialize()
60+
self.downgradeErrors = try deserializer.deserialize()
5661
}
5762
}
5863

@@ -67,7 +72,12 @@ public final class SwiftABICheckerToolSpec : GenericCommandLineToolSpec, SpecIde
6772

6873
// Override this func to ensure we can see these diagnostics in unit tests.
6974
public override func customOutputParserType(for task: any ExecutableTask) -> (any TaskOutputParser.Type)? {
70-
return SerializedDiagnosticsOutputParser.self
75+
let payload = task.payload! as! ABICheckerPayload
76+
if payload.downgradeErrors {
77+
return APIDigesterDowngradingSerializedDiagnosticsOutputParser.self
78+
} else {
79+
return SerializedDiagnosticsOutputParser.self
80+
}
7181
}
7282
public func constructABICheckingTask(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, _ serializedDiagsPath: Path, _ baselinePath: Path?, _ allowlistPath: Path?) async {
7383
let toolSpecInfo: DiscoveredSwiftCompilerToolSpecInfo
@@ -86,6 +96,10 @@ public final class SwiftABICheckerToolSpec : GenericCommandLineToolSpec, SpecIde
8696
if let allowlistPath {
8797
commandLine += ["-breakage-allowlist-path", allowlistPath.normalize().str]
8898
}
99+
let downgradeErrors = cbc.scope.evaluate(BuiltinMacros.SWIFT_ABI_CHECKER_DOWNGRADE_ERRORS)
100+
if downgradeErrors {
101+
commandLine += ["-disable-fail-on-error"]
102+
}
89103
let allInputs = cbc.inputs.map { delegate.createNode($0.absolutePath) } + [baselinePath, allowlistPath].compactMap { $0 }.map { delegate.createNode($0.normalize()) }
90104
// Add import search paths
91105
for searchPath in SwiftCompilerSpec.collectInputSearchPaths(cbc, toolInfo: toolSpecInfo) {
@@ -95,7 +109,10 @@ public final class SwiftABICheckerToolSpec : GenericCommandLineToolSpec, SpecIde
95109
commandLine += cbc.scope.evaluate(BuiltinMacros.SWIFT_SYSTEM_INCLUDE_PATHS).flatMap { ["-I", $0] }
96110
commandLine += cbc.scope.evaluate(BuiltinMacros.SYSTEM_FRAMEWORK_SEARCH_PATHS).flatMap { ["-F", $0] }
97111
delegate.createTask(type: self,
98-
payload: ABICheckerPayload(serializedDiagnosticsPath: serializedDiagsPath),
112+
payload: ABICheckerPayload(
113+
serializedDiagnosticsPath: serializedDiagsPath,
114+
downgradeErrors: downgradeErrors
115+
),
99116
ruleInfo: defaultRuleInfo(cbc, delegate),
100117
commandLine: commandLine,
101118
environment: environmentFromSpec(cbc, delegate),
@@ -105,3 +122,40 @@ public final class SwiftABICheckerToolSpec : GenericCommandLineToolSpec, SpecIde
105122
enableSandboxing: enableSandboxing)
106123
}
107124
}
125+
126+
public final class APIDigesterDowngradingSerializedDiagnosticsOutputParser: TaskOutputParser {
127+
private let task: any ExecutableTask
128+
129+
public let workspaceContext: WorkspaceContext
130+
public let buildRequestContext: BuildRequestContext
131+
public let delegate: any TaskOutputParserDelegate
132+
133+
required public init(for task: any ExecutableTask, workspaceContext: WorkspaceContext, buildRequestContext: BuildRequestContext, delegate: any TaskOutputParserDelegate, progressReporter: (any SubtaskProgressReporter)?) {
134+
self.task = task
135+
self.workspaceContext = workspaceContext
136+
self.buildRequestContext = buildRequestContext
137+
self.delegate = delegate
138+
}
139+
140+
public func write(bytes: ByteString) {
141+
// Forward the unparsed bytes immediately (without line buffering).
142+
delegate.emitOutput(bytes)
143+
144+
// Disable diagnostic scraping, since we use serialized diagnostics.
145+
}
146+
147+
public func close(result: TaskResult?) {
148+
defer {
149+
delegate.close()
150+
}
151+
// Don't try to read diagnostics if the process crashed or got cancelled as they were almost certainly not written in this case.
152+
if result.shouldSkipParsingDiagnostics { return }
153+
154+
for path in task.type.serializedDiagnosticsPaths(task, workspaceContext.fs) {
155+
let diagnostics = delegate.readSerializedDiagnostics(at: path, workingDirectory: task.workingDirectory, workspaceContext: workspaceContext)
156+
for diagnostic in diagnostics {
157+
delegate.diagnosticsEngine.emit(diagnostic.with(behavior: diagnostic.behavior == .error ? .warning : diagnostic.behavior))
158+
}
159+
}
160+
}
161+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Testing
14+
import Foundation
15+
16+
import SWBBuildSystem
17+
import SWBCore
18+
import SWBTestSupport
19+
import SWBTaskExecution
20+
import SWBUtil
21+
import SWBProtocol
22+
23+
@Suite
24+
fileprivate struct APIDigesterBuildOperationTests: CoreBasedTests {
25+
@Test(.requireSDKs(.host), .skipHostOS(.windows, "Windows toolchains are missing swift-api-digester"))
26+
func apiDigesterDisableFailOnError() async throws {
27+
try await withTemporaryDirectory { (tmpDir: Path) in
28+
let testProject = try await TestProject(
29+
"TestProject",
30+
sourceRoot: tmpDir,
31+
groupTree: TestGroup(
32+
"SomeFiles",
33+
children: [
34+
TestFile("foo.swift"),
35+
]),
36+
buildConfigurations: [
37+
TestBuildConfiguration("Debug", buildSettings: [
38+
"ARCHS": "$(ARCHS_STANDARD)",
39+
"PRODUCT_NAME": "$(TARGET_NAME)",
40+
"SDKROOT": "$(HOST_PLATFORM)",
41+
"SUPPORTED_PLATFORMS": "$(HOST_PLATFORM)",
42+
"SWIFT_VERSION": swiftVersion,
43+
"CODE_SIGNING_ALLOWED": "NO",
44+
])
45+
],
46+
targets: [
47+
TestStandardTarget(
48+
"foo",
49+
type: .dynamicLibrary,
50+
buildConfigurations: [
51+
TestBuildConfiguration("Debug", buildSettings: [:])
52+
],
53+
buildPhases: [
54+
TestSourcesBuildPhase(["foo.swift"]),
55+
]
56+
),
57+
])
58+
let core = try await getCore()
59+
let tester = try await BuildOperationTester(core, testProject, simulated: false)
60+
61+
let projectDir = tester.workspace.projects[0].sourceRoot
62+
63+
try await tester.fs.writeFileContents(projectDir.join("foo.swift")) { stream in
64+
stream <<< "public func foo() -> Int { 42 }"
65+
}
66+
67+
try await tester.checkBuild(parameters: BuildParameters(configuration: "Debug", overrides: [
68+
"RUN_SWIFT_ABI_GENERATION_TOOL": "YES",
69+
"SWIFT_API_DIGESTER_MODE": "api",
70+
"SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR": tmpDir.join("baseline").join("ABI").str,
71+
]), runDestination: .host) { results in
72+
results.checkNoErrors()
73+
}
74+
75+
try await tester.fs.writeFileContents(projectDir.join("foo.swift")) { stream in
76+
stream <<< "public func foo() -> String { \"hello, world!\" }"
77+
}
78+
79+
try await tester.checkBuild(parameters: BuildParameters(configuration: "Debug", overrides: [
80+
"RUN_SWIFT_ABI_CHECKER_TOOL": "YES",
81+
"SWIFT_API_DIGESTER_MODE": "api",
82+
"SWIFT_ABI_CHECKER_BASELINE_DIR": tmpDir.join("baseline").str,
83+
"SWIFT_ABI_CHECKER_DOWNGRADE_ERRORS": "YES",
84+
]), runDestination: .host) { results in
85+
results.checkWarning(.contains("func foo() has return type change from Swift.Int to Swift.String"))
86+
results.checkNoDiagnostics()
87+
}
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)