@@ -15,12 +15,12 @@ import Foundation
15
15
import SKCore
16
16
17
17
import struct TSCBasic. AbsolutePath
18
+ import class TSCBasic. Process
18
19
19
20
public struct DiagnoseCommand : AsyncParsableCommand {
20
21
public static var configuration : CommandConfiguration = CommandConfiguration (
21
22
commandName: " diagnose " ,
22
- abstract: " Reduce sourcekitd crashes " ,
23
- shouldDisplay: false
23
+ abstract: " Creates a bundle containing information that help diagnose issues with sourcekit-lsp "
24
24
)
25
25
26
26
@Option (
@@ -56,15 +56,19 @@ public struct DiagnoseCommand: AsyncParsableCommand {
56
56
var predicate : String ?
57
57
#endif
58
58
59
+ var toolchainRegistry : ToolchainRegistry {
60
+ get throws {
61
+ let installPath = try AbsolutePath ( validating: Bundle . main. bundlePath)
62
+ return ToolchainRegistry ( installPath: installPath)
63
+ }
64
+ }
65
+
59
66
var sourcekitd : String ? {
60
67
get async throws {
61
68
if let sourcekitdOverride {
62
69
return sourcekitdOverride
63
70
}
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
68
72
}
69
73
}
70
74
@@ -83,16 +87,19 @@ public struct DiagnoseCommand: AsyncParsableCommand {
83
87
84
88
public init ( ) { }
85
89
86
- public func run ( ) async throws {
90
+ private func addSourcekitdCrashReproducer ( toBundle bundlePath : URL ) async throws {
87
91
guard let sourcekitd = try await sourcekitd else {
88
92
throw ReductionError ( " Unable to find sourcekitd.framework " )
89
93
}
90
94
91
- var reproducerBundle : URL ?
92
95
for (name, requestInfo) in try requestInfos ( ) {
93
- print ( " -- Diagnosing \( name) " )
96
+ print ( " -- Reducing \( name) " )
94
97
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
+ )
96
103
// If reduce didn't throw, we have found a reproducer. Stop.
97
104
// Looking further probably won't help because other crashes are likely the same cause.
98
105
break
@@ -101,26 +108,132 @@ public struct DiagnoseCommand: AsyncParsableCommand {
101
108
print ( error)
102
109
}
103
110
}
111
+ }
104
112
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)
108
119
}
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 {
109
199
print (
110
200
"""
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
+ )
115
213
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)
118
231
"""
119
232
)
120
233
121
234
}
122
235
123
- private func reduce( requestInfo: RequestInfo , sourcekitd: String ) async throws -> URL {
236
+ private func reduce( requestInfo: RequestInfo , sourcekitd: String , bundlePath : URL ) async throws {
124
237
var requestInfo = requestInfo
125
238
var nspredicate : NSPredicate ? = nil
126
239
#if canImport(Darwin)
@@ -135,6 +248,6 @@ public struct DiagnoseCommand: AsyncParsableCommand {
135
248
requestInfo = try await requestInfo. reduceInputFile ( using: executor)
136
249
requestInfo = try await requestInfo. reduceCommandLineArguments ( using: executor)
137
250
138
- return try makeReproducerBundle ( for: requestInfo)
251
+ try makeReproducerBundle ( for: requestInfo, bundlePath : bundlePath )
139
252
}
140
253
}
0 commit comments