diff --git a/include/vcpkg/base/chrono.h b/include/vcpkg/base/chrono.h index 87be1030c8..e67e2aebd7 100644 --- a/include/vcpkg/base/chrono.h +++ b/include/vcpkg/base/chrono.h @@ -23,6 +23,12 @@ namespace vcpkg return std::chrono::duration_cast(m_duration); } + ElapsedTime& operator+=(const ElapsedTime& other) + { + m_duration += other.m_duration; + return *this; + } + std::string to_string() const; void to_string(std::string& into) const; @@ -79,6 +85,8 @@ namespace vcpkg mutable tm m_tm; }; + Optional to_utc_time(const std::time_t& t); + tm get_current_date_time(); tm get_current_date_time_local(); diff --git a/include/vcpkg/base/messages.h b/include/vcpkg/base/messages.h index d1a428b650..7c9611434e 100644 --- a/include/vcpkg/base/messages.h +++ b/include/vcpkg/base/messages.h @@ -46,6 +46,12 @@ namespace vcpkg m_data.append(s.begin(), s.end()); return *this; } + template + LocalizedString& append_fmt_raw(fmt::string_view s, const Args&... args) + { + m_data.append(fmt::format(s, args...)); + return *this; + } LocalizedString& append(const LocalizedString& s) { m_data.append(s.m_data); diff --git a/include/vcpkg/base/xmlserializer.h b/include/vcpkg/base/xmlserializer.h index 33b7c0811f..d3bfb8215f 100644 --- a/include/vcpkg/base/xmlserializer.h +++ b/include/vcpkg/base/xmlserializer.h @@ -13,10 +13,16 @@ namespace vcpkg XmlSerializer& open_tag(StringLiteral sl); XmlSerializer& start_complex_open_tag(StringLiteral sl); XmlSerializer& text_attr(StringLiteral name, StringView content); + template + XmlSerializer& attr(StringLiteral name, const T& content) + { + return text_attr(name, Strings::concat_or_view(content)); + } XmlSerializer& finish_complex_open_tag(); XmlSerializer& finish_self_closing_complex_tag(); XmlSerializer& close_tag(StringLiteral sl); XmlSerializer& text(StringView sv); + XmlSerializer& cdata(StringView sv); XmlSerializer& simple_tag(StringLiteral tag, StringView content); XmlSerializer& line_break(); diff --git a/include/vcpkg/build.h b/include/vcpkg/build.h index 0f724e2caf..eb45376c61 100644 --- a/include/vcpkg/build.h +++ b/include/vcpkg/build.h @@ -31,7 +31,6 @@ namespace vcpkg::Build { enum class BuildResult { - NULLVALUE = 0, SUCCEEDED, BUILD_FAILED, POST_BUILD_CHECKS_FAILED, @@ -197,7 +196,6 @@ namespace vcpkg::Build struct BuildResultCounts { - int null_value = 0; int succeeded = 0; int build_failed = 0; int post_build_checks_failed = 0; @@ -254,7 +252,7 @@ namespace vcpkg::Build struct ExtendedBuildResult { - ExtendedBuildResult(BuildResult code); + explicit ExtendedBuildResult(BuildResult code); ExtendedBuildResult(BuildResult code, std::vector&& unmet_deps); ExtendedBuildResult(BuildResult code, std::unique_ptr&& bcf); diff --git a/include/vcpkg/install.h b/include/vcpkg/install.h index 3ddb0b705a..1952620659 100644 --- a/include/vcpkg/install.h +++ b/include/vcpkg/install.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -11,6 +12,7 @@ #include #include +#include #include #include #include @@ -25,15 +27,19 @@ namespace vcpkg::Install struct SpecSummary { - SpecSummary(const PackageSpec& spec, const Dependencies::InstallPlanAction* action); + explicit SpecSummary(const Dependencies::InstallPlanAction& action); + explicit SpecSummary(const Dependencies::RemovePlanAction& action); const BinaryParagraph* get_binary_paragraph() const; - - PackageSpec spec; - Build::ExtendedBuildResult build_result; + const PackageSpec& get_spec() const { return m_spec; } + bool is_user_requested_install() const; + Optional build_result; vcpkg::ElapsedTime timing; + std::chrono::system_clock::time_point start_time; - const Dependencies::InstallPlanAction* action; + private: + const Dependencies::InstallPlanAction* m_install_action; + PackageSpec m_spec; }; struct InstallSummary diff --git a/include/vcpkg/packagespec.h b/include/vcpkg/packagespec.h index cc23fade88..b743864f47 100644 --- a/include/vcpkg/packagespec.h +++ b/include/vcpkg/packagespec.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -194,20 +195,6 @@ namespace vcpkg Optional parse_qualified_specifier(ParserBase& parser); } -template -struct fmt::formatter -{ - constexpr auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) - { - return vcpkg::basic_format_parse_impl(ctx); - } - template - auto format(const vcpkg::PackageSpec& spec, FormatContext& ctx) const -> decltype(ctx.out()) - { - return fmt::formatter{}.format(spec.to_string(), ctx); - } -}; - template<> struct std::hash { @@ -230,3 +217,6 @@ struct std::hash return hash; } }; + +VCPKG_FORMAT_WITH_TO_STRING(vcpkg::PackageSpec); +VCPKG_FORMAT_WITH_TO_STRING(vcpkg::FeatureSpec); diff --git a/include/vcpkg/remove.h b/include/vcpkg/remove.h index 80a17ba613..6087e4ce20 100644 --- a/include/vcpkg/remove.h +++ b/include/vcpkg/remove.h @@ -4,6 +4,8 @@ #include #include +#include + #include namespace vcpkg::Remove @@ -14,6 +16,8 @@ namespace vcpkg::Remove YES }; + DECLARE_MESSAGE(RemovingPackage, (msg::spec), "", "Removing {spec}"); + void perform_remove_plan_action(const VcpkgPaths& paths, const Dependencies::RemovePlanAction& action, const Purge purge, diff --git a/locales/messages.en.json b/locales/messages.en.json index 31f6b20c25..9ee23f575e 100644 --- a/locales/messages.en.json +++ b/locales/messages.en.json @@ -6,6 +6,8 @@ "AddTripletExpressionNotAllowed": "Error: triplet expressions are not allowed here. You may want to change `{package_name}:{triplet}` to `{package_name}` instead.", "AllFormatArgsRawArgument": "format string \"{value}\" contains a raw format argument", "AllFormatArgsUnbalancedBraces": "unbalanced brace in format string \"{value}\"", + "AlreadyInstalled": "{spec} is already installed", + "AlreadyInstalledNotHead": "{spec} is already installed -- not building from HEAD", "AttemptingToFetchPackagesFromVendor": "Attempting to fetch {count} package(s) from {vendor}", "BothYesAndNoOptionSpecifiedError": "error: cannot specify both --no-{option} and --{option}.", "BuildResultBuildFailed": "BUILD_FAILED", @@ -18,8 +20,11 @@ "BuildResultSucceeded": "SUCCEEDED", "BuildResultSummaryHeader": "SUMMARY FOR {triplet}", "BuildResultSummaryLine": " {build_result}: {count}", + "BuildingFromHead": "Building {spec} from HEAD...", + "BuildingPackage": "Building {spec}...", "BuildingPackageFailed": "building {spec} failed with: {build_result}", "BuildingPackageFailedDueToMissingDeps": "due to the following missing dependencies:", + "CMakeTargetsUsage": "{package_name} provides CMake targets:", "ChecksFailedCheck": "vcpkg has crashed; no additional details are available.", "ChecksUnreachableCode": "unreachable code was reached", "ChecksUpdateVcpkg": "updating vcpkg by rerunning bootstrap-vcpkg may resolve this failure.", @@ -30,8 +35,10 @@ "CmakeTargetsExcluded": "note: {count} additional targets are not displayed.", "CouldNotDeduceNugetIdAndVersion": "Could not deduce nuget id and version from filename: {path}", "CurlReportedUnexpectedResults": "curl has reported unexpected results to vcpkg and vcpkg cannot continue.\nPlease review the following text for sensitive information and open an issue on the Microsoft/vcpkg GitHub to help fix this problem!\ncmd: {command_line}\n=== curl output ===\n{actual}\n=== end curl output ===\n", + "DownloadedSources": "Downloaded sources for {spec}", "DownloadingVcpkgCeBundle": "Downloading vcpkg-ce bundle {version}...", "DownloadingVcpkgCeBundleLatest": "Downloading latest vcpkg-ce bundle...", + "ElapsedForPackage": "Elapsed time to handle {spec}: {elapsed}", "EmptyLicenseExpression": "SPDX license expression was empty.", "ErrorIndividualPackagesUnsupported": "Error: In manifest mode, `vcpkg install` does not support individual package arguments.\nTo install additional packages, edit vcpkg.json and then run `vcpkg install` without any package arguments.", "ErrorInvalidClassicModeOption": "Error: The option --{option} is not supported in classic mode and no manifest was found.", @@ -46,6 +53,7 @@ "ErrorRequirePackagesList": "Error: `vcpkg install` requires a list of packages to install in classic mode.", "ErrorRequirePackagesToInstall": "Error: No packages were listed for installation and no manifest was found.", "ErrorVcvarsUnsupported": "Error: in triplet {triplet}: Use of Visual Studio's Developer Prompt is unsupported on non-Windows hosts.\nDefine 'VCPKG_CMAKE_SYSTEM_NAME' or 'VCPKG_CHAINLOAD_TOOLCHAIN_FILE' in the triplet file.", + "ExcludedPackage": "Excluded {spec}", "ExpectedCharacterHere": "expected '{expected}' here", "ExpectedFailOrSkip": "expected 'fail' or 'skip' here", "ExpectedPortName": "expected a port name here", @@ -59,8 +67,10 @@ "GenerateMsgNoCommentValue": " {{{value}}} was used in the message, but not commented.", "GraphCycleDetected": "Cycle detected within graph at {package_name}:", "HashFileFailureToRead": "failed to read file '{path}' for hashing: {error}", + "HeaderOnlyUsage": "{package_name} is header-only and can be used from CMake via:", "IllegalFeatures": "error: List of features is not allowed in this contect", "IllegalPlatformSpec": "error: Platform qualifier is not allowed in this context", + "InstallingPackage": "Installing {spec}...", "InternalErrorMessage": "internal error: ", "InternalErrorMessageContact": "Please open an issue at https://github.com/microsoft/vcpkg/issues/new?template=other-type-of-bug-report.md&labels=category:vcpkg-bug with detailed steps to reproduce the problem.", "LicenseExpressionContainsExtraPlus": "SPDX license expression contains an extra '+'. These are only allowed directly after a license identifier.", @@ -87,6 +97,7 @@ "ProcessorArchitectureMalformed": "Failed to parse %PROCESSOR_ARCHITECTURE% ({arch}) as a valid CPU architecture.", "ProcessorArchitectureMissing": "The required environment variable %PROCESSOR_ARCHITECTURE% is missing.", "ProcessorArchitectureW6432Malformed": "Failed to parse %PROCESSOR_ARCHITEW6432% ({arch}) as a valid CPU architecture. Falling back to %PROCESSOR_ARCHITECTURE%.", + "RemovingPackage": "Removing {spec}", "RestoredPackagesFromVendor": "Restored {count} package(s) from {vendor} in {elapsed}", "ResultsHeader": "RESULTS", "SeeURL": "See {url} for more information.", diff --git a/locales/messages.json b/locales/messages.json index b4377f6024..776b465b29 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -12,6 +12,10 @@ "_AllFormatArgsRawArgument.comment": "example of {value} is 'foo {} bar'\n", "AllFormatArgsUnbalancedBraces": "unbalanced brace in format string \"{value}\"", "_AllFormatArgsUnbalancedBraces.comment": "example of {value} is 'foo bar {'\n", + "AlreadyInstalled": "{spec} is already installed", + "_AlreadyInstalled.comment": "example of {spec} is 'zlib:x64-windows'.\n", + "AlreadyInstalledNotHead": "{spec} is already installed -- not building from HEAD", + "_AlreadyInstalledNotHead.comment": "'HEAD' means the most recent version of source code\nexample of {spec} is 'zlib:x64-windows'.\n", "AttemptingToFetchPackagesFromVendor": "Attempting to fetch {count} package(s) from {vendor}", "_AttemptingToFetchPackagesFromVendor.comment": "example of {count} is '42'.\nexample of {vendor} is 'Azure'.\n", "BothYesAndNoOptionSpecifiedError": "error: cannot specify both --no-{option} and --{option}.", @@ -36,10 +40,16 @@ "_BuildResultSummaryHeader.comment": "Displayed before a list of a summary installation results.\nexample of {triplet} is 'x64-windows'.\n", "BuildResultSummaryLine": " {build_result}: {count}", "_BuildResultSummaryLine.comment": "Displayed to show a count of results of a build_result in a summary.\nexample of {build_result} is 'One of the BuildResultXxx messages (such as BuildResultSucceeded/SUCCEEDED)'.\nexample of {count} is '42'.\n", + "BuildingFromHead": "Building {spec} from HEAD...", + "_BuildingFromHead.comment": "'HEAD' means the most recent version of source code\nexample of {spec} is 'zlib:x64-windows'.\n", + "BuildingPackage": "Building {spec}...", + "_BuildingPackage.comment": "example of {spec} is 'zlib:x64-windows'.\n", "BuildingPackageFailed": "building {spec} failed with: {build_result}", "_BuildingPackageFailed.comment": "example of {spec} is 'zlib:x64-windows'.\nexample of {build_result} is 'One of the BuildResultXxx messages (such as BuildResultSucceeded/SUCCEEDED)'.\n", "BuildingPackageFailedDueToMissingDeps": "due to the following missing dependencies:", "_BuildingPackageFailedDueToMissingDeps.comment": "Printed after BuildingPackageFailed, and followed by a list of dependencies that were missing.\n", + "CMakeTargetsUsage": "{package_name} provides CMake targets:", + "_CMakeTargetsUsage.comment": "'targets' are a CMake and Makefile concept\nexample of {package_name} is 'zlib'.\n", "ChecksFailedCheck": "vcpkg has crashed; no additional details are available.", "ChecksUnreachableCode": "unreachable code was reached", "ChecksUpdateVcpkg": "updating vcpkg by rerunning bootstrap-vcpkg may resolve this failure.", @@ -56,10 +66,14 @@ "_CouldNotDeduceNugetIdAndVersion.comment": "example of {path} is '/foo/bar'.\n", "CurlReportedUnexpectedResults": "curl has reported unexpected results to vcpkg and vcpkg cannot continue.\nPlease review the following text for sensitive information and open an issue on the Microsoft/vcpkg GitHub to help fix this problem!\ncmd: {command_line}\n=== curl output ===\n{actual}\n=== end curl output ===\n", "_CurlReportedUnexpectedResults.comment": "{command_line} is the command line to call curl.exe, {actual} is the console output of curl.exe locale-invariant download results.\nexample of {command_line} is 'vcpkg install zlib'.\n", + "DownloadedSources": "Downloaded sources for {spec}", + "_DownloadedSources.comment": "example of {spec} is 'zlib:x64-windows'.\n", "DownloadingVcpkgCeBundle": "Downloading vcpkg-ce bundle {version}...", "_DownloadingVcpkgCeBundle.comment": "example of {version} is '1.3.8'.\n", "DownloadingVcpkgCeBundleLatest": "Downloading latest vcpkg-ce bundle...", "_DownloadingVcpkgCeBundleLatest.comment": "This message is normally displayed only in development.\n", + "ElapsedForPackage": "Elapsed time to handle {spec}: {elapsed}", + "_ElapsedForPackage.comment": "example of {spec} is 'zlib:x64-windows'.\nexample of {elapsed} is '3.532 min'.\n", "EmptyLicenseExpression": "SPDX license expression was empty.", "ErrorIndividualPackagesUnsupported": "Error: In manifest mode, `vcpkg install` does not support individual package arguments.\nTo install additional packages, edit vcpkg.json and then run `vcpkg install` without any package arguments.", "ErrorInvalidClassicModeOption": "Error: The option --{option} is not supported in classic mode and no manifest was found.", @@ -82,6 +96,8 @@ "ErrorRequirePackagesToInstall": "Error: No packages were listed for installation and no manifest was found.", "ErrorVcvarsUnsupported": "Error: in triplet {triplet}: Use of Visual Studio's Developer Prompt is unsupported on non-Windows hosts.\nDefine 'VCPKG_CMAKE_SYSTEM_NAME' or 'VCPKG_CHAINLOAD_TOOLCHAIN_FILE' in the triplet file.", "_ErrorVcvarsUnsupported.comment": "example of {triplet} is 'x64-windows'.\n", + "ExcludedPackage": "Excluded {spec}", + "_ExcludedPackage.comment": "example of {spec} is 'zlib:x64-windows'.\n", "ExpectedCharacterHere": "expected '{expected}' here", "_ExpectedCharacterHere.comment": "{expected} is a locale-invariant delimiter; for example, the ':' or '=' in 'zlib:x64-windows=skip'\n", "ExpectedFailOrSkip": "expected 'fail' or 'skip' here", @@ -103,8 +119,12 @@ "_GraphCycleDetected.comment": "A list of package names comprising the cycle will be printed after this message.\nexample of {package_name} is 'zlib'.\n", "HashFileFailureToRead": "failed to read file '{path}' for hashing: {error}", "_HashFileFailureToRead.comment": "example of {error} is 'no such file or directory'\nexample of {path} is '/foo/bar'.\n", + "HeaderOnlyUsage": "{package_name} is header-only and can be used from CMake via:", + "_HeaderOnlyUsage.comment": "'header' refers to C/C++ .h files\nexample of {package_name} is 'zlib'.\n", "IllegalFeatures": "error: List of features is not allowed in this contect", "IllegalPlatformSpec": "error: Platform qualifier is not allowed in this context", + "InstallingPackage": "Installing {spec}...", + "_InstallingPackage.comment": "example of {spec} is 'zlib:x64-windows'.\n", "InternalErrorMessage": "internal error: ", "InternalErrorMessageContact": "Please open an issue at https://github.com/microsoft/vcpkg/issues/new?template=other-type-of-bug-report.md&labels=category:vcpkg-bug with detailed steps to reproduce the problem.", "LicenseExpressionContainsExtraPlus": "SPDX license expression contains an extra '+'. These are only allowed directly after a license identifier.", @@ -144,6 +164,8 @@ "ProcessorArchitectureMissing": "The required environment variable %PROCESSOR_ARCHITECTURE% is missing.", "ProcessorArchitectureW6432Malformed": "Failed to parse %PROCESSOR_ARCHITEW6432% ({arch}) as a valid CPU architecture. Falling back to %PROCESSOR_ARCHITECTURE%.", "_ProcessorArchitectureW6432Malformed.comment": "example of {arch} is 'x64'.\n", + "RemovingPackage": "Removing {spec}", + "_RemovingPackage.comment": "example of {spec} is 'zlib:x64-windows'.\n", "RestoredPackagesFromVendor": "Restored {count} package(s) from {vendor} in {elapsed}", "_RestoredPackagesFromVendor.comment": "example of {count} is '42'.\nexample of {elapsed} is '3.532 min'.\nexample of {vendor} is 'Azure'.\n", "ResultsHeader": "RESULTS", diff --git a/src/vcpkg/base/chrono.cpp b/src/vcpkg/base/chrono.cpp index 91087208f2..06d6f2693f 100644 --- a/src/vcpkg/base/chrono.cpp +++ b/src/vcpkg/base/chrono.cpp @@ -29,7 +29,7 @@ namespace vcpkg return parts; } - static Optional to_utc_time(const std::time_t& t) + Optional to_utc_time(const std::time_t& t) { tm parts{}; #if defined(_WIN32) diff --git a/src/vcpkg/base/xmlserializer.cpp b/src/vcpkg/base/xmlserializer.cpp index 535a0a92b3..128a7be39e 100644 --- a/src/vcpkg/base/xmlserializer.cpp +++ b/src/vcpkg/base/xmlserializer.cpp @@ -90,6 +90,16 @@ namespace vcpkg } return *this; } + XmlSerializer& XmlSerializer::cdata(StringView sv) + { + emit_pending_indent(); + Checks::check_exit( + VCPKG_LINE_INFO, Strings::search(sv, "]]>") == sv.end(), "]]> is not supported in a CDATA block"); + buf.append(""); + return *this; + } XmlSerializer& XmlSerializer::simple_tag(StringLiteral tag, StringView content) { return emit_pending_indent().open_tag(tag).text(content).close_tag(tag); diff --git a/src/vcpkg/build.cpp b/src/vcpkg/build.cpp index 69260904ce..04b27de0e6 100644 --- a/src/vcpkg/build.cpp +++ b/src/vcpkg/build.cpp @@ -229,7 +229,7 @@ namespace vcpkg::Build if (result.code != BuildResult::SUCCEEDED) { - print2(Color::error, Build::create_error_message(result.code, spec), '\n'); + print2(Color::error, Build::create_error_message(result, spec), '\n'); print2(Build::create_user_troubleshooting_message(*action, paths), '\n'); return 1; } @@ -1002,7 +1002,7 @@ namespace vcpkg::Build { // TODO: Capture executed command output and evaluate whether the failure was intended. // If an unintended error occurs then return a BuildResult::DOWNLOAD_FAILURE status. - return BuildResult::DOWNLOADED; + return ExtendedBuildResult{BuildResult::DOWNLOADED}; } const auto buildtimeus = timer.microseconds(); @@ -1023,7 +1023,7 @@ namespace vcpkg::Build { metrics->track_property("error", "build failed"); metrics->track_property("build_error", spec_string); - return BuildResult::BUILD_FAILED; + return ExtendedBuildResult{BuildResult::BUILD_FAILED}; } } @@ -1039,7 +1039,7 @@ namespace vcpkg::Build if (error_count != 0 && action.build_options.backcompat_features == BackcompatFeatures::PROHIBIT) { - return BuildResult::POST_BUILD_CHECKS_FAILED; + return ExtendedBuildResult{BuildResult::POST_BUILD_CHECKS_FAILED}; } for (auto&& feature : action.feature_list) @@ -1394,7 +1394,6 @@ namespace vcpkg::Build { switch (build_result) { - case BuildResult::NULLVALUE: ++null_value; return; case BuildResult::SUCCEEDED: ++succeeded; return; case BuildResult::BUILD_FAILED: ++build_failed; return; case BuildResult::POST_BUILD_CHECKS_FAILED: ++post_build_checks_failed; return; @@ -1440,7 +1439,6 @@ namespace vcpkg::Build { switch (build_result) { - case BuildResult::NULLVALUE: return "vcpkg::Commands::Build::BuildResult_NULLVALUE"; case BuildResult::SUCCEEDED: return "SUCCEEDED"; case BuildResult::BUILD_FAILED: return "BUILD_FAILED"; case BuildResult::POST_BUILD_CHECKS_FAILED: return "POST_BUILD_CHECKS_FAILED"; @@ -1457,8 +1455,6 @@ namespace vcpkg::Build { switch (build_result) { - case BuildResult::NULLVALUE: - return LocalizedString::from_raw(to_string_locale_invariant(BuildResult::NULLVALUE)); case BuildResult::SUCCEEDED: return msg::format(msgBuildResultSucceeded); case BuildResult::BUILD_FAILED: return msg::format(msgBuildResultBuildFailed); case BuildResult::POST_BUILD_CHECKS_FAILED: return msg::format(msgBuildResultPostBuildChecksFailed); diff --git a/src/vcpkg/commands.ci.cpp b/src/vcpkg/commands.ci.cpp index c57fab300b..ee42e9dc35 100644 --- a/src/vcpkg/commands.ci.cpp +++ b/src/vcpkg/commands.ci.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -157,163 +158,166 @@ namespace vcpkg::Commands::CI nullptr, }; + // https://xunit.net/docs/format-xml-v2 struct XunitTestResults { public: - XunitTestResults() { m_assembly_run_datetime = CTime::get_current_date_time(); } - - void add_test_results(const std::string& spec, - const Build::BuildResult& build_result, + void add_test_results(const PackageSpec& spec, + Build::BuildResult build_result, const ElapsedTime& elapsed_time, + const std::chrono::system_clock::time_point& start_time, const std::string& abi_tag, const std::vector& features) { - m_collections.back().tests.push_back({spec, build_result, elapsed_time, abi_tag, features}); + m_tests[spec.name()].push_back( + {spec.to_string(), + Strings::concat(spec.name(), '[', Strings::join(",", features), "]:", spec.triplet()), + spec.triplet().to_string(), + build_result, + elapsed_time, + start_time, + abi_tag, + features}); } - // Starting a new test collection - void push_collection(const std::string& name) { m_collections.push_back({name}); } - - void collection_time(const vcpkg::ElapsedTime& time) { m_collections.back().time = time; } - - const std::string& build_xml() + std::string build_xml(Triplet controlling_triplet) { - m_xml.clear(); - xml_start_assembly(); - - for (const auto& collection : m_collections) + XmlSerializer xml; + xml.emit_declaration(); + xml.open_tag("assemblies").line_break(); + for (const auto& test_group : m_tests) { - xml_start_collection(collection); - for (const auto& test : collection.tests) + const auto& port_name = test_group.first; + const auto& port_results = test_group.second; + + ElapsedTime elapsed_sum{}; + for (auto&& port_result : port_results) + { + elapsed_sum += port_result.time; + } + + const auto elapsed_seconds = elapsed_sum.as().count(); + + auto earliest_start_time = std::min_element(port_results.begin(), + port_results.end(), + [](const XunitTest& lhs, const XunitTest& rhs) { + return lhs.start_time < rhs.start_time; + }) + ->start_time; + + const auto as_time_t = std::chrono::system_clock::to_time_t(earliest_start_time); + const auto as_tm = to_utc_time(as_time_t).value_or_exit(VCPKG_LINE_INFO); + char run_date_time[80]; + strftime(run_date_time, sizeof(run_date_time), "%Y-%m-%d%H:%M:%S", &as_tm); + + StringView run_date{run_date_time, 10}; + StringView run_time{run_date_time + 10, 8}; + + xml.start_complex_open_tag("assembly") + .attr("name", port_name) + .attr("run-date", run_date) + .attr("run-time", run_time) + .attr("time", elapsed_seconds) + .finish_complex_open_tag() + .line_break(); + xml.start_complex_open_tag("collection") + .attr("name", controlling_triplet) + .attr("time", elapsed_seconds) + .finish_complex_open_tag() + .line_break(); + for (const auto& port_result : port_results) { - xml_test(test); + xml_test(xml, port_result); } - xml_finish_collection(); + xml.close_tag("collection").line_break(); + xml.close_tag("assembly").line_break(); } - xml_finish_assembly(); - return m_xml; + xml.close_tag("assemblies").line_break(); + return std::move(xml.buf); } - void assembly_time(const vcpkg::ElapsedTime& assembly_time) { m_assembly_time = assembly_time; } - private: struct XunitTest { std::string name; + std::string method; + std::string owner; vcpkg::Build::BuildResult result; vcpkg::ElapsedTime time; + std::chrono::system_clock::time_point start_time; std::string abi_tag; std::vector features; }; - struct XunitCollection - { - std::string name; - vcpkg::ElapsedTime time; - std::vector tests; - }; - - void xml_start_assembly() - { - std::string datetime; - if (m_assembly_run_datetime) - { - auto rawDateTime = m_assembly_run_datetime.get()->to_string(); - // The expected format is "yyyy-mm-ddThh:mm:ss.0Z" - // 0123456789012345678901 - datetime = Strings::format( - R"(run-date="%s" run-time="%s")", rawDateTime.substr(0, 10), rawDateTime.substr(11, 8)); - } - - std::string time = Strings::format(R"(time="%lld")", m_assembly_time.as().count()); - - m_xml += Strings::format(R"()" - "\n" - R"( )" - "\n", - datetime, - time); - } - void xml_finish_assembly() - { - m_xml += " \n" - "\n"; - } - - void xml_start_collection(const XunitCollection& collection) + static void xml_test(XmlSerializer& xml, const XunitTest& test) { - m_xml += Strings::format(R"( )" - "\n", - collection.name, - collection.time.as().count()); - } - void xml_finish_collection() { m_xml += " \n"; } - - void xml_test(const XunitTest& test) - { - std::string message_block; - const char* result_string = ""; + StringLiteral result_string = ""; switch (test.result) { case BuildResult::POST_BUILD_CHECKS_FAILED: case BuildResult::FILE_CONFLICTS: - case BuildResult::BUILD_FAILED: - result_string = "Fail"; - message_block = Strings::format("", - to_string_locale_invariant(test.result)); - break; + case BuildResult::BUILD_FAILED: result_string = "Fail"; break; case BuildResult::EXCLUDED: - case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: - result_string = "Skip"; - message_block = - Strings::format("", to_string_locale_invariant(test.result)); - break; + case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: result_string = "Skip"; break; case BuildResult::SUCCEEDED: result_string = "Pass"; break; default: Checks::unreachable(VCPKG_LINE_INFO); } - std::string traits_block; + xml.start_complex_open_tag("test") + .attr("name", test.name) + .attr("method", test.method) + .attr("time", test.time.as().count()) + .attr("method", result_string) + .finish_complex_open_tag() + .line_break(); + xml.open_tag("traits").line_break(); if (!test.abi_tag.empty()) { - traits_block += Strings::format(R"()", test.abi_tag); + xml.start_complex_open_tag("trait") + .attr("name", "abi_tag") + .attr("value", test.abi_tag) + .finish_self_closing_complex_tag() + .line_break(); } if (!test.features.empty()) { - std::string feature_list; - for (const auto& feature : test.features) - { - if (!feature_list.empty()) - { - feature_list += ", "; - } - feature_list += feature; - } - - traits_block += Strings::format(R"()", feature_list); + xml.start_complex_open_tag("trait") + .attr("name", "features") + .attr("value", Strings::join(", ", test.features)) + .finish_self_closing_complex_tag() + .line_break(); } - if (!traits_block.empty()) + xml.start_complex_open_tag("trait") + .attr("name", "owner") + .attr("value", test.owner) + .finish_self_closing_complex_tag() + .line_break(); + xml.close_tag("traits").line_break(); + + if (result_string == "Fail") { - traits_block = "" + traits_block + ""; + xml.open_tag("failure") + .open_tag("message") + .cdata(to_string_locale_invariant(test.result)) + .close_tag("failure") + .close_tag("message") + .line_break(); } - - m_xml += Strings::format(R"( %s%s)" - "\n", - test.name, - test.name, - test.time.as().count(), - result_string, - traits_block, - message_block); + else if (result_string == "Skip") + { + xml.open_tag("reason").cdata(to_string_locale_invariant(test.result)).close_tag("reason").line_break(); + } + else + { + Checks::check_exit(VCPKG_LINE_INFO, result_string == "Pass"); + } + xml.close_tag("test").line_break(); } - Optional m_assembly_run_datetime; - vcpkg::ElapsedTime m_assembly_time; - std::vector m_collections; - - std::string m_xml; + std::map> m_tests; }; struct UnknownCIPortsResults @@ -493,27 +497,27 @@ namespace vcpkg::Commands::CI LocalizedString output; for (auto&& port_result : result.summary.results) { - switch (port_result.build_result.code) + auto& build_result = port_result.build_result.value_or_exit(VCPKG_LINE_INFO); + switch (build_result.code) { case Build::BuildResult::BUILD_FAILED: case Build::BuildResult::POST_BUILD_CHECKS_FAILED: case Build::BuildResult::FILE_CONFLICTS: - if (!expected_failures.contains(port_result.spec)) + if (!expected_failures.contains(port_result.get_spec())) { - output.append(msg::format( - msgCiBaselineRegression, - msg::spec = port_result.spec.to_string(), - msg::build_result = - Build::to_string_locale_invariant(port_result.build_result.code).to_string(), - msg::path = ci_baseline_file_name)); + output.append(msg::format(msgCiBaselineRegression, + msg::spec = port_result.get_spec().to_string(), + msg::build_result = + Build::to_string_locale_invariant(build_result.code).to_string(), + msg::path = ci_baseline_file_name)); output.appendnl(); } break; case Build::BuildResult::SUCCEEDED: - if (!allow_unexpected_passing && expected_failures.contains(port_result.spec)) + if (!allow_unexpected_passing && expected_failures.contains(port_result.get_spec())) { output.append(msg::format(msgCiBaselineUnexpectedPass, - msg::spec = port_result.spec.to_string(), + msg::spec = port_result.get_spec().to_string(), msg::path = ci_baseline_file_name)); output.appendnl(); } @@ -603,9 +607,6 @@ namespace vcpkg::Commands::CI std::vector results; auto timer = ElapsedTimer::create_started(); - - xunitTestResults.push_collection(target_triplet.canonical_name()); - std::vector all_port_names = Util::fmap(provider.load_all_control_files(), Paragraphs::get_name_of_control_file); // Install the default features for every package @@ -710,8 +711,6 @@ namespace vcpkg::Commands::CI else { StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); - - auto collection_timer = ElapsedTimer::create_started(); auto summary = Install::perform(args, action_plan, Install::KeepGoing::YES, @@ -720,17 +719,17 @@ namespace vcpkg::Commands::CI binary_cache, build_logs_recorder, var_provider); - auto collection_time_elapsed = collection_timer.elapsed(); // Adding results for ports that were built or pulled from an archive for (auto&& result : summary.results) { - auto& port_features = split_specs->features.at(result.spec); - split_specs->known.erase(result.spec); - xunitTestResults.add_test_results(result.spec.to_string(), - result.build_result.code, + auto& port_features = split_specs->features.at(result.get_spec()); + split_specs->known.erase(result.get_spec()); + xunitTestResults.add_test_results(result.get_spec(), + result.build_result.value_or_exit(VCPKG_LINE_INFO).code, result.timing, - split_specs->abi_map.at(result.spec), + result.start_time, + split_specs->abi_map.at(result.get_spec()), port_features); } @@ -738,9 +737,10 @@ namespace vcpkg::Commands::CI for (auto&& port : split_specs->known) { auto& port_features = split_specs->features.at(port.first); - xunitTestResults.add_test_results(port.first.to_string(), + xunitTestResults.add_test_results(port.first, port.second, ElapsedTime{}, + std::chrono::system_clock::time_point{}, split_specs->abi_map.at(port.first), port_features); } @@ -748,12 +748,8 @@ namespace vcpkg::Commands::CI all_known_results.emplace_back(std::move(split_specs->known)); results.push_back({target_triplet, std::move(summary)}); - - xunitTestResults.collection_time(collection_time_elapsed); } - xunitTestResults.assembly_time(timer.elapsed()); - for (auto&& result : results) { print2("\nTriplet: ", result.triplet, "\n"); @@ -769,7 +765,7 @@ namespace vcpkg::Commands::CI auto it_xunit = settings.find(OPTION_XUNIT); if (it_xunit != settings.end()) { - filesystem.write_contents(it_xunit->second, xunitTestResults.build_xml(), VCPKG_LINE_INFO); + filesystem.write_contents(it_xunit->second, xunitTestResults.build_xml(target_triplet), VCPKG_LINE_INFO); } Checks::exit_success(VCPKG_LINE_INFO); diff --git a/src/vcpkg/install.cpp b/src/vcpkg/install.cpp index b3c11de595..a954733de1 100644 --- a/src/vcpkg/install.cpp +++ b/src/vcpkg/install.cpp @@ -34,6 +34,31 @@ namespace (msg::count), "", "note: {count} additional targets are not displayed."); + DECLARE_AND_REGISTER_MESSAGE(AlreadyInstalledNotHead, + (msg::spec), + "'HEAD' means the most recent version of source code", + "{spec} is already installed -- not building from HEAD"); + DECLARE_AND_REGISTER_MESSAGE(AlreadyInstalled, (msg::spec), "", "{spec} is already installed"); + DECLARE_AND_REGISTER_MESSAGE(BuildingPackage, (msg::spec), "", "Building {spec}..."); + DECLARE_AND_REGISTER_MESSAGE(BuildingFromHead, + (msg::spec), + "'HEAD' means the most recent version of source code", + "Building {spec} from HEAD..."); + DECLARE_AND_REGISTER_MESSAGE(DownloadedSources, (msg::spec), "", "Downloaded sources for {spec}"); + DECLARE_AND_REGISTER_MESSAGE(ExcludedPackage, (msg::spec), "", "Excluded {spec}"); + DECLARE_AND_REGISTER_MESSAGE(InstallingPackage, (msg::spec), "", "Installing {spec}..."); + DECLARE_AND_REGISTER_MESSAGE(ElapsedForPackage, + (msg::spec, msg::elapsed), + "", + "Elapsed time to handle {spec}: {elapsed}"); + DECLARE_AND_REGISTER_MESSAGE(HeaderOnlyUsage, + (msg::package_name), + "'header' refers to C/C++ .h files", + "{package_name} is header-only and can be used from CMake via:"); + DECLARE_AND_REGISTER_MESSAGE(CMakeTargetsUsage, + (msg::package_name), + "'targets' are a CMake and Makefile concept", + "{package_name} provides CMake targets:"); } namespace vcpkg::Install @@ -324,8 +349,6 @@ namespace vcpkg::Install { auto& fs = paths.get_filesystem(); const InstallPlanType& plan_type = action.plan_type; - const std::string display_name = action.spec.to_string(); - const std::string display_name_with_features = action.displayname(); const bool is_user_requested = action.request_type == RequestType::USER_REQUESTED; const bool use_head_version = Util::Enum::to_bool(action.build_options.use_head_version); @@ -333,11 +356,10 @@ namespace vcpkg::Install if (plan_type == InstallPlanType::ALREADY_INSTALLED) { if (use_head_version && is_user_requested) - vcpkg::printf( - Color::warning, "Package %s is already installed -- not building from HEAD\n", display_name); + msg::println(Color::warning, msgAlreadyInstalledNotHead, msg::spec = action.spec); else - vcpkg::printf(Color::success, "Package %s is already installed\n", display_name); - return BuildResult::SUCCEEDED; + msg::println(Color::success, msgAlreadyInstalled, msg::spec = action.spec); + return ExtendedBuildResult{BuildResult::SUCCEEDED}; } if (plan_type == InstallPlanType::BUILD_AND_INSTALL) @@ -351,20 +373,20 @@ namespace vcpkg::Install } else if (action.build_options.build_missing == Build::BuildMissing::NO) { - return BuildResult::CACHE_MISSING; + return ExtendedBuildResult{BuildResult::CACHE_MISSING}; } else { if (use_head_version) - vcpkg::printf("Building package %s from HEAD...\n", display_name_with_features); + msg::println(msgBuildingFromHead, msg::spec = action.displayname()); else - vcpkg::printf("Building package %s...\n", display_name_with_features); + msg::println(msgBuildingPackage, msg::spec = action.displayname()); auto result = Build::build_package(args, paths, action, binary_cache, build_logs_recorder, status_db); if (BuildResult::DOWNLOADED == result.code) { - print2(Color::success, "Downloaded sources for package ", display_name_with_features, "\n"); + msg::println(Color::success, msgDownloadedSources, msg::spec = action.displayname()); return result; } @@ -379,7 +401,6 @@ namespace vcpkg::Install // Build or restore succeeded and `bcf` is populated with the control file. Checks::check_exit(VCPKG_LINE_INFO, bcf != nullptr); - vcpkg::printf("Installing package %s...\n", display_name_with_features); const auto install_result = install_package(paths, *bcf, &status_db); BuildResult code; switch (install_result) @@ -408,8 +429,8 @@ namespace vcpkg::Install if (plan_type == InstallPlanType::EXCLUDED) { - vcpkg::printf(Color::warning, "Package %s is excluded\n", display_name); - return BuildResult::EXCLUDED; + msg::println(Color::warning, msgExcludedPackage, msg::spec = action.spec); + return ExtendedBuildResult{BuildResult::EXCLUDED}; } Checks::unreachable(VCPKG_LINE_INFO); @@ -421,14 +442,17 @@ namespace vcpkg::Install for (const SpecSummary& result : this->results) { - msg::println(LocalizedString().append_indent().append_raw( - fmt::format("{}: {}: {}", result.spec, Build::to_string(result.build_result.code), result.timing))); + msg::println(LocalizedString().append_indent().append_fmt_raw( + "{}: {}: {}", + result.get_spec(), + Build::to_string(result.build_result.value_or_exit(VCPKG_LINE_INFO).code), + result.timing)); } std::map summary; for (const SpecSummary& r : this->results) { - summary[r.spec.triplet()].increment(r.build_result.code); + summary[r.get_spec().triplet()].increment(r.build_result.value_or_exit(VCPKG_LINE_INFO).code); } msg::println(); @@ -447,18 +471,31 @@ namespace vcpkg::Install TrackedPackageInstallGuard(const size_t action_index, const size_t action_count, std::vector& results, - const PackageSpec& spec) + const Dependencies::InstallPlanAction& action) + { + results.emplace_back(action); + current_summary = &results.back(); + + msg::println(msg::format(msgInstallingPackage, msg::spec = action.spec) + .append_fmt_raw(" ({}/{})...", action_index, action_count)); + } + + TrackedPackageInstallGuard(const size_t action_index, + const size_t action_count, + std::vector& results, + const Dependencies::RemovePlanAction& action) { - results.emplace_back(spec, nullptr); + results.emplace_back(action); current_summary = &results.back(); - vcpkg::printf("Starting package %zd/%zd: %s\n", action_index, action_count, spec.to_string()); + msg::println(msg::format(Remove::msgRemovingPackage, msg::spec = action.spec) + .append_fmt_raw(" ({}/{})...", action_index, action_count)); } ~TrackedPackageInstallGuard() { current_summary->timing = build_timer.elapsed(); - vcpkg::printf( - "Elapsed time for package %s: %s\n", current_summary->spec.to_string(), current_summary->timing); + msg::println( + msgElapsedForPackage, msg::spec = current_summary->get_spec(), msg::elapsed = current_summary->timing); } TrackedPackageInstallGuard(const TrackedPackageInstallGuard&) = delete; @@ -480,22 +517,22 @@ namespace vcpkg::Install for (auto&& action : action_plan.remove_actions) { - TrackedPackageInstallGuard this_install(action_index++, action_count, results, action.spec); + TrackedPackageInstallGuard this_install(action_index++, action_count, results, action); Remove::perform_remove_plan_action(paths, action, Remove::Purge::YES, &status_db); } for (auto&& action : action_plan.already_installed) { - results.emplace_back(action.spec, &action); - results.back().build_result = - perform_install_plan_action(args, paths, action, status_db, binary_cache, build_logs_recorder); + results.emplace_back(action); + results.back().build_result.emplace( + perform_install_plan_action(args, paths, action, status_db, binary_cache, build_logs_recorder)); } Build::compute_all_abis(paths, action_plan, var_provider, status_db); binary_cache.prefetch(action_plan.install_actions); for (auto&& action : action_plan.install_actions) { - TrackedPackageInstallGuard this_install(action_index++, action_count, results, action.spec); + TrackedPackageInstallGuard this_install(action_index++, action_count, results, action); auto result = perform_install_plan_action(args, paths, action, status_db, binary_cache, build_logs_recorder); if (result.code != BuildResult::SUCCEEDED && keep_going == KeepGoing::NO) @@ -504,8 +541,7 @@ namespace vcpkg::Install Checks::exit_fail(VCPKG_LINE_INFO); } - this_install.current_summary->action = &action; - this_install.current_summary->build_result = std::move(result); + this_install.current_summary->build_result.emplace(std::move(result)); } return InstallSummary{std::move(results)}; @@ -762,8 +798,8 @@ namespace vcpkg::Install }; const auto name = cmakeify(bpgh.spec.name()); - auto msg = Strings::concat( - "The package ", bpgh.spec.name(), " is header only and can be used from CMake via:\n\n"); + auto msg = msg::format(msgHeaderOnlyUsage, msg::package_name = bpgh.spec.name()).extract_data(); + Strings::append(msg, "\n\n"); Strings::append(msg, " find_path(", name, "_INCLUDE_DIRS \"", header_path, "\")\n"); Strings::append(msg, " target_include_directories(main PRIVATE ${", name, "_INCLUDE_DIRS})\n\n"); @@ -772,7 +808,7 @@ namespace vcpkg::Install } else { - auto msg = Strings::concat("The package ", bpgh.spec.name(), " provides CMake targets:\n\n"); + auto msg = msg::format(msgCMakeTargetsUsage, msg::package_name = bpgh.spec.name()).extract_data(); for (auto&& library_target_pair : library_targets) { @@ -1214,9 +1250,9 @@ namespace vcpkg::Install std::set printed_usages; for (auto&& result : summary.results) { - if (!result.action) continue; - if (result.action->request_type != RequestType::USER_REQUESTED) continue; + if (!result.is_user_requested_install()) continue; auto bpgh = result.get_binary_paragraph(); + assert(bpgh); if (!bpgh) continue; print_usage_information(*bpgh, printed_usages, fs, paths.installed()); } @@ -1232,21 +1268,40 @@ namespace vcpkg::Install Install::perform_and_exit(args, paths, default_triplet, host_triplet); } - SpecSummary::SpecSummary(const PackageSpec& spec, const Dependencies::InstallPlanAction* action) - : spec(spec), build_result{BuildResult::NULLVALUE, nullptr}, action(action) + SpecSummary::SpecSummary(const Dependencies::InstallPlanAction& action) + : build_result() + , timing() + , start_time(std::chrono::system_clock::now()) + , m_install_action(&action) + , m_spec(action.spec) + { + } + + SpecSummary::SpecSummary(const Dependencies::RemovePlanAction& action) + : build_result() + , timing() + , start_time(std::chrono::system_clock::now()) + , m_install_action(nullptr) + , m_spec(action.spec) { } const BinaryParagraph* SpecSummary::get_binary_paragraph() const { - if (build_result.binary_control_file) + // if we actually built this package, the build result will contain the BinaryParagraph for what we built. + if (const auto br = build_result.get()) { - return &build_result.binary_control_file->core_paragraph; + if (br->binary_control_file) + { + return &br->binary_control_file->core_paragraph; + } } - if (action) + // if the package was already installed, the installed_package record will contain the BinaryParagraph for what + // was built before. + if (m_install_action) { - if (auto p_status = action->installed_package.get()) + if (auto p_status = m_install_action->installed_package.get()) { return &p_status->core->package; } @@ -1255,6 +1310,11 @@ namespace vcpkg::Install return nullptr; } + bool SpecSummary::is_user_requested_install() const + { + return m_install_action && m_install_action->request_type == RequestType::USER_REQUESTED; + } + static std::string xunit_result(const PackageSpec& spec, ElapsedTime time, BuildResult code) { std::string message_block; @@ -1292,7 +1352,8 @@ namespace vcpkg::Install std::string xunit_doc; for (auto&& result : results) { - xunit_doc += xunit_result(result.spec, result.timing, result.build_result.code); + xunit_doc += + xunit_result(result.get_spec(), result.timing, result.build_result.value_or_exit(VCPKG_LINE_INFO).code); } return xunit_doc; diff --git a/src/vcpkg/remove.cpp b/src/vcpkg/remove.cpp index 4267f34a20..a3cd53a419 100644 --- a/src/vcpkg/remove.cpp +++ b/src/vcpkg/remove.cpp @@ -20,6 +20,8 @@ namespace vcpkg::Remove using Dependencies::RequestType; using Update::OutdatedPackage; + REGISTER_MESSAGE(RemovingPackage); + static void remove_package(Filesystem& fs, const InstalledPaths& installed, const PackageSpec& spec, @@ -152,13 +154,8 @@ namespace vcpkg::Remove switch (action.plan_type) { - case RemovePlanType::NOT_INSTALLED: - vcpkg::printf(Color::success, "Package %s is not installed\n", display_name); - break; - case RemovePlanType::REMOVE: - vcpkg::printf("Removing package %s...\n", display_name); - remove_package(fs, paths.installed(), action.spec, status_db); - break; + case RemovePlanType::NOT_INSTALLED: break; + case RemovePlanType::REMOVE: remove_package(fs, paths.installed(), action.spec, status_db); break; case RemovePlanType::UNKNOWN: default: Checks::unreachable(VCPKG_LINE_INFO); } @@ -305,8 +302,12 @@ namespace vcpkg::Remove Checks::exit_success(VCPKG_LINE_INFO); } - for (const RemovePlanAction& action : remove_plan) + // note that we try to "remove" things that aren't installed to trigger purge actions + for (std::size_t idx = 0; idx < remove_plan.size(); ++idx) { + const RemovePlanAction& action = remove_plan[idx]; + msg::println(msg::format(msgRemovingPackage, msg::spec = action.spec) + .append_fmt_raw(" ({}/{})...", idx + 1, remove_plan.size())); perform_remove_plan_action(paths, action, purge, &status_db); }