diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ed0c4823e..f77682a665 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ project(vcpkg LANGUAGES ${LANGUAGES} ) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") include(cmake/utilities.cmake) # ============= @@ -120,6 +121,12 @@ endif() include(GNUInstallDirs) +# ==================== +# === Dependencies === +# ==================== + +find_package(fmt REQUIRED) + # =============== # === Targets === # =============== @@ -142,7 +149,12 @@ target_compile_definitions(vcpkglib PUBLIC set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -target_link_libraries(vcpkglib PRIVATE Threads::Threads) +target_link_libraries(vcpkglib + PUBLIC + fmt::fmt + PRIVATE + Threads::Threads +) if(MSVC) get_target_property(_srcs vcpkglib SOURCES) diff --git a/LocProject.json b/LocProject.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/LocProject.json @@ -0,0 +1 @@ +{} diff --git a/NOTICE.txt b/NOTICE.txt index 0e2e960483..12efa81572 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -44,3 +44,21 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF Catch2 NOTICES, INFORMATION, AND LICENSE + +2. fmt + +%% fmt NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2012 - present, Victor Zverovich + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. +========================================= +END OF fmt NOTICES, INFORMATION, AND LICENSE diff --git a/azure-pipelines/signing.yml b/azure-pipelines/signing.yml index cd990c1546..049b99de67 100644 --- a/azure-pipelines/signing.yml +++ b/azure-pipelines/signing.yml @@ -29,13 +29,31 @@ jobs: displayName: 'MacOS Build' pool: vmImage: macOS-10.15 + variables: + - group: vcpkg-dependency-source-blobs + - name: FMT_TARBALL_URL + value: "$(fmt-tarball-url)" + - name: FMT_TARBALL_SHA + value: "$(fmt-tarball-sha)" + - name: FMT_TARBALL_DIRNAME + value: "$(fmt-tarball-dirname)" steps: + - task: CmdLine@2 + displayName: "Download fmt library" + inputs: + failOnStderr: true + workingDirectory: "$(Build.BinariesDirectory)" + script: | + curl -sSL "$FMT_TARBALL_URL" --output ./fmtlib.tar.gz + printf "$FMT_TARBALL_SHA *./fmtlib.tar.gz\n" >checksum + shasum -c ./checksum -a 512 || exit 1 + tar -xf ./fmtlib.tar.gz - task: CmdLine@2 displayName: "Build vcpkg with CMake" inputs: failOnStderr: true script: | - cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_EMBED_GIT_SHA=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -B "$(Build.BinariesDirectory)/build" + cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_EMBED_GIT_SHA=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DFETCHCONTENT_FULLY_DISCONNECTED=ON "-DFETCHCONTENT_SOURCE_DIR_FMT=$(Build.BinariesDirectory)/$FMT_TARBALL_DIRNAME" -B "$(Build.BinariesDirectory)/build" make -j 8 -C "$(Build.BinariesDirectory)/build" zip -j "$(Build.ArtifactStagingDirectory)/vcpkg-macos.zip" "$(Build.BinariesDirectory)/build/vcpkg" - task: PublishBuildArtifacts@1 @@ -47,13 +65,32 @@ jobs: displayName: 'glibc (RHEL) Build' pool: name: 'vcpkg-rhel-7-5' + variables: + - group: vcpkg-dependency-source-blobs + - name: FMT_TARBALL_URL + value: "$(fmt-tarball-url)" + - name: FMT_TARBALL_SHA + value: "$(fmt-tarball-sha)" + - name: FMT_TARBALL_DIRNAME + value: "$(fmt-tarball-dirname)" steps: + - task: CmdLine@2 + displayName: "Download fmt library" + inputs: + failOnStderr: true + workingDirectory: "$(Build.BinariesDirectory)" + script: | + curl -sSL "$FMT_TARBALL_URL" --output "./fmtlib.tar.gz" + printf "$FMT_TARBALL_SHA *./fmtlib.tar.gz\n" >checksum + # shasum doesn't exist on rhel + sha512sum -c ./checksum || exit 1 + tar -xf ./fmtlib.tar.gz - task: CmdLine@2 displayName: "Build vcpkg with CMake" inputs: failOnStderr: true script: | - scl enable devtoolset-9 'cmake3 -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_EMBED_GIT_SHA=ON -DCMAKE_CXX_FLAGS="-static-libgcc -static-libstdc++" -B "$(Build.BinariesDirectory)/build"' + scl enable devtoolset-9 'cmake3 -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_EMBED_GIT_SHA=ON -DCMAKE_CXX_FLAGS="-static-libgcc -static-libstdc++" -DFETCHCONTENT_FULLY_DISCONNECTED=ON "-DFETCHCONTENT_SOURCE_DIR_FMT=$(Build.BinariesDirectory)/$FMT_TARBALL_DIRNAME" -B "$(Build.BinariesDirectory)/build"' make -j 4 -C "$(Build.BinariesDirectory)/build" mv "$(Build.BinariesDirectory)/build/vcpkg" "$(Build.ArtifactStagingDirectory)/vcpkg-glibc" - task: PublishBuildArtifacts@1 @@ -65,12 +102,31 @@ jobs: displayName: 'muslc (Alpine) Build' pool: name: 'vcpkg-ubuntu-20-04-docker' + variables: + - group: vcpkg-dependency-source-blobs + - name: FMT_TARBALL_URL + value: "$(fmt-tarball-url)" + - name: FMT_TARBALL_SHA + value: "$(fmt-tarball-sha)" + - name: FMT_TARBALL_DIRNAME + value: "$(fmt-tarball-dirname)" steps: + - task: CmdLine@2 + displayName: "Download fmt library" + inputs: + failOnStderr: true + workingDirectory: "$(Build.BinariesDirectory)" + script: | + curl -sSL "$FMT_TARBALL_URL" --output "./fmtlib.tar.gz" + printf "$FMT_TARBALL_SHA *./fmtlib.tar.gz\n" >checksum + shasum -c ./checksum -a 512 || exit 1 + tar -xf ./fmtlib.tar.gz - task: CmdLine@2 displayName: "Build vcpkg in Alpine" inputs: failOnStderr: true script: | + mv "$(Build.BinariesDirectory)/$FMT_TARBALL_DIRNAME" external-fmtlib docker build -t vcpkg-muslc-image -f azure-pipelines/vcpkg-alpine/Dockerfile . docker create -ti --name vcpkg-muslc-container vcpkg-muslc-image sh docker cp vcpkg-muslc-container:/build/vcpkg "$(Build.ArtifactStagingDirectory)/vcpkg-muslc" @@ -91,6 +147,14 @@ jobs: name: 'VSEngSS-MicroBuild2019-1ES' demands: - CMAKE + variables: + - group: vcpkg-dependency-source-blobs + - name: FMT_TARBALL_URL + value: "$(fmt-tarball-url)" + - name: FMT_TARBALL_SHA + value: "$(fmt-tarball-sha)" + - name: FMT_TARBALL_DIRNAME + value: "$(fmt-tarball-dirname)" steps: - task: DownloadBuildArtifacts@0 displayName: 'Download Unsigned POSIX Binaries' @@ -104,6 +168,22 @@ jobs: mkdir "$(Build.BinariesDirectory)\build" copy /Y "$(Build.ArtifactStagingDirectory)\staging\vcpkg-glibc" "$(Build.BinariesDirectory)\vcpkg-glibc" copy /Y "$(Build.ArtifactStagingDirectory)\staging\vcpkg-muslc" "$(Build.BinariesDirectory)\vcpkg-muslc" + - task: PowerShell@2 + displayName: "Download fmt library" + inputs: + targetType: 'inline' + pwsh: true + failOnStderr: true + workingDirectory: "$(Build.BinariesDirectory)" + script: | + curl.exe -sSL "$env:FMT_TARBALL_URL" --output ./fmtlib.tar.gz + $hash = (Get-FileHash -Algorithm SHA512 -Path ./fmtlib.tar.gz).Hash + if ($hash -ne "$env:FMT_TARBALL_SHA") { + throw "Unexpected hash for fmtlib download; + expected: $env:FMT_TARBALL_SHA + found : $hash" + } + tar.exe -xf ./fmtlib.tar.gz - task: PoliCheck@1 inputs: inputType: 'Basic' @@ -116,7 +196,7 @@ jobs: failOnStderr: true script: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=x86 -host_arch=x86 - cmake.exe -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_BUILD_TLS12_DOWNLOADER=ON -DVCPKG_EMBED_GIT_SHA=ON -B "$(Build.BinariesDirectory)\x86" + cmake.exe -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_BUILD_TLS12_DOWNLOADER=ON -DVCPKG_EMBED_GIT_SHA=ON -DFETCHCONTENT_FULLY_DISCONNECTED=ON "-DFETCHCONTENT_SOURCE_DIR_FMT=$(Build.BinariesDirectory)\%FMT_TARBALL_DIRNAME%" -B "$(Build.BinariesDirectory)\x86" ninja.exe -C "$(Build.BinariesDirectory)\x86" - task: CmdLine@2 displayName: "Build vcpkg arm64 with CMake" @@ -124,7 +204,7 @@ jobs: failOnStderr: true script: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=x86 - cmake.exe -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_BUILD_TLS12_DOWNLOADER=ON -DVCPKG_EMBED_GIT_SHA=ON -DVCPKG_PDB_SUFFIX="-arm64" -B "$(Build.BinariesDirectory)\arm64" + cmake.exe -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_BUILD_TLS12_DOWNLOADER=ON -DVCPKG_EMBED_GIT_SHA=ON -DVCPKG_PDB_SUFFIX="-arm64" -DFETCHCONTENT_FULLY_DISCONNECTED=ON "-DFETCHCONTENT_SOURCE_DIR_FMT=$(Build.BinariesDirectory)\%FMT_TARBALL_DIRNAME%" -B "$(Build.BinariesDirectory)\arm64" ninja.exe -C "$(Build.BinariesDirectory)\arm64" - task: MicroBuildSigningPlugin@2 inputs: diff --git a/azure-pipelines/vcpkg-alpine/Dockerfile b/azure-pipelines/vcpkg-alpine/Dockerfile index 88e09f6ec3..cb82923ec7 100644 --- a/azure-pipelines/vcpkg-alpine/Dockerfile +++ b/azure-pipelines/vcpkg-alpine/Dockerfile @@ -2,8 +2,8 @@ FROM alpine:3.11 RUN apk add alpine-sdk cmake ninja git curl tar gzip zip -ADD . /source +COPY . /source -RUN cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_EMBED_GIT_SHA=OFF -DCMAKE_CXX_FLAGS="-static -s -static-libgcc -static-libstdc++" -S /source -B /build +RUN cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DVCPKG_DEVELOPMENT_WARNINGS=ON -DVCPKG_WARNINGS_AS_ERRORS=ON -DVCPKG_BUILD_FUZZING=OFF -DVCPKG_EMBED_GIT_SHA=OFF -DCMAKE_CXX_FLAGS="-static -s -static-libgcc -static-libstdc++" -DFETCHCONTENT_FULLY_DISCONNECTED=ON -DFETCHCONTENT_SOURCE_DIR_FMT=/source/external-fmtlib -S /source -B /build RUN ninja -C build diff --git a/cgmanifest.json b/cgmanifest.json new file mode 100644 index 0000000000..ee8c35f98a --- /dev/null +++ b/cgmanifest.json @@ -0,0 +1,26 @@ +{ + "Registrations": [ + { + "Component": { + "Type": "git", + "git": { + "RepositoryUrl": "https://github.com/catchorg/Catch2", + "CommitHash": "2f631bb8087a0355d2b23a75a28d936ce237659d" + } + }, + "DevelopmentDependency": true, + "DependencyRoots": [] + }, + { + "Component": { + "Type": "git", + "git": { + "RepositoryUrl": "https://github.com/fmtlib/fmt", + "CommitHash": "d141cdbeb0fb422a3fb7173b285fd38e0d1772dc" + } + }, + "DevelopmentDependency": false, + "DependencyRoots": [] + } + ] +} diff --git a/cmake/Findfmt.cmake b/cmake/Findfmt.cmake new file mode 100644 index 0000000000..917cdd73ed --- /dev/null +++ b/cmake/Findfmt.cmake @@ -0,0 +1,19 @@ +option(VCPKG_DEPENDENCY_EXTERNAL_FMT "Use an external version of the fmt library" OFF) + +include(FetchContent) +FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt + GIT_TAG d141cdbeb0fb422a3fb7173b285fd38e0d1772dc # 8.0.1 +) + +if(NOT fmt_FIND_REQUIRED) + message(FATAL_ERROR "fmt must be REQUIRED") +endif() + +if(VCPKG_DEPENDENCY_EXTERNAL_FMT) + find_package(fmt CONFIG REQUIRED) +else() + FetchContent_MakeAvailable(fmt) +endif() + diff --git a/include/pch.h b/include/pch.h index 2dc65195aa..471d440bec 100644 --- a/include/pch.h +++ b/include/pch.h @@ -3,6 +3,7 @@ #include #include +#include #include #if defined(_WIN32) diff --git a/include/vcpkg/base/expected.h b/include/vcpkg/base/expected.h index a06206d17d..f230aad9e7 100644 --- a/include/vcpkg/base/expected.h +++ b/include/vcpkg/base/expected.h @@ -2,8 +2,8 @@ #include #include +#include #include -#include #include #include @@ -227,7 +227,7 @@ namespace vcpkg { if (m_s.has_error()) { - print2(Color::error, m_s.to_string(), "\n"); + msg::write_unlocalized_text_to_stdout(Color::error, Strings::concat(m_s.to_string(), '\n')); Checks::exit_fail(line_info); } } diff --git a/include/vcpkg/base/files.h b/include/vcpkg/base/files.h index 304ad03adb..95585730fd 100644 --- a/include/vcpkg/base/files.h +++ b/include/vcpkg/base/files.h @@ -385,3 +385,21 @@ namespace vcpkg } }; } + +namespace fmt +{ + template<> + struct formatter + { + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) + { + return vcpkg::basic_format_parse_impl(ctx); + } + template + auto format(const vcpkg::Path& path, FormatContext& ctx) -> decltype(ctx.out()) + { + return format_to(ctx.out(), "{}", path.native()); + } + }; + +} diff --git a/include/vcpkg/base/format.h b/include/vcpkg/base/format.h new file mode 100644 index 0000000000..5ecd24b66a --- /dev/null +++ b/include/vcpkg/base/format.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +VCPKG_MSVC_WARNING(push) +// notes: +// C6239 is not a useful warning for external code; it is +// ( && ) always evaluates to the result of . +// C6385 is a useful warning, but it's incorrect in this case; it thinks that (on line 1238), +// const char* top = data::digits[exp / 100]; +// accesses outside the bounds of data::digits; however, `exp < 10000 => exp / 100 < 100`, +// and thus the access is safe. +VCPKG_MSVC_WARNING(disable : 6239 6385) +#include +VCPKG_MSVC_WARNING(pop) + +namespace vcpkg +{ + constexpr auto basic_format_parse_impl(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) + { + if (ctx.begin() != ctx.end() && *ctx.begin() != '}') + { + throw fmt::format_error("invalid format - must be empty"); + } + + return ctx.begin(); + } +} + +namespace fmt +{ + template<> + struct formatter + { + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) + { + return vcpkg::basic_format_parse_impl(ctx); + } + template + auto format(const vcpkg::LineInfo& li, FormatContext& ctx) -> decltype(ctx.out()) + { + return format_to(ctx.out(), "{}({})", li.file_name, li.line_number); + } + }; + +} diff --git a/include/vcpkg/base/fwd/files.h b/include/vcpkg/base/fwd/files.h new file mode 100644 index 0000000000..8b2585215a --- /dev/null +++ b/include/vcpkg/base/fwd/files.h @@ -0,0 +1,8 @@ +#pragma once + +namespace vcpkg +{ + struct IgnoreErrors; + struct Filesystem; + struct Path; +} diff --git a/include/vcpkg/base/messages.h b/include/vcpkg/base/messages.h new file mode 100644 index 0000000000..f281d7bf11 --- /dev/null +++ b/include/vcpkg/base/messages.h @@ -0,0 +1,200 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace vcpkg +{ +#if defined(_WIN32) + enum class Color : unsigned short + { + none = 0, + success = 0x0A, // FOREGROUND_GREEN | FOREGROUND_INTENSITY + error = 0xC, // FOREGROUND_RED | FOREGROUND_INTENSITY + warning = 0xE, // FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY + }; +#else + enum class Color : char + { + none = 0, + success = '2', // [with 9] bright green + error = '1', // [with 9] bright red + warning = '3', // [with 9] bright yellow + }; +#endif +} + +namespace vcpkg::msg +{ + struct LocalizedString; + + namespace detail + { + template + struct MessageArgument + { + const Type* parameter; // always valid + }; + + template + struct MessageCheckFormatArgs + { + static constexpr void check_format_args(const Tags&...) noexcept { } + }; + + LocalizedString internal_vformat(::size_t index, fmt::format_args args); + + template + MessageCheckFormatArgs make_message_check_format_args(const Args&... args); + + ::size_t startup_register_message(StringLiteral name, StringLiteral format_string, StringLiteral comment); + + ::size_t number_of_messages(); + + // REQUIRES: index < last_message_index() + StringView get_format_string(::size_t index); + // REQUIRES: index < last_message_index() + StringView get_message_name(::size_t index); + // REQUIRES: index < last_message_index() + StringView get_default_format_string(::size_t index); + // REQUIRES: index < last_message_index() + StringView get_localization_comment(::size_t index); + } + + // load from "locale_base/${language}.json" + void threadunsafe_initialize_context(const Filesystem& fs, StringView language, const Path& locale_base); + // initialize without any localized messages (use default messages only) + void threadunsafe_initialize_context(); + + struct LocalizedString + { + LocalizedString() = default; + operator StringView() const { return m_data; } + const std::string& data() const { return m_data; } + + static LocalizedString from_string_unchecked(std::string&& s) + { + LocalizedString res; + res.m_data = std::move(s); + return res; + } + + LocalizedString& append(const LocalizedString& s); + + private: + std::string m_data; + // to avoid lock-in on LocalizedString, these are free functions + // this allows us to convert to `std::string` in the future without changing All The Code + friend LocalizedString& append_newline(LocalizedString&); + friend LocalizedString&& append_newline(LocalizedString&& self) { return std::move(append_newline(self)); } + friend LocalizedString& appendnl(LocalizedString& self, const LocalizedString& to_append) + { + return append_newline(self.append(to_append)); + } + }; + + struct LocalizedStringMapLess + { + using is_transparent = void; + bool operator()(const LocalizedString& lhs, const LocalizedString& rhs) const + { + return lhs.data() < rhs.data(); + } + }; + + void write_unlocalized_text_to_stdout(Color c, StringView sv); + + template + LocalizedString format(Message, detail::MessageArgument... args) + { + // avoid generating code, but still typeck + // (and avoid unused typedef warnings) + static_assert((Message::check_format_args((Tags{})...), true), ""); + return detail::internal_vformat(Message::index, + fmt::make_format_args(fmt::arg(Tags::name(), *args.parameter)...)); + } + + inline void println() { write_unlocalized_text_to_stdout(Color::none, "\n"); } + + inline void print(Color c, const LocalizedString& s) { write_unlocalized_text_to_stdout(c, s); } + inline void print(const LocalizedString& s) { write_unlocalized_text_to_stdout(Color::none, s); } + + template + void print(Message m, Ts... args) + { + print(format(m, args...)); + } + template + void println(Message m, Ts... args) + { + print(append_newline(format(m, args...))); + } + + template + void print(Color c, Message m, Ts... args) + { + print(c, format(m, args...)); + } + template + void println(Color c, Message m, Ts... args) + { + print(c, append_newline(format(m, args...))); + } + +// these use `constexpr static` instead of `inline` in order to work with GCC 6; +// they are trivial and empty, and their address does not matter, so this is not a problem +#define DECLARE_MSG_ARG(NAME) \ + constexpr static struct NAME##_t \ + { \ + constexpr static const char* name() { return #NAME; } \ + template \ + detail::MessageArgument operator=(const T& t) const noexcept \ + { \ + return detail::MessageArgument{&t}; \ + } \ + } NAME = {} + + DECLARE_MSG_ARG(email); + DECLARE_MSG_ARG(error); + DECLARE_MSG_ARG(version); + DECLARE_MSG_ARG(value); + DECLARE_MSG_ARG(pretty_value); +#undef DECLARE_MSG_ARG + +// These are `...` instead of +#define DECLARE_MESSAGE(NAME, ARGS, COMMENT, ...) \ + constexpr struct NAME##_msg_t : decltype(::vcpkg::msg::detail::make_message_check_format_args ARGS) \ + { \ + static ::vcpkg::StringLiteral name() { return #NAME; } \ + static ::vcpkg::StringLiteral localization_comment() { return COMMENT; }; \ + static ::vcpkg::StringLiteral default_format_string() noexcept { return __VA_ARGS__; } \ + static const ::size_t index; \ + } msg##NAME = {} +#define REGISTER_MESSAGE(NAME) \ + const ::size_t NAME##_msg_t ::index = ::vcpkg::msg::detail::startup_register_message( \ + NAME##_msg_t::name(), NAME##_msg_t::default_format_string(), NAME##_msg_t::localization_comment()) +#define DECLARE_AND_REGISTER_MESSAGE(NAME, ARGS, COMMENT, ...) \ + DECLARE_MESSAGE(NAME, ARGS, COMMENT, __VA_ARGS__); \ + REGISTER_MESSAGE(NAME) +} + +namespace fmt +{ + template<> + struct formatter : formatter + { + // parse is inherited from formatter + template + auto format(const vcpkg::msg::LocalizedString& s, FormatContext& ctx) + { + return formatter::format(s.data(), ctx); + } + }; + +} diff --git a/include/vcpkg/base/stringliteral.h b/include/vcpkg/base/stringliteral.h index 43d1718295..6cf434464c 100644 --- a/include/vcpkg/base/stringliteral.h +++ b/include/vcpkg/base/stringliteral.h @@ -16,3 +16,17 @@ namespace vcpkg operator std::string() const { return std::string(data(), size()); } }; } + +namespace fmt +{ + template<> + struct formatter : formatter + { + template + auto format(const vcpkg::StringLiteral& s, FormatContext& ctx) -> decltype(ctx.out()) + { + return formatter::format(s, ctx); + } + }; + +} diff --git a/include/vcpkg/base/strings.h b/include/vcpkg/base/strings.h index 983938f081..dcf47263e3 100644 --- a/include/vcpkg/base/strings.h +++ b/include/vcpkg/base/strings.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/include/vcpkg/base/stringview.h b/include/vcpkg/base/stringview.h index 76f972a9c3..f98a6c9d8f 100644 --- a/include/vcpkg/base/stringview.h +++ b/include/vcpkg/base/stringview.h @@ -2,6 +2,8 @@ #include +#include + #include #include @@ -55,3 +57,18 @@ namespace vcpkg bool operator<=(StringView lhs, StringView rhs) noexcept; bool operator>=(StringView lhs, StringView rhs) noexcept; } + +namespace fmt +{ + template<> + struct formatter : formatter + { + // parse is inherited from formatter. + template + auto format(vcpkg::StringView sv, FormatContext& ctx) + { + return formatter::format(string_view(sv.data(), sv.size()), ctx); + } + }; + +} diff --git a/include/vcpkg/base/system.debug.h b/include/vcpkg/base/system.debug.h index 8ba49b38c3..86d5d3f91c 100644 --- a/include/vcpkg/base/system.debug.h +++ b/include/vcpkg/base/system.debug.h @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include @@ -13,7 +14,7 @@ namespace vcpkg::Debug template void print(const Args&... args) { - if (g_debugging) print2("[DEBUG] ", args...); + if (g_debugging) msg::write_unlocalized_text_to_stdout(Color::none, Strings::concat("[DEBUG] ", args...)); } template, class = std::enable_if_t::value>> @@ -23,7 +24,7 @@ namespace vcpkg::Debug { auto timer = ElapsedTimer::create_started(); auto&& result = f(); - print2("[DEBUG] ", line, " took ", timer, '\n'); + print(line, " took ", timer, '\n'); return static_cast(result); } else @@ -37,7 +38,7 @@ namespace vcpkg::Debug { auto timer = ElapsedTimer::create_started(); f(); - print2("[DEBUG] ", line, " took ", timer, '\n'); + print(line, " took ", timer, '\n'); } else f(); diff --git a/include/vcpkg/base/system.print.h b/include/vcpkg/base/system.print.h index c041f41eb8..2b5001d981 100644 --- a/include/vcpkg/base/system.print.h +++ b/include/vcpkg/base/system.print.h @@ -1,17 +1,11 @@ #pragma once +#include #include #include namespace vcpkg { - enum class Color - { - success = 10, - error = 12, - warning = 14, - }; - namespace details { void print(StringView message); diff --git a/include/vcpkg/base/zstringview.h b/include/vcpkg/base/zstringview.h index ab2c232519..7a171cd984 100644 --- a/include/vcpkg/base/zstringview.h +++ b/include/vcpkg/base/zstringview.h @@ -50,3 +50,17 @@ namespace vcpkg inline bool operator==(const char* l, ZStringView r) { return strcmp(l, r.c_str()) == 0; } inline bool operator==(ZStringView l, const char* r) { return strcmp(l.c_str(), r) == 0; } } + +namespace fmt +{ + template<> + struct formatter : formatter + { + template + auto format(const vcpkg::ZStringView& s, FormatContext& ctx) -> decltype(ctx.out()) + { + return formatter::format(s, ctx); + } + }; + +} diff --git a/include/vcpkg/commands.generate-message-map.h b/include/vcpkg/commands.generate-message-map.h new file mode 100644 index 0000000000..fc5c03f0a8 --- /dev/null +++ b/include/vcpkg/commands.generate-message-map.h @@ -0,0 +1,12 @@ +#include + +#include +#include + +namespace vcpkg::Commands +{ + struct GenerateDefaultMessageMapCommand : BasicCommand + { + void perform_and_exit(const VcpkgCmdArguments&, Filesystem&) const override; + }; +} diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h index 965f18c8e2..96df16be63 100644 --- a/include/vcpkg/sourceparagraph.h +++ b/include/vcpkg/sourceparagraph.h @@ -5,9 +5,9 @@ #include #include +#include #include #include -#include #include #include diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000000..5eb68ae34c --- /dev/null +++ b/locales/en.json @@ -0,0 +1,10 @@ +{ + "NoLocalizationForMessages": "No localization for the following messages:", + "VcpkgDebugTimeTaken": "[DEBUG] Exiting after %s (%d us)\n", + "_VcpkgDebugTimeTaken.comment": "{LOCKED}", + "VcpkgHasCrashed": "vcpkg.exe has crashed.\nPlease send an email to:\n {email}\ncontaining a brief summary of what you were trying to do and the following data blob:\n\nVersion={vcpkg_version}\nEXCEPTION='{error}'\nCMD=", + "VcpkgHasCrashedArgument": "{value}|", + "_VcpkgHasCrashedArgument.comment": "{LOCKED}", + "VcpkgInvalidCommand": "invalid command: {value}", + "VcpkgSendMetricsButDisabled": "Warning: passed --sendmetrics, but metrics are disabled." +} diff --git a/src/vcpkg-test/commands.cpp b/src/vcpkg-test/commands.cpp index de72ba0a99..fa3207b634 100644 --- a/src/vcpkg-test/commands.cpp +++ b/src/vcpkg-test/commands.cpp @@ -34,6 +34,7 @@ TEST_CASE ("get_available_basic_commands works", "[commands]") "version", "x-download", "x-init-registry", + "x-generate-default-message-map", #if defined(_WIN32) "x-upload-metrics", #endif // defined(_WIN32) diff --git a/src/vcpkg.cpp b/src/vcpkg.cpp index 7a7eea68c8..7754a93697 100644 --- a/src/vcpkg.cpp +++ b/src/vcpkg.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -21,6 +20,8 @@ #include #include +#include + #include #include #include @@ -32,6 +33,31 @@ using namespace vcpkg; +namespace +{ + DECLARE_AND_REGISTER_MESSAGE(VcpkgInvalidCommand, (msg::value), "", "invalid command: {value}"); + DECLARE_AND_REGISTER_MESSAGE(VcpkgDebugTimeTaken, + (msg::pretty_value, msg::value), + "{LOCKED}", + "[DEBUG] Exiting after %s (%d us)\n"); + DECLARE_AND_REGISTER_MESSAGE(VcpkgSendMetricsButDisabled, + (), + "", + "Warning: passed --sendmetrics, but metrics are disabled."); + DECLARE_AND_REGISTER_MESSAGE(VcpkgHasCrashed, + (msg::email, msg::version, msg::error), + "", + R"(vcpkg.exe has crashed. +Please send an email to: + {email} +containing a brief summary of what you were trying to do and the following data blob: + +Version={vcpkg_version} +EXCEPTION='{error}' +CMD=)"); + DECLARE_AND_REGISTER_MESSAGE(VcpkgHasCrashedArgument, (msg::value), "{LOCKED}", "{value}|"); +} + // 24 hours/day * 30 days/month * 6 months static constexpr int SURVEY_INTERVAL_IN_HOURS = 24 * 30 * 6; @@ -40,7 +66,7 @@ static constexpr int SURVEY_INITIAL_OFFSET_IN_HOURS = SURVEY_INTERVAL_IN_HOURS - static void invalid_command(const std::string& cmd) { - print2(Color::error, "invalid command: ", cmd, '\n'); + msg::println(Color::error, msgVcpkgInvalidCommand, msg::value = cmd); print_usage(); Checks::exit_fail(VCPKG_LINE_INFO); } @@ -163,6 +189,26 @@ int main(const int argc, const char* const* const argv) if (argc == 0) std::abort(); auto& fs = get_real_filesystem(); + { + auto locale = get_environment_variable("VCPKG_LOCALE"); + auto locale_base = get_environment_variable("VCPKG_LOCALE_BASE"); + + if (locale.has_value() && locale_base.has_value()) + { + msg::threadunsafe_initialize_context(fs, *locale.get(), *locale_base.get()); + } + else if (locale.has_value() || locale_base.has_value()) + { + msg::write_unlocalized_text_to_stdout( + Color::error, "If either VCPKG_LOCALE or VCPKG_LOCALE_BASE is initialized, then both must be.\n"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + else + { + msg::threadunsafe_initialize_context(); + } + } + *(LockGuardPtr(GlobalState::timer)) = ElapsedTimer::create_started(); #if defined(_WIN32) @@ -211,9 +257,9 @@ int main(const int argc, const char* const* const argv) #endif if (debugging) - vcpkg::printf("[DEBUG] Exiting after %s us (%d us)\n", - LockGuardPtr(GlobalState::timer)->to_string(), - static_cast(elapsed_us_inner)); + msg::println(msgVcpkgDebugTimeTaken, + msg::pretty_value = LockGuardPtr(GlobalState::timer)->to_string(), + msg::value = static_cast(elapsed_us_inner)); }); LockGuardPtr(g_metrics)->track_property("version", Commands::Version::version()); @@ -269,7 +315,7 @@ int main(const int argc, const char* const* const argv) if (args.send_metrics.value_or(false) && !metrics->metrics_enabled()) { - print2(Color::warning, "Warning: passed --sendmetrics, but metrics are disabled.\n"); + msg::println(Color::warning, msgVcpkgSendMetricsButDisabled); } } // unlock g_metrics @@ -300,24 +346,17 @@ int main(const int argc, const char* const* const argv) LockGuardPtr(g_metrics)->track_property("error", exc_msg); fflush(stdout); - vcpkg::printf("vcpkg.exe has crashed.\n" - "Please send an email to:\n" - " %s\n" - "containing a brief summary of what you were trying to do and the following data blob:\n" - "\n" - "Version=%s\n" - "EXCEPTION='%s'\n" - "CMD=\n", - Commands::Contact::email(), - Commands::Version::version(), - exc_msg); + msg::println(msgVcpkgHasCrashed, + msg::email = Commands::Contact::email(), + msg::version = Commands::Version::version(), + msg::error = exc_msg); fflush(stdout); for (int x = 0; x < argc; ++x) { #if defined(_WIN32) - print2(Strings::to_utf8(argv[x]), "|\n"); + msg::println(msgVcpkgHasCrashedArgument, msg::value = Strings::to_utf8(argv[x])); #else - print2(argv[x], "|\n"); + msg::println(msgVcpkgHasCrashedArgument, msg::value = argv[x]); #endif } fflush(stdout); diff --git a/src/vcpkg/base/checks.cpp b/src/vcpkg/base/checks.cpp index e2022ee901..b91985114c 100644 --- a/src/vcpkg/base/checks.cpp +++ b/src/vcpkg/base/checks.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp index 4ebb8ab139..66d49dde0c 100644 --- a/src/vcpkg/base/json.cpp +++ b/src/vcpkg/base/json.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/base/messages.cpp b/src/vcpkg/base/messages.cpp new file mode 100644 index 0000000000..187eaf6957 --- /dev/null +++ b/src/vcpkg/base/messages.cpp @@ -0,0 +1,295 @@ +#include +#include +#include + +namespace vcpkg::msg +{ + DECLARE_AND_REGISTER_MESSAGE(NoLocalizationForMessages, (), "", "No localization for the following messages:"); + + // 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); } + + void write_unlocalized_text_to_stdout(Color c, StringView sv) + { + if (sv.empty()) return; + + static const HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE); + static const bool stdout_is_console = is_console(stdout_handle); + + if (stdout_is_console) + { + WORD original_color = 0; + if (c != Color::none) + { + CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{}; + ::GetConsoleScreenBufferInfo(stdout_handle, &console_screen_buffer_info); + original_color = console_screen_buffer_info.wAttributes; + ::SetConsoleTextAttribute(stdout_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(stdout_handle, pointer, size_to_write(size), &written, nullptr)); + pointer += written; + size -= written; + } + + if (c != Color::none) + { + ::SetConsoleTextAttribute(stdout_handle, original_color); + } + } + else + { + const char* pointer = sv.data(); + ::size_t size = sv.size(); + + while (size != 0) + { + DWORD written = 0; + check_write(::WriteFile(stdout_handle, pointer, size_to_write(size), &written, nullptr)); + pointer += written; + size -= written; + } + } + } +#else + static void write_all(const char* ptr, size_t to_write) + { + while (to_write != 0) + { + auto written = ::write(STDOUT_FILENO, ptr, to_write); + if (written == -1) + { + ::fprintf(stderr, "[DEBUG] Failed to print to stdout: %d\n", errno); + std::abort(); + } + ptr += written; + to_write -= written; + } + } + + void write_unlocalized_text_to_stdout(Color c, StringView sv) + { + static constexpr char reset_color_sequence[] = {'\033', '[', '0', 'm'}; + + if (sv.empty()) return; + + static bool is_a_tty = ::isatty(STDOUT_FILENO); + + 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)); + } + + write_all(sv.data(), sv.size()); + + if (reset_color) + { + write_all(reset_color_sequence, sizeof(reset_color_sequence)); + } + } +#endif + + namespace + { + struct Messages + { + // this is basically a SoA - each index is: + // { + // name + // default_string + // localization_comment + // localized_string + // } + // requires: names.size() == default_strings.size() == localized_strings.size() + std::vector names; + std::vector default_strings; // const after startup + std::vector localization_comments; // const after startup + + bool initialized = false; + std::vector localized_strings; + }; + + // to avoid static initialization order issues, + // everything that needs the messages needs to get it from this function + Messages& messages() + { + static Messages m; + return m; + } + } + + LocalizedString& append_newline(LocalizedString& s) + { + s.m_data.push_back('\n'); + return s; + } + + void threadunsafe_initialize_context() + { + Messages& m = messages(); + if (m.initialized) + { + write_unlocalized_text_to_stdout( + Color::error, "double-initialized message context; this is a very serious bug in vcpkg\n"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + m.localized_strings.resize(m.names.size()); + m.initialized = true; + + std::set> names_set(m.names.begin(), m.names.end()); + if (names_set.size() < m.names.size()) + { + // This will not trigger on any correct code path, so it's fine to use a naive O(n^2) + for (size_t i = 0; i < m.names.size() - 1; ++i) + { + for (size_t j = i + 1; j < m.names.size(); ++j) + { + if (m.names[i] == m.names[j]) + { + write_unlocalized_text_to_stdout( + Color::error, + fmt::format("INTERNAL ERROR: localization message '{}' has been declared multiple times\n", + m.names[i])); + write_unlocalized_text_to_stdout(Color::error, fmt::format("INTERNAL ERROR: first message:\n")); + write_unlocalized_text_to_stdout(Color::none, m.default_strings[i]); + write_unlocalized_text_to_stdout(Color::error, + fmt::format("\nINTERNAL ERROR: second message:\n")); + write_unlocalized_text_to_stdout(Color::none, m.default_strings[j]); + write_unlocalized_text_to_stdout(Color::none, "\n"); + ::abort(); + } + } + } + Checks::unreachable(VCPKG_LINE_INFO); + } + } + static void load_from_message_map(const Json::Object& message_map) + { + Messages& m = messages(); + std::vector names_without_localization; + + for (::size_t index = 0; index < m.names.size(); ++index) + { + const auto& name = m.names[index]; + if (auto p = message_map.get(m.names[index])) + { + m.localized_strings[index] = p->string().to_string(); + } + else if (Debug::g_debugging) + { + // we only want to print these in debug + names_without_localization.push_back(name); + } + } + + if (!names_without_localization.empty()) + { + println(Color::warning, msgNoLocalizationForMessages); + for (const auto& name : names_without_localization) + { + write_unlocalized_text_to_stdout(Color::warning, fmt::format(" - {}\n", name)); + } + } + } + + void threadunsafe_initialize_context(const Filesystem& fs, StringView language, const Path& locale_base) + { + threadunsafe_initialize_context(); + + auto path_to_locale = locale_base; + path_to_locale /= language; + path_to_locale += ".json"; + + auto message_map = Json::parse_file(VCPKG_LINE_INFO, fs, path_to_locale); + if (!message_map.first.is_object()) + { + write_unlocalized_text_to_stdout( + Color::error, + fmt::format("Invalid locale file '{}' - locale file must be an object.\n", path_to_locale)); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + load_from_message_map(message_map.first.object()); + } + + ::size_t detail::number_of_messages() { return messages().names.size(); } + + ::size_t detail::startup_register_message(StringLiteral name, StringLiteral format_string, StringLiteral comment) + { + Messages& m = messages(); + const auto res = m.names.size(); + m.names.push_back(name); + m.default_strings.push_back(format_string); + m.localization_comments.push_back(comment); + return res; + } + + StringView detail::get_format_string(::size_t index) + { + Messages& m = messages(); + Checks::check_exit(VCPKG_LINE_INFO, m.localized_strings.size() == m.default_strings.size()); + Checks::check_exit(VCPKG_LINE_INFO, index < m.default_strings.size()); + const auto& localized = m.localized_strings[index]; + if (localized.empty()) + { + return m.default_strings[index]; + } + else + { + return localized; + } + } + StringView detail::get_message_name(::size_t index) + { + Messages& m = messages(); + Checks::check_exit(VCPKG_LINE_INFO, index < m.names.size()); + return m.names[index]; + } + StringView detail::get_default_format_string(::size_t index) + { + Messages& m = messages(); + Checks::check_exit(VCPKG_LINE_INFO, index < m.default_strings.size()); + return m.default_strings[index]; + } + StringView detail::get_localization_comment(::size_t index) + { + Messages& m = messages(); + Checks::check_exit(VCPKG_LINE_INFO, index < m.localization_comments.size()); + return m.localization_comments[index]; + } + + LocalizedString detail::internal_vformat(::size_t index, fmt::format_args args) + { + auto fmt_string = get_format_string(index); + return LocalizedString::from_string_unchecked(fmt::vformat({fmt_string.data(), fmt_string.size()}, args)); + } +} diff --git a/src/vcpkg/base/system.process.cpp b/src/vcpkg/base/system.process.cpp index 58697505a0..168bb8b267 100644 --- a/src/vcpkg/base/system.process.cpp +++ b/src/vcpkg/base/system.process.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/cmakevars.cpp b/src/vcpkg/cmakevars.cpp index 2919e89ed3..55744a4784 100644 --- a/src/vcpkg/cmakevars.cpp +++ b/src/vcpkg/cmakevars.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/commands.add-version.cpp b/src/vcpkg/commands.add-version.cpp index 248636fc99..4f875cfbe6 100644 --- a/src/vcpkg/commands.add-version.cpp +++ b/src/vcpkg/commands.add-version.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/commands.civerifyversions.cpp b/src/vcpkg/commands.civerifyversions.cpp index e7fa6792f2..e235098527 100644 --- a/src/vcpkg/commands.civerifyversions.cpp +++ b/src/vcpkg/commands.civerifyversions.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/commands.cpp b/src/vcpkg/commands.cpp index 131a7dbaed..d2c59e0d5b 100644 --- a/src/vcpkg/commands.cpp +++ b/src/vcpkg/commands.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ namespace vcpkg::Commands static const Contact::ContactCommand contact{}; static const InitRegistry::InitRegistryCommand init_registry{}; static const X_Download::XDownloadCommand xdownload{}; + static const GenerateDefaultMessageMapCommand generate_message_map{}; #if defined(_WIN32) static const UploadMetrics::UploadMetricsCommand upload_metrics{}; #endif // defined(_WIN32) @@ -55,6 +57,7 @@ namespace vcpkg::Commands {"contact", &contact}, {"x-init-registry", &init_registry}, {"x-download", &xdownload}, + {"x-generate-default-message-map", &generate_message_map}, #if defined(_WIN32) {"x-upload-metrics", &upload_metrics}, diff --git a/src/vcpkg/commands.format-manifest.cpp b/src/vcpkg/commands.format-manifest.cpp index a1bc41899d..73b8aacb83 100644 --- a/src/vcpkg/commands.format-manifest.cpp +++ b/src/vcpkg/commands.format-manifest.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/commands.generate-message-map.cpp b/src/vcpkg/commands.generate-message-map.cpp new file mode 100644 index 0000000000..edec7d7889 --- /dev/null +++ b/src/vcpkg/commands.generate-message-map.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include + +namespace vcpkg::Commands +{ + using namespace msg; + void GenerateDefaultMessageMapCommand::perform_and_exit(const VcpkgCmdArguments&, Filesystem&) const + { + // in order to implement sorting, we create a vector of messages before converting into a JSON object + struct Message + { + std::string name; + std::string value; + std::string comment; + }; + struct MessageSorter + { + bool operator()(const Message& lhs, const Message& rhs) const { return lhs.name < rhs.name; } + }; + + const ::size_t size = detail::number_of_messages(); + std::vector messages(size); + for (::size_t index = 0; index < size; ++index) + { + messages[index].name = detail::get_message_name(index).to_string(); + messages[index].value = detail::get_default_format_string(index).to_string(); + messages[index].comment = detail::get_localization_comment(index).to_string(); + } + std::sort(messages.begin(), messages.end(), MessageSorter{}); + + Json::Object obj; + for (Message& msg : messages) + { + obj.insert(msg.name, Json::Value::string(std::move(msg.value))); + if (!msg.comment.empty()) + { + obj.insert(fmt::format("_{}.comment", msg.name), Json::Value::string(std::move(msg.comment))); + } + } + msg::write_unlocalized_text_to_stdout(Color::none, Json::stringify(obj, {})); + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/src/vcpkg/commands.info.cpp b/src/vcpkg/commands.info.cpp index b480f2f0da..1c98ab656c 100644 --- a/src/vcpkg/commands.info.cpp +++ b/src/vcpkg/commands.info.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/registries.cpp b/src/vcpkg/registries.cpp index bd8852b4b9..bc376b5d00 100644 --- a/src/vcpkg/registries.cpp +++ b/src/vcpkg/registries.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/src/vcpkg/vcpkgpaths.cpp b/src/vcpkg/vcpkgpaths.cpp index 720fab4861..fb7db74bcf 100644 --- a/src/vcpkg/vcpkgpaths.cpp +++ b/src/vcpkg/vcpkgpaths.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include