Skip to content

Commit e6080e0

Browse files
authored
Merge pull request #1084 from ahoppen/ahoppen/more-diagnose-info
Add more information to the diagnostic bundle
2 parents 747d2f3 + 2584ba5 commit e6080e0

File tree

3 files changed

+137
-30
lines changed

3 files changed

+137
-30
lines changed

Sources/Diagnose/DiagnoseCommand.swift

Lines changed: 134 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import Foundation
1515
import SKCore
1616

1717
import struct TSCBasic.AbsolutePath
18+
import class TSCBasic.Process
1819

1920
public struct DiagnoseCommand: AsyncParsableCommand {
2021
public static var configuration: CommandConfiguration = CommandConfiguration(
2122
commandName: "diagnose",
22-
abstract: "Reduce sourcekitd crashes",
23-
shouldDisplay: false
23+
abstract: "Creates a bundle containing information that help diagnose issues with sourcekit-lsp"
2424
)
2525

2626
@Option(
@@ -56,15 +56,19 @@ public struct DiagnoseCommand: AsyncParsableCommand {
5656
var predicate: String?
5757
#endif
5858

59+
var toolchainRegistry: ToolchainRegistry {
60+
get throws {
61+
let installPath = try AbsolutePath(validating: Bundle.main.bundlePath)
62+
return ToolchainRegistry(installPath: installPath)
63+
}
64+
}
65+
5966
var sourcekitd: String? {
6067
get async throws {
6168
if let sourcekitdOverride {
6269
return sourcekitdOverride
6370
}
64-
65-
let installPath = try AbsolutePath(validating: Bundle.main.bundlePath)
66-
let toolchainRegistry = ToolchainRegistry(installPath: installPath)
67-
return await toolchainRegistry.default?.sourcekitd?.pathString
71+
return try await toolchainRegistry.default?.sourcekitd?.pathString
6872
}
6973
}
7074

@@ -83,16 +87,19 @@ public struct DiagnoseCommand: AsyncParsableCommand {
8387

8488
public init() {}
8589

86-
public func run() async throws {
90+
private func addSourcekitdCrashReproducer(toBundle bundlePath: URL) async throws {
8791
guard let sourcekitd = try await sourcekitd else {
8892
throw ReductionError("Unable to find sourcekitd.framework")
8993
}
9094

91-
var reproducerBundle: URL?
9295
for (name, requestInfo) in try requestInfos() {
93-
print("-- Diagnosing \(name)")
96+
print("-- Reducing \(name)")
9497
do {
95-
reproducerBundle = try await reduce(requestInfo: requestInfo, sourcekitd: sourcekitd)
98+
try await reduce(
99+
requestInfo: requestInfo,
100+
sourcekitd: sourcekitd,
101+
bundlePath: bundlePath.appendingPathComponent("reproducer")
102+
)
96103
// If reduce didn't throw, we have found a reproducer. Stop.
97104
// Looking further probably won't help because other crashes are likely the same cause.
98105
break
@@ -101,26 +108,132 @@ public struct DiagnoseCommand: AsyncParsableCommand {
101108
print(error)
102109
}
103110
}
111+
}
104112

105-
guard let reproducerBundle else {
106-
print("No reducible crashes found")
107-
throw ExitCode(1)
113+
/// Execute body and if it throws, log the error.
114+
private func orPrintError(_ body: () async throws -> Void) async {
115+
do {
116+
try await body()
117+
} catch {
118+
print(error)
108119
}
120+
}
121+
122+
private func addOsLog(toBundle bundlePath: URL) async throws {
123+
#if os(macOS)
124+
print("-- Collecting log messages")
125+
let outputFileUrl = bundlePath.appendingPathComponent("log.txt")
126+
FileManager.default.createFile(atPath: outputFileUrl.path, contents: nil)
127+
let fileHandle = try FileHandle(forWritingTo: outputFileUrl)
128+
let process = Process(
129+
arguments: [
130+
"/usr/bin/log",
131+
"show",
132+
"--predicate", #"subsystem = "org.swift.sourcekit-lsp" AND process = "sourcekit-lsp""#,
133+
"--info",
134+
"--debug",
135+
],
136+
outputRedirection: .stream(
137+
stdout: { try? fileHandle.write(contentsOf: $0) },
138+
stderr: { _ in }
139+
)
140+
)
141+
try process.launch()
142+
try await process.waitUntilExit()
143+
#endif
144+
}
145+
146+
private func addCrashLogs(toBundle bundlePath: URL) throws {
147+
#if os(macOS)
148+
print("-- Collecting crash reports")
149+
150+
let destinationDir = bundlePath.appendingPathComponent("crashes")
151+
try FileManager.default.createDirectory(at: destinationDir, withIntermediateDirectories: true)
152+
153+
let processesToIncludeCrashReportsOf = ["SourceKitService", "sourcekit-lsp", "swift-frontend"]
154+
let directoriesToScanForCrashReports = ["/Library/Logs/DiagnosticReports", "~/Library/Logs/DiagnosticReports"]
155+
156+
for directoryToScan in directoriesToScanForCrashReports {
157+
let diagnosticReports = URL(filePath: (directoryToScan as NSString).expandingTildeInPath)
158+
let enumerator = FileManager.default.enumerator(at: diagnosticReports, includingPropertiesForKeys: nil)
159+
while let fileUrl = enumerator?.nextObject() as? URL {
160+
guard processesToIncludeCrashReportsOf.contains(where: { fileUrl.lastPathComponent.hasPrefix($0) }) else {
161+
continue
162+
}
163+
try? FileManager.default.copyItem(
164+
at: fileUrl,
165+
to: destinationDir.appendingPathComponent(fileUrl.lastPathComponent)
166+
)
167+
}
168+
}
169+
#endif
170+
}
171+
172+
private func addSwiftVersion(toBundle bundlePath: URL) async throws {
173+
print("-- Collecting installed Swift versions")
174+
175+
let outputFileUrl = bundlePath.appendingPathComponent("swift-versions.txt")
176+
FileManager.default.createFile(atPath: outputFileUrl.path, contents: nil)
177+
let fileHandle = try FileHandle(forWritingTo: outputFileUrl)
178+
179+
for toolchain in try await toolchainRegistry.toolchains {
180+
guard let swiftUrl = toolchain.swift?.asURL else {
181+
continue
182+
}
183+
184+
try fileHandle.write(contentsOf: "\(swiftUrl.path) --version\n".data(using: .utf8)!)
185+
let process = Process(
186+
arguments: [swiftUrl.path, "--version"],
187+
outputRedirection: .stream(
188+
stdout: { try? fileHandle.write(contentsOf: $0) },
189+
stderr: { _ in }
190+
)
191+
)
192+
try process.launch()
193+
try await process.waitUntilExit()
194+
fileHandle.write("\n".data(using: .utf8)!)
195+
}
196+
}
197+
198+
public func run() async throws {
109199
print(
110200
"""
111-
----------------------------------------
112-
Reduced SourceKit issue and created a bundle that contains a reduced sourcekitd request exhibiting the issue
113-
and all the files referenced from the request.
114-
The information in this bundle should be sufficient to reproduce the issue.
201+
sourcekit-lsp diagnose collects information that helps the developers of sourcekit-lsp diagnose and fix issues.
202+
This information contains:
203+
- Crash logs from SourceKit
204+
- Log messages emitted by SourceKit
205+
- Versions of Swift installed on your system
206+
- If possible, a minimized project that caused SourceKit to crash
207+
208+
All information is collected locally.
209+
The collection might take a few minutes.
210+
----------------------------------------
211+
"""
212+
)
115213

116-
Please file an issue at https://github.com/apple/sourcekit-lsp/issues/new and attach the bundle located at
117-
\(reproducerBundle.path)
214+
let date = ISO8601DateFormatter().string(from: Date()).replacingOccurrences(of: ":", with: "-")
215+
let bundlePath = FileManager.default.temporaryDirectory
216+
.appendingPathComponent("sourcekitd-reproducer-\(date)")
217+
try FileManager.default.createDirectory(at: bundlePath, withIntermediateDirectories: true)
218+
219+
await orPrintError { try addCrashLogs(toBundle: bundlePath) }
220+
await orPrintError { try await addOsLog(toBundle: bundlePath) }
221+
await orPrintError { try await addSwiftVersion(toBundle: bundlePath) }
222+
await orPrintError { try await addSourcekitdCrashReproducer(toBundle: bundlePath) }
223+
224+
print(
225+
"""
226+
----------------------------------------
227+
Bundle created.
228+
When filing an issue at https://github.com/apple/sourcekit-lsp/issues/new,
229+
please attach the bundle located at
230+
\(bundlePath.path)
118231
"""
119232
)
120233

121234
}
122235

123-
private func reduce(requestInfo: RequestInfo, sourcekitd: String) async throws -> URL {
236+
private func reduce(requestInfo: RequestInfo, sourcekitd: String, bundlePath: URL) async throws {
124237
var requestInfo = requestInfo
125238
var nspredicate: NSPredicate? = nil
126239
#if canImport(Darwin)
@@ -135,6 +248,6 @@ public struct DiagnoseCommand: AsyncParsableCommand {
135248
requestInfo = try await requestInfo.reduceInputFile(using: executor)
136249
requestInfo = try await requestInfo.reduceCommandLineArguments(using: executor)
137250

138-
return try makeReproducerBundle(for: requestInfo)
251+
try makeReproducerBundle(for: requestInfo, bundlePath: bundlePath)
139252
}
140253
}

Sources/Diagnose/OSLogScraper.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ struct OSLogScraper {
8181
///
8282
/// Name is a human readable name that identifies the crash.
8383
func getCrashedRequests() throws -> [(name: String, info: RequestInfo)] {
84-
let crashedReqeusts = try crashedSourceKitLSPRequests().reversed()
85-
return try crashedReqeusts.map { ($0.name, try requestInfo(for: $0.logCategory)) }
84+
let crashedRequests = try crashedSourceKitLSPRequests().reversed()
85+
return try crashedRequests.map { ($0.name, try requestInfo(for: $0.logCategory)) }
8686
}
8787
}
8888
#endif

Sources/Diagnose/ReproducerBundle.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@
1313
import Foundation
1414

1515
/// Create a folder that contains all files that should be necessary to reproduce a sourcekitd crash.
16-
func makeReproducerBundle(for requestInfo: RequestInfo) throws -> URL {
17-
let date = ISO8601DateFormatter().string(from: Date()).replacingOccurrences(of: ":", with: "-")
18-
let bundlePath = FileManager.default.temporaryDirectory
19-
.appendingPathComponent("sourcekitd-reproducer-\(date)")
20-
try FileManager.default.createDirectory(at: bundlePath, withIntermediateDirectories: true)
21-
16+
func makeReproducerBundle(for requestInfo: RequestInfo, bundlePath: URL) throws {
2217
try requestInfo.fileContents.write(
2318
to: bundlePath.appendingPathComponent("input.swift"),
2419
atomically: true,
@@ -41,5 +36,4 @@ func makeReproducerBundle(for requestInfo: RequestInfo) throws -> URL {
4136
try? FileManager.default.copyItem(at: URL(fileURLWithPath: compilerArg), to: dest)
4237
}
4338
}
44-
return bundlePath
4539
}

0 commit comments

Comments
 (0)