Skip to content

Commit b3d2df7

Browse files
authored
Merge pull request #1122 from ahoppen/ahoppen/reduce-command
Create separate `reduce` subcommand to reduce an exiting sourcekitd request
2 parents 6efe162 + 96a0e7e commit b3d2df7

File tree

4 files changed

+121
-15
lines changed

4 files changed

+121
-15
lines changed

Sources/Diagnose/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_library(Diagnose STATIC
22
CommandLineArgumentsReducer.swift
33
DiagnoseCommand.swift
44
OSLogScraper.swift
5+
ReduceCommand.swift
56
ReductionError.swift
67
ReproducerBundle.swift
78
RequestInfo.swift

Sources/Diagnose/DiagnoseCommand.swift

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@ public struct DiagnoseCommand: AsyncParsableCommand {
3131
abstract: "Creates a bundle containing information that help diagnose issues with sourcekit-lsp"
3232
)
3333

34-
@Option(
35-
name: .customLong("request-file"),
36-
help:
37-
"Path to a sourcekitd request. If not specified, the command will look for crashed sourcekitd requests that have been logged to OSLog"
38-
)
39-
var sourcekitdRequestPath: String?
40-
4134
@Option(
4235
name: .customLong("os-log-history"),
4336
help: "If no request file is passed, how many minutes of OS Log history should be scraped for a crash."
@@ -46,8 +39,10 @@ public struct DiagnoseCommand: AsyncParsableCommand {
4639

4740
@Option(
4841
name: .customLong("sourcekitd"),
49-
help:
50-
"Path to sourcekitd.framework/sourcekitd. If not specified, the toolchain is found in the same way that sourcekit-lsp finds it"
42+
help: """
43+
Path to sourcekitd.framework/sourcekitd. \
44+
If not specified, the toolchain is found in the same way that sourcekit-lsp finds it
45+
"""
5146
)
5247
var sourcekitdOverride: String?
5348

@@ -82,14 +77,10 @@ public struct DiagnoseCommand: AsyncParsableCommand {
8277

8378
/// Request infos of crashes that should be diagnosed.
8479
func requestInfos() throws -> [(name: String, info: RequestInfo)] {
85-
if let sourcekitdRequestPath {
86-
let request = try String(contentsOfFile: sourcekitdRequestPath)
87-
return [(sourcekitdRequestPath, try RequestInfo(request: request))]
88-
}
8980
#if canImport(OSLog)
9081
return try OSLogScraper(searchDuration: TimeInterval(osLogScrapeDuration * 60)).getCrashedRequests()
9182
#else
92-
throw ReductionError("--request-file must be specified on all platforms other than macOS")
83+
throw ReductionError("Reduction of sourcekitd crashes is not supported on platforms other than macOS")
9384
#endif
9485
}
9586

@@ -259,7 +250,6 @@ public struct DiagnoseCommand: AsyncParsableCommand {
259250
await orPrintError { try await addSourcekitdCrashReproducer(toBundle: bundlePath) }
260251

261252
progressBar?.complete(success: true)
262-
progressBar?.clear()
263253

264254
print(
265255
"""

Sources/Diagnose/ReduceCommand.swift

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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 ArgumentParser
14+
import Foundation
15+
import SKCore
16+
17+
import struct TSCBasic.AbsolutePath
18+
import class TSCBasic.Process
19+
import var TSCBasic.stderrStream
20+
import class TSCUtility.PercentProgressAnimation
21+
22+
public struct ReduceCommand: AsyncParsableCommand {
23+
public static var configuration: CommandConfiguration = CommandConfiguration(
24+
commandName: "reduce",
25+
abstract: "Reduce a single sourcekitd crash",
26+
shouldDisplay: false
27+
)
28+
29+
@Option(name: .customLong("request-file"), help: "Path to a sourcekitd request to reduce.")
30+
var sourcekitdRequestPath: String
31+
32+
@Option(
33+
name: .customLong("sourcekitd"),
34+
help: """
35+
Path to sourcekitd.framework/sourcekitd. \
36+
If not specified, the toolchain is found in the same way that sourcekit-lsp finds it
37+
"""
38+
)
39+
var sourcekitdOverride: String?
40+
41+
#if canImport(Darwin)
42+
// Creating an NSPredicate from a string is not supported in corelibs-foundation.
43+
@Option(
44+
help: """
45+
If the sourcekitd response matches this predicate, consider it as reproducing the issue.
46+
sourcekitd crashes are always considered as reproducers.
47+
48+
The predicate is an NSPredicate and `self` is the sourcekitd response.
49+
"""
50+
)
51+
var predicate: String?
52+
#endif
53+
54+
var toolchainRegistry: ToolchainRegistry {
55+
get throws {
56+
let installPath = try AbsolutePath(validating: Bundle.main.bundlePath)
57+
return ToolchainRegistry(installPath: installPath)
58+
}
59+
}
60+
61+
var sourcekitd: String? {
62+
get async throws {
63+
if let sourcekitdOverride {
64+
return sourcekitdOverride
65+
}
66+
return try await toolchainRegistry.default?.sourcekitd?.pathString
67+
}
68+
}
69+
70+
public init() {}
71+
72+
public func run() async throws {
73+
guard let sourcekitd = try await sourcekitd else {
74+
throw ReductionError("Unable to find sourcekitd.framework")
75+
}
76+
77+
let progressBar = PercentProgressAnimation(stream: stderrStream, header: "Reducing sourcekitd issue")
78+
79+
let request = try String(contentsOfFile: sourcekitdRequestPath)
80+
var requestInfo = try RequestInfo(request: request)
81+
82+
var nspredicate: NSPredicate? = nil
83+
#if canImport(Darwin)
84+
if let predicate {
85+
nspredicate = NSPredicate(format: predicate)
86+
}
87+
#endif
88+
let executor = OutOfProcessSourceKitRequestExecutor(
89+
sourcekitd: URL(fileURLWithPath: sourcekitd),
90+
reproducerPredicate: nspredicate
91+
)
92+
93+
// How much time of the reduction is expected to be spent reducing the source compared to command line argument
94+
// reduction.
95+
let sourceReductionPercentage = 0.7
96+
97+
requestInfo = try await requestInfo.reduceInputFile(using: executor) { progress, message in
98+
let progress = progress * sourceReductionPercentage
99+
progressBar.update(step: Int(progress * 100), total: 100, text: message)
100+
}
101+
requestInfo = try await requestInfo.reduceCommandLineArguments(using: executor) { progress, message in
102+
let progress = sourceReductionPercentage + progress * (1 - sourceReductionPercentage)
103+
progressBar.update(step: Int(progress * 100), total: 100, text: message)
104+
}
105+
106+
progressBar.complete(success: true)
107+
108+
let reducedSourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("reduced.swift")
109+
try requestInfo.fileContents.write(to: reducedSourceFile, atomically: true, encoding: .utf8)
110+
111+
print("Reduced Request:")
112+
print(try requestInfo.request(for: reducedSourceFile))
113+
}
114+
}

Sources/sourcekit-lsp/SourceKitLSP.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ struct SourceKitLSP: AsyncParsableCommand {
105105
abstract: "Language Server Protocol implementation for Swift and C-based languages",
106106
subcommands: [
107107
DiagnoseCommand.self,
108+
ReduceCommand.self,
108109
SourceKitdRequestCommand.self,
109110
]
110111
)

0 commit comments

Comments
 (0)