mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Support multi-file compilation, and in particular imports of files from the prelude, in `carbon language_server`. In order to properly interface with `CompileDriver`, also switch over to building a proper VFS from the documents we're given. Assisted-by: Gemini via Antigravity
233 lines
8.1 KiB
C++
233 lines
8.1 KiB
C++
// 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/driver/config_subcommand.h"
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <variant>
|
|
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Lex/HeaderSearch.h"
|
|
#include "common/check.h"
|
|
#include "common/command_line.h"
|
|
#include "common/filesystem.h"
|
|
#include "common/version.h"
|
|
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "llvm/TargetParser/Triple.h"
|
|
#include "toolchain/base/clang_invocation.h"
|
|
#include "toolchain/diagnostics/consumer.h"
|
|
#include "toolchain/diagnostics/diagnostic.h"
|
|
#include "toolchain/diagnostics/emitter.h"
|
|
#include "toolchain/driver/clang_runner.h"
|
|
#include "toolchain/driver/driver_env.h"
|
|
#include "toolchain/driver/driver_subcommand.h"
|
|
|
|
namespace Carbon {
|
|
|
|
auto ConfigOptions::Build(CommandLine::CommandBuilder& b) -> void {
|
|
b.AddFlag(
|
|
{
|
|
.name = "json",
|
|
.help = R"""(
|
|
Render output as a JSON map for easy parsing.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.Default(false);
|
|
arg_b.Set(&json_output);
|
|
});
|
|
|
|
codegen_options.Build(b);
|
|
}
|
|
|
|
static constexpr CommandLine::CommandInfo SubcommandInfo = {
|
|
.name = "config",
|
|
.help = R"""(
|
|
Print configuration info for the Carbon toolchain.
|
|
|
|
This subcommand displays configuration information for the Carbon toolchain.
|
|
This can be useful for build systems as well as debugging issues.
|
|
)""",
|
|
};
|
|
|
|
ConfigSubcommand::ConfigSubcommand() : DriverSubcommand(SubcommandInfo) {}
|
|
|
|
namespace {
|
|
struct ConfigDataEntry {
|
|
std::string key;
|
|
std::variant<std::string, llvm::SmallVector<std::string>> value;
|
|
};
|
|
} // namespace
|
|
|
|
// Creates a Clang invocation and queries it for config data to render.
|
|
//
|
|
// This includes the sysroot and the include directories searched during
|
|
// compilation.
|
|
//
|
|
// If there are any errors setting up Clang, this will diagnose them using
|
|
// `driver_env.consumer` and return `false`. If successful, returns `true`.
|
|
static auto ComputeClangConfig(DriverEnv& driver_env,
|
|
llvm::StringRef target_str,
|
|
llvm::SmallVectorImpl<ConfigDataEntry>& data)
|
|
-> bool {
|
|
// Build a library invocation of Clang in order to query its header search
|
|
// paths.
|
|
std::shared_ptr clang_invocation =
|
|
BuildClangInvocation(*driver_env.consumer, driver_env.fs,
|
|
*driver_env.installation, target_str, {});
|
|
clang_invocation->getFrontendOpts().DisableFree = false;
|
|
|
|
// Setup up a driver-style diagnostic engine for the compiler invocation and
|
|
// instance below as we won't go past that while computing the include dirs.
|
|
Diagnostics::ErrorTrackingConsumer error_tracker(*driver_env.consumer);
|
|
Diagnostics::NoLocEmitter emitter(&error_tracker);
|
|
ClangDriverDiagnosticConsumer diagnostic_consumer(&emitter);
|
|
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
|
|
clang::CompilerInstance::createDiagnostics(
|
|
*driver_env.fs, clang_invocation->getDiagnosticOpts(),
|
|
&diagnostic_consumer,
|
|
/*ShouldOwnClient=*/false));
|
|
|
|
auto clang_instance =
|
|
std::make_unique<clang::CompilerInstance>(clang_invocation);
|
|
clang_instance->setDiagnostics(diags);
|
|
clang_instance->setVirtualFileSystem(driver_env.fs);
|
|
clang_instance->createFileManager();
|
|
clang_instance->createSourceManager();
|
|
if (!clang_instance->createTarget()) {
|
|
CARBON_DIAGNOSTIC(ConfigFailedToSetupTarget, Error,
|
|
"unable to setup the requested target `{0}`",
|
|
std::string);
|
|
driver_env.emitter.Emit(ConfigFailedToSetupTarget, target_str.str());
|
|
return false;
|
|
}
|
|
|
|
auto header_search = std::make_unique<clang::HeaderSearch>(
|
|
clang_instance->getHeaderSearchOpts(), clang_instance->getSourceManager(),
|
|
clang_instance->getDiagnostics(), clang_instance->getLangOpts(),
|
|
&clang_instance->getTarget());
|
|
clang::ApplyHeaderSearchOptions(
|
|
*header_search, clang_instance->getHeaderSearchOpts(),
|
|
clang_instance->getLangOpts(), clang_instance->getTarget().getTriple());
|
|
|
|
// If we ended up diagnosing any errors, just return. They will have been
|
|
// converted to Carbon diagnostics.
|
|
if (error_tracker.seen_error()) {
|
|
return false;
|
|
}
|
|
|
|
data.push_back({.key = "CLANG_SYSROOT",
|
|
.value = clang_instance->getHeaderSearchOpts().Sysroot});
|
|
|
|
llvm::SmallVector<std::string> search_paths;
|
|
for (const auto& search_dir : header_search->search_dir_range()) {
|
|
search_paths.push_back(search_dir.getName().str());
|
|
}
|
|
data.push_back(
|
|
{.key = "CLANG_INCLUDE_DIRS", .value = std::move(search_paths)});
|
|
|
|
return true;
|
|
}
|
|
|
|
static auto RenderDataAsJson(llvm::ArrayRef<ConfigDataEntry> data,
|
|
llvm::raw_ostream& out) -> void {
|
|
out << "{\n";
|
|
llvm::ListSeparator data_sep(",\n");
|
|
for (const auto& entry : data) {
|
|
out << data_sep << " \"" << entry.key << "\": ";
|
|
|
|
if (const auto* value = std::get_if<std::string>(&entry.value)) {
|
|
out << "\"" << *value << "\"";
|
|
} else if (const auto* value =
|
|
std::get_if<llvm::SmallVector<std::string>>(&entry.value)) {
|
|
out << "[\n";
|
|
llvm::ListSeparator element_sep(",\n");
|
|
for (const std::string& value_element : *value) {
|
|
out << element_sep << " \"" << value_element << "\"";
|
|
}
|
|
out << "\n ]";
|
|
} else {
|
|
CARBON_FATAL("Invalid value in config data entry!");
|
|
}
|
|
}
|
|
out << "\n}\n";
|
|
}
|
|
|
|
static auto RenderData(llvm::ArrayRef<ConfigDataEntry> data,
|
|
llvm::raw_ostream& out) -> void {
|
|
for (const auto& entry : data) {
|
|
out << entry.key << ":";
|
|
if (const auto* value = std::get_if<std::string>(&entry.value)) {
|
|
out << " " << *value << "\n";
|
|
} else if (const auto* value =
|
|
std::get_if<llvm::SmallVector<std::string>>(&entry.value)) {
|
|
out << "\n";
|
|
for (const std::string& value_element : *value) {
|
|
out << " " << value_element << "\n";
|
|
}
|
|
} else {
|
|
CARBON_FATAL("Invalid value in config data entry!");
|
|
}
|
|
}
|
|
}
|
|
|
|
auto ConfigSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
|
|
bool result = true;
|
|
|
|
// Start with basic data available from the driver or global constants.
|
|
llvm::SmallVector<ConfigDataEntry> data = {
|
|
{.key = "CLANG_RESOURCE_DIR",
|
|
.value = driver_env.installation->clang_resource_path()},
|
|
{.key = "INSTALL_ROOT", .value = driver_env.installation->root()},
|
|
{.key = "LLVM_BINDIR",
|
|
.value = driver_env.installation->llvm_install_bin()},
|
|
{.key = "VERSION", .value = Version::String.str()},
|
|
};
|
|
|
|
// Try to read the installation digest and include that.
|
|
auto read_result = Filesystem::Cwd().ReadFileToString(
|
|
driver_env.installation->digest_path());
|
|
if (!read_result.ok()) {
|
|
CARBON_DIAGNOSTIC(ConfigFailedToReadDigest, Error,
|
|
"unable to read the installation's digest file: {0}",
|
|
std::string);
|
|
driver_env.emitter.Emit(ConfigFailedToReadDigest,
|
|
read_result.error().ToString());
|
|
|
|
// Remember that we encountered an error but continue to give a minimally
|
|
// useful `config` output.
|
|
result = false;
|
|
} else {
|
|
data.push_back({.key = "INSTALL_DIGEST",
|
|
.value = llvm::StringRef(*read_result).rtrim().str()});
|
|
}
|
|
|
|
// Compute and print Clang's config entries if we can. This will have been
|
|
// diagnosed while computing, so just track if we hit errors.
|
|
result &=
|
|
ComputeClangConfig(driver_env, options_.codegen_options.target, data);
|
|
|
|
llvm::sort(data, [](const ConfigDataEntry& lhs, const ConfigDataEntry& rhs) {
|
|
return lhs.key < rhs.key;
|
|
});
|
|
|
|
if (options_.json_output) {
|
|
RenderDataAsJson(data, *driver_env.output_stream);
|
|
} else {
|
|
RenderData(data, *driver_env.output_stream);
|
|
}
|
|
|
|
return {.success = result};
|
|
}
|
|
|
|
} // namespace Carbon
|