Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/include/clang-c/Dependencies.h
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,14 @@ CINDEX_LINKAGE void
clang_experimental_DependencyScannerReproducerOptions_dispose(
CXDependencyScannerReproducerOptions);

/**
* Specify the object store and action cache databases, and CAS options for
* generating a reproducer. Should be used if the original compilation uses CAS.
*/
CINDEX_LINKAGE void
clang_experimental_DependencyScannerReproducerOptions_setCASOptions(
CXDependencyScannerReproducerOptions, CXCASDatabases, CXCASOptions);

/**
* Generates a reproducer to compile a requested file with required modules.
*
Expand Down
29 changes: 26 additions & 3 deletions clang/test/Modules/reproducer-with-module-dependencies.c
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
// Test generating a reproducer for a modular build where required modules are
// built explicitly as separate steps.
// REQUIRES: shell

// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: sed -e "s|DIR|%/t|g" %t/existing.yaml.in > %t/existing.yaml
//
// RUN: c-index-test core -gen-deps-reproducer -working-dir %t \
// RUN: -- clang-executable -c %t/reproducer.c -o %t/reproducer.o \
// RUN: -fmodules -fmodules-cache-path=%t \
// RUN: -fmodules -fmodules-cache-path=%t.modulecache \
// RUN: -ivfsoverlay %t/existing.yaml -I /virtual | FileCheck %t/reproducer.c

// Test a failed attempt at generating a reproducer.
// RUN: not c-index-test core -gen-deps-reproducer -working-dir %t \
// RUN: -- clang-executable -c %t/failed-reproducer.c -o %t/reproducer.o \
// RUN: -fmodules -fmodules-cache-path=%t 2>&1 | FileCheck %t/failed-reproducer.c
// RUN: -fmodules -fmodules-cache-path=%t.modulecache 2>&1 | FileCheck %t/failed-reproducer.c

// Test the content of a reproducer script.
// RUN: c-index-test core -gen-deps-reproducer -working-dir %t -o %t/repro-content \
// RUN: -- clang-executable -c %t/reproducer.c -o %t/reproducer.o \
// RUN: -fmodules -fmodules-cache-path=%t \
// RUN: -fmodules -fmodules-cache-path=%t.modulecache \
// RUN: -DMACRO="\$foo" \
// RUN: -ivfsoverlay %t/existing.yaml -I /virtual \
// RUN: -MMD -MT dependencies -MF %t/deps.d
// RUN: FileCheck %t/script-expectations.txt --input-file %t/repro-content/reproducer.sh

// Test the content of a reproducer script with CAS enabled.
// RUN: c-index-test core -gen-deps-reproducer -working-dir %t -cas-path %t/cas -o %t/repro-cas-content \
// RUN: -- %clang -c %t/reproducer.c -o %t/reproducer.o \
// RUN: -fmodules -fmodules-cache-path=%t.modulecache.cas \
// RUN: -I %t/include
// RUN: FileCheck %t/cas-script-expectations.txt --input-file %t/repro-cas-content/reproducer.sh

// Verify can reproduce the original compilations with the files captured by reproducers.
// RUN: rm -rf %t.modulecache
// RUN: rm -rf %t.modulecache.cas
// RUN: rm -rf %t/cas
// RUN: rm %t/include/modular-header.h
// RUN: cd %t/repro-content
// RUN: env CLANG=%clang bash ./reproducer.sh
// RUN: cd %t/repro-cas-content
// RUN: bash ./reproducer.sh

//--- include/modular-header.h
void fn_in_modular_header(void);

Expand Down Expand Up @@ -76,3 +94,8 @@ CHECK: -ivfsoverlay "reproducer.cache/vfs/vfs.yaml"
CHECK: "-ivfsoverlay" "{{.*}}/existing.yaml"
CHECK: MACRO=\$foo
CHECK: "-dependency-file" "reproducer.cache/deps.d"

//--- cas-script-expectations.txt
CHECK: -fcas-path "reproducer.cache/cas"
CHECK: "-fcas-include-tree" "llvmcas://
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it is worth it (or common) to check the presence of an include tree in a generated CAS.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For swift, I just find a way to execute the reproducer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, it seems to be a more reliable way to check the logic.

CHECK: "-fmodule-file-cache-key" "Test-{{.*}}.pcm" "llvmcas://
7 changes: 5 additions & 2 deletions clang/tools/c-index-test/core_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,8 @@ static int scanDeps(ArrayRef<const char *> Args, std::string WorkingDirectory,

static int generateDepsReproducer(ArrayRef<const char *> Args,
std::string WorkingDirectory,
std::string ReproLocation) {
std::string ReproLocation,
CXCASDatabases DBs) {
CXDependencyScannerReproducerOptions Opts =
clang_experimental_DependencyScannerReproducerOptions_create(
Args.size(), Args.data(), /*ModuleName=*/nullptr,
Expand All @@ -965,6 +966,8 @@ static int generateDepsReproducer(ArrayRef<const char *> Args,
auto DisposeOpts = llvm::make_scope_exit([&] {
clang_experimental_DependencyScannerReproducerOptions_dispose(Opts);
});
clang_experimental_DependencyScannerReproducerOptions_setCASOptions(Opts, DBs,
nullptr);
CXString MessageString;
auto DisposeMessageString = llvm::make_scope_exit([&]() {
clang_disposeString(MessageString);
Expand Down Expand Up @@ -1611,7 +1614,7 @@ int indextest_core_main(int argc, const char **argv) {
return 1;
}
return generateDepsReproducer(CompArgs, options::WorkingDir,
options::OutputFile);
options::OutputFile, DBs);
}

if (options::Action == ActionType::UploadCachedJob) {
Expand Down
96 changes: 90 additions & 6 deletions clang/tools/libclang/CDependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,9 @@ struct DependencyScannerReproducerOptions {
std::optional<std::string> WorkingDirectory;
std::optional<std::string> ReproducerLocation;
bool UseUniqueReproducerName;
CASOptions CASOpts;
std::shared_ptr<cas::ObjectStore> CAS;
std::shared_ptr<cas::ActionCache> Cache;

DependencyScannerReproducerOptions(int argc, const char *const *argv,
const char *ModuleName,
Expand Down Expand Up @@ -750,6 +753,20 @@ clang_experimental_DependencyScannerReproducerOptions_create(
UseUniqueReproducerName});
}

void clang_experimental_DependencyScannerReproducerOptions_setCASOptions(
CXDependencyScannerReproducerOptions CXOptions, CXCASDatabases CDBs,
CXCASOptions CASOpts) {
DependencyScannerReproducerOptions &Opts = *unwrap(CXOptions);
if (CDBs) {
cas::WrappedCASDatabases &DBs = *cas::unwrap(CDBs);
Opts.CASOpts = DBs.CASOpts;
Opts.CAS = DBs.CAS;
Opts.Cache = DBs.Cache;
}
if (CASOpts)
Opts.CASOpts = *cas::unwrap(CASOpts);
}

void clang_experimental_DependencyScannerReproducerOptions_dispose(
CXDependencyScannerReproducerOptions Options) {
delete unwrap(Options);
Expand All @@ -773,11 +790,18 @@ enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
return Report(CXError_InvalidArguments)
<< "non-unique reproducer is allowed only in a custom location";

CASOptions CASOpts;
std::shared_ptr<llvm::cas::ObjectStore> UpstreamCAS = Opts.CAS;
bool IsReproducerCASBased(UpstreamCAS);
DependencyScanningService DepsService(
ScanningMode::DependencyDirectivesScan, ScanningOutputFormat::Full,
CASOpts, /*CAS=*/nullptr, /*ActionCache=*/nullptr);
DependencyScanningTool DepsTool(DepsService);
ScanningMode::DependencyDirectivesScan,
IsReproducerCASBased ? ScanningOutputFormat::FullIncludeTree
: ScanningOutputFormat::Full,
Opts.CASOpts, UpstreamCAS, Opts.Cache);
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS =
llvm::vfs::createPhysicalFileSystem();
if (UpstreamCAS)
FS = llvm::cas::createCASProvidingFileSystem(UpstreamCAS, std::move(FS));
DependencyScanningTool DepsTool(DepsService, FS);

llvm::SmallString<128> ReproScriptPath;
int ScriptFD;
Expand Down Expand Up @@ -844,7 +868,7 @@ enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
// it is easier to run the reproducer with a different compiler and to
// simplify running an individual command manually.
std::string ReproExecutable = "\"${CLANG:-" + Opts.BuildArgs.front() + "}\"";
auto PrintArguments = [&ReproExecutable, &FileCacheName,
auto PrintArguments = [IsReproducerCASBased, &ReproExecutable, &FileCacheName,
&ClangOpts](llvm::raw_fd_ostream &OS,
ArrayRef<std::string> Arguments,
bool RedirectOutput) {
Expand All @@ -856,7 +880,8 @@ enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
ClangOpts.ParseArgs(CharArgs, MissingArgIndex, MissingArgCount,
llvm::opt::Visibility(options::CC1Option));

bool DidAddVFSOverlay = false;
// CAS-based reproducer doesn't use VFS overlays.
bool DidAddVFSOverlay = IsReproducerCASBased;
OS << ReproExecutable;
for (const llvm::opt::Arg *Arg : ParsedArgs) {
const llvm::opt::Option &Opt = Arg->getOption();
Expand All @@ -866,6 +891,10 @@ enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
DidAddVFSOverlay = true;
}
}
if (Opt.matches(options::OPT_fcas_path)) {
OS << " -fcas-path \"" << FileCacheName << "/cas\"";
continue;
}
bool IsOutputArg = Opt.matches(options::OPT_o) ||
Opt.matches(options::OPT_dependency_file);
llvm::opt::ArgStringList OutArgs;
Expand Down Expand Up @@ -899,6 +928,60 @@ enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
auto RealFS = llvm::vfs::getRealFileSystem();
RealFS->setCurrentWorkingDirectory(*Opts.WorkingDirectory);

if (IsReproducerCASBased) {
SmallString<128> CASPath = FileCachePath;
llvm::sys::path::append(CASPath, "cas");
clang::CASOptions ReproducerCASOpts;
ReproducerCASOpts.CASPath = CASPath.str();
ReproducerCASOpts.PluginPath = Opts.CASOpts.PluginPath;
ReproducerCASOpts.PluginOptions = Opts.CASOpts.PluginOptions;
auto DBsOrErr = ReproducerCASOpts.getOrCreateDatabases();
if (!DBsOrErr)
return ReportFailure() << "failed to create a CAS database\n"
<< toString(DBsOrErr.takeError());
std::shared_ptr<llvm::cas::ObjectStore> ReproCAS = DBsOrErr->first;

auto transplantCASIncludeTree =
[UpstreamCAS, ReproCAS](
const std::optional<std::string> &IncludeTreeID) -> llvm::Error {
if (!IncludeTreeID.has_value())
// Missing `IncludeTreeID` likely indicates a problem but ignore it, so
// can capture enough data to reproduce it later.
return llvm::Error::success();
auto IDOrErr = UpstreamCAS->parseID(*IncludeTreeID);
if (!IDOrErr)
return llvm::make_error<llvm::StringError>(
"failure to parse include tree id '" + *IncludeTreeID +
"':" + toString(IDOrErr.takeError()),
llvm::inconvertibleErrorCode());
std::optional<cas::ObjectRef> UpstreamRef =
UpstreamCAS->getReference(*IDOrErr);
if (!UpstreamRef.has_value())
return llvm::make_error<llvm::StringError>(
"missing include tree with ID '" + *IncludeTreeID +
"' in the provided CAS object storage",
llvm::inconvertibleErrorCode());
auto ReproRefOrErr = ReproCAS->importObject(*UpstreamCAS, *UpstreamRef);
if (!ReproRefOrErr)
return llvm::make_error<llvm::StringError>(
"failure to import an include tree with id '" + *IncludeTreeID +
"':" + toString(ReproRefOrErr.takeError()),
llvm::inconvertibleErrorCode());
return llvm::Error::success();
};

if (auto Err = transplantCASIncludeTree(TU.IncludeTreeID))
return ReportFailure()
<< "failed to transplant a translation unit include tree due to "
<< toString(std::move(Err));
for (const ModuleDeps &ModuleDep : TU.ModuleGraph) {
if (auto Err = transplantCASIncludeTree(ModuleDep.IncludeTreeID))
return ReportFailure()
<< "failed to transplant a module '" + ModuleDep.ID.ModuleName +
"' include tree due to "
<< toString(std::move(Err));
}
Comment on lines +973 to +983
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made this code not as involved as in Swift reproducers on purpose as I don't have corresponding use cases. @cachemeifyoucan do you have any use cases in mind that I should address?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What use case are you talking about here? clang inputs are entirely captured in include tree but swift doesn't have such a representation so it has to be more involved.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. I didn't know about this difference between swift and clang.

I don't have a specific use case in mind. I was concerned my test case wasn't representative enough and worked by accident. But seems like my approach is sufficient (both in testing and in code).

} else {
SmallString<128> VFSCachePath = FileCachePath;
llvm::sys::path::append(VFSCachePath, "vfs");
std::string VFSCachePathStr = VFSCachePath.str().str();
Expand All @@ -919,6 +1002,7 @@ enum CXErrorCode clang_experimental_DependencyScanner_generateReproducer(
llvm::sys::path::append(VFSOverlayPath, "vfs.yaml");
if (FileCollector.writeMapping(VFSOverlayPath))
return ReportFailure() << "failed to write a VFS overlay mapping";
}

return Report(CXError_Success)
<< "Created a reproducer. Sources and associated run script(s) are "
Expand Down
1 change: 1 addition & 0 deletions clang/tools/libclang/libclang.map
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ LLVM_21 {
LLVM_22 {
global:
clang_experimental_DependencyScannerServiceOptions_setCacheNegativeStats;
clang_experimental_DependencyScannerReproducerOptions_setCASOptions;
};

# Example of how to add a new symbol version entry. If you do add a new symbol
Expand Down