aboutsummaryrefslogtreecommitdiff
path: root/tools/driver
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-21 14:06:54 +0000
committer_ov4 <ov4@ov4.org>2026-10-21 14:06:54 +0000
commit899c2af576124df2918fd2ae976434df0ebbae5b (patch)
treebb67a979eca30a6d447c2819576b81dc84741f17 /tools/driver
parentb4ffb2d9498560557821714e2d4651279dd6817c (diff)
feat(driver): color diagnostics like gcc with -fdiagnostics-color
Add -fdiagnostics-color[=auto|always|never] and -fno-diagnostics-color. The default auto mode colors only a terminal stderr (TERM set and not dumb on Unix; a console that accepts VT sequences on Windows), so test harnesses, the desktop bundle and other pipes stay plain. Every diagnostic label now comes from cps::diagnosticPrefix, which emits gcc's default GCC_COLORS bytes: bold locus, bold red error/fatal error, bold magenta warning, bold cyan note. compilation terminated., runtime messages of the compiled program and lld output are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/driver')
-rw-r--r--tools/driver/EmbeddedRunner.cc43
-rw-r--r--tools/driver/NativeCompiler.cc9
-rw-r--r--tools/driver/StandardLibrary.cc9
-rw-r--r--tools/driver/main.cc101
4 files changed, 123 insertions, 39 deletions
diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc
index 2833cfd..b635e0f 100644
--- a/tools/driver/EmbeddedRunner.cc
+++ b/tools/driver/EmbeddedRunner.cc
@@ -3,6 +3,7 @@
#include "NativeCompiler.h"
#include "llvm/Object/ObjectFile.h"
#include "cps/CodeGen.h"
+#include "cps/Diagnostics.h"
#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
@@ -194,17 +195,20 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
// Pipes are fully buffered by default. Use the host CRT's stdout, also
// preferred by the JIT on Windows, so OUTPUT reaches desktop clients now.
if (std::setvbuf(stdout, nullptr, _IONBF, 0) != 0) {
- llvm::errs() << "error: cannot configure embedded stdout\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot configure embedded stdout\n";
return 1;
}
if (llvm::InitializeNativeTarget() || llvm::InitializeNativeTargetAsmPrinter()) {
- llvm::errs() << "error: cannot initialize the embedded LLVM target\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot initialize the embedded LLVM target\n";
return 1;
}
auto JTMBOrErr = llvm::orc::JITTargetMachineBuilder::detectHost();
if (!JTMBOrErr) {
- llvm::errs() << "error: cannot detect the embedded JIT target: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot detect the embedded JIT target: "
<< llvm::toString(JTMBOrErr.takeError()) << "\n";
return 1;
}
@@ -227,19 +231,22 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
auto JITOrErr = Builder.create();
if (!JITOrErr) {
- llvm::errs() << "error: cannot create the embedded LLVM engine: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot create the embedded LLVM engine: "
<< llvm::toString(JITOrErr.takeError()) << "\n";
return 1;
}
std::unique_ptr<llvm::orc::LLJIT> J = std::move(*JITOrErr);
if (auto Err = defineRuntimeSymbols(*J)) {
- llvm::errs() << "error: cannot define embedded runtime symbols: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot define embedded runtime symbols: "
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
if (auto Err = loadCxxRuntime(*J)) {
- llvm::errs() << "error: cannot load embedded C++ runtime: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot load embedded C++ runtime: "
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
@@ -250,7 +257,8 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
auto CRT = llvm::orc::DynamicLibrarySearchGenerator::Load(
"msvcrt.dll", J->getDataLayout().getGlobalPrefix());
if (!CRT) {
- llvm::errs() << "error: cannot load the embedded C runtime: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot load the embedded C runtime: "
<< llvm::toString(CRT.takeError()) << "\n";
return 1;
}
@@ -262,7 +270,8 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
llvm::orc::ThreadSafeModule TSM =
llvm::orc::cloneExternalModuleToContext(CG.getModule(), TSCtx);
if (!TSM) {
- llvm::errs() << "error: cannot clone the embedded module for JIT execution\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot clone the embedded module for JIT execution\n";
return 1;
}
TSM.withModuleDo([&](llvm::Module &M) {
@@ -271,32 +280,37 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
});
if (auto Err = J->addIRModule(J->getMainJITDylib(), std::move(TSM))) {
- llvm::errs() << "error: embedded LLVM execution failed: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "embedded LLVM execution failed: "
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
for (const auto &Path : Objects) {
auto Buffer = llvm::MemoryBuffer::getFile(Path);
if (!Buffer) {
- llvm::errs() << "error: cannot read object file '" << Path << "': "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot read object file '" << Path << "': "
<< Buffer.getError().message() << "\n";
return 1;
}
if (auto Err = J->addObjectFile(std::move(*Buffer))) {
- llvm::errs() << "error: cannot load object file '" << Path << "': "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "cannot load object file '" << Path << "': "
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
}
if (auto Err = J->initialize(J->getMainJITDylib())) {
- llvm::errs() << "error: embedded LLVM execution failed: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "embedded LLVM execution failed: "
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
auto MainSymOrErr = J->lookup("main");
if (!MainSymOrErr) {
- llvm::errs() << "error: embedded LLVM could not resolve main: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "embedded LLVM could not resolve main: "
<< llvm::toString(MainSymOrErr.takeError()) << "\n";
return 1;
}
@@ -306,7 +320,8 @@ int runEmbedded(const cps::CodeGen &CG, const std::vector<std::string> &Objects)
// Run platform deinitializers while JIT code is still mapped; frame
// deregistration runs when the JIT releases its memory below.
if (auto Err = J->deinitialize(J->getMainJITDylib())) {
- llvm::errs() << "error: embedded LLVM execution failed: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error, "")
+ << "embedded LLVM execution failed: "
<< llvm::toString(std::move(Err)) << "\n";
return 1;
}
diff --git a/tools/driver/NativeCompiler.cc b/tools/driver/NativeCompiler.cc
index e09fe98..324785f 100644
--- a/tools/driver/NativeCompiler.cc
+++ b/tools/driver/NativeCompiler.cc
@@ -1,5 +1,6 @@
#include "NativeCompiler.h"
#include "cps/CodeGen.h"
+#include "cps/Diagnostics.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/Compilation.h"
@@ -48,7 +49,7 @@ struct TemporaryDirectory {
};
bool fail(const llvm::Twine &Message) {
- llvm::errs() << "cpsc: fatal error: " << Message << "\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << Message << "\n";
return false;
}
@@ -58,8 +59,10 @@ class DriverDiagnostics : public clang::DiagnosticConsumer {
clang::DiagnosticConsumer::HandleDiagnostic(Level, Info);
llvm::SmallString<256> Message;
Info.FormatDiagnostic(Message);
- llvm::errs() << "cpsc: " << (Level >= clang::DiagnosticsEngine::Error
- ? "error: " : "warning: ") << Message << "\n";
+ llvm::errs() << cps::diagnosticPrefix(Level >= clang::DiagnosticsEngine::Error
+ ? cps::Severity::Error
+ : cps::Severity::Warning)
+ << Message << "\n";
}
};
diff --git a/tools/driver/StandardLibrary.cc b/tools/driver/StandardLibrary.cc
index ac88745..7680054 100644
--- a/tools/driver/StandardLibrary.cc
+++ b/tools/driver/StandardLibrary.cc
@@ -1,4 +1,5 @@
#include "StandardLibrary.h"
+#include "cps/Diagnostics.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
@@ -19,7 +20,8 @@ StandardLibrary::~StandardLibrary() {
bool StandardLibrary::load(std::vector<std::string> &Inputs) {
auto Error = llvm::sys::fs::createUniqueDirectory("cpsc-std", Directory);
if (Error) {
- llvm::errs() << "cpsc: fatal error: cannot extract standard library: " << Error.message() << "\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal)
+ << "cannot extract standard library: " << Error.message() << "\n";
return false;
}
for (const auto &File : LibraryFiles) {
@@ -32,8 +34,9 @@ bool StandardLibrary::load(std::vector<std::string> &Inputs) {
Error = Output.error();
}
if (Error) {
- llvm::errs() << "cpsc: fatal error: cannot extract standard library file '"
- << File.Name << "': " << Error.message() << "\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal)
+ << "cannot extract standard library file '" << File.Name
+ << "': " << Error.message() << "\n";
Output.clear_error();
return false;
}
diff --git a/tools/driver/main.cc b/tools/driver/main.cc
index b705843..10b665b 100644
--- a/tools/driver/main.cc
+++ b/tools/driver/main.cc
@@ -20,6 +20,22 @@
#include "llvm/Support/raw_ostream.h"
#include <fstream>
#include <string>
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include <windows.h>
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
+#endif
+#else
+#include <cstdlib>
+#include <cstring>
+#include <unistd.h>
+#endif
namespace {
@@ -28,11 +44,30 @@ int compilationTerminated() {
return 1;
}
+bool isColorOption(const std::string &Arg) {
+ return Arg == "-fdiagnostics-color" || Arg == "-fno-diagnostics-color" ||
+ Arg.rfind("-fdiagnostics-color=", 0) == 0;
+}
+
+// gcc's "auto": color only a terminal stderr. On Windows the console must
+// also accept ANSI escape sequences.
+bool colorTerminal() {
+#ifdef _WIN32
+ HANDLE Error = GetStdHandle(STD_ERROR_HANDLE);
+ DWORD Mode;
+ return GetConsoleMode(Error, &Mode) &&
+ SetConsoleMode(Error, Mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+#else
+ const char *Term = std::getenv("TERM");
+ return Term && std::strcmp(Term, "dumb") != 0 && isatty(STDERR_FILENO);
+#endif
+}
+
#ifdef CPSC_RUN_PATH
bool checkLliVersion(llvm::StringRef LliPath) {
llvm::SmallString<256> VersionPath;
if (auto EC = llvm::sys::fs::createTemporaryFile("cpsc-lli-version", "txt", VersionPath)) {
- llvm::errs() << "cpsc: fatal error: cannot check lli version: " << EC.message() << "\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot check lli version: " << EC.message() << "\n";
return false;
}
llvm::FileRemover TemporaryVersion(VersionPath);
@@ -41,7 +76,7 @@ bool checkLliVersion(llvm::StringRef LliPath) {
std::nullopt, {llvm::StringRef(""), llvm::StringRef(VersionPath), llvm::StringRef("")},
5, 0, &ExecutionError);
if (Result != 0) {
- llvm::errs() << "cpsc: fatal error: cannot check lli version at '" << LliPath
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot check lli version at '" << LliPath
<< "' (status " << Result << ")";
if (!ExecutionError.empty()) llvm::errs() << ": " << ExecutionError;
llvm::errs() << "\n";
@@ -49,18 +84,18 @@ bool checkLliVersion(llvm::StringRef LliPath) {
}
auto Version = llvm::MemoryBuffer::getFile(VersionPath);
if (!Version) {
- llvm::errs() << "cpsc: fatal error: cannot read lli version: "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot read lli version: "
<< Version.getError().message() << "\n";
return false;
}
llvm::SmallVector<llvm::StringRef, 2> Matches;
if (!llvm::Regex("LLVM version ([0-9]+)\\.").match((*Version)->getBuffer(), &Matches)) {
- llvm::errs() << "cpsc: fatal error: cannot identify LLVM version of lli at '"
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot identify LLVM version of lli at '"
<< LliPath << "'; LLVM 23 is required\n";
return false;
}
if (Matches[1] != "23") {
- llvm::errs() << "cpsc: fatal error: -r requires lli from LLVM 23; found LLVM "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "-r requires lli from LLVM 23; found LLVM "
<< Matches[1] << " at '" << LliPath << "'\n";
return false;
}
@@ -88,6 +123,32 @@ int main(int argc, char **argv) {
}
}
+ // Like gcc, the last valid -fdiagnostics-color option wins, and it is
+ // applied before any other diagnostic.
+ enum { ColorAuto, ColorAlways, ColorNever } ColorMode = ColorAuto;
+ std::string BadColor;
+ for (int I = 1; I < argc; ++I) {
+ std::string Arg = argv[I];
+ if (Arg == "-fdiagnostics-color" || Arg == "-fdiagnostics-color=always")
+ ColorMode = ColorAlways;
+ else if (Arg == "-fdiagnostics-color=auto")
+ ColorMode = ColorAuto;
+ else if (Arg == "-fdiagnostics-color=never" || Arg == "-fno-diagnostics-color")
+ ColorMode = ColorNever;
+ else if (isColorOption(Arg) && BadColor.empty())
+ BadColor = Arg;
+ }
+ // colorTerminal() also enables escape sequences on a Windows console.
+ const bool Terminal = ColorMode != ColorNever && colorTerminal();
+ cps::DiagnosticColors = ColorMode == ColorAlways || Terminal;
+ if (!BadColor.empty()) {
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error)
+ << "unrecognized argument in option '" << BadColor << "'\n"
+ << cps::diagnosticPrefix(cps::Severity::Note)
+ << "valid arguments to '-fdiagnostics-color=' are: always auto never\n";
+ return compilationTerminated();
+ }
+
std::vector<std::string> InputPaths;
std::string OutputPath;
bool Run = false;
@@ -103,16 +164,18 @@ int main(int argc, char **argv) {
NoStd = true;
} else if (Arg == "-o") {
if (!OutputPath.empty()) {
- llvm::errs() << "cpsc: error: -o may only be specified once\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "-o may only be specified once\n";
return compilationTerminated();
}
if (I + 1 == argc || argv[I + 1][0] == '-' || argv[I + 1][0] == '\0') {
- llvm::errs() << "cpsc: error: -o requires an output path\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "-o requires an output path\n";
return compilationTerminated();
}
OutputPath = argv[++I];
+ } else if (isColorOption(Arg)) {
+ continue;
} else if (Arg.empty() || Arg[0] == '-') {
- llvm::errs() << "cpsc: error: unknown argument '" << Arg << "'\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "unknown argument '" << Arg << "'\n";
return compilationTerminated();
} else {
InputPaths.push_back(Arg);
@@ -120,7 +183,7 @@ int main(int argc, char **argv) {
}
if (InputPaths.empty()) {
- llvm::errs() << "cpsc: fatal error: no input files\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "no input files\n";
return compilationTerminated();
}
#ifdef CPSC_RUN_PATH
@@ -130,13 +193,13 @@ int main(int argc, char **argv) {
#ifdef CPSC_RUN_PATH
auto Lli = llvm::sys::findProgramByName("lli");
if (!Lli) {
- llvm::errs() << "cpsc: fatal error: -r requires lli from LLVM 23 in PATH: " << Lli.getError().message() << "\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "-r requires lli from LLVM 23 in PATH: " << Lli.getError().message() << "\n";
return compilationTerminated();
}
LliPath = *Lli;
if (!checkLliVersion(LliPath)) return compilationTerminated();
#elif defined(CPSC_RUN_NONE)
- llvm::errs() << "cpsc: fatal error: -r is disabled in this build (CPSC_RUN_MODE=NONE)\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "-r is disabled in this build (CPSC_RUN_MODE=NONE)\n";
return compilationTerminated();
#endif
}
@@ -150,13 +213,13 @@ int main(int argc, char **argv) {
const auto &InputPath = InputPaths[I];
for (size_t J = 0; J < I; ++J) {
if (llvm::sys::fs::equivalent(InputPath, InputPaths[J])) {
- llvm::errs() << "cpsc: error: duplicate input file '" << InputPath << "'\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "duplicate input file '" << InputPath << "'\n";
return compilationTerminated();
}
}
std::ifstream Input(InputPath, std::ios::binary);
if (!Input) {
- llvm::errs() << "cpsc: fatal error: cannot open source file '" << InputPath << "'\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot open source file '" << InputPath << "'\n";
return compilationTerminated();
}
cps::DiagnosticSource = InputPath;
@@ -164,7 +227,7 @@ int main(int argc, char **argv) {
cps::Parser Parser(Lex);
Programs[I] = Parser.Parse();
if (Input.bad()) {
- llvm::errs() << "cpsc: fatal error: cannot read source file '" << InputPath << "'\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot read source file '" << InputPath << "'\n";
return compilationTerminated();
}
if (Lex.hadError() || Parser.hadError()) return compilationTerminated();
@@ -274,7 +337,7 @@ int main(int argc, char **argv) {
}
for (const auto &InputPath : InputPaths) {
if (Save && llvm::sys::fs::equivalent(InputPath, OutputPath)) {
- llvm::errs() << "cpsc: error: output file must not be the source file\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "output file must not be the source file\n";
return compilationTerminated();
}
}
@@ -301,7 +364,7 @@ int main(int argc, char **argv) {
EC = llvm::sys::fs::openFileForWrite(IRPath, OutputFD);
}
if (EC) {
- llvm::errs() << "cpsc: fatal error: cannot open output file '" << IRPath
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot open output file '" << IRPath
<< "': " << EC.message() << "\n";
return compilationTerminated();
}
@@ -309,7 +372,7 @@ int main(int argc, char **argv) {
CG.print(Output);
Output.close();
if (Output.has_error()) {
- llvm::errs() << "cpsc: fatal error: cannot write output file '" << IRPath
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal) << "cannot write output file '" << IRPath
<< "': " << Output.error().message() << "\n";
Output.clear_error();
return compilationTerminated();
@@ -319,7 +382,7 @@ int main(int argc, char **argv) {
#ifdef CPSC_RUN_PATH
llvm::SmallString<256> RunPath(IRPath);
if (auto PathError = llvm::sys::fs::make_absolute(RunPath)) {
- llvm::errs() << "cpsc: error: cannot resolve IR path: " << PathError.message() << "\n";
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "cannot resolve IR path: " << PathError.message() << "\n";
return 1;
}
std::string ExecutionError;
@@ -330,7 +393,7 @@ int main(int argc, char **argv) {
int Result = llvm::sys::ExecuteAndWait(LliPath, LliArgv,
std::nullopt, {}, 0, 0, &ExecutionError);
if (Result < 0) {
- llvm::errs() << "cpsc: error: lli execution failed (status " << Result << "): "
+ llvm::errs() << cps::diagnosticPrefix(cps::Severity::Error) << "lli execution failed (status " << Result << "): "
<< ExecutionError << "\n";
return 1;
}