Skip to content

Commit 52c2328

Browse files
committed
[libclang] Add API to generate a reproducer for the explicitly-built modules. (#10577)
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 40371c0 commit 52c2328

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

clang/include/clang-c/Dependencies.h

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

328+
/**
329+
* Generates a reproducer to compile a requested file with required modules.
330+
*
331+
* Here the reproducer means the required input data with the commands to
332+
* compile intermediate modules and a requested file. Required intermediate
333+
* modules and the order of their compilation are determined by the function
334+
* and don't need to be provided.
335+
*
336+
* \param argc the number of compiler invocation arguments (including argv[0]).
337+
* \param argv the compiler driver invocation arguments (including argv[0]).
338+
* \param ModuleName If non-NULL, reproduce building the named module and all
339+
* the intermediate modules. Otherwise, reproduce building
340+
* the whole translation unit.
341+
* \param WorkingDirectory the directory in which the invocation runs.
342+
* \param [out] MessageOut A pointer to store the human-readable message
343+
* describing the result of the operation. If non-NULL,
344+
* owned and should be disposed by the caller.
345+
*
346+
* \returns \c CXError_Success on success; otherwise a non-zero \c CXErrorCode
347+
* indicating the kind of error. \p MessageOut is guaranteed to be populated
348+
* for a success case but is allowed to be empty when encountered an error.
349+
*/
350+
CINDEX_LINKAGE enum CXErrorCode
351+
clang_experimental_DependencyScanner_generateReproducer(
352+
int argc, const char *const *argv, const char *ModuleName,
353+
const char *WorkingDirectory, CXString *MessageOut);
354+
328355
/**
329356
* Produces the dependency graph for a particular compiler invocation.
330357
*
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: 29 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,24 @@ 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(), /*ModuleName=*/nullptr,
936+
WorkingDirectory.c_str(), &MessageString);
937+
if (ExitCode == CXError_Success) {
938+
llvm::outs() << clang_getCString(MessageString) << "\n";
939+
} else {
940+
llvm::errs() << "error: " << clang_getCString(MessageString) << "\n";
941+
}
942+
return (ExitCode == CXError_Success) ? 0 : 1;
943+
}
944+
924945
static int uploadCachedJob(std::string CacheKey, CXCASDatabases DBs) {
925946
CXError Err = nullptr;
926947
CXCASCachedCompilation CComp = clang_experimental_cas_getCachedCompilation(
@@ -1546,6 +1567,14 @@ int indextest_core_main(int argc, const char **argv) {
15461567
options::OutputDir, DBs, options::ModuleName);
15471568
}
15481569

1570+
if (options::Action == ActionType::GenerateDepsReproducer) {
1571+
if (options::WorkingDir.empty()) {
1572+
errs() << "error: missing -working-dir\n";
1573+
return 1;
1574+
}
1575+
return generateDepsReproducer(CompArgs, options::WorkingDir);
1576+
}
1577+
15491578
if (options::Action == ActionType::UploadCachedJob) {
15501579
if (options::InputFiles.empty()) {
15511580
errs() << "error: missing cache key\n";

clang/tools/libclang/CDependencies.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,131 @@ 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 *ModuleName,
348+
const char *WorkingDirectory, 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 Arg : Compilation)
398+
ScriptOS << ' ' << Arg;
399+
ScriptOS << "\n\n";
400+
401+
ScriptOS << "# Dependencies:\n";
402+
std::string ReproExecutable = std::string(argv[0]);
403+
auto PrintArguments = [&ReproExecutable,
404+
&FileCacheName](llvm::raw_fd_ostream &OS,
405+
ArrayRef<std::string> Arguments) {
406+
OS << ReproExecutable;
407+
for (int I = 0, E = Arguments.size(); I < E; ++I)
408+
OS << ' ' << Arguments[I];
409+
OS << " -ivfsoverlay \"" << FileCacheName << "/vfs/vfs.yaml\"";
410+
OS << '\n';
411+
};
412+
for (ModuleDeps &Dep : TU.ModuleGraph)
413+
PrintArguments(ScriptOS, Dep.getBuildArguments());
414+
ScriptOS << "\n# Translation unit:\n";
415+
for (const Command &BuildCommand : TU.Commands)
416+
PrintArguments(ScriptOS, BuildCommand.Arguments);
417+
418+
SmallString<128> VFSCachePath = FileCachePath;
419+
llvm::sys::path::append(VFSCachePath, "vfs");
420+
std::string VFSCachePathStr = VFSCachePath.str().str();
421+
llvm::FileCollector FileCollector(VFSCachePathStr,
422+
/*OverlayRoot=*/VFSCachePathStr);
423+
for (const auto &FileDep : TU.FileDeps) {
424+
FileCollector.addFile(FileDep);
425+
}
426+
for (ModuleDeps &ModuleDep : TU.ModuleGraph) {
427+
ModuleDep.forEachFileDep([&FileCollector](StringRef FileDep) {
428+
FileCollector.addFile(FileDep);
429+
});
430+
}
431+
if (FileCollector.copyFiles(/*StopOnError=*/true))
432+
return ReportFailure()
433+
<< "failed to copy the files used for the compilation";
434+
SmallString<128> VFSOverlayPath = VFSCachePath;
435+
llvm::sys::path::append(VFSOverlayPath, "vfs.yaml");
436+
if (FileCollector.writeMapping(VFSOverlayPath))
437+
return ReportFailure() << "failed to write a VFS overlay mapping";
438+
439+
return Report(CXError_Success)
440+
<< "Created a reproducer. Sources and associated run script(s) are "
441+
"located at:\n "
442+
<< FileCachePath << "\n " << ReproScriptPath;
443+
}
444+
320445
enum CXErrorCode clang_experimental_DependencyScannerWorker_getDepGraph(
321446
CXDependencyScannerWorker W,
322447
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)