diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-21 14:06:54 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-21 14:06:54 +0000 |
| commit | 899c2af576124df2918fd2ae976434df0ebbae5b (patch) | |
| tree | bb67a979eca30a6d447c2819576b81dc84741f17 /tools/driver | |
| parent | b4ffb2d9498560557821714e2d4651279dd6817c (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.cc | 43 | ||||
| -rw-r--r-- | tools/driver/NativeCompiler.cc | 9 | ||||
| -rw-r--r-- | tools/driver/StandardLibrary.cc | 9 | ||||
| -rw-r--r-- | tools/driver/main.cc | 101 |
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; } |
