mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 12:28:32 +00:00
Diagnostic rendering was part of the data model: `Loc::FormatLocation` and `Loc::FormatSnippet` wrote plain text to a stream, so no capability, color, or terminal width could reach them. A diagnostic is now one message with labels, contexts, and location information attached, and `Diagnostics::Renderer` draws it into a `Terminal::Buffer`. The design is in `toolchain/docs/diagnostics_rendering.md` (#7668), authoring in `toolchain/docs/diagnostics.md`. Before: ``` example.carbon:6:3: error: 1 argument passed to function expecting 0 arguments Run0(1); ^~~~~~~ example.carbon:3:1: note: calling function declared here fn Run0() {} ^~~~~~~~~~~ ``` After: ``` error: 1 argument passed to function expecting 0 arguments ╭─┤ example.carbon:6:3 ┆ 3 │ fn Run0() {} · ─────┬───── · ╰──┤ calling function declared here ┆ -> 6 │ Run0(1); · ─────── ╰──────────── ``` `--no-diagnostic-snippets` gives a compact form, `example.carbon:6:3-9`. File tests use it; the rendering tests under `toolchain/diagnostics/testdata` ask for snippets. C++ interop diagnostics are data now rather than text Clang drew: `SemIR::ClangSourceLocId` names a `clang::SourceRange`, and a Clang location is marked as the point it names. `StreamConsumer` moves to a `:renderer` target that `:emitter` does not depend on, keeping `common/terminal` out of `consumer.h`. Capabilities come from the file behind the error stream, so tests get plain ASCII. `renderer_fuzzer` draws diagnostics built from fuzzer bytes with every capability combination, since a shape the renderer doesn't expect must never be why the compiler dies while reporting a problem. TODOs mark the follow-ups: line weights and crossing lines, wrapping that keeps a quoted type whole, and ordering diagnostics that share a byte offset. Also: `IncorrectExtension`'s hint moves into its message, since its note had no location of its own, and `strip_noise` in `check_diagnostics.py` blanks multi-line `#define`s instead of deleting them. Assisted-by: Claude Code
403 lines
14 KiB
C++
403 lines
14 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/driver.h"
|
|
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
#include "common/command_line.h"
|
|
#include "common/pretty_stack_trace_function.h"
|
|
#include "common/version.h"
|
|
#include "toolchain/diagnostics/stream_consumer.h"
|
|
#include "toolchain/driver/build_runtimes_subcommand.h"
|
|
#include "toolchain/driver/build_subcommand.h"
|
|
#include "toolchain/driver/clang_subcommand.h"
|
|
#include "toolchain/driver/compile_subcommand.h"
|
|
#include "toolchain/driver/config_subcommand.h"
|
|
#include "toolchain/driver/format_subcommand.h"
|
|
#include "toolchain/driver/language_server_subcommand.h"
|
|
#include "toolchain/driver/link_subcommand.h"
|
|
#include "toolchain/driver/lld_subcommand.h"
|
|
#include "toolchain/driver/llvm_subcommand.h"
|
|
|
|
namespace Carbon {
|
|
|
|
namespace {
|
|
struct Options {
|
|
static const CommandLine::CommandInfo Info;
|
|
|
|
auto Build(CommandLine::CommandBuilder& b) -> void;
|
|
|
|
bool verbose = false;
|
|
bool fuzzing = false;
|
|
bool include_diagnostic_kind = false;
|
|
bool diagnostic_snippets = true;
|
|
bool threads = true;
|
|
bool build_runtimes_on_demand = false;
|
|
|
|
Terminal::Preferences terminal;
|
|
|
|
llvm::StringRef runtimes_cache_path;
|
|
llvm::StringRef prebuilt_runtimes_path;
|
|
|
|
BuildRuntimesSubcommand runtimes;
|
|
BuildSubcommand build;
|
|
ClangSubcommand clang;
|
|
CompileSubcommand compile;
|
|
ConfigSubcommand config;
|
|
FormatSubcommand format;
|
|
LanguageServerSubcommand language_server;
|
|
LinkSubcommand link;
|
|
LldSubcommand lld;
|
|
LLVMSubcommand llvm;
|
|
|
|
// On success, this is set to the subcommand to run.
|
|
DriverSubcommand* selected_subcommand = nullptr;
|
|
};
|
|
} // namespace
|
|
|
|
// Note that this is not constexpr so that it can include information generated
|
|
// in separate translation units and potentially overridden at link time in the
|
|
// version string.
|
|
const CommandLine::CommandInfo Options::Info = {
|
|
.name = "carbon",
|
|
.version = Version::ToolchainInfo,
|
|
.help = R"""(
|
|
This is the unified Carbon Language toolchain driver. Its subcommands provide
|
|
all of the core behavior of the toolchain, including compilation, linking, and
|
|
developer tools. Each of these has its own subcommand, and you can pass a
|
|
specific subcommand to the `help` subcommand to get details about its usage.
|
|
)""",
|
|
.help_epilogue = R"""(
|
|
For questions, issues, or bug reports, please use our GitHub project:
|
|
|
|
https://github.com/carbon-language/carbon-lang
|
|
)""",
|
|
};
|
|
|
|
auto Options::Build(CommandLine::CommandBuilder& b) -> void {
|
|
b.AddFlag(
|
|
{
|
|
.name = "verbose",
|
|
.short_name = "v",
|
|
.help = "Enable verbose logging to the stderr stream.",
|
|
},
|
|
[&](CommandLine::FlagBuilder& arg_b) { arg_b.Set(&verbose); });
|
|
|
|
b.AddStringOption(
|
|
{
|
|
.name = "runtimes-cache",
|
|
.value_name = "PATH",
|
|
.help = R"""(
|
|
Specify a custom runtimes cache location.
|
|
|
|
By default, the runtimes cache is located in the `carbon_runtimes` subdirectory
|
|
of `$XDG_CACHE_HOME` (or `$HOME/.cache` if not set). If unable to use either, it
|
|
will be placed in a temporary directory that is removed when the command
|
|
completes. This flag overrides that logic with a specific path. It has no effect
|
|
if --prebuilt-runtimes is set.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) { arg_b.Set(&runtimes_cache_path); });
|
|
|
|
b.AddStringOption(
|
|
{
|
|
.name = "prebuilt-runtimes",
|
|
.value_name = "PATH",
|
|
.help = R"""(
|
|
Path to prebuilt runtimes tree.
|
|
|
|
If this option is provided, runtimes will not be built on demand and this path
|
|
will be used instead.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) { arg_b.Set(&prebuilt_runtimes_path); });
|
|
b.AddFlag(
|
|
{
|
|
.name = "build-runtimes",
|
|
.help = R"""(
|
|
Enables on-demand building of target-specific runtimes.
|
|
|
|
When enabled (the default), any link actions using `clang` will build the
|
|
necessary runtimes on-demand. This build will use any customization it can from
|
|
the link command line flags to build the runtimes for the correct target and
|
|
with any desired features enabled.
|
|
|
|
Note: this only has an effect when `--prebuilt-runtimes` are not provided. If
|
|
there are no prebuilt runtimes and building runtimes is disabled, then it is
|
|
assumed the installed toolchain has had the necessary target runtimes added to
|
|
the installation tree in the default searched locations.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.Default(true);
|
|
arg_b.Set(&build_runtimes_on_demand);
|
|
});
|
|
|
|
b.AddFlag(
|
|
{
|
|
.name = "diagnostic-snippets",
|
|
.help = R"""(
|
|
Whether diagnostics show snippets of the source they point at.
|
|
|
|
By default a diagnostic shows one view of each file it points into, with the
|
|
lines between its ranges elided, so a message and a label near it read as one
|
|
piece of code. `--no-diagnostic-snippets` drops that: each part of a diagnostic
|
|
is one line, led by its location and the extent of its range, positioned
|
|
against nothing, which is what a build log and a golden file want.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.Default(true);
|
|
arg_b.Set(&diagnostic_snippets);
|
|
});
|
|
|
|
b.AddOneOfOption(
|
|
{
|
|
.name = "terminal-background",
|
|
.value_name = "(auto|dark|light)",
|
|
.help = R"""(
|
|
What the terminal draws its text on, which decides the colors a diagnostic is
|
|
drawn in.
|
|
|
|
By default this is read from `COLORFGBG`, which some terminals set, and is
|
|
otherwise taken to be dark: guessing dark when the terminal is light costs
|
|
contrast, while guessing light when it is dark puts pale text on a pale
|
|
background.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.SetOneOf(
|
|
{
|
|
arg_b.OneOfValue("auto", Terminal::BackgroundPreference::Auto)
|
|
.Default(true),
|
|
arg_b.OneOfValue("dark", Terminal::BackgroundPreference::Dark),
|
|
arg_b.OneOfValue("light",
|
|
Terminal::BackgroundPreference::Light),
|
|
},
|
|
&terminal.background);
|
|
});
|
|
|
|
b.AddOneOfOption(
|
|
{
|
|
.name = "color",
|
|
.value_name = "(auto|always|never)",
|
|
.help = R"""(
|
|
Whether to color diagnostics.
|
|
|
|
By default, color is used when the error stream is a terminal that is known to
|
|
render it. `NO_COLOR` in the environment disables it, and `CLICOLOR_FORCE` or
|
|
`FORCE_COLOR` enables it even when the stream isn't a terminal.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.SetOneOf(
|
|
{
|
|
arg_b.OneOfValue("auto", Terminal::Preference::Auto)
|
|
.Default(true),
|
|
arg_b.OneOfValue("always", Terminal::Preference::Always),
|
|
arg_b.OneOfValue("never", Terminal::Preference::Never),
|
|
},
|
|
&terminal.color);
|
|
});
|
|
|
|
b.AddOneOfOption(
|
|
{
|
|
.name = "terminal-unicode",
|
|
.value_name = "(auto|always|never)",
|
|
.help = R"""(
|
|
Whether to draw output with Unicode box drawing characters.
|
|
|
|
By default, they are used when the locale says the terminal decodes UTF-8.
|
|
Drawing them for a terminal that doesn't misaligns every line that isn't plain
|
|
ASCII, so this is deliberately separate from `--color`.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.SetOneOf(
|
|
{
|
|
arg_b.OneOfValue("auto", Terminal::Preference::Auto)
|
|
.Default(true),
|
|
arg_b.OneOfValue("always", Terminal::Preference::Always),
|
|
arg_b.OneOfValue("never", Terminal::Preference::Never),
|
|
},
|
|
&terminal.utf8);
|
|
});
|
|
|
|
b.AddFlag(
|
|
{
|
|
.name = "fuzzing",
|
|
.help = "Configure the command line for fuzzing.",
|
|
},
|
|
[&](CommandLine::FlagBuilder& arg_b) { arg_b.Set(&fuzzing); });
|
|
|
|
b.AddFlag(
|
|
{
|
|
.name = "include-diagnostic-kind",
|
|
.help = R"""(
|
|
When printing diagnostics, include the diagnostic kind as part of output. This
|
|
applies to each label as well as to the message.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) { arg_b.Set(&include_diagnostic_kind); });
|
|
|
|
b.AddFlag(
|
|
{
|
|
.name = "threads",
|
|
.help = R"""(
|
|
Controls whether threads are used to build runtimes.
|
|
|
|
When enabled (the default), Carbon will try to build runtime libraries using
|
|
threads to parallelize the operation. How many threads is controlled
|
|
automatically by the system.
|
|
|
|
Disabling threads ensures a single threaded build of the runtimes which can help
|
|
when there are errors or other output.
|
|
)""",
|
|
},
|
|
[&](auto& arg_b) {
|
|
arg_b.Default(true);
|
|
arg_b.Set(&threads);
|
|
});
|
|
|
|
runtimes.AddTo(b, &selected_subcommand);
|
|
build.AddTo(b, &selected_subcommand);
|
|
clang.AddTo(b, &selected_subcommand);
|
|
compile.AddTo(b, &selected_subcommand);
|
|
config.AddTo(b, &selected_subcommand);
|
|
format.AddTo(b, &selected_subcommand);
|
|
language_server.AddTo(b, &selected_subcommand);
|
|
link.AddTo(b, &selected_subcommand);
|
|
lld.AddTo(b, &selected_subcommand);
|
|
llvm.AddTo(b, &selected_subcommand);
|
|
|
|
b.RequiresSubcommand();
|
|
}
|
|
|
|
static auto HandleRuntimesOptions(const Options& options, DriverEnv& driver_env)
|
|
-> bool {
|
|
if (!options.prebuilt_runtimes_path.empty()) {
|
|
auto result = Runtimes::OpenExisting(options.prebuilt_runtimes_path.str(),
|
|
driver_env.vlog_stream);
|
|
if (!result.ok()) {
|
|
// TODO: We should provide a better diagnostic than the raw error.
|
|
CARBON_DIAGNOSTIC(DriverPrebuiltRuntimesInvalid, Error, "{0}",
|
|
std::string);
|
|
driver_env.emitter.Emit(DriverPrebuiltRuntimesInvalid,
|
|
result.error().message());
|
|
return false;
|
|
}
|
|
driver_env.prebuilt_runtimes = *std::move(result);
|
|
return true;
|
|
}
|
|
|
|
if (!options.build_runtimes_on_demand) {
|
|
// Nothing else needed if we're not building runtimes on demand.
|
|
CARBON_CHECK(!driver_env.build_runtimes_on_demand);
|
|
return true;
|
|
}
|
|
driver_env.build_runtimes_on_demand = true;
|
|
|
|
// If we don't have prebuilt runtimes and are building them on demand, we need
|
|
// to configure the runtimes cache.
|
|
auto cache_result =
|
|
options.runtimes_cache_path.empty()
|
|
? Runtimes::Cache::MakeSystem(*driver_env.installation,
|
|
driver_env.vlog_stream)
|
|
: Runtimes::Cache::MakeCustom(
|
|
*driver_env.installation,
|
|
std::filesystem::absolute(options.runtimes_cache_path.str()),
|
|
driver_env.vlog_stream);
|
|
if (!cache_result.ok()) {
|
|
// TODO: We should provide a better diagnostic than the raw error.
|
|
CARBON_DIAGNOSTIC(DriverRuntimesCacheInvalid, Error, "{0}", std::string);
|
|
driver_env.emitter.Emit(DriverRuntimesCacheInvalid,
|
|
cache_result.error().message());
|
|
return false;
|
|
}
|
|
driver_env.runtimes_cache = std::move(*cache_result);
|
|
return true;
|
|
}
|
|
|
|
auto Driver::ErrorCapabilities(Terminal::Preferences preferences) const
|
|
-> Terminal::Capabilities {
|
|
if (error_file_.is_valid()) {
|
|
return Terminal::Capabilities::Detect(error_file_, preferences);
|
|
}
|
|
|
|
// With no file behind the error stream, as in tests and in-process uses,
|
|
// nothing is detected: detection would make output depend on the environment
|
|
// a test happens to run in. Only an explicit preference has any effect.
|
|
return {.color_mode = Terminal::ChooseColorMode(preferences.color, {},
|
|
/*is_terminal=*/false),
|
|
.charset = Terminal::ChooseCharset(preferences.utf8, /*locale=*/""),
|
|
.background = Terminal::ChooseBackground(preferences.background,
|
|
/*colorfgbg=*/"")};
|
|
}
|
|
|
|
auto Driver::RunCommand(llvm::ArrayRef<llvm::StringRef> args) -> DriverResult {
|
|
PrettyStackTraceFunction trace_version([&](llvm::raw_ostream& out) {
|
|
out << "Carbon version: " << Version::String << "\n";
|
|
});
|
|
|
|
Options options;
|
|
Diagnostics::StreamConsumer consumer(error_stream_);
|
|
DriverEnv env(fs_, installation_, input_stream_, output_stream_,
|
|
error_stream_, fuzzing_, enable_leaking_, &consumer);
|
|
env.mem_usage = mem_usage_;
|
|
|
|
ErrorOr<CommandLine::ParseResult> result = CommandLine::Parse(
|
|
args, *env.output_stream, Options::Info,
|
|
[&](CommandLine::CommandBuilder& b) { options.Build(b); });
|
|
|
|
// Regardless of whether the parse succeeded, try to use the diagnostic kind
|
|
// flag and render for whatever is behind the error stream.
|
|
consumer.set_include_diagnostic_kind(options.include_diagnostic_kind);
|
|
consumer.set_diagnostic_snippets(options.diagnostic_snippets);
|
|
consumer.set_capabilities(ErrorCapabilities(options.terminal));
|
|
|
|
if (env.installation->error()) {
|
|
CARBON_DIAGNOSTIC(DriverInstallInvalid, Error, "{0}", std::string);
|
|
env.emitter.Emit(DriverInstallInvalid, env.installation->error()->str());
|
|
return {.success = false};
|
|
}
|
|
|
|
if (!result.ok()) {
|
|
CARBON_DIAGNOSTIC(DriverCommandLineParseFailed, Error, "{0}", std::string);
|
|
env.emitter.Emit(DriverCommandLineParseFailed,
|
|
PrintToString(result.error()));
|
|
return {.success = false};
|
|
} else if (*result == CommandLine::ParseResult::MetaSuccess) {
|
|
return {.success = true};
|
|
}
|
|
|
|
if (!HandleRuntimesOptions(options, env)) {
|
|
return {.success = false};
|
|
}
|
|
|
|
if (options.verbose) {
|
|
// Note this implies streamed output in order to interleave.
|
|
env.vlog_stream = env.error_stream;
|
|
}
|
|
if (options.fuzzing) {
|
|
env.fuzzing = true;
|
|
}
|
|
|
|
llvm::SingleThreadExecutor single_thread({.ThreadsRequested = 1});
|
|
std::optional<llvm::DefaultThreadPool> threads;
|
|
env.thread_pool = &single_thread;
|
|
if (options.threads) {
|
|
threads.emplace(llvm::optimal_concurrency());
|
|
env.thread_pool = &*threads;
|
|
}
|
|
|
|
CARBON_CHECK(options.selected_subcommand != nullptr);
|
|
return options.selected_subcommand->Run(env);
|
|
}
|
|
|
|
} // namespace Carbon
|