diff --git a/toolchain/base/BUILD b/toolchain/base/BUILD index a8fd57b7a092..e167880a68cb 100644 --- a/toolchain/base/BUILD +++ b/toolchain/base/BUILD @@ -47,6 +47,19 @@ cc_test( ], ) +cc_library( + name = "clang_invocation", + srcs = ["clang_invocation.cpp"], + hdrs = ["clang_invocation.h"], + deps = [ + "//common:check", + "//toolchain/diagnostics:diagnostic_emitter", + "@llvm-project//clang:basic", + "@llvm-project//clang:frontend", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "fixed_size_value_store", hdrs = ["fixed_size_value_store.h"], diff --git a/toolchain/base/clang_invocation.cpp b/toolchain/base/clang_invocation.cpp new file mode 100644 index 000000000000..a424a91df3ba --- /dev/null +++ b/toolchain/base/clang_invocation.cpp @@ -0,0 +1,113 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/base/clang_invocation.h" + +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/Utils.h" + +namespace Carbon { + +// The fake file name to use for the synthesized includes file. +static constexpr const char IncludesFileName[] = ""; + +namespace { + +// Used to convert diagnostics from the Clang driver to Carbon diagnostics. +class ClangDriverDiagnosticConsumer : public clang::DiagnosticConsumer { + public: + // Creates an instance with the location that triggers calling Clang. + // `context` must not be null. + explicit ClangDriverDiagnosticConsumer(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 + +static auto BuildClangInvocationImpl( + Diagnostics::NoLocEmitter& emitter, + llvm::IntrusiveRefCntPtr fs, + llvm::ArrayRef clang_path_and_args) + -> std::unique_ptr { + ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter); + + // The clang driver inconveniently wants an array of `const char*`, so convert + // the arguments. + llvm::SmallVector driver_args(llvm::map_range( + clang_path_and_args, [](const std::string& str) { return str.c_str(); })); + + // Add our include file name as the input file, and force it to be interpreted + // as C++. + driver_args.push_back("-x"); + driver_args.push_back("c++"); + driver_args.push_back(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 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. + return clang::createInvocation(driver_args, + {.Diags = driver_diags, .VFS = fs}); +} + +auto BuildClangInvocation(Diagnostics::Consumer& consumer, + llvm::IntrusiveRefCntPtr fs, + llvm::ArrayRef clang_path_and_args) + -> std::unique_ptr { + Diagnostics::ErrorTrackingConsumer error_tracker(consumer); + Diagnostics::NoLocEmitter emitter(&error_tracker); + + // Forward to the implementation to avoid exposing `import_cpp` outside check. + auto invocation = BuildClangInvocationImpl(emitter, fs, clang_path_and_args); + + // If Clang produced an error, throw away its invocation. + if (error_tracker.seen_error()) { + return nullptr; + } + + return invocation; +} + +} // namespace Carbon diff --git a/toolchain/base/clang_invocation.h b/toolchain/base/clang_invocation.h new file mode 100644 index 000000000000..bae2b58a7557 --- /dev/null +++ b/toolchain/base/clang_invocation.h @@ -0,0 +1,28 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_ +#define CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_ + +#include + +#include "clang/Frontend/CompilerInvocation.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "toolchain/diagnostics/diagnostic_emitter.h" + +namespace Carbon { + +// Builds and returns a clang `CompilerInvocation` to use when building code for +// interop, from a list of clang driver arguments. Emits diagnostics to +// `consumer` if the arguments are invalid. +auto BuildClangInvocation(Diagnostics::Consumer& consumer, + llvm::IntrusiveRefCntPtr fs, + llvm::ArrayRef clang_path_and_args) + -> std::unique_ptr; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_ diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 4d2dcada5584..84f06f2254ac 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -208,6 +208,7 @@ cc_library( "//toolchain/sem_ir:file", "//toolchain/sem_ir:formatter", "//toolchain/sem_ir:typed_insts", + "@llvm-project//clang:frontend", "@llvm-project//llvm:Support", ], ) diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index a4ca8722f491..9d2bddc437bc 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -13,7 +13,9 @@ #include "toolchain/check/context.h" #include "toolchain/check/diagnostic_emitter.h" #include "toolchain/check/diagnostic_helpers.h" +#include "toolchain/check/import_cpp.h" #include "toolchain/diagnostics/diagnostic.h" +#include "toolchain/diagnostics/diagnostic_consumer.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/lex/token_kind.h" #include "toolchain/parse/node_ids.h" @@ -390,8 +392,8 @@ auto CheckParseTrees( llvm::MutableArrayRef units, llvm::ArrayRef tree_and_subtrees_getters, llvm::IntrusiveRefCntPtr fs, - llvm::StringRef clang_path, llvm::StringRef target, - const CheckParseTreesOptions& options) -> void { + const CheckParseTreesOptions& options, + std::shared_ptr clang_invocation) -> void { // UnitAndImports is big due to its SmallVectors, so we default to 0 on the // stack. llvm::SmallVector unit_infos( @@ -446,7 +448,7 @@ auto CheckParseTrees( for (int check_index = 0; check_index < static_cast(ready_to_check.size()); ++check_index) { auto* unit_info = ready_to_check[check_index]; - CheckUnit(unit_info, tree_and_subtrees_getters, fs, clang_path, target, + CheckUnit(unit_info, tree_and_subtrees_getters, fs, clang_invocation, options.vlog_stream) .Run(); for (auto* incoming_import : unit_info->incoming_imports) { @@ -495,7 +497,7 @@ auto CheckParseTrees( // incomplete imports. for (auto& unit_info : unit_infos) { if (unit_info.imports_remaining > 0) { - CheckUnit(&unit_info, tree_and_subtrees_getters, fs, clang_path, target, + CheckUnit(&unit_info, tree_and_subtrees_getters, fs, clang_invocation, options.vlog_stream) .Run(); } diff --git a/toolchain/check/check.h b/toolchain/check/check.h index d3d6ceb36804..05ac4b2411b1 100644 --- a/toolchain/check/check.h +++ b/toolchain/check/check.h @@ -5,6 +5,7 @@ #ifndef CARBON_TOOLCHAIN_CHECK_CHECK_H_ #define CARBON_TOOLCHAIN_CHECK_CHECK_H_ +#include "clang/Frontend/CompilerInvocation.h" #include "common/ostream.h" #include "toolchain/base/shared_value_stores.h" #include "toolchain/base/timings.h" @@ -75,8 +76,8 @@ auto CheckParseTrees( llvm::MutableArrayRef units, llvm::ArrayRef tree_and_subtrees_getters, llvm::IntrusiveRefCntPtr fs, - llvm::StringRef clang_path, llvm::StringRef target, - const CheckParseTreesOptions& options) -> void; + const CheckParseTreesOptions& options, + std::shared_ptr clang_invocation) -> void; } // namespace Carbon::Check diff --git a/toolchain/check/check_unit.cpp b/toolchain/check/check_unit.cpp index 7bd11a56b656..d42037cc33b8 100644 --- a/toolchain/check/check_unit.cpp +++ b/toolchain/check/check_unit.cpp @@ -57,7 +57,7 @@ CheckUnit::CheckUnit( UnitAndImports* unit_and_imports, llvm::ArrayRef tree_and_subtrees_getters, llvm::IntrusiveRefCntPtr fs, - llvm::StringRef clang_path, llvm::StringRef target, + std::shared_ptr clang_invocation, llvm::raw_ostream* vlog_stream) : unit_and_imports_(unit_and_imports), tree_and_subtrees_getter_( @@ -65,8 +65,7 @@ CheckUnit::CheckUnit( [unit_and_imports->unit->sem_ir->check_ir_id().index]), total_ir_count_(tree_and_subtrees_getters.size()), fs_(std::move(fs)), - clang_path_(clang_path), - target_(target), + clang_invocation_(std::move(clang_invocation)), emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters, unit_and_imports_->unit->sem_ir), context_( @@ -158,7 +157,7 @@ auto CheckUnit::InitPackageScopeAndImports() -> void { CARBON_CHECK(cpp_ast); CARBON_CHECK(!cpp_ast->get()); *cpp_ast = - ImportCppFiles(context_, cpp_import_names, fs_, clang_path_, target_); + ImportCppFiles(context_, cpp_import_names, fs_, clang_invocation_); } } diff --git a/toolchain/check/check_unit.h b/toolchain/check/check_unit.h index 6badd19f3343..600bc96d5312 100644 --- a/toolchain/check/check_unit.h +++ b/toolchain/check/check_unit.h @@ -5,6 +5,7 @@ #ifndef CARBON_TOOLCHAIN_CHECK_CHECK_UNIT_H_ #define CARBON_TOOLCHAIN_CHECK_CHECK_UNIT_H_ +#include "clang/Frontend/CompilerInvocation.h" #include "common/map.h" #include "llvm/ADT/SmallVector.h" #include "toolchain/check/check.h" @@ -124,7 +125,7 @@ class CheckUnit { UnitAndImports* unit_and_imports, llvm::ArrayRef tree_and_subtrees_getters, llvm::IntrusiveRefCntPtr fs, - llvm::StringRef clang_path, llvm::StringRef target, + std::shared_ptr clang_invocation, llvm::raw_ostream* vlog_stream); // Produces and checks the IR for the provided unit. @@ -187,8 +188,7 @@ class CheckUnit { // The number of IRs being checked in total. int total_ir_count_; llvm::IntrusiveRefCntPtr fs_; - llvm::StringRef clang_path_; - llvm::StringRef target_; + std::shared_ptr clang_invocation_; DiagnosticEmitter emitter_; Context context_; diff --git a/toolchain/check/import_cpp.cpp b/toolchain/check/import_cpp.cpp index 1e1125a22c00..48e6b0807527 100644 --- a/toolchain/check/import_cpp.cpp +++ b/toolchain/check/import_cpp.cpp @@ -45,9 +45,6 @@ namespace Carbon::Check { -// The fake file name to use for the synthesized includes file. -static constexpr const char IncludesFileName[] = ""; - // Generates C++ file contents to #include all requested imports. static auto GenerateCppIncludesHeaderCode( Context& context, llvm::ArrayRef 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 fs, - const std::string& clang_path, const std::string& target) - -> std::unique_ptr { - 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 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 imports, llvm::IntrusiveRefCntPtr fs, - const std::string& clang_path, - const std::string& target) + std::shared_ptr invocation) -> std::pair, bool> { - // Build the options to use to invoke the Clang frontend. - std::shared_ptr 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 imports, llvm::IntrusiveRefCntPtr fs, - llvm::StringRef clang_path, llvm::StringRef target) + std::shared_ptr invocation) -> std::unique_ptr { 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); diff --git a/toolchain/check/import_cpp.h b/toolchain/check/import_cpp.h index 141a2485b829..c6c20db49a84 100644 --- a/toolchain/check/import_cpp.h +++ b/toolchain/check/import_cpp.h @@ -5,9 +5,13 @@ #ifndef CARBON_TOOLCHAIN_CHECK_IMPORT_CPP_H_ #define CARBON_TOOLCHAIN_CHECK_IMPORT_CPP_H_ +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/VirtualFileSystem.h" #include "toolchain/check/context.h" #include "toolchain/check/diagnostic_helpers.h" +#include "toolchain/diagnostics/diagnostic_emitter.h" namespace Carbon::Check { @@ -17,7 +21,7 @@ namespace Carbon::Check { auto ImportCppFiles(Context& context, llvm::ArrayRef imports, llvm::IntrusiveRefCntPtr fs, - llvm::StringRef clang_path, llvm::StringRef target) + std::shared_ptr invocation) -> std::unique_ptr; // Looks up the given name in the Clang AST generated when importing C++ code. diff --git a/toolchain/diagnostics/coverage_test.cpp b/toolchain/diagnostics/coverage_test.cpp index 915f6395cf97..d807c1eb4ea5 100644 --- a/toolchain/diagnostics/coverage_test.cpp +++ b/toolchain/diagnostics/coverage_test.cpp @@ -58,12 +58,6 @@ constexpr Kind UntestedKinds[] = { // conversion cannot fail. This should be covered once we support `ref` // binding syntax. Kind::ConversionFailureNonRefToRef, - - // TODO: These are temporarily unreachable because we don't pass invalid - // driver options to Clang, but will become reachable once we support - // passing custom Clang arguments. - Kind::CppInteropDriverError, - Kind::CppInteropDriverWarning, }; // Looks for diagnostic kinds that aren't covered by a file_test. diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index 74c58a4614ef..c5c036f0af01 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -125,6 +125,7 @@ cc_library( "//common:raw_string_ostream", "//common:version", "//common:vlog", + "//toolchain/base:clang_invocation", "//toolchain/base:llvm_tools", "//toolchain/base:shared_value_stores", "//toolchain/base:timings", diff --git a/toolchain/driver/compile_subcommand.cpp b/toolchain/driver/compile_subcommand.cpp index 33734750b50f..d7631c9ff244 100644 --- a/toolchain/driver/compile_subcommand.cpp +++ b/toolchain/driver/compile_subcommand.cpp @@ -15,6 +15,7 @@ #include "common/vlog.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" +#include "toolchain/base/clang_invocation.h" #include "toolchain/base/timings.h" #include "toolchain/check/check.h" #include "toolchain/codegen/codegen.h" @@ -62,6 +63,30 @@ compile to machine code. &phase); }); + b.AddStringOption( + { + .name = "clang-arg", + .value_name = "CLANG-ARG", + .help = R"""( +An argument to pass to the Clang compiler for use when compiling imported C++ +code. + +All flags that are accepted by the Clang driver are supported. However, you +cannot specify arguments that would result in additional compilations being +performed. Use `carbon clang` instead to compile additional source files. +)""", + }, + [&](auto& arg_b) { arg_b.Append(&clang_args); }); + + b.AddStringPositionalArg( + { + .name = "CLANG-ARG", + .help = R"""( +Additional Clang arguments. See help for `--clang-arg` for details. +)""", + }, + [&](auto& arg_b) { arg_b.Append(&clang_args); }); + // TODO: Rearrange the code setting this option and two related ones to // allow them to reference each other instead of hard-coding their names. b.AddStringOption( @@ -820,6 +845,34 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult { return {.success = false}; } + std::shared_ptr clang_invocation; + // Build a clang invocation. We do this regardless of whether we're running + // check, because this is essentially performing further option validation, + // and we generally validate all options even if we're not using them for the + // selected phases of compilation. + // TODO: Share any arguments we specify here with the `carbon clang` + // subcommand. + { + llvm::SmallVector clang_path_and_args = { + driver_env.installation->clang_path(), + // Propagate the target to Clang. + llvm::formatv("--target={0}", options_.codegen_options.target).str(), + // Enable PIE by default, but allow it to be overridden by Clang + // arguments. Clang's default is configurable, but we'd like our + // defaults to be more stable. + // TODO: Decide if we want this. + "-fPIE", + }; + for (auto str : options_.clang_args) { + clang_path_and_args.push_back(str.str()); + } + clang_invocation = BuildClangInvocation(driver_env.consumer, driver_env.fs, + clang_path_and_args); + if (!clang_invocation) { + return {.success = false}; + } + } + // Find the files comprising the prelude if we are importing it. // TODO: Replace this with a search for library api files in a // package-specific search path based on the library name. @@ -944,8 +997,7 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult { } } Check::CheckParseTrees(check_units, cache.tree_and_subtrees_getters(), - driver_env.fs, driver_env.installation->clang_path(), - options_.codegen_options.target, options); + driver_env.fs, options, clang_invocation); CARBON_VLOG_TO(driver_env.vlog_stream, "*** Check::CheckParseTrees done ***\n"); for (auto& unit : units) { diff --git a/toolchain/driver/compile_subcommand.h b/toolchain/driver/compile_subcommand.h index c8fb5d96bc95..f1bd598f4eeb 100644 --- a/toolchain/driver/compile_subcommand.h +++ b/toolchain/driver/compile_subcommand.h @@ -41,6 +41,7 @@ struct CompileOptions { llvm::StringRef output_filename; llvm::SmallVector input_filenames; + llvm::SmallVector clang_args; bool asm_output = false; bool force_obj_output = false; diff --git a/toolchain/driver/testdata/compile/clang_args_warning.carbon b/toolchain/driver/testdata/compile/clang_args_warning.carbon new file mode 100644 index 000000000000..fe2af322342d --- /dev/null +++ b/toolchain/driver/testdata/compile/clang_args_warning.carbon @@ -0,0 +1,15 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// ARGS: --include-diagnostic-kind compile --clang-arg=-L/usr/lib foo.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/clang_args_warning.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/clang_args_warning.carbon +// CHECK:STDERR: warning: argument unused during compilation: '-L/usr/lib' [CppInteropDriverWarning] +// CHECK:STDERR: + +// --- foo.carbon diff --git a/toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon b/toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon new file mode 100644 index 000000000000..d9fdaed7a9b9 --- /dev/null +++ b/toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon @@ -0,0 +1,17 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// ARGS: --include-diagnostic-kind compile foo.carbon -- bar.cpp +// +// NOAUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_arg_extra_input.carbon +// CHECK:STDERR: error: unable to handle compilation, expected exactly one compiler job in ' "{{.*}}/clang" "-cc1" {{.*}} "-x" "c++" "bar.cpp"; "{{.*}}/clang" "-cc1" {{.*}} "-x" "c++" ""; ' [CppInteropDriverError] +// CHECK:STDERR: + +// --- foo.carbon + +// --- bar.cpp diff --git a/toolchain/driver/testdata/compile/fail_clang_args.carbon b/toolchain/driver/testdata/compile/fail_clang_args.carbon new file mode 100644 index 000000000000..a30462213bca --- /dev/null +++ b/toolchain/driver/testdata/compile/fail_clang_args.carbon @@ -0,0 +1,18 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// ARGS: --include-diagnostic-kind compile --target=x86-pc-linux-gnu --clang-arg=-Wall --clang-arg=-Wextra foo.carbon -- -Wuninitialized -Wno-all -### +// +// SET-CAPTURE-CONSOLE-OUTPUT +// SET-CHECK-SUBSET +// NOAUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_clang_args.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_args.carbon +// CHECK:STDERR: clang version {{.*}} +// CHECK:STDERR: InstalledDir: {{.*}}/toolchain/install/prefix_root/lib/carbon/../../lib/carbon/llvm/bin +// CHECK:STDERR: "{{.*}}/toolchain/install/prefix_root/lib/carbon/../../lib/carbon/llvm/bin/clang" "-cc1" {{.*}}"-triple" "x86-pc-linux-gnu" {{.*}}"-fsyntax-only" {{.*}} "-resource-dir" {{.*}} "-Wall" "-Wextra" "-Wuninitialized" "-Wno-all" {{.*}} + +// --- foo.carbon diff --git a/toolchain/driver/testdata/compile/fail_clang_args_error.carbon b/toolchain/driver/testdata/compile/fail_clang_args_error.carbon new file mode 100644 index 000000000000..c849961fdc9f --- /dev/null +++ b/toolchain/driver/testdata/compile/fail_clang_args_error.carbon @@ -0,0 +1,15 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// ARGS: --include-diagnostic-kind compile --clang-arg=-fclang-flag-that-doesnt-exist foo.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_clang_args_error.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_args_error.carbon +// CHECK:STDERR: error: unknown argument: '-fclang-flag-that-doesnt-exist' [CppInteropDriverError] +// CHECK:STDERR: + +// --- foo.carbon diff --git a/toolchain/language_server/BUILD b/toolchain/language_server/BUILD index a029d1e2dd1c..ad9f0bec2a64 100644 --- a/toolchain/language_server/BUILD +++ b/toolchain/language_server/BUILD @@ -35,6 +35,7 @@ cc_library( "//common:check", "//common:map", "//common:raw_string_ostream", + "//toolchain/base:clang_invocation", "//toolchain/base:shared_value_stores", "//toolchain/check", "//toolchain/diagnostics:diagnostic_emitter", diff --git a/toolchain/language_server/context.cpp b/toolchain/language_server/context.cpp index 26b74896d57d..48d027743fe9 100644 --- a/toolchain/language_server/context.cpp +++ b/toolchain/language_server/context.cpp @@ -11,10 +11,12 @@ #include "common/check.h" #include "common/raw_string_ostream.h" #include "llvm/TargetParser/Host.h" +#include "toolchain/base/clang_invocation.h" #include "toolchain/base/shared_value_stores.h" #include "toolchain/check/check.h" #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/diagnostics/diagnostic_consumer.h" +#include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/lex/lex.h" #include "toolchain/lex/tokenized_buffer.h" #include "toolchain/parse/parse.h" @@ -157,16 +159,20 @@ auto Context::File::SetText(Context& context, std::optional version, auto getter = [this]() -> const Parse::TreeAndSubtrees& { return *tree_and_subtrees_; }; - llvm::IntrusiveRefCntPtr fs = - new llvm::vfs::InMemoryFileSystem; + // TODO: Include any unsaved files as an overlay on the real file system. + llvm::IntrusiveRefCntPtr fs = + llvm::vfs::getRealFileSystem(); // TODO: Include the prelude. Check::CheckParseTreesOptions check_options; check_options.vlog_stream = context.vlog_stream(); + + auto clang_invocation = + BuildClangInvocation(consumer, fs, {context.installation().clang_path()}); + Check::CheckParseTrees(units, llvm::ArrayRef(getter), - fs, context.installation().clang_path(), - llvm::sys::getDefaultTargetTriple(), check_options); + fs, check_options, std::move(clang_invocation)); // Note we need to publish diagnostics even when empty. // TODO: Consider caching previously published diagnostics and only publishing