#include <fcntl.h>
-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 <path> to <absolute path> in the following two cases, where X may
- // be optional ANSI CSI sequences:
- //
- // In file included from X<path>X:1:
- // X<path>X: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
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<core::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
// 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).
//
#include <core/ResultRetriever.hpp>
#include <core/Statistics.hpp>
#include <core/StatsLog.hpp>
+#include <core/common.hpp>
#include <core/exceptions.hpp>
#include <core/mainoptions.hpp>
#include <core/types.hpp>
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
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))),
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))),
#include <Stat.hpp>
#include <Util.hpp>
#include <core/MsvcShowIncludesOutput.hpp>
+#include <core/common.hpp>
#include <core/exceptions.hpp>
#include <core/wincompat.hpp>
#include <fmtmacros.hpp>
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()) {
#include "common.hpp"
+#include <Context.hpp>
+#include <Finalizer.hpp>
#include <core/exceptions.hpp>
#include <fmtmacros.hpp>
+#include <util/Tokenizer.hpp>
+#include <util/expected.hpp>
#include <util/filesystem.hpp>
+#include <util/path.hpp>
+
+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 <path> to <absolute path> in the following two cases, where X may
+ // be optional ANSI CSI sequences:
+ //
+ // In file included from X<path>X:1:
+ // X<path>X: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
}
}
+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<core::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
#pragma once
+#include <string>
#include <string_view>
+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
#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("") == "");
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();