diff --git a/include/vcpkg/base/fwd/system.console.h b/include/vcpkg/base/fwd/system.console.h new file mode 100644 index 0000000000..54ba2e11e4 --- /dev/null +++ b/include/vcpkg/base/fwd/system.console.h @@ -0,0 +1,8 @@ +#pragma once + +namespace vcpkg +{ + class Console; + extern Console& std_out; + extern Console& std_error; +} diff --git a/include/vcpkg/base/messages.h b/include/vcpkg/base/messages.h index affda7f87d..80375952de 100644 --- a/include/vcpkg/base/messages.h +++ b/include/vcpkg/base/messages.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -191,16 +192,8 @@ namespace vcpkg::msg inline void print(Color c, const LocalizedString& s) { msg::write_unlocalized_text(c, s); } inline void print(const LocalizedString& s) { msg::write_unlocalized_text(Color::none, s); } - inline void println(Color c, const LocalizedString& s) - { - msg::write_unlocalized_text(c, s); - msg::write_unlocalized_text(Color::none, "\n"); - } - inline void println(const LocalizedString& s) - { - msg::write_unlocalized_text(Color::none, s); - msg::write_unlocalized_text(Color::none, "\n"); - } + inline void println(Color c, LocalizedString s) { msg::write_unlocalized_text(c, s.append_raw('\n')); } + inline void println(LocalizedString s) { msg::write_unlocalized_text(Color::none, s.append_raw('\n')); } [[nodiscard]] LocalizedString format_error(const LocalizedString& s); template diff --git a/include/vcpkg/base/system.console.h b/include/vcpkg/base/system.console.h new file mode 100644 index 0000000000..5f1480ca92 --- /dev/null +++ b/include/vcpkg/base/system.console.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +namespace vcpkg +{ + class Console + { + public: +#ifdef _WIN32 + explicit Console(unsigned long std_device) noexcept; +#else + explicit Console(int fd) noexcept; +#endif + ~Console() = default; + + // This function is safe to call from multiple threads. + // When called from multiple threads, calls are atomic with respect to other callers + void print(Color color, StringView text) + { + std::lock_guard lck{mtx}; + print_unlocked(color, text); + flush(); + } + + // This function is safe to call from multiple threads. + // When called from multiple threads, calls are atomic with respect to other callers + // If a line doesn't end with \n, a \n is automatically printed. + void print_lines(View> lines); + + // This function is safe to call from multiple threads. + // When called from multiple threads, calls are atomic with respect to other callers + void println(Color color, StringView text) + { + std::lock_guard lck{mtx}; + print_unlocked(color, text); + print_unlocked(Color::none, "\n"); + flush(); + } + + // This function is safe to call from multiple threads. + // When called from multiple threads, calls are atomic with respect to other callers + void println(Color color, std::string&& text) + { + text.push_back('\n'); + print(color, text); + } + + private: + void print_unlocked(Color color, StringView text) noexcept; + void write(const char* text, size_t count) noexcept; + void flush() noexcept; + bool check_is_terminal() noexcept; + + std::mutex mtx; +#ifdef _WIN32 + void* fd; +#else + int fd; +#endif + bool is_terminal; + }; +} // namespace vcpkg diff --git a/src/vcpkg/base/messages.cpp b/src/vcpkg/base/messages.cpp index c6f664f06f..f8ef95324a 100644 --- a/src/vcpkg/base/messages.cpp +++ b/src/vcpkg/base/messages.cpp @@ -284,138 +284,9 @@ namespace vcpkg } namespace vcpkg::msg { - // basic implementation - the write_unlocalized_text_to_stdout -#if defined(_WIN32) - static bool is_console(HANDLE h) - { - DWORD mode = 0; - // GetConsoleMode succeeds iff `h` is a console - // we do not actually care about the mode of the console - return GetConsoleMode(h, &mode); - } - - static void check_write(BOOL success) - { - if (!success) - { - ::fwprintf(stderr, L"[DEBUG] Failed to write to stdout: %lu\n", GetLastError()); - std::abort(); - } - } - static DWORD size_to_write(::size_t size) { return size > MAXDWORD ? MAXDWORD : static_cast(size); } - - static void write_unlocalized_text_impl(Color c, StringView sv, HANDLE the_handle, bool is_console) - { - if (sv.empty()) return; - - if (is_console) - { - WORD original_color = 0; - if (c != Color::none) - { - CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{}; - ::GetConsoleScreenBufferInfo(the_handle, &console_screen_buffer_info); - original_color = console_screen_buffer_info.wAttributes; - ::SetConsoleTextAttribute(the_handle, static_cast(c) | (original_color & 0xF0)); - } - - auto as_wstr = Strings::to_utf16(sv); - - const wchar_t* pointer = as_wstr.data(); - ::size_t size = as_wstr.size(); - - while (size != 0) - { - DWORD written = 0; - check_write(::WriteConsoleW(the_handle, pointer, size_to_write(size), &written, nullptr)); - pointer += written; - size -= written; - } - - if (c != Color::none) - { - ::SetConsoleTextAttribute(the_handle, original_color); - } - } - else - { - const char* pointer = sv.data(); - ::size_t size = sv.size(); - - while (size != 0) - { - DWORD written = 0; - check_write(::WriteFile(the_handle, pointer, size_to_write(size), &written, nullptr)); - pointer += written; - size -= written; - } - } - } + void write_unlocalized_text_to_stdout(Color c, StringView sv) { return std_out.print(c, sv); } - void write_unlocalized_text_to_stdout(Color c, StringView sv) - { - static const HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE); - static const bool stdout_is_console = is_console(stdout_handle); - return write_unlocalized_text_impl(c, sv, stdout_handle, stdout_is_console); - } - - void write_unlocalized_text_to_stderr(Color c, StringView sv) - { - static const HANDLE stderr_handle = ::GetStdHandle(STD_ERROR_HANDLE); - static const bool stderr_is_console = is_console(stderr_handle); - return write_unlocalized_text_impl(c, sv, stderr_handle, stderr_is_console); - } -#else - static void write_all(const char* ptr, size_t to_write, int fd) - { - while (to_write != 0) - { - auto written = ::write(fd, ptr, to_write); - if (written == -1) - { - ::fprintf(stderr, "[DEBUG] Failed to print to stdout: %d\n", errno); - std::abort(); - } - ptr += written; - to_write -= written; - } - } - - static void write_unlocalized_text_impl(Color c, StringView sv, int fd, bool is_a_tty) - { - static constexpr char reset_color_sequence[] = {'\033', '[', '0', 'm'}; - - if (sv.empty()) return; - - bool reset_color = false; - if (is_a_tty && c != Color::none) - { - reset_color = true; - - const char set_color_sequence[] = {'\033', '[', '9', static_cast(c), 'm'}; - write_all(set_color_sequence, sizeof(set_color_sequence), fd); - } - - write_all(sv.data(), sv.size(), fd); - - if (reset_color) - { - write_all(reset_color_sequence, sizeof(reset_color_sequence), fd); - } - } - - void write_unlocalized_text_to_stdout(Color c, StringView sv) - { - static bool is_a_tty = ::isatty(STDOUT_FILENO); - return write_unlocalized_text_impl(c, sv, STDOUT_FILENO, is_a_tty); - } - - void write_unlocalized_text_to_stderr(Color c, StringView sv) - { - static bool is_a_tty = ::isatty(STDERR_FILENO); - return write_unlocalized_text_impl(c, sv, STDERR_FILENO, is_a_tty); - } -#endif + void write_unlocalized_text_to_stderr(Color c, StringView sv) { return std_error.print(c, sv); } OutputStream default_output_stream = OutputStream::StdOut; diff --git a/src/vcpkg/base/system.console.cpp b/src/vcpkg/base/system.console.cpp new file mode 100644 index 0000000000..8fed810d90 --- /dev/null +++ b/src/vcpkg/base/system.console.cpp @@ -0,0 +1,152 @@ +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace vcpkg +{ +#ifdef _WIN32 + Console::Console(unsigned long std_device) noexcept + : fd(::GetStdHandle(std_device)), is_terminal(this->check_is_terminal()) + { + } +#else + Console::Console(int fd) noexcept : fd(fd), is_terminal(this->check_is_terminal()) { } +#endif + + void Console::flush() noexcept + { +#ifdef _WIN32 + ::FlushFileBuffers(fd); +#else + ::fsync(fd); +#endif + } + + bool Console::check_is_terminal() noexcept + { +#ifdef _WIN32 + DWORD mode = 0; + // GetConsoleMode succeeds if `h` is a console + // we do not actually care about the mode of the console + return GetConsoleMode(fd, &mode); +#else + return ::isatty(fd) == 1; +#endif + } + + void Console::print_lines(View> lines) + { + std::lock_guard lck{mtx}; + + for (auto&& [color, text] : lines) + { + print_unlocked(color, text); + + if (text.back() != '\n') + { + print_unlocked(Color::none, "\n"); + } + } + } + +#ifdef _WIN32 + void Console::print_unlocked(Color c, StringView sv) noexcept + { + if (sv.empty()) return; + + WORD original_color = 0; + + if (is_terminal && c != Color::none) + { + CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{}; + ::GetConsoleScreenBufferInfo(fd, &console_screen_buffer_info); + original_color = console_screen_buffer_info.wAttributes; + ::SetConsoleTextAttribute(fd, static_cast(c) | (original_color & 0xF0)); + } + + write(sv.data(), sv.size()); + + if (is_terminal && c != Color::none) + { + ::SetConsoleTextAttribute(fd, original_color); + } + } + + static constexpr DWORD size_to_write(size_t size) noexcept + { + return size > MAXDWORD ? MAXDWORD : static_cast(size); + } + + void Console::write(const char* text, size_t count) noexcept + { + while (count != 0) + { + DWORD written = 0; + if (!::WriteFile(fd, text, size_to_write(count), &written, nullptr)) + { + ::fwprintf(stderr, L"[DEBUG] Failed to write to stdout: %lu\n", GetLastError()); + std::abort(); + } + text += written; + count -= written; + } + } + +#else + + void Console::print_unlocked(Color c, StringView sv) noexcept + { + static constexpr char reset_color_sequence[] = {'\033', '[', '0', 'm'}; + + if (sv.empty()) return; + + bool reset_color = false; + // Only write color sequence if file descriptor is a terminal + if (is_terminal && c != Color::none) + { + reset_color = true; + + const char set_color_sequence[] = {'\033', '[', '9', static_cast(c), 'm'}; + write(set_color_sequence, sizeof(set_color_sequence)); + } + + write(sv.data(), sv.size()); + + if (reset_color) + { + write(reset_color_sequence, sizeof(reset_color_sequence)); + } + } + + void Console::write(const char* text, size_t count) noexcept + { + while (count != 0) + { + auto written = ::write(fd, text, count); + if (written == -1) + { + ::fprintf(stderr, "[DEBUG] Failed to print to stdout: %d\n", errno); + std::abort(); + } + text += written; + count -= written; + } + } +#endif +#ifdef _WIN32 + Console std_out_instance(STD_OUTPUT_HANDLE); + Console std_error_instance(STD_ERROR_HANDLE); +#else + Console std_out_instance(STDOUT_FILENO); + Console std_error_instance(STDERR_FILENO); +#endif + + Console& std_out = std_out_instance; + Console& std_error = std_error_instance; +} // namespace vcpkg