|
|
@@ -0,0 +1,228 @@
|
|
|
+// 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/diagnostic.h"
|
|
|
+#include "toolchain/diagnostics/diagnostic_consumer.h"
|
|
|
+#include "toolchain/diagnostics/diagnostic_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 the include directories
|
|
|
+// searched during compilation. If there are any errors setting up Clang, this
|
|
|
+// will diagnose them using `driver_env.consumer` and return `std::nullopt`.
|
|
|
+static auto ComputeClangIncludeDirs(DriverEnv& driver_env,
|
|
|
+ llvm::StringRef target_str)
|
|
|
+ -> std::optional<llvm::SmallVector<std::string>> {
|
|
|
+ // 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 std::nullopt;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 std::nullopt;
|
|
|
+ }
|
|
|
+
|
|
|
+ llvm::SmallVector<std::string> search_paths;
|
|
|
+ for (const auto& search_dir : header_search->search_dir_range()) {
|
|
|
+ search_paths.push_back(search_dir.getName().str());
|
|
|
+ }
|
|
|
+ return search_paths;
|
|
|
+}
|
|
|
+
|
|
|
+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 include dirs if we can.
|
|
|
+ std::optional<llvm::SmallVector<std::string>> clang_include_dirs =
|
|
|
+ ComputeClangIncludeDirs(driver_env, options_.codegen_options.target);
|
|
|
+ if (clang_include_dirs) {
|
|
|
+ data.push_back(
|
|
|
+ {.key = "CLANG_INCLUDE_DIRS", .value = *std::move(clang_include_dirs)});
|
|
|
+ } else {
|
|
|
+ // This will have been diagnosed while computing, continue with degraded
|
|
|
+ // data.
|
|
|
+ result = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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
|