Skip to content

Commit 332ca30

Browse files
committed
[Explicit Module Builds] Add support for creating a reproducer when clang process crashes.
For the explicitly built modules generated script contains commands how to build all necessary .pcm files and how to compile the final translation unit. rdar://59743925
1 parent 1d66c4f commit 332ca30

File tree

6 files changed

+119
-13
lines changed

6 files changed

+119
-13
lines changed

Sources/SWBCSupport/CLibclang.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,12 @@ extern "C" {
859859
typedef struct CXOpaqueDependencyScannerServiceOptions
860860
*CXDependencyScannerServiceOptions;
861861

862+
enum CXErrorCode
863+
(*clang_experimental_DependencyScanner_generateReproducer)(
864+
int argc, const char *const *argv, const char *WorkingDirectory,
865+
const char *ReproducerLocation, bool UseUniqueReproducerName,
866+
CXString *messageOut);
867+
862868
/**
863869
* Creates a default set of service options.
864870
* Must be disposed with \c
@@ -1423,6 +1429,7 @@ struct LibclangWrapper {
14231429
LOOKUP_OPTIONAL(clang_experimental_cas_replayCompilation);
14241430
LOOKUP_OPTIONAL(clang_experimental_cas_ReplayResult_dispose);
14251431
LOOKUP_OPTIONAL(clang_experimental_cas_ReplayResult_getStderr);
1432+
LOOKUP_OPTIONAL(clang_experimental_DependencyScanner_generateReproducer);
14261433
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerServiceOptions_create);
14271434
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerServiceOptions_dispose);
14281435
LOOKUP_OPTIONAL(clang_experimental_DependencyScannerServiceOptions_setDependencyMode);
@@ -1775,6 +1782,10 @@ extern "C" {
17751782
lib->wrapper->fns.clang_experimental_DependencyScannerServiceOptions_setCWDOptimization;
17761783
}
17771784

1785+
bool libclang_has_reproducer_feature(libclang_t lib) {
1786+
return lib->wrapper->fns.clang_experimental_DependencyScanner_generateReproducer;
1787+
}
1788+
17781789
libclang_casoptions_t libclang_casoptions_create(libclang_t lib) {
17791790
auto opts = lib->wrapper->fns.clang_experimental_cas_Options_create();
17801791
return new libclang_casoptions_t_{{lib->wrapper, opts}};
@@ -2159,6 +2170,22 @@ extern "C" {
21592170
return depGraph != nullptr;
21602171
}
21612172

2173+
bool libclang_scanner_generate_reproducer(libclang_scanner_t scanner,
2174+
int argc, char *const *argv,
2175+
const char *workingDirectory,
2176+
const char **message) {
2177+
auto lib = scanner->scanner->lib;
2178+
LibclangFunctions::CXString messageString;
2179+
auto result = lib->fns.clang_experimental_DependencyScanner_generateReproducer(
2180+
argc, const_cast<const char**>(argv), workingDirectory,
2181+
/*ReproducerLocation=*/NULL, /*UseUniqueReproducerName=*/true, &messageString);
2182+
if (message) {
2183+
*message = strdup(lib->fns.clang_getCString(messageString));
2184+
}
2185+
lib->fns.clang_disposeString(messageString);
2186+
return result == LibclangFunctions::CXError_Success;
2187+
}
2188+
21622189
bool libclang_driver_get_actions(libclang_t wrapped_lib,
21632190
int argc,
21642191
char* const* argv,

Sources/SWBCSupport/CLibclang.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ CSUPPORT_EXPORT bool libclang_has_cas_up_to_date_checks_feature(libclang_t lib);
128128
/// Whether the libclang has current working directory optimization support.
129129
CSUPPORT_EXPORT bool libclang_has_current_working_directory_optimization(libclang_t lib);
130130

131+
bool libclang_has_reproducer_feature(libclang_t lib);
132+
131133
/// Create the CAS options object.
132134
CSUPPORT_EXPORT libclang_casoptions_t libclang_casoptions_create(libclang_t lib);
133135

@@ -203,6 +205,10 @@ CSUPPORT_EXPORT bool libclang_scanner_scan_dependencies(
203205
void (^diagnostics_callback)(const libclang_diagnostic_set_t),
204206
void (^error_callback)(const char *));
205207

208+
bool libclang_scanner_generate_reproducer(
209+
libclang_scanner_t scanner, int argc, char *const *argv, const char *workingDirectory,
210+
const char **message);
211+
206212
/// Get the list of commands invoked by the given Clang driver command line.
207213
///
208214
/// \param argc - The number of arguments.

Sources/SWBCore/LibclangVendored/Libclang.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ public final class Libclang {
102102
public var supportsCurrentWorkingDirectoryOptimization: Bool {
103103
libclang_has_current_working_directory_optimization(lib)
104104
}
105+
106+
public var supportsReproducerGeneration: Bool {
107+
libclang_has_reproducer_feature(lib)
108+
}
105109
}
106110

107111
enum DependencyScanningError: Error {
@@ -269,6 +273,22 @@ public final class DependencyScanner {
269273
}
270274
return fileDeps
271275
}
276+
277+
public func generateReproducer(
278+
commandLine: [String],
279+
workingDirectory: String
280+
) throws -> String {
281+
let args = CStringArray(commandLine)
282+
var messageUnsafe: UnsafePointer<Int8>!
283+
defer { messageUnsafe?.deallocate() }
284+
// The count is `- 1` here, because CStringArray appends a trailing nullptr.
285+
let success = libclang_scanner_generate_reproducer(scanner, CInt(args.cArray.count - 1), args.cArray, workingDirectory, &messageUnsafe);
286+
let message = String(cString: messageUnsafe)
287+
guard success else {
288+
throw Error.dependencyScanErrorString(message)
289+
}
290+
return message
291+
}
272292
}
273293

274294
fileprivate struct ClangDiagnosticSet {

Sources/SWBTaskExecution/DynamicTaskSpecs/ClangModuleDependencyGraph.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ package final class ClangModuleDependencyGraph {
109109
/// for example, when using `-save-temps`.
110110
package let commands: [CompileCommand]
111111

112+
package let scanningCommandLine: [String]
113+
112114
package let transitiveIncludeTreeIDs: OrderedSet<String>
113115
package let transitiveCompileCommandCacheKeys: OrderedSet<String>
114116

@@ -121,6 +123,7 @@ package final class ClangModuleDependencyGraph {
121123
moduleDependencies: OrderedSet<Path>,
122124
workingDirectory: Path,
123125
commands: [CompileCommand],
126+
scanningCommandLine: [String],
124127
transitiveIncludeTreeIDs: OrderedSet<String>,
125128
transitiveCompileCommandCacheKeys: OrderedSet<String>,
126129
usesSerializedDiagnostics: Bool
@@ -131,6 +134,7 @@ package final class ClangModuleDependencyGraph {
131134
self.modules = moduleDependencies
132135
self.workingDirectory = workingDirectory
133136
self.commands = commands
137+
self.scanningCommandLine = scanningCommandLine
134138
self.transitiveIncludeTreeIDs = transitiveIncludeTreeIDs
135139
self.transitiveCompileCommandCacheKeys = transitiveCompileCommandCacheKeys
136140
self.usesSerializedDiagnostics = usesSerializedDiagnostics
@@ -143,6 +147,7 @@ package final class ClangModuleDependencyGraph {
143147
moduleDependencies: OrderedSet<Path>,
144148
workingDirectory: Path,
145149
command: CompileCommand,
150+
scanningCommandLine: [String],
146151
transitiveIncludeTreeIDs: OrderedSet<String>,
147152
transitiveCompileCommandCacheKeys: OrderedSet<String>,
148153
usesSerializedDiagnostics: Bool
@@ -153,33 +158,36 @@ package final class ClangModuleDependencyGraph {
153158
self.modules = moduleDependencies
154159
self.workingDirectory = workingDirectory
155160
self.commands = [command]
161+
self.scanningCommandLine = scanningCommandLine
156162
self.transitiveIncludeTreeIDs = transitiveIncludeTreeIDs
157163
self.transitiveCompileCommandCacheKeys = transitiveCompileCommandCacheKeys
158164
self.usesSerializedDiagnostics = usesSerializedDiagnostics
159165
}
160166

161167
package func serialize<T>(to serializer: T) where T : Serializer {
162-
serializer.serializeAggregate(9) {
168+
serializer.serializeAggregate(10) {
163169
serializer.serialize(kind)
164170
serializer.serialize(files)
165171
serializer.serialize(includeTreeID)
166172
serializer.serialize(modules)
167173
serializer.serialize(workingDirectory)
168174
serializer.serialize(commands)
175+
serializer.serialize(scanningCommandLine)
169176
serializer.serialize(transitiveIncludeTreeIDs)
170177
serializer.serialize(transitiveCompileCommandCacheKeys)
171178
serializer.serialize(usesSerializedDiagnostics)
172179
}
173180
}
174181

175182
package init(from deserializer: any Deserializer) throws {
176-
try deserializer.beginAggregate(9)
183+
try deserializer.beginAggregate(10)
177184
self.kind = try deserializer.deserialize()
178185
self.files = try deserializer.deserialize()
179186
self.includeTreeID = try deserializer.deserialize()
180187
self.modules = try deserializer.deserialize()
181188
self.workingDirectory = try deserializer.deserialize()
182189
self.commands = try deserializer.deserialize()
190+
self.scanningCommandLine = try deserializer.deserialize()
183191
self.transitiveIncludeTreeIDs = try deserializer.deserialize()
184192
self.transitiveCompileCommandCacheKeys = try deserializer.deserialize()
185193
self.usesSerializedDiagnostics = try deserializer.deserialize()
@@ -334,12 +342,13 @@ package final class ClangModuleDependencyGraph {
334342
var moduleTransitiveCacheKeys: [String: OrderedSet<String>] = [:]
335343

336344
let fileDeps: DependencyScanner.FileDependencies
345+
let scanningCommandLine = [compiler] + originalFileArgs
337346
let modulesCallbackErrors = LockedValue<[any Error]>([])
338347
let dependencyPaths = LockedValue<Set<Path>>([])
339348
let requiredTargetDependencies = LockedValue<Set<ScanResult.RequiredDependency>>([])
340349
do {
341350
fileDeps = try clangWithScanner.scanner.scanDependencies(
342-
commandLine: [compiler] + originalFileArgs,
351+
commandLine: scanningCommandLine,
343352
workingDirectory: workingDirectory.str,
344353
lookupOutput: { name, contextHash, kind in
345354
let moduleOutputPath = outputPathForModule(name, contextHash)
@@ -432,6 +441,7 @@ package final class ClangModuleDependencyGraph {
432441
// Cached builds do not rely on the process working directory, and different scanner working directories should not inhibit task deduplication. The same is true if the scanner reports the working directory can be ignored.
433442
workingDirectory: module.cache_key != nil || module.is_cwd_ignored ? Path.root : workingDirectory,
434443
command: DependencyInfo.CompileCommand(cacheKey: module.cache_key, arguments: commandLine),
444+
scanningCommandLine: scanningCommandLine,
435445
transitiveIncludeTreeIDs: transitiveIncludeTreeIDs,
436446
transitiveCompileCommandCacheKeys: transitiveCommandCacheKeys,
437447
usesSerializedDiagnostics: usesSerializedDiagnostics)
@@ -513,6 +523,7 @@ package final class ClangModuleDependencyGraph {
513523
// Cached builds do not rely on the process working directory, and different scanner working directories should not inhibit task deduplication
514524
workingDirectory: fileDeps.commands.allSatisfy { $0.cache_key != nil } ? Path.root : workingDirectory,
515525
commands: commands,
526+
scanningCommandLine: scanningCommandLine,
516527
transitiveIncludeTreeIDs: transitiveIncludeTreeIDs,
517528
transitiveCompileCommandCacheKeys: transitiveCommandCacheKeys,
518529
usesSerializedDiagnostics: usesSerializedDiagnostics)
@@ -549,6 +560,21 @@ package final class ClangModuleDependencyGraph {
549560
return clangWithScanner.casDBs
550561
}
551562

563+
package func generateReproducer(forFailedDependency dependency: DependencyInfo,
564+
libclangPath: Path, casOptions: CASOptions?) throws -> String? {
565+
let clangWithScanner = try libclangWithScanner(
566+
forPath: libclangPath,
567+
casOptions: casOptions,
568+
cacheFallbackIfNotAvailable: false,
569+
core: core
570+
)
571+
guard clangWithScanner.libclang.supportsReproducerGeneration else {
572+
return nil
573+
}
574+
return try clangWithScanner.scanner.generateReproducer(
575+
commandLine: dependency.scanningCommandLine, workingDirectory: dependency.workingDirectory.str)
576+
}
577+
552578
package var isEmpty: Bool {
553579
recordedDependencyInfoRegistry.isEmpty
554580
}

Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,19 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA
301301
outputDelegate.emitOutput("Failed frontend command:\n")
302302
outputDelegate.emitOutput(ByteString(encodingAsUTF8: commandString) + "\n")
303303
}
304+
305+
if case .some(.exit(.uncaughtSignal, _)) = outputDelegate.result {
306+
do {
307+
if let reproducerMessage = try clangModuleDependencyGraph.generateReproducer(
308+
forFailedDependency: dependencyInfo,
309+
libclangPath: explicitModulesPayload.libclangPath,
310+
casOptions: explicitModulesPayload.casOptions) {
311+
outputDelegate.emitOutput(ByteString(encodingAsUTF8: reproducerMessage) + "\n")
312+
}
313+
} catch {
314+
outputDelegate.error(error.localizedDescription)
315+
}
316+
}
304317
return lastResult ?? .failed
305318
}
306319
}

Sources/SWBTaskExecution/TaskActions/PrecompileClangModuleTaskAction.swift

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,16 +215,30 @@ final public class PrecompileClangModuleTaskAction: TaskAction, BuildValueValida
215215
enableStrictCASErrors: key.casOptions!.enableStrictCASErrors
216216
)
217217
}
218-
} else if result == .failed && !executionDelegate.userPreferences.enableDebugActivityLogs && !executionDelegate.emitFrontendCommandLines {
219-
let commandString = UNIXShellCommandCodec(
220-
encodingStrategy: .backslashes,
221-
encodingBehavior: .fullCommandLine
222-
).encode(commandLine)
223-
224-
// <rdar://59354519> We need to find a way to use the generic infrastructure for displaying the command line in
225-
// the build log.
226-
outputDelegate.emitOutput("Failed frontend command:\n")
227-
outputDelegate.emitOutput(ByteString(encodingAsUTF8: commandString) + "\n")
218+
} else if result == .failed {
219+
if !executionDelegate.userPreferences.enableDebugActivityLogs && !executionDelegate.emitFrontendCommandLines {
220+
let commandString = UNIXShellCommandCodec(
221+
encodingStrategy: .backslashes,
222+
encodingBehavior: .fullCommandLine
223+
).encode(commandLine)
224+
225+
// <rdar://59354519> We need to find a way to use the generic infrastructure for displaying the command line in
226+
// the build log.
227+
outputDelegate.emitOutput("Failed frontend command:\n")
228+
outputDelegate.emitOutput(ByteString(encodingAsUTF8: commandString) + "\n")
229+
}
230+
if case .some(.exit(.uncaughtSignal, _)) = outputDelegate.result {
231+
do {
232+
if let reproducerMessage = try clangModuleDependencyGraph.generateReproducer(
233+
forFailedDependency: dependencyInfo,
234+
libclangPath: key.libclangPath,
235+
casOptions: key.casOptions) {
236+
outputDelegate.emitOutput(ByteString(encodingAsUTF8: reproducerMessage) + "\n")
237+
}
238+
} catch {
239+
outputDelegate.error(error.localizedDescription)
240+
}
241+
}
228242
}
229243
return result
230244
} catch {

0 commit comments

Comments
 (0)