Skip to content

Commit ab9b28f

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 88f12a5 commit ab9b28f

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",
@@ -921,6 +924,23 @@ static int scanDeps(ArrayRef<const char *> Args, std::string WorkingDirectory,
921924
return 1;
922925
}
923926

927+
static int generateDepsReproducer(ArrayRef<const char *> Args,
928+
std::string WorkingDirectory) {
929+
CXString MessageString;
930+
auto DisposeMessageString = llvm::make_scope_exit([&]() {
931+
clang_disposeString(MessageString);
932+
});
933+
CXErrorCode ExitCode =
934+
clang_experimental_DependencyScanner_generateReproducer(
935+
Args.size(), Args.data(), WorkingDirectory.c_str(), &MessageString);
936+
if (ExitCode == CXError_Success) {
937+
llvm::outs() << clang_getCString(MessageString) << "\n";
938+
} else {
939+
llvm::errs() << "error: " << clang_getCString(MessageString) << "\n";
940+
}
941+
return (ExitCode == CXError_Success) ? 0 : 1;
942+
}
943+
924944
static int uploadCachedJob(std::string CacheKey, CXCASDatabases DBs) {
925945
CXError Err = nullptr;
926946
CXCASCachedCompilation CComp = clang_experimental_cas_getCachedCompilation(
@@ -1546,6 +1566,14 @@ int indextest_core_main(int argc, const char **argv) {
15461566
options::OutputDir, DBs, options::ModuleName);
15471567
}
15481568

1569+
if (options::Action == ActionType::GenerateDepsReproducer) {
1570+
if (options::WorkingDir.empty()) {
1571+
errs() << "error: missing -working-dir\n";
1572+
return 1;
1573+
}
1574+
return generateDepsReproducer(CompArgs, options::WorkingDir);
1575+
}
1576+
15491577
if (options::Action == ActionType::UploadCachedJob) {
15501578
if (options::InputFiles.empty()) {
15511579
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
@@ -317,6 +317,132 @@ void clang_experimental_DependencyScannerWorkerScanSettings_dispose(
317317
delete unwrap(Settings);
318318
}
319319

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