Skip to content

Commit 5185b66

Browse files
committed
[libclang] Add API to generate a reproducer for the explicitly-built modules.
Capturing a single `-cc1` command to reproduce a bug with the explicitly-built modules isn't sufficient, we need to know what .pcm files were built and how. rdar://59743925
1 parent 283cba1 commit 5185b66

File tree

5 files changed

+192
-0
lines changed

5 files changed

+192
-0
lines changed

clang/include/clang-c/Dependencies.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,11 @@ CINDEX_LINKAGE void
325325
clang_experimental_DependencyScannerWorkerScanSettings_dispose(
326326
CXDependencyScannerWorkerScanSettings);
327327

328+
CINDEX_LINKAGE enum CXErrorCode
329+
clang_experimental_DependencyScanner_generateReproducer(
330+
int argc, const char *const *argv, const char *WorkingDirectory,
331+
CXString *messageOut);
332+
328333
/**
329334
* Produces the dependency graph for a particular compiler invocation.
330335
*
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Test generating a reproducer for a modular build where required modules are
2+
// built explicitly as separate steps.
3+
4+
// RUN: rm -rf %t
5+
// RUN: split-file %s %t
6+
//
7+
// RUN: c-index-test core -gen-deps-reproducer -working-dir %t \
8+
// RUN: -- clang-executable -c %t/reproducer.c -o %t/reproducer.o \
9+
// RUN: -fmodules -fmodules-cache-path=%t | FileCheck %t/reproducer.c
10+
11+
// Test a failed attempt at generating a reproducer.
12+
// RUN: not c-index-test core -gen-deps-reproducer -working-dir %t \
13+
// RUN: -- clang-executable -c %t/failed-reproducer.c -o %t/reproducer.o \
14+
// RUN: -fmodules -fmodules-cache-path=%t 2>&1 | FileCheck %t/failed-reproducer.c
15+
16+
//--- modular-header.h
17+
void fn_in_modular_header(void);
18+
19+
//--- module.modulemap
20+
module Test { header "modular-header.h" export * }
21+
22+
//--- reproducer.c
23+
// CHECK: Sources and associated run script(s) are located at:
24+
#include "modular-header.h"
25+
26+
void test(void) {
27+
fn_in_modular_header();
28+
}
29+
30+
//--- failed-reproducer.c
31+
// CHECK: fatal error: 'non-existing-header.h' file not found
32+
#include "non-existing-header.h"

clang/tools/c-index-test/core_main.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ enum class ActionType {
5959
AggregateAsJSON,
6060
ScanDeps,
6161
ScanDepsByModuleName,
62+
GenerateDepsReproducer,
6263
UploadCachedJob,
6364
MaterializeCachedJob,
6465
ReplayCachedJob,
@@ -87,6 +88,8 @@ Action(cl::desc("Action:"), cl::init(ActionType::None),
8788
"Get file dependencies"),
8889
clEnumValN(ActionType::ScanDepsByModuleName, "scan-deps-by-mod-name",
8990
"Get file dependencies by module name alone"),
91+
clEnumValN(ActionType::GenerateDepsReproducer, "gen-deps-reproducer",
92+
"Generate a reproducer for the file"),
9093
clEnumValN(ActionType::UploadCachedJob, "upload-cached-job",
9194
"Upload cached compilation data to upstream CAS"),
9295
clEnumValN(ActionType::MaterializeCachedJob, "materialize-cached-job",
@@ -910,6 +913,23 @@ static int scanDeps(ArrayRef<const char *> Args, std::string WorkingDirectory,
910913
return 1;
911914
}
912915

916+
static int generateDepsReproducer(ArrayRef<const char *> Args,
917+
std::string WorkingDirectory) {
918+
CXString MessageString;
919+
auto DisposeMessageString = llvm::make_scope_exit([&]() {
920+
clang_disposeString(MessageString);
921+
});
922+
CXErrorCode ExitCode =
923+
clang_experimental_DependencyScanner_generateReproducer(
924+
Args.size(), Args.data(), WorkingDirectory.c_str(), &MessageString);
925+
if (ExitCode == CXError_Success) {
926+
llvm::outs() << clang_getCString(MessageString) << "\n";
927+
} else {
928+
llvm::errs() << "error: " << clang_getCString(MessageString) << "\n";
929+
}
930+
return (ExitCode == CXError_Success) ? 0 : 1;
931+
}
932+
913933
static int uploadCachedJob(std::string CacheKey, CXCASDatabases DBs) {
914934
CXError Err = nullptr;
915935
CXCASCachedCompilation CComp = clang_experimental_cas_getCachedCompilation(
@@ -1535,6 +1555,14 @@ int indextest_core_main(int argc, const char **argv) {
15351555
options::OutputDir, DBs, options::ModuleName);
15361556
}
15371557

1558+
if (options::Action == ActionType::GenerateDepsReproducer) {
1559+
if (options::WorkingDir.empty()) {
1560+
errs() << "error: missing -working-dir\n";
1561+
return 1;
1562+
}
1563+
return generateDepsReproducer(CompArgs, options::WorkingDir);
1564+
}
1565+
15381566
if (options::Action == ActionType::UploadCachedJob) {
15391567
if (options::InputFiles.empty()) {
15401568
errs() << "error: missing cache key\n";

clang/tools/libclang/CDependencies.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,132 @@ void clang_experimental_DependencyScannerWorkerScanSettings_dispose(
312312
delete unwrap(Settings);
313313
}
314314

315+
namespace {
316+
// Helper class to capture a returnable error code and to return a formatted
317+
// message in a provided CXString pointer.
318+
class MessageEmitter {
319+
const CXErrorCode ErrorCode;
320+
CXString *OutputString;
321+
std::string Buffer;
322+
llvm::raw_string_ostream Stream;
323+
324+
public:
325+
MessageEmitter(CXErrorCode Code, CXString *Output)
326+
: ErrorCode(Code), OutputString(Output), Stream(Buffer) {}
327+
~MessageEmitter() {
328+
if (OutputString)
329+
*OutputString = clang::cxstring::createDup(Buffer.c_str());
330+
}
331+
332+
operator CXErrorCode() const { return ErrorCode; }
333+
334+
template <typename T> MessageEmitter &operator<<(const T &t) {
335+
Stream << t;
336+
return *this;
337+
}
338+
};
339+
} // end anonymous namespace
340+
341+
enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
342+
int argc, const char *const *argv, const char *WorkingDirectory,
343+
CXString *messageOut) {
344+
auto report = [messageOut](CXErrorCode errorCode) -> MessageEmitter {
345+
return MessageEmitter(errorCode, messageOut);
346+
};
347+
auto reportFailure = [&report]() -> MessageEmitter {
348+
return report(CXError_Failure);
349+
};
350+
351+
if (argc < 2 || !argv)
352+
return report(CXError_InvalidArguments) << "missing compilation command";
353+
if (!WorkingDirectory)
354+
return report(CXError_InvalidArguments) << "missing working directory";
355+
356+
CASOptions CASOpts;
357+
IntrusiveRefCntPtr<llvm::cas::CachingOnDiskFileSystem> FS;
358+
DependencyScanningService DepsService(
359+
ScanningMode::DependencyDirectivesScan, ScanningOutputFormat::Full,
360+
CASOpts, /*CAS=*/nullptr, /*ActionCache=*/nullptr, FS);
361+
DependencyScanningTool DepsTool(DepsService);
362+
363+
llvm::SmallString<128> ReproScriptPath;
364+
int ScriptFD;
365+
if (auto EC = llvm::sys::fs::createTemporaryFile("reproducer", "sh", ScriptFD,
366+
ReproScriptPath)) {
367+
return reportFailure() << "failed to create a reproducer script file";
368+
}
369+
SmallString<128> FileCachePath = ReproScriptPath;
370+
llvm::sys::path::replace_extension(FileCachePath, ".cache");
371+
372+
std::string FileCacheName = llvm::sys::path::filename(FileCachePath).str();
373+
auto LookupOutput = [&FileCacheName](const ModuleDeps &MD,
374+
ModuleOutputKind MOK) -> std::string {
375+
if (MOK != ModuleOutputKind::ModuleFile)
376+
return "";
377+
return FileCacheName + "/explicitly-built-modules/" +
378+
MD.ID.ModuleName + "-" + MD.ID.ContextHash + ".pcm";
379+
};
380+
381+
std::vector<std::string> Compilation{argv, argv + argc};
382+
llvm::DenseSet<ModuleID> AlreadySeen;
383+
auto TUDepsOrErr = DepsTool.getTranslationUnitDependencies(
384+
Compilation, WorkingDirectory, AlreadySeen, std::move(LookupOutput));
385+
if (!TUDepsOrErr)
386+
return reportFailure() << "failed to generate a reproducer\n"
387+
<< toString(TUDepsOrErr.takeError());
388+
389+
TranslationUnitDeps TU = *TUDepsOrErr;
390+
llvm::raw_fd_ostream ScriptOS(ScriptFD, /*shouldClose=*/true);
391+
ScriptOS << "# Original command:\n#";
392+
for (StringRef cliArg : Compilation) {
393+
ScriptOS << ' ' << cliArg;
394+
}
395+
ScriptOS << "\n\n";
396+
397+
ScriptOS << "# Dependencies:\n";
398+
std::string ReproExecutable = std::string(argv[0]);
399+
auto PrintArguments = [&ReproExecutable,
400+
&FileCacheName](llvm::raw_fd_ostream &OS,
401+
ArrayRef<std::string> Arguments) {
402+
OS << ReproExecutable;
403+
for (int I = 0, E = Arguments.size(); I < E; ++I)
404+
OS << ' ' << Arguments[I];
405+
OS << " -ivfsoverlay \"" << FileCacheName << "/vfs/vfs.yaml\"";
406+
OS << '\n';
407+
};
408+
for (ModuleDeps &dep : TU.ModuleGraph)
409+
PrintArguments(ScriptOS, dep.getBuildArguments());
410+
ScriptOS << "\n# Translation unit:\n";
411+
for (const Command &buildCommand : TU.Commands)
412+
PrintArguments(ScriptOS, buildCommand.Arguments);
413+
414+
SmallString<128> VFSCachePath = FileCachePath;
415+
llvm::sys::path::append(VFSCachePath, "vfs");
416+
std::string VFSCachePathStr = VFSCachePath.str().str();
417+
llvm::FileCollector fileCollector(VFSCachePathStr,
418+
/*OverlayRoot=*/VFSCachePathStr);
419+
for (const auto &fileDep : TU.FileDeps) {
420+
fileCollector.addFile(fileDep);
421+
}
422+
for (ModuleDeps &dep : TU.ModuleGraph) {
423+
dep.forEachFileDep([&fileCollector](StringRef fileDep) {
424+
fileCollector.addFile(fileDep);
425+
});
426+
}
427+
if (fileCollector.copyFiles(/*StopOnError=*/true))
428+
return reportFailure()
429+
<< "failed to copy the files used for the compilation";
430+
SmallString<128> VFSOverlayPath = VFSCachePath;
431+
llvm::sys::path::append(VFSOverlayPath, "vfs.yaml");
432+
if (fileCollector.writeMapping(VFSOverlayPath))
433+
return reportFailure() << "failed to write a VFS overlay mapping";
434+
435+
return report(CXError_Success)
436+
<< "Created a reproducer. Sources and associated run script(s) are "
437+
"located at:\n "
438+
<< FileCachePath << "\n " << ReproScriptPath;
439+
}
440+
315441
enum CXErrorCode clang_experimental_DependencyScannerWorker_getDepGraph(
316442
CXDependencyScannerWorker W,
317443
CXDependencyScannerWorkerScanSettings CXSettings, CXDepGraph *Out) {

clang/tools/libclang/libclang.map

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ LLVM_16 {
496496
clang_experimental_cas_replayCompilation;
497497
clang_experimental_cas_ReplayResult_dispose;
498498
clang_experimental_cas_ReplayResult_getStderr;
499+
clang_experimental_DependencyScanner_generateReproducer;
499500
clang_experimental_DependencyScannerService_create_v1;
500501
clang_experimental_DependencyScannerServiceOptions_create;
501502
clang_experimental_DependencyScannerServiceOptions_dispose;

0 commit comments

Comments
 (0)