From: Joel Rosdahl Date: Tue, 25 Jul 2023 19:43:15 +0000 (+0200) Subject: refactor: Move Util::send_to_fd to core X-Git-Tag: v4.9~86 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8630a8882259c53baa8ec723e3581c9f246b54a9;p=thirdparty%2Fccache.git refactor: Move Util::send_to_fd to core --- diff --git a/src/Util.cpp b/src/Util.cpp index da87144b6..d2c8b5cc8 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -47,91 +47,8 @@ #include -using IncludeDelimiter = util::Tokenizer::IncludeDelimiter; - namespace fs = util::filesystem; -namespace { - -// Search for the first match of the following regular expression: -// -// \x1b\[[\x30-\x3f]*[\x20-\x2f]*[Km] -// -// The primary reason for not using std::regex is that it's not available for -// GCC 4.8. It's also a bit bloated. The reason for not using POSIX regex -// functionality is that it's are not available in MinGW. -std::string_view -find_first_ansi_csi_seq(std::string_view string) -{ - size_t pos = 0; - while (pos < string.length() && string[pos] != 0x1b) { - ++pos; - } - if (pos + 1 >= string.length() || string[pos + 1] != '[') { - return {}; - } - size_t start = pos; - pos += 2; - while (pos < string.length() - && (string[pos] >= 0x30 && string[pos] <= 0x3f)) { - ++pos; - } - while (pos < string.length() - && (string[pos] >= 0x20 && string[pos] <= 0x2f)) { - ++pos; - } - if (pos < string.length() && (string[pos] == 'K' || string[pos] == 'm')) { - return string.substr(start, pos + 1 - start); - } else { - return {}; - } -} - -std::string -rewrite_stderr_to_absolute_paths(std::string_view text) -{ - static const std::string in_file_included_from = "In file included from "; - - std::string result; - using util::Tokenizer; - for (auto line : Tokenizer(text, - "\n", - Tokenizer::Mode::include_empty, - Tokenizer::IncludeDelimiter::yes)) { - // Rewrite to in the following two cases, where X may - // be optional ANSI CSI sequences: - // - // In file included from XX:1: - // XX:1:2: ... - - if (util::starts_with(line, in_file_included_from)) { - result += in_file_included_from; - line = line.substr(in_file_included_from.length()); - } - while (!line.empty() && line[0] == 0x1b) { - auto csi_seq = find_first_ansi_csi_seq(line); - result.append(csi_seq.data(), csi_seq.length()); - line = line.substr(csi_seq.length()); - } - size_t path_end = line.find(':'); - if (path_end == std::string_view::npos) { - result.append(line.data(), line.length()); - } else { - std::string path(line.substr(0, path_end)); - if (Stat::stat(path)) { - result += util::real_path(path); - auto tail = line.substr(path_end); - result.append(tail.data(), tail.length()); - } else { - result.append(line.data(), line.length()); - } - } - } - return result; -} - -} // namespace - namespace Util { std::string_view @@ -468,61 +385,6 @@ remove_extension(std::string_view path) return path.substr(0, path.length() - get_extension(path).length()); } -void -send_to_fd(const Context& ctx, std::string_view text, int fd) -{ - std::string_view text_to_send = text; - std::string modified_text; - -#ifdef _WIN32 - // stdout/stderr are normally opened in text mode, which would convert - // newlines a second time since we treat output as binary data. Make sure to - // switch to binary mode. - int oldmode = _setmode(fd, _O_BINARY); - Finalizer binary_mode_restorer([=] { _setmode(fd, oldmode); }); -#endif - - if (ctx.args_info.strip_diagnostics_colors) { - try { - modified_text = strip_ansi_csi_seqs(text); - text_to_send = modified_text; - } catch (const core::Error&) { - // Ignore. - } - } - - if (ctx.config.absolute_paths_in_stderr()) { - modified_text = rewrite_stderr_to_absolute_paths(text_to_send); - text_to_send = modified_text; - } - - util::throw_on_error( - util::write_fd(fd, text_to_send.data(), text_to_send.length()), - FMT("Failed to write to fd {}: ", fd)); -} - -std::string -strip_ansi_csi_seqs(std::string_view string) -{ - size_t pos = 0; - std::string result; - - while (true) { - auto seq_span = find_first_ansi_csi_seq(string.substr(pos)); - auto data_start = string.data() + pos; - auto data_length = - seq_span.empty() ? string.length() - pos : seq_span.data() - data_start; - result.append(data_start, data_length); - if (seq_span.empty()) { - // Reached tail. - break; - } - pos += data_length + seq_span.length(); - } - - return result; -} - #ifdef HAVE_DIRENT_H void diff --git a/src/Util.hpp b/src/Util.hpp index 9dc99a8f7..717b371b5 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -123,15 +123,6 @@ std::string normalize_concrete_absolute_path(const std::string& path); // extension as determined by `get_extension()`. std::string_view remove_extension(std::string_view path); -// Send `text` to file descriptor `fd`, optionally stripping ANSI color -// sequences if `ctx.args_info.strip_diagnostics_colors` is true and rewriting -// paths to absolute if `ctx.config.absolute_paths_in_stderr` is true. Throws -// `core::Error` on error. -void send_to_fd(const Context& ctx, std::string_view text, int fd); - -// Returns a copy of string with the specified ANSI CSI sequences removed. -[[nodiscard]] std::string strip_ansi_csi_seqs(std::string_view string); - // Traverse `path` recursively (postorder, i.e. files are visited before their // parent directory). // diff --git a/src/ccache.cpp b/src/ccache.cpp index 48aac63e6..c9e6ed431 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -1018,7 +1019,7 @@ rewrite_stdout_from_compiler(const Context& ctx, util::Bytes&& stdout_data) Mode::include_empty, IncludeDelimiter::yes)) { if (util::starts_with(line, "__________")) { - Util::send_to_fd(ctx, line, STDOUT_FILENO); + core::send_to_console(ctx, line, STDOUT_FILENO); } // Ninja uses the lines with 'Note: including file: ' to determine the // used headers. Headers within basedir need to be changed into relative @@ -1138,9 +1139,9 @@ to_cache(Context& ctx, LOG("Compiler gave exit status {}", result->exit_status); // We can output stderr immediately instead of rerunning the compiler. - Util::send_to_fd( + core::send_to_console( ctx, util::to_string_view(result->stderr_data), STDERR_FILENO); - Util::send_to_fd( + core::send_to_console( ctx, util::to_string_view(core::MsvcShowIncludesOutput::strip_includes( ctx, std::move(result->stdout_data))), @@ -1201,10 +1202,10 @@ to_cache(Context& ctx, MTR_END("result", "result_put"); // Everything OK. - Util::send_to_fd( + core::send_to_console( ctx, util::to_string_view(result->stderr_data), STDERR_FILENO); // Send stdout after stderr, it makes the output clearer with MSVC. - Util::send_to_fd( + core::send_to_console( ctx, util::to_string_view(core::MsvcShowIncludesOutput::strip_includes( ctx, std::move(result->stdout_data))), diff --git a/src/core/ResultRetriever.cpp b/src/core/ResultRetriever.cpp index eefb62264..5e5b3a9b9 100644 --- a/src/core/ResultRetriever.cpp +++ b/src/core/ResultRetriever.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -63,12 +64,12 @@ ResultRetriever::on_embedded_file(uint8_t file_number, data.size()); if (file_type == FileType::stdout_output) { - Util::send_to_fd( + core::send_to_console( m_ctx, util::to_string_view(MsvcShowIncludesOutput::strip_includes(m_ctx, data)), STDOUT_FILENO); } else if (file_type == FileType::stderr_output) { - Util::send_to_fd(m_ctx, util::to_string_view(data), STDERR_FILENO); + core::send_to_console(m_ctx, util::to_string_view(data), STDERR_FILENO); } else { const auto dest_path = get_dest_path(file_type); if (dest_path.empty()) { diff --git a/src/core/common.cpp b/src/core/common.cpp index c1eebaa64..5fd0ba59a 100644 --- a/src/core/common.cpp +++ b/src/core/common.cpp @@ -18,12 +18,98 @@ #include "common.hpp" +#include +#include #include #include +#include +#include #include +#include + +using IncludeDelimiter = util::Tokenizer::IncludeDelimiter; namespace fs = util::filesystem; +namespace { + +// Search for the first match of the following regular expression: +// +// \x1b\[[\x30-\x3f]*[\x20-\x2f]*[Km] +// +// The primary reason for not using std::regex is that it's not available for +// GCC 4.8. It's also a bit bloated. The reason for not using POSIX regex +// functionality is that it's are not available in MinGW. +std::string_view +find_first_ansi_csi_seq(std::string_view string) +{ + size_t pos = 0; + while (pos < string.length() && string[pos] != 0x1b) { + ++pos; + } + if (pos + 1 >= string.length() || string[pos + 1] != '[') { + return {}; + } + size_t start = pos; + pos += 2; + while (pos < string.length() && string[pos] >= 0x30 && string[pos] <= 0x3f) { + ++pos; + } + while (pos < string.length() && string[pos] >= 0x20 && string[pos] <= 0x2f) { + ++pos; + } + if (pos < string.length() && (string[pos] == 'K' || string[pos] == 'm')) { + return string.substr(start, pos + 1 - start); + } else { + return {}; + } +} + +std::string +rewrite_stderr_to_absolute_paths(std::string_view text) +{ + const std::string_view in_file_included_from = "In file included from "; + + std::string result; + using util::Tokenizer; + for (auto line : Tokenizer(text, + "\n", + Tokenizer::Mode::include_empty, + Tokenizer::IncludeDelimiter::yes)) { + // Rewrite to in the following two cases, where X may + // be optional ANSI CSI sequences: + // + // In file included from XX:1: + // XX:1:2: ... + + if (util::starts_with(line, in_file_included_from)) { + result += in_file_included_from; + line = line.substr(in_file_included_from.length()); + } + while (!line.empty() && line[0] == 0x1b) { + auto csi_seq = find_first_ansi_csi_seq(line); + result.append(csi_seq.data(), csi_seq.length()); + line = line.substr(csi_seq.length()); + } + size_t path_end = line.find(':'); + if (path_end == std::string_view::npos) { + result.append(line.data(), line.length()); + } else { + std::string path(line.substr(0, path_end)); + if (Stat::stat(path)) { + result += util::real_path(path); + auto tail = line.substr(path_end); + result.append(tail.data(), tail.length()); + } else { + result.append(line.data(), line.length()); + } + } + } + return result; +} + +} // namespace + namespace core { void @@ -35,4 +121,55 @@ ensure_dir_exists(std::string_view dir) } } +void +send_to_console(const Context& ctx, std::string_view text, int fd) +{ + std::string_view text_to_send = text; + std::string modified_text; + +#ifdef _WIN32 + // stdout/stderr are normally opened in text mode, which would convert + // newlines a second time since we treat output as binary data. Make sure to + // switch to binary mode. + int oldmode = _setmode(fd, _O_BINARY); + Finalizer binary_mode_restorer([=] { _setmode(fd, oldmode); }); +#endif + + if (ctx.args_info.strip_diagnostics_colors) { + modified_text = strip_ansi_csi_seqs(text); + text_to_send = modified_text; + } + + if (ctx.config.absolute_paths_in_stderr()) { + modified_text = rewrite_stderr_to_absolute_paths(text_to_send); + text_to_send = modified_text; + } + + util::throw_on_error( + util::write_fd(fd, text_to_send.data(), text_to_send.length()), + FMT("Failed to write to fd {}: ", fd)); +} + +std::string +strip_ansi_csi_seqs(std::string_view string) +{ + size_t pos = 0; + std::string result; + + while (true) { + auto seq_span = find_first_ansi_csi_seq(string.substr(pos)); + auto data_start = string.data() + pos; + auto data_length = + seq_span.empty() ? string.length() - pos : seq_span.data() - data_start; + result.append(data_start, data_length); + if (seq_span.empty()) { + // Reached tail. + break; + } + pos += data_length + seq_span.length(); + } + + return result; +} + } // namespace core diff --git a/src/core/common.hpp b/src/core/common.hpp index 695238e9e..4247d77cc 100644 --- a/src/core/common.hpp +++ b/src/core/common.hpp @@ -18,11 +18,24 @@ #pragma once +#include #include +class Context; + namespace core { // Like std::filesystem::create_directories but throws core::Fatal on error. void ensure_dir_exists(std::string_view dir); +// Send `text` to file descriptor `fd` (typically stdout or stderr, which +// potentially is connected to a console), optionally stripping ANSI color +// sequences if `ctx.args_info.strip_diagnostics_colors` is true and rewriting +// paths to absolute if `ctx.config.absolute_paths_in_stderr()` is true. Throws +// `core::Error` on error. +void send_to_console(const Context& ctx, std::string_view text, int fd); + +// Returns a copy of string with the specified ANSI CSI sequences removed. +[[nodiscard]] std::string strip_ansi_csi_seqs(std::string_view string); + } // namespace core diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp index 498e6d3ff..43a648700 100644 --- a/unittest/test_Util.cpp +++ b/unittest/test_Util.cpp @@ -113,17 +113,6 @@ TEST_CASE("Util::dir_name") #endif } -TEST_CASE("Util::strip_ansi_csi_seqs") -{ - const char input[] = - "Normal," - " \x1B[K\x1B[1mbold\x1B[m," - " \x1B[31mred\x1B[m," - " \x1B[1;32mbold green\x1B[m.\n"; - - CHECK(Util::strip_ansi_csi_seqs(input) == "Normal, bold, red, bold green.\n"); -} - TEST_CASE("Util::get_extension") { CHECK(Util::get_extension("") == ""); diff --git a/unittest/test_core_common.cpp b/unittest/test_core_common.cpp index 66cd6e031..9c8e27ec2 100644 --- a/unittest/test_core_common.cpp +++ b/unittest/test_core_common.cpp @@ -43,4 +43,15 @@ TEST_CASE("core::ensure_dir_exists") doctest::Contains("Failed to create directory create/dir/file:")); } +TEST_CASE("core::strip_ansi_csi_seqs") +{ + const char input[] = + "Normal," + " \x1B[K\x1B[1mbold\x1B[m," + " \x1B[31mred\x1B[m," + " \x1B[1;32mbold green\x1B[m.\n"; + + CHECK(core::strip_ansi_csi_seqs(input) == "Normal, bold, red, bold green.\n"); +} + TEST_SUITE_END();