Build the clang::CompilerInvocation in the driver. (#5784)

Add driver flags to specify clang driver arguments.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
Richard Smith
2025-07-18 16:30:47 +00:00
committed by GitHub
co-authored by Chandler Carruth
parent bcba76aca7
commit 553dd6e531
20 changed files with 322 additions and 136 deletions
+15 -110
View File
@@ -45,9 +45,6 @@
namespace Carbon::Check {
// The fake file name to use for the synthesized includes file.
static constexpr const char IncludesFileName[] = "<carbon Cpp imports>";
// Generates C++ file contents to #include all requested imports.
static auto GenerateCppIncludesHeaderCode(
Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
@@ -101,99 +98,6 @@ static auto AddImportIRInst(Context& context,
namespace {
// Used to convert diagnostics from the Clang driver to Carbon diagnostics.
class CarbonClangDriverDiagnosticConsumer : public clang::DiagnosticConsumer {
public:
// Creates an instance with the location that triggers calling Clang.
// `context` must not be null.
explicit CarbonClangDriverDiagnosticConsumer(
Diagnostics::NoLocEmitter* emitter)
: emitter_(emitter) {}
// Generates a Carbon warning for each Clang warning and a Carbon error for
// each Clang error or fatal.
auto HandleDiagnostic(clang::DiagnosticsEngine::Level diag_level,
const clang::Diagnostic& info) -> void override {
DiagnosticConsumer::HandleDiagnostic(diag_level, info);
llvm::SmallString<256> message;
info.FormatDiagnostic(message);
switch (diag_level) {
case clang::DiagnosticsEngine::Ignored:
case clang::DiagnosticsEngine::Note:
case clang::DiagnosticsEngine::Remark: {
// TODO: Emit notes and remarks.
break;
}
case clang::DiagnosticsEngine::Warning:
case clang::DiagnosticsEngine::Error:
case clang::DiagnosticsEngine::Fatal: {
CARBON_DIAGNOSTIC(CppInteropDriverWarning, Warning, "{0}", std::string);
CARBON_DIAGNOSTIC(CppInteropDriverError, Error, "{0}", std::string);
emitter_->Emit(diag_level == clang::DiagnosticsEngine::Warning
? CppInteropDriverWarning
: CppInteropDriverError,
message.str().str());
break;
}
}
}
private:
// Diagnostic emitter. Note that driver diagnostics don't have meaningful
// locations attached.
Diagnostics::NoLocEmitter* emitter_;
};
} // namespace
// Builds a clang `CompilerInvocation` describing the options to use to build an
// imported C++ AST.
// TODO: Cache the compiler invocation created here and reuse it if building
// multiple AST units. Consider building the `CompilerInvocation` from the
// driver and passing it into check. This would also allow us to have a shared
// set of defaults between the Clang invocation we use for imports and the
// invocation we use for `carbon clang`.
static auto BuildCompilerInvocation(
Context& context, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const std::string& clang_path, const std::string& target)
-> std::unique_ptr<clang::CompilerInvocation> {
Diagnostics::NoLocEmitter emitter(context.emitter());
CarbonClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
const char* driver_args[] = {
clang_path.c_str(),
// Propagate the target to Clang.
"-target",
target.c_str(),
// Require PIE. Note its default is configurable in Clang.
"-fPIE",
// Parse as a C++ (not C) header.
"-x",
"c++",
IncludesFileName,
};
// Build a diagnostics engine. Note that we don't have any diagnostic options
// yet; they're produced by running the driver.
clang::DiagnosticOptions driver_diag_opts;
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> driver_diags(
clang::CompilerInstance::createDiagnostics(*fs, driver_diag_opts,
&diagnostics_consumer,
/*ShouldOwnClient=*/false));
// Ask the driver to process the arguments and build a corresponding clang
// frontend invocation.
auto invocation =
clang::createInvocation(driver_args, {.Diags = driver_diags, .VFS = fs});
// Emit any queued diagnostics from parsing our driver arguments.
return invocation;
}
namespace {
// Used to convert Clang diagnostics to Carbon diagnostics.
class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
public:
@@ -310,16 +214,8 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
static auto GenerateAst(Context& context,
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const std::string& clang_path,
const std::string& target)
std::shared_ptr<clang::CompilerInvocation> invocation)
-> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
// Build the options to use to invoke the Clang frontend.
std::shared_ptr<clang::CompilerInvocation> invocation =
BuildCompilerInvocation(context, fs, clang_path, target);
if (!invocation) {
return {nullptr, true};
}
// Build a diagnostics engine.
CarbonClangDiagnosticConsumer diagnostics_consumer(&context,
invocation.get());
@@ -328,11 +224,20 @@ static auto GenerateAst(Context& context,
*fs, invocation->getDiagnosticOpts(), &diagnostics_consumer,
/*ShouldOwnClient=*/false));
// Extract the input from the frontend invocation and make sure it makes
// sense.
const auto& inputs = invocation->getFrontendOpts().Inputs;
CARBON_CHECK(inputs.size() == 1 &&
inputs[0].getKind().getLanguage() == clang::Language::CXX &&
inputs[0].getKind().getFormat() == clang::InputKind::Source);
llvm::StringRef file_name = inputs[0].getFile();
// Remap the imports file name to the corresponding `#include`s.
// TODO: Modify the frontend options to specify this memory buffer as input
// instead of remapping the file.
std::string includes = GenerateCppIncludesHeaderCode(context, imports);
auto includes_buffer =
llvm::MemoryBuffer::getMemBuffer(includes, IncludesFileName);
invocation->getPreprocessorOpts().addRemappedFile(IncludesFileName,
auto includes_buffer = llvm::MemoryBuffer::getMemBuffer(includes, file_name);
invocation->getPreprocessorOpts().addRemappedFile(file_name,
includes_buffer.get());
// Create the AST unit.
@@ -388,7 +293,7 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
auto ImportCppFiles(Context& context,
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::StringRef clang_path, llvm::StringRef target)
std::shared_ptr<clang::CompilerInvocation> invocation)
-> std::unique_ptr<clang::ASTUnit> {
if (imports.empty()) {
return nullptr;
@@ -404,7 +309,7 @@ auto ImportCppFiles(Context& context,
auto name_scope_id = AddNamespace(context, package_id, imports);
auto [generated_ast, ast_has_error] =
GenerateAst(context, imports, fs, clang_path.str(), target.str());
GenerateAst(context, imports, fs, std::move(invocation));
SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id);
name_scope.set_is_closed_import(true);