diff --git a/include/vcpkg-test/util.h b/include/vcpkg-test/util.h index d496b8bee4..e13e4dacf8 100644 --- a/include/vcpkg-test/util.h +++ b/include/vcpkg-test/util.h @@ -5,7 +5,9 @@ #include #include #include +#include +#include #include #include @@ -55,10 +57,18 @@ namespace Catch { static const std::string convert(const vcpkg::LocalizedString& value) { return "LL\"" + value.data() + "\""; } }; + + template<> + struct StringMaker + { + static const std::string convert(const vcpkg::PackageSpec& value) { return value.to_string(); } + }; } namespace vcpkg { + inline std::ostream& operator<<(std::ostream& os, const PackageSpec& value) { return os << value.to_string(); } + inline std::ostream& operator<<(std::ostream& os, const LocalizedString& value) { return os << "LL" << std::quoted(value.data()); diff --git a/include/vcpkg/base/parse.h b/include/vcpkg/base/parse.h index 053f8a6d7b..73a2fea18e 100644 --- a/include/vcpkg/base/parse.h +++ b/include/vcpkg/base/parse.h @@ -58,6 +58,9 @@ namespace vcpkg { std::unique_ptr error; std::vector warnings; + + void exit_if_errors_or_warnings(StringView origin) const; + bool good() const { return !error && warnings.empty(); } }; struct ParserBase @@ -84,42 +87,35 @@ namespace vcpkg } static constexpr bool is_word_char(char32_t ch) { return is_alphanum(ch) || ch == '_'; } - StringView skip_whitespace() { return match_zero_or_more(is_whitespace); } - StringView skip_tabs_spaces() - { - return match_zero_or_more([](char32_t ch) { return ch == ' ' || ch == '\t'; }); - } - void skip_to_eof() { m_it = m_it.end(); } - void skip_newline() - { - if (cur() == '\r') next(); - if (cur() == '\n') next(); - } - void skip_line() - { - match_until(is_lineend); - skip_newline(); - } + StringView skip_whitespace(); + StringView skip_tabs_spaces(); + void skip_to_eof(); + void skip_newline(); + void skip_line(); template - StringView match_zero_or_more(Pred p) + StringView match_while(Pred p) { const char* start = m_it.pointer_to_current(); auto ch = cur(); while (ch != Unicode::end_of_file && p(ch)) + { ch = next(); + } + return {start, m_it.pointer_to_current()}; } + template StringView match_until(Pred p) { - const char* start = m_it.pointer_to_current(); - auto ch = cur(); - while (ch != Unicode::end_of_file && !p(ch)) - ch = next(); - return {start, m_it.pointer_to_current()}; + return match_while([p](char32_t ch) { return !p(ch); }); } + bool require_character(char ch); + + bool try_match_keyword(StringView keyword_content); + StringView text() const { return m_text; } Unicode::Utf8Decoder it() const { return m_it; } char32_t cur() const { return m_it == m_it.end() ? Unicode::end_of_file : *m_it; } @@ -140,7 +136,7 @@ namespace vcpkg std::unique_ptr extract_error() { return std::move(m_messages.error); } const ParseMessages& messages() const { return m_messages; } - ParseMessages extract_messages() { return std::move(m_messages); } + ParseMessages&& extract_messages() { return std::move(m_messages); } private: Unicode::Utf8Decoder m_it; diff --git a/include/vcpkg/base/sortedvector.h b/include/vcpkg/base/sortedvector.h index 708f5c51ae..e190d221fc 100644 --- a/include/vcpkg/base/sortedvector.h +++ b/include/vcpkg/base/sortedvector.h @@ -1,36 +1,51 @@ #pragma once #include +#include +#include +#include #include // Add more forwarding functions to the m_data std::vector as needed. namespace vcpkg { - template + template> struct SortedVector { - using size_type = typename std::vector::size_type; - using iterator = typename std::vector::const_iterator; + using size_type = typename std::vector::size_type; + using iterator = typename std::vector::const_iterator; - SortedVector() : m_data() { } + SortedVector() : m_data(), m_comp() { } + SortedVector(const SortedVector&) = default; + SortedVector(SortedVector&&) = default; + SortedVector& operator=(const SortedVector&) = default; + SortedVector& operator=(SortedVector&&) = default; - explicit SortedVector(std::vector v) : m_data(std::move(v)) + explicit SortedVector(const std::vector& data) : m_data(data), m_comp() { sort_uniqueify(); } + explicit SortedVector(const std::vector& data, Compare comp) : m_data(data), m_comp(comp) { - if (!std::is_sorted(m_data.begin(), m_data.end())) - { - std::sort(m_data.begin(), m_data.end()); - } + sort_uniqueify(); + } + explicit SortedVector(std::vector&& data) : m_data(std::move(data)), m_comp() { sort_uniqueify(); } + explicit SortedVector(std::vector&& data, Compare comp) : m_data(std::move(data)), m_comp(comp) + { + sort_uniqueify(); } - template - SortedVector(std::vector v, Compare comp) : m_data(std::move(v)) + template + explicit SortedVector(InIt first, InIt last) : m_data(first, last), m_comp() { - if (!std::is_sorted(m_data.cbegin(), m_data.cend(), comp)) - { - std::sort(m_data.begin(), m_data.end(), comp); - } + sort_uniqueify(); } + template + explicit SortedVector(InIt first, InIt last, Compare comp) : m_data(first, last), m_comp(comp) + { + sort_uniqueify(); + } + + SortedVector(std::initializer_list elements) : m_data(elements), m_comp() { sort_uniqueify(); } + iterator begin() const { return this->m_data.cbegin(); } iterator end() const { return this->m_data.cend(); } @@ -43,9 +58,53 @@ namespace vcpkg size_type size() const { return this->m_data.size(); } - const T& operator[](int i) const { return this->m_data[i]; } + const Ty& operator[](std::size_t i) const { return this->m_data[i]; } + + bool contains(const Ty& element) const { return std::binary_search(m_data.begin(), m_data.end(), element); } + + void append(const SortedVector& other) + { + // This could use a more efficient merge algorithm than inplace_merge with an understanding that we will + // allocate the whole result if perf becomes a problem + auto merge_point = m_data.insert(m_data.end(), other.begin(), other.end()); + std::inplace_merge(m_data.begin(), merge_point, m_data.end(), m_comp); + uniqueify(); + } + + void append(SortedVector&& other) + { + auto merge_point = m_data.insert( + m_data.end(), std::make_move_iterator(other.begin()), std::make_move_iterator(other.end())); + std::inplace_merge(m_data.begin(), merge_point, m_data.end(), m_comp); + uniqueify(); + } + + friend bool operator==(const SortedVector& lhs, const SortedVector& rhs) { return lhs.m_data == rhs.m_data; } + friend bool operator!=(const SortedVector& lhs, const SortedVector& rhs) { return lhs.m_data != rhs.m_data; } private: - std::vector m_data; + void uniqueify() + { + Compare comp = m_comp; + m_data.erase(std::unique(m_data.begin(), + m_data.end(), + [comp](const Ty& lhs, const Ty& rhs) { + // note that we know !comp(rhs, lhs) because m_data is sorted + return !comp(lhs, rhs); + }), + m_data.end()); + } + void sort_uniqueify() + { + if (!std::is_sorted(m_data.begin(), m_data.end(), m_comp)) + { + std::sort(m_data.begin(), m_data.end(), m_comp); + } + + uniqueify(); + } + + std::vector m_data; + Compare m_comp; }; } diff --git a/include/vcpkg/ci-baseline.h b/include/vcpkg/ci-baseline.h new file mode 100644 index 0000000000..b11d6d20ef --- /dev/null +++ b/include/vcpkg/ci-baseline.h @@ -0,0 +1,58 @@ +#pragma once +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace vcpkg +{ + struct CiBaselineLine + { + std::string port_name; + Triplet triplet; + CiBaselineState state; + + friend bool operator==(const CiBaselineLine& lhs, const CiBaselineLine& rhs) + { + return lhs.port_name == rhs.port_name && lhs.triplet == rhs.triplet && lhs.state == rhs.state; + } + + friend bool operator!=(const CiBaselineLine& lhs, const CiBaselineLine& rhs) { return !(lhs == rhs); } + }; + + struct TripletExclusions + { + Triplet triplet; + SortedVector exclusions; + + TripletExclusions(const Triplet& triplet); + TripletExclusions(const Triplet& triplet, SortedVector&& exclusions); + }; + + struct ExclusionsMap + { + std::vector triplets; + + void insert(Triplet triplet); + void insert(Triplet triplet, SortedVector&& exclusions); + }; + + struct ExclusionPredicate + { + const ExclusionsMap* data; + + bool operator()(const PackageSpec& spec) const; + }; + + std::vector parse_ci_baseline(StringView text, StringView origin, ParseMessages& messages); + + SortedVector parse_and_apply_ci_baseline(View lines, ExclusionsMap& exclusions_map); +} diff --git a/include/vcpkg/fwd/ci-baseline.h b/include/vcpkg/fwd/ci-baseline.h new file mode 100644 index 0000000000..c40014bc77 --- /dev/null +++ b/include/vcpkg/fwd/ci-baseline.h @@ -0,0 +1,16 @@ +#pragma once + +namespace vcpkg +{ + struct CiBaseline; + struct CiBaselineLine; + struct TripletExclusions; + struct ExclusionsMap; + struct ExclusionPredicate; + + enum class CiBaselineState + { + Skip, + Fail, + }; +} diff --git a/include/vcpkg/packagespec.h b/include/vcpkg/packagespec.h index 30a59f1b92..cc23fade88 100644 --- a/include/vcpkg/packagespec.h +++ b/include/vcpkg/packagespec.h @@ -23,7 +23,8 @@ namespace vcpkg struct PackageSpec { PackageSpec() = default; - PackageSpec(std::string name, Triplet triplet) : m_name(std::move(name)), m_triplet(triplet) { } + PackageSpec(const std::string& name, Triplet triplet) : m_name(name), m_triplet(triplet) { } + PackageSpec(std::string&& name, Triplet triplet) : m_name(std::move(name)), m_triplet(triplet) { } const std::string& name() const; diff --git a/locales/messages.en.json b/locales/messages.en.json index 8bf88891ea..eda81d97de 100644 --- a/locales/messages.en.json +++ b/locales/messages.en.json @@ -25,6 +25,10 @@ "ChecksLineInfo": "{vcpkg_line_info}: ", "ChecksUnreachableCode": "unreachable code was reached", "ChecksUpdateVcpkg": "updating vcpkg by rerunning bootstrap-vcpkg may resolve this failure.", + "CiBaselineAllowUnexpectedPassingRequiresBaseline": "--allow-unexpected-passing can only be used if a baseline is provided via --ci-baseline.", + "CiBaselineRegression": "REGRESSION: {spec} failed with {build_result}. If expected, add {spec}=fail to {path}.", + "CiBaselineRegressionHeader": "REGRESSIONS:", + "CiBaselineUnexpectedPass": "PASSING, REMOVE FROM FAIL LIST: {spec} ({path}).", "CmakeTargetLinkLibraries": " target_link_libraries(main PRIVATE {list})", "CmakeTargetsExcluded": " # note: {count} targets were omitted.", "CouldNotDeduceNugetIdAndVersion": "Could not deduce nuget id and version from filename: {path}", @@ -45,10 +49,13 @@ "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.", + "ExpectedCharacterHere": "expected '{expected}' here", + "ExpectedFailOrSkip": "expected 'fail' or 'skip' here", + "ExpectedPortName": "expected a port name here", + "ExpectedTripletName": "expected a triplet name here", "FailedToProvisionCe": "Failed to provision vcpkg-ce.", "ForceSystemBinariesOnWeirdPlatforms": "Environment variable VCPKG_FORCE_SYSTEM_BINARIES must be set on arm, s390x, and ppc64le platforms.", "FormattedParseMessageExpression": " on expression: {value}", - "FormattedParseMessageLocation": "{path}:{row}:{column}: ", "GenerateMsgErrorParsingFormatArgs": "error: parsing format string for {value}:", "GenerateMsgIncorrectComment": "message {value} has an incorrect comment:", "GenerateMsgNoArgumentValue": " {{{value}}} was specified in a comment, but was not used in the message.", @@ -87,6 +94,7 @@ "ResultsLine": " {spec}: {build_result}: {elapsed}", "SeeURL": "See {url} for more information.", "SuggestNewVersionScheme": "Use the version scheme \"{new_scheme}\" instead of \"{old_scheme}\" in port \"{package_name}\".\nUse `--{option}` to disable this check.", + "UnknownBaselineFileContent": "unrecognizable baseline entry; expected 'port:triplet=(fail|skip)'", "UnsupportedSystemName": "Error: Could not map VCPKG_CMAKE_SYSTEM_NAME '{system_name}' to a vcvarsall platform. Supported system names are '', 'Windows' and 'WindowsStore'.", "UnsupportedToolchain": "Error: in triplet {triplet}: Unable to find a valid toolchain combination.\n The requested target architecture was {arch}\n The selected Visual Studio instance is at {path}\n The available toolchain combinations are {list}\n", "UsingManifestAt": "Using manifest file at {path}.", @@ -105,5 +113,6 @@ "VersionInvalidRelaxed": "`{version}` is not a valid relaxed version (semver with arbitrary numeric element count).", "VersionInvalidSemver": "`{version}` is not a valid semantic version, consult .", "VersionSpecMismatch": "error: Failed to load port because version specs did not match\n Path: {path}\n Expected: {expected_version}\n Actual: {actual_version}", - "WarningMessage": "warning: " + "WarningMessage": "warning: ", + "WarningsTreatedAsErrors": "previous warnings being interpreted as errors" } diff --git a/locales/messages.json b/locales/messages.json index d88522edbc..50b803ca4c 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -47,6 +47,13 @@ "_ChecksLineInfo.comment": "{Locked}", "ChecksUnreachableCode": "unreachable code was reached", "ChecksUpdateVcpkg": "updating vcpkg by rerunning bootstrap-vcpkg may resolve this failure.", + "CiBaselineAllowUnexpectedPassingRequiresBaseline": "--allow-unexpected-passing can only be used if a baseline is provided via --ci-baseline.", + "CiBaselineRegression": "REGRESSION: {spec} failed with {build_result}. If expected, add {spec}=fail to {path}.", + "_CiBaselineRegression.comment": "example of {spec} is 'zlib:x64-windows'.\nexample of {build_result} is 'One of the BuildResultXxx messages (such as BuildResultSucceeded/SUCCEEDED)'.\nexample of {path} is '/foo/bar'.\n", + "CiBaselineRegressionHeader": "REGRESSIONS:", + "_CiBaselineRegressionHeader.comment": "Printed before a series of CiBaselineRegression and/or CiBaselineUnexpectedPass messages.\n", + "CiBaselineUnexpectedPass": "PASSING, REMOVE FROM FAIL LIST: {spec} ({path}).", + "_CiBaselineUnexpectedPass.comment": "example of {spec} is 'zlib:x64-windows'.\nexample of {path} is '/foo/bar'.\n", "CmakeTargetLinkLibraries": " target_link_libraries(main PRIVATE {list})", "_CmakeTargetLinkLibraries.comment": "{Locked}", "CmakeTargetsExcluded": " # note: {count} targets were omitted.", @@ -81,12 +88,15 @@ "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", + "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", + "ExpectedPortName": "expected a port name here", + "ExpectedTripletName": "expected a triplet name here", "FailedToProvisionCe": "Failed to provision vcpkg-ce.", "ForceSystemBinariesOnWeirdPlatforms": "Environment variable VCPKG_FORCE_SYSTEM_BINARIES must be set on arm, s390x, and ppc64le platforms.", "FormattedParseMessageExpression": " on expression: {value}", "_FormattedParseMessageExpression.comment": "Example of {value} is 'x64 & windows'\n", - "FormattedParseMessageLocation": "{path}:{row}:{column}: ", - "_FormattedParseMessageLocation.comment": "{Locked}", "GenerateMsgErrorParsingFormatArgs": "error: parsing format string for {value}:", "_GenerateMsgErrorParsingFormatArgs.comment": "example of {value} 'GenerateMsgNoComment'\n", "GenerateMsgIncorrectComment": "message {value} has an incorrect comment:", @@ -148,6 +158,7 @@ "_SeeURL.comment": "example of {url} is 'https://github.com/microsoft/vcpkg'.\n", "SuggestNewVersionScheme": "Use the version scheme \"{new_scheme}\" instead of \"{old_scheme}\" in port \"{package_name}\".\nUse `--{option}` to disable this check.", "_SuggestNewVersionScheme.comment": "example of {new_scheme} is 'version'.\nexample of {old_scheme} is 'version-string'.\nexample of {package_name} is 'zlib'.\nexample of {option} is 'editable'.\n", + "UnknownBaselineFileContent": "unrecognizable baseline entry; expected 'port:triplet=(fail|skip)'", "UnsupportedSystemName": "Error: Could not map VCPKG_CMAKE_SYSTEM_NAME '{system_name}' to a vcvarsall platform. Supported system names are '', 'Windows' and 'WindowsStore'.", "_UnsupportedSystemName.comment": "example of {system_name} is 'Darwin'.\n", "UnsupportedToolchain": "Error: in triplet {triplet}: Unable to find a valid toolchain combination.\n The requested target architecture was {arch}\n The selected Visual Studio instance is at {path}\n The available toolchain combinations are {list}\n", @@ -177,5 +188,6 @@ "_VersionInvalidSemver.comment": "example of {version} is '1.3.8'.\n", "VersionSpecMismatch": "error: Failed to load port because version specs did not match\n Path: {path}\n Expected: {expected_version}\n Actual: {actual_version}", "_VersionSpecMismatch.comment": "example of {path} is '/foo/bar'.\nexample of {expected_version} is '1.3.8'.\nexample of {actual_version} is '1.3.8'.\n", - "WarningMessage": "warning: " + "WarningMessage": "warning: ", + "WarningsTreatedAsErrors": "previous warnings being interpreted as errors" } diff --git a/src/vcpkg-test/ci-baseline.cpp b/src/vcpkg-test/ci-baseline.cpp new file mode 100644 index 0000000000..736a7ebd44 --- /dev/null +++ b/src/vcpkg-test/ci-baseline.cpp @@ -0,0 +1,311 @@ +#include + +#include +#include + +#include +#include + +#include +#include + +using namespace vcpkg; + +namespace vcpkg +{ + static std::ostream& operator<<(std::ostream& os, const CiBaselineLine& line) + { + os << line.port_name << ':' << line.triplet.canonical_name() << '='; + switch (line.state) + { + case CiBaselineState::Fail: os << "fail"; break; + case CiBaselineState::Skip: os << "skip"; break; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + + return os; + } +} + +TEST_CASE ("Parse Empty", "[ci-baseline]") +{ + ParseMessages m; + auto actual = parse_ci_baseline("", "test", m); + CHECK(m.good()); + CHECK(actual.empty()); +} + +namespace +{ + static constexpr StringLiteral example_input = + R"(########################################################################### +## This file defines the current expected build state of ports in CI. +## +## States +## pass - (default) the port builds in the CI system. If a port is +## missing from this file then it is assumed to build. +## fail - the port does not build in the CI system. +## This is not necessarily the same as if a port is expected to build +## on a developers machine because it may fail due to the machine +## configuration. When set to fail the CI system will still attempt +## to build the port and will report a CI failure until this file is updated. +## skip - Do not build this port in the CI system. +## This is added to ports that may be flaky or conflict with other +## ports. Please comment for why a port is skipped so it can be +## removed when the issue is resolved. +## +## +## CI tested triplets: +## arm64-windows +## arm-uwp +## x64-linux +## x64-osx +## x64-uwp +## x64-windows +## x64-windows-static +## x64-windows-static-md +## x86-windows +## + +# Add new items alphabetically + +# script ports +#vcpkg-cmake:arm64-windows=fail +#vcpkg-cmake:arm-uwp=fail +#vcpkg-cmake:x64-uwp=fail +#vcpkg-cmake:x64-windows-static=fail +#vcpkg-cmake:x64-windows-static-md=fail +#vcpkg-cmake:x86-windows=fail + +#vcpkg-cmake-config:arm64-windows=fail +#vcpkg-cmake-config:arm-uwp=fail +#vcpkg-cmake-config:x64-uwp=fail +#vcpkg-cmake-config:x64-windows-static=fail +#vcpkg-cmake-config:x64-windows-static-md=fail +#vcpkg-cmake-config:x86-windows=fail + +# other ports +# Cross compiling CI machine cannot run gen_test_char to generate apr_escape_test_char.h +apr:arm64-windows=fail +# Requires ATL for ARM64 to be installed in CI +azure-storage-cpp:arm64-windows=fail + +aubio:arm-uwp=fail +aubio:x64-uwp=fail +# broken when `python` is python3, https://github.com/microsoft/vcpkg/issues/18937 +bde:x64-linux=fail +bitserializer:x64-osx=fail +blitz:x64-uwp=fail +blitz:arm64-windows=fail +blitz:arm-uwp=fail +blosc:arm64-windows=fail +blosc:arm-uwp=fail +blosc:x64-uwp=fail +bond:arm-uwp=fail +bond:x64-osx=fail +bond:x64-uwp=fail +botan:x64-uwp=fail +breakpad:arm64-windows=fail +buck-yeh-bux:x64-linux=fail +buck-yeh-bux-mariadb-client:x64-linux=fail +caf:arm-uwp=fail +caf:x64-uwp=fail +caffe2:x86-windows=fail +caffe2:arm64-windows=fail +c-ares:arm-uwp=fail +c-ares:x64-uwp=fail +casclib:arm-uwp=fail +casclib:x64-uwp=fail +catch-classic:arm64-windows = skip +catch-classic:arm-uwp = skip +catch-classic:x64-linux = skip +catch-classic:x64-osx = skip +catch-classic:x64-uwp = skip +catch-classic:x64-windows = skip +catch-classic:x64-windows-static = skip +catch-classic:x64-windows-static-md=skip +catch-classic:x86-windows = skip +bill-made-up-another-skip:x64-linux=skip)"; // note no trailing newline + + const auto x86_windows = Triplet::from_canonical_name("x86-windows"); + const auto x64_windows = Triplet::from_canonical_name("x64-windows"); + const auto x64_windows_static = Triplet::from_canonical_name("x64-windows-static"); + const auto x64_windows_static_md = Triplet::from_canonical_name("x64-windows-static-md"); + const auto x64_uwp = Triplet::from_canonical_name("x64-uwp"); + const auto arm64_windows = Triplet::from_canonical_name("arm64-windows"); + const auto arm_uwp = Triplet::from_canonical_name("arm-uwp"); + const auto x64_osx = Triplet::from_canonical_name("x64-osx"); + const auto x64_linux = Triplet::from_canonical_name("x64-linux"); + + std::vector expected_from_example_input{ + CiBaselineLine{"apr", arm64_windows, CiBaselineState::Fail}, + CiBaselineLine{"azure-storage-cpp", arm64_windows, CiBaselineState::Fail}, + CiBaselineLine{"aubio", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"aubio", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"bde", x64_linux, CiBaselineState::Fail}, + CiBaselineLine{"bitserializer", x64_osx, CiBaselineState::Fail}, + CiBaselineLine{"blitz", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"blitz", arm64_windows, CiBaselineState::Fail}, + CiBaselineLine{"blitz", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"blosc", arm64_windows, CiBaselineState::Fail}, + CiBaselineLine{"blosc", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"blosc", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"bond", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"bond", x64_osx, CiBaselineState::Fail}, + CiBaselineLine{"bond", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"botan", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"breakpad", arm64_windows, CiBaselineState::Fail}, + CiBaselineLine{"buck-yeh-bux", x64_linux, CiBaselineState::Fail}, + CiBaselineLine{"buck-yeh-bux-mariadb-client", x64_linux, CiBaselineState::Fail}, + CiBaselineLine{"caf", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"caf", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"caffe2", x86_windows, CiBaselineState::Fail}, + CiBaselineLine{"caffe2", arm64_windows, CiBaselineState::Fail}, + CiBaselineLine{"c-ares", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"c-ares", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"casclib", arm_uwp, CiBaselineState::Fail}, + CiBaselineLine{"casclib", x64_uwp, CiBaselineState::Fail}, + CiBaselineLine{"catch-classic", arm64_windows, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", arm_uwp, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x64_linux, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x64_osx, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x64_uwp, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x64_windows, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x64_windows_static, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x64_windows_static_md, CiBaselineState::Skip}, + CiBaselineLine{"catch-classic", x86_windows, CiBaselineState::Skip}, + CiBaselineLine{"bill-made-up-another-skip", x64_linux, CiBaselineState::Skip}}; +} // unnamed namespace + +TEST_CASE ("Parse Real Prefix", "[ci-baseline]") +{ + ParseMessages m; + auto actual = parse_ci_baseline(example_input, "test", m); + CHECK(m.good()); + CHECK(expected_from_example_input == actual); +} + +TEST_CASE ("Parse Real Prefix With Trailing Newline", "[ci-baseline]") +{ + ParseMessages m; + std::string newlined_input(example_input.data(), example_input.size()); + newlined_input.push_back('\n'); + auto actual = parse_ci_baseline(newlined_input, "test", m); + CHECK(m.good()); + CHECK(expected_from_example_input == actual); +} + +static void check_error(const std::string& input, const std::string& expected_error) +{ + ParseMessages m; + auto actual = parse_ci_baseline(input, "test", m); + CHECK(actual.empty()); + CHECK(m.warnings.empty()); + CHECK(m.error->format() == expected_error); +} + +TEST_CASE ("Parse Errors", "[ci-baseline]") +{ + check_error("hello", R"(test:1:6: error: expected ':' here + on expression: hello + ^ +)"); + + check_error("hello\n:", R"(test:1:6: error: expected ':' here + on expression: hello + ^ +)"); + + check_error("?example:x64-windows=fail", R"(test:1:1: error: expected a port name here + on expression: ?example:x64-windows=fail + ^ +)"); + + check_error("x64-windows:", R"(test:1:13: error: expected a triplet name here + on expression: x64-windows: + ^ +)"); + + check_error("x64-windows:\nport:x64-windows=skip", R"(test:1:13: error: expected a triplet name here + on expression: x64-windows: + ^ +)"); + + check_error("x64-windows:#", R"(test:1:13: error: expected a triplet name here + on expression: x64-windows:# + ^ +)"); + + // clang-format off + check_error(" \tx64-windows:", R"(test:1:21: error: expected a triplet name here + on expression: )" "\t" R"(x64-windows: + )" "\t" R"( ^ +)"); + // clang-format on + + check_error("port:x64-windows\n=fail", R"(test:1:17: error: expected '=' here + on expression: port:x64-windows + ^ +)"); + + check_error("example:x64-windows = \n fail", R"(test:1:26: error: expected 'fail' or 'skip' here + on expression: example:x64-windows = + ^ +)"); + + check_error("example:x64-windows = pass", R"(test:1:28: error: expected 'fail' or 'skip' here + on expression: example:x64-windows = pass + ^ +)"); + + // note that there is 'fail' but doesn't end on a word boundary: + check_error("example:x64-windows = fails", R"(test:1:28: error: expected 'fail' or 'skip' here + on expression: example:x64-windows = fails + ^ +)"); + + check_error("example:x64-windows = fail extra stuff", + R"(test:1:33: error: unrecognizable baseline entry; expected 'port:triplet=(fail|skip)' + on expression: example:x64-windows = fail extra stuff + ^ +)"); + + check_error("example:x64-windows = fail example:x64-windows = fail", + R"(test:1:33: error: unrecognizable baseline entry; expected 'port:triplet=(fail|skip)' + on expression: example:x64-windows = fail example:x64-windows = fail + ^ +)"); + + check_error("example:x64-windows = fail # extra stuff\n" + "example:x64-uwp=skip extra stuff\n", + R"(test:2:22: error: unrecognizable baseline entry; expected 'port:triplet=(fail|skip)' + on expression: example:x64-uwp=skip extra stuff + ^ +)"); +} + +TEST_CASE ("Applies Skips and Fails", "[ci-baseline]") +{ + ExclusionsMap exclusions; + exclusions.insert(x64_uwp); // example triplet + exclusions.insert(x64_linux); // example host triplet + auto actual_expected_failures = parse_and_apply_ci_baseline(expected_from_example_input, exclusions); + const SortedVector expected_expected_failures{ + PackageSpec{"aubio", x64_uwp}, + PackageSpec{"bde", x64_linux}, + PackageSpec{"blitz", x64_uwp}, + PackageSpec{"blosc", x64_uwp}, + PackageSpec{"bond", x64_uwp}, + PackageSpec{"botan", x64_uwp}, + PackageSpec{"buck-yeh-bux", x64_linux}, + PackageSpec{"buck-yeh-bux-mariadb-client", x64_linux}, + PackageSpec{"c-ares", x64_uwp}, + PackageSpec{"caf", x64_uwp}, + PackageSpec{"casclib", x64_uwp}, + }; + + CHECK(actual_expected_failures == expected_expected_failures); + CHECK(exclusions.triplets.size() == 2); + CHECK(exclusions.triplets[0].exclusions == SortedVector{"catch-classic"}); + CHECK(exclusions.triplets[1].exclusions == SortedVector{"catch-classic", "bill-made-up-another-skip"}); +} diff --git a/src/vcpkg-test/update.cpp b/src/vcpkg-test/update.cpp index 5db1125994..c3561b6bb5 100644 --- a/src/vcpkg-test/update.cpp +++ b/src/vcpkg-test/update.cpp @@ -27,8 +27,8 @@ TEST_CASE ("find outdated packages basic", "[update]") map.emplace("a", SourceControlFileAndLocation{std::move(scf), ""}); PortFileProvider::MapPortFileProvider provider(map); - auto pkgs = SortedVector(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); + auto pkgs = SortedVector( + Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); REQUIRE(pkgs.size() == 1); REQUIRE(pkgs[0].version_diff.left.to_string() == "2"); @@ -51,8 +51,8 @@ TEST_CASE ("find outdated packages features", "[update]") map.emplace("a", SourceControlFileAndLocation{std::move(scf), ""}); PortFileProvider::MapPortFileProvider provider(map); - auto pkgs = SortedVector(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); + auto pkgs = SortedVector( + Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); REQUIRE(pkgs.size() == 1); REQUIRE(pkgs[0].version_diff.left.to_string() == "2"); @@ -77,8 +77,8 @@ TEST_CASE ("find outdated packages features 2", "[update]") map.emplace("a", SourceControlFileAndLocation{std::move(scf), ""}); PortFileProvider::MapPortFileProvider provider(map); - auto pkgs = SortedVector(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); + auto pkgs = SortedVector( + Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); REQUIRE(pkgs.size() == 1); REQUIRE(pkgs[0].version_diff.left.to_string() == "2"); @@ -98,8 +98,8 @@ TEST_CASE ("find outdated packages none", "[update]") map.emplace("a", SourceControlFileAndLocation{std::move(scf), ""}); PortFileProvider::MapPortFileProvider provider(map); - auto pkgs = SortedVector(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); + auto pkgs = SortedVector( + Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); REQUIRE(pkgs.size() == 0); } diff --git a/src/vcpkg/base/downloads.cpp b/src/vcpkg/base/downloads.cpp index 0148654744..97ad9e84e7 100644 --- a/src/vcpkg/base/downloads.cpp +++ b/src/vcpkg/base/downloads.cpp @@ -311,11 +311,10 @@ namespace vcpkg if (out->size() != start_size + urls.size()) { - Checks::msg_exit_with_message(VCPKG_LINE_INFO, - msg::format(msg::msgErrorMessage) - .append(msg::format(msgCurlReportedUnexpectedResults, - msg::command_line = cmd.command_line(), - msg::actual = Strings::join("\n", lines)))); + Checks::msg_exit_with_error(VCPKG_LINE_INFO, + msgCurlReportedUnexpectedResults, + msg::command_line = cmd.command_line(), + msg::actual = Strings::join("\n", lines)); } } std::vector url_heads(View urls, View headers) diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp index 4701749d20..6c41f07bff 100644 --- a/src/vcpkg/base/json.cpp +++ b/src/vcpkg/base/json.cpp @@ -994,11 +994,11 @@ namespace vcpkg::Json if (!parser.at_eof()) { parser.add_error("Unexpected character; expected EOF"); - return std::move(parser).extract_error(); + return parser.extract_error(); } else if (parser.get_error()) { - return std::move(parser).extract_error(); + return parser.extract_error(); } else { diff --git a/src/vcpkg/base/parse.cpp b/src/vcpkg/base/parse.cpp index 6b97cb6602..17bb5e5b2e 100644 --- a/src/vcpkg/base/parse.cpp +++ b/src/vcpkg/base/parse.cpp @@ -8,6 +8,22 @@ using namespace vcpkg; +namespace +{ + DECLARE_AND_REGISTER_MESSAGE(WarningsTreatedAsErrors, (), "", "previous warnings being interpreted as errors"); + + DECLARE_AND_REGISTER_MESSAGE(FormattedParseMessageExpression, + (msg::value), + "Example of {value} is 'x64 & windows'", + " on expression: {value}"); + + DECLARE_AND_REGISTER_MESSAGE( + ExpectedCharacterHere, + (msg::expected), + "{expected} is a locale-invariant delimiter; for example, the ':' or '=' in 'zlib:x64-windows=skip'", + "expected '{expected}' here"); +} + namespace vcpkg { static void advance_rowcol(char32_t ch, int& row, int& column) @@ -37,21 +53,10 @@ namespace vcpkg return res; } - DECLARE_AND_REGISTER_MESSAGE(FormattedParseMessageLocation, - (msg::path, msg::row, msg::column), - "{Locked}", - "{path}:{row}:{column}: "); - DECLARE_AND_REGISTER_MESSAGE(FormattedParseMessageExpression, - (msg::value), - "Example of {value} is 'x64 & windows'", - " on expression: {value}"); - LocalizedString ParseMessage::format(StringView origin, MessageKind kind) const { - LocalizedString res = msg::format(msgFormattedParseMessageLocation, - msg::path = origin, - msg::row = location.row, - msg::column = location.column); + LocalizedString res = + LocalizedString::from_raw(fmt::format("{}:{}:{}: ", origin, location.row, location.column)); if (kind == MessageKind::Warning) { res.append(msg::format(msg::msgWarningMessage)); @@ -95,6 +100,24 @@ namespace vcpkg const std::string& ParseError::get_message() const { return this->message; } + void ParseMessages::exit_if_errors_or_warnings(StringView origin) const + { + for (const auto& warning : warnings) + { + msg::println(warning.format(origin, MessageKind::Warning)); + } + + if (error) + { + Checks::msg_exit_with_message(VCPKG_LINE_INFO, LocalizedString::from_raw(error->format())); + } + + if (!warnings.empty()) + { + Checks::msg_exit_with_error(VCPKG_LINE_INFO, msgWarningsTreatedAsErrors); + } + } + ParserBase::ParserBase(StringView text, StringView origin, TextRowCol init_rowcol) : m_it(text.begin(), text.end()) , m_start_of_line(m_it) @@ -105,6 +128,62 @@ namespace vcpkg { } + StringView ParserBase::skip_whitespace() { return match_while(is_whitespace); } + StringView ParserBase::skip_tabs_spaces() + { + return match_while([](char32_t ch) { return ch == ' ' || ch == '\t'; }); + } + + void ParserBase::skip_to_eof() { m_it = m_it.end(); } + void ParserBase::skip_newline() + { + if (cur() == '\r') next(); + if (cur() == '\n') next(); + } + void ParserBase::skip_line() + { + match_until(is_lineend); + skip_newline(); + } + + bool ParserBase::require_character(char ch) + { + if (static_cast(ch) == cur()) + { + next(); + return false; + } + + add_error(msg::format(msgExpectedCharacterHere, msg::expected = ch)); + return true; + } + + bool ParserBase::try_match_keyword(StringView keyword_content) + { + auto encoded = m_it; + // check that the encoded stream matches the keyword: + for (const char ch : keyword_content) + { + if (encoded.is_eof() || *encoded != static_cast(ch)) + { + return false; + } + + ++encoded; + } + + // whole keyword matched, now check for a word boundary: + if (!encoded.is_eof() && !is_whitespace(*encoded)) + { + return false; + } + + // success + m_it = encoded; + m_column += static_cast(keyword_content.size()); + return true; + } + char32_t ParserBase::next() { if (m_it == m_it.end()) diff --git a/src/vcpkg/ci-baseline.cpp b/src/vcpkg/ci-baseline.cpp new file mode 100644 index 0000000000..20ff40f45f --- /dev/null +++ b/src/vcpkg/ci-baseline.cpp @@ -0,0 +1,194 @@ +#include +#include + +#include + +#include + +using namespace vcpkg; + +namespace +{ + DECLARE_AND_REGISTER_MESSAGE(ExpectedPortName, (), "", "expected a port name here"); + DECLARE_AND_REGISTER_MESSAGE(ExpectedTripletName, (), "", "expected a triplet name here"); + DECLARE_AND_REGISTER_MESSAGE(ExpectedFailOrSkip, (), "", "expected 'fail' or 'skip' here"); + DECLARE_AND_REGISTER_MESSAGE(UnknownBaselineFileContent, + (), + "", + "unrecognizable baseline entry; expected 'port:triplet=(fail|skip)'"); +} + +namespace vcpkg +{ + TripletExclusions::TripletExclusions(const Triplet& triplet) : triplet(triplet), exclusions() { } + + TripletExclusions::TripletExclusions(const Triplet& triplet, SortedVector&& exclusions) + : triplet(triplet), exclusions(std::move(exclusions)) + { + } + + void ExclusionsMap::insert(Triplet triplet) + { + for (auto& triplet_exclusions : triplets) + { + if (triplet_exclusions.triplet == triplet) + { + return; + } + } + + triplets.emplace_back(triplet); + } + + void ExclusionsMap::insert(Triplet triplet, SortedVector&& exclusions) + { + for (auto& triplet_exclusions : triplets) + { + if (triplet_exclusions.triplet == triplet) + { + triplet_exclusions.exclusions.append(std::move(exclusions)); + return; + } + } + + triplets.emplace_back(triplet, std::move(exclusions)); + } + + bool ExclusionPredicate::operator()(const PackageSpec& spec) const + { + for (const auto& triplet_exclusions : data->triplets) + { + if (triplet_exclusions.triplet == spec.triplet()) + { + return triplet_exclusions.exclusions.contains(spec.name()); + } + } + + return false; + } + + std::vector parse_ci_baseline(StringView text, StringView origin, ParseMessages& messages) + { + std::vector result; + ParserBase parser(text, origin); + for (;;) + { + parser.skip_whitespace(); + if (parser.at_eof()) + { + // success + return result; + } + + if (parser.cur() == '#') + { + parser.skip_line(); + continue; + } + + // port-name:triplet = (fail|skip)\b + auto port = parser.match_while(ParserBase::is_package_name_char); + if (port.empty()) + { + parser.add_error(msg::format(msgExpectedPortName)); + break; + } + + if (parser.require_character(':')) + { + break; + } + + auto triplet = parser.match_while(ParserBase::is_package_name_char); + if (triplet.empty()) + { + parser.add_error(msg::format(msgExpectedTripletName)); + break; + } + + parser.skip_tabs_spaces(); + if (parser.require_character('=')) + { + break; + } + + parser.skip_tabs_spaces(); + + static constexpr StringLiteral FAIL = "fail"; + static constexpr StringLiteral SKIP = "skip"; + CiBaselineState state; + if (parser.try_match_keyword(FAIL)) + { + state = CiBaselineState::Fail; + } + else if (parser.try_match_keyword(SKIP)) + { + state = CiBaselineState::Skip; + } + else + { + parser.add_error(msg::format(msgExpectedFailOrSkip)); + break; + } + + parser.skip_tabs_spaces(); + auto trailing = parser.cur(); + if (trailing == '#') + { + parser.skip_line(); + } + else if (trailing == '\r' || trailing == '\n') + { + parser.skip_newline(); + } + else if (trailing != Unicode::end_of_file) + { + parser.add_error(msg::format(msgUnknownBaselineFileContent)); + break; + } + + result.emplace_back( + CiBaselineLine{port.to_string(), Triplet::from_canonical_name(triplet.to_string()), state}); + } + + // failure + messages = std::move(parser).extract_messages(); + result.clear(); + return result; + } + + SortedVector parse_and_apply_ci_baseline(View lines, ExclusionsMap& exclusions_map) + { + std::vector expected_failures; + std::map> added_exclusions; + for (const auto& triplet_entry : exclusions_map.triplets) + { + added_exclusions.emplace( + std::piecewise_construct, std::forward_as_tuple(triplet_entry.triplet), std::tuple<>{}); + } + + for (auto& line : lines) + { + auto triplet_match = added_exclusions.find(line.triplet); + if (triplet_match != added_exclusions.end()) + { + if (line.state == CiBaselineState::Skip) + { + triplet_match->second.push_back(line.port_name); + } + else if (line.state == CiBaselineState::Fail) + { + expected_failures.emplace_back(line.port_name, line.triplet); + } + } + } + + for (auto& triplet_entry : exclusions_map.triplets) + { + triplet_entry.exclusions.append( + SortedVector(std::move(added_exclusions.find(triplet_entry.triplet)->second))); + } + + return SortedVector{std::move(expected_failures)}; + } +} diff --git a/src/vcpkg/commands.ci.cpp b/src/vcpkg/commands.ci.cpp index 86f61a278b..1b2b27db23 100644 --- a/src/vcpkg/commands.ci.cpp +++ b/src/vcpkg/commands.ci.cpp @@ -2,14 +2,17 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -25,6 +28,8 @@ #include #include +#include + using namespace vcpkg; namespace @@ -74,6 +79,29 @@ namespace private: Path base_path; }; + + DECLARE_AND_REGISTER_MESSAGE( + CiBaselineRegressionHeader, + (), + "Printed before a series of CiBaselineRegression and/or CiBaselineUnexpectedPass messages.", + "REGRESSIONS:"); + + DECLARE_AND_REGISTER_MESSAGE( + CiBaselineRegression, + (msg::spec, msg::build_result, msg::path), + "", + "REGRESSION: {spec} failed with {build_result}. If expected, add {spec}=fail to {path}."); + + DECLARE_AND_REGISTER_MESSAGE(CiBaselineUnexpectedPass, + (msg::spec, msg::path), + "", + "PASSING, REMOVE FROM FAIL LIST: {spec} ({path})."); + + DECLARE_AND_REGISTER_MESSAGE( + CiBaselineAllowUnexpectedPassingRequiresBaseline, + (), + "", + "--allow-unexpected-passing can only be used if a baseline is provided via --ci-baseline."); } namespace vcpkg::Commands::CI @@ -93,15 +121,18 @@ namespace vcpkg::Commands::CI static constexpr StringLiteral OPTION_HOST_EXCLUDE = "host-exclude"; static constexpr StringLiteral OPTION_FAILURE_LOGS = "failure-logs"; static constexpr StringLiteral OPTION_XUNIT = "x-xunit"; + static constexpr StringLiteral OPTION_CI_BASELINE = "ci-baseline"; + static constexpr StringLiteral OPTION_ALLOW_UNEXPECTED_PASSING = "allow-unexpected-passing"; static constexpr StringLiteral OPTION_RANDOMIZE = "x-randomize"; static constexpr StringLiteral OPTION_OUTPUT_HASHES = "output-hashes"; static constexpr StringLiteral OPTION_PARENT_HASHES = "parent-hashes"; static constexpr StringLiteral OPTION_SKIPPED_CASCADE_COUNT = "x-skipped-cascade-count"; - static constexpr std::array CI_SETTINGS = { + static constexpr std::array CI_SETTINGS = { {{OPTION_EXCLUDE, "Comma separated list of ports to skip"}, {OPTION_HOST_EXCLUDE, "Comma separated list of ports to skip for the host triplet"}, {OPTION_XUNIT, "File to output results in XUnit format (internal)"}, + {OPTION_CI_BASELINE, "Path to the ci.baseline.txt file. Used to skip ports and detect regressions."}, {OPTION_FAILURE_LOGS, "Directory to which failure logs will be copied"}, {OPTION_OUTPUT_HASHES, "File to output all determined package hashes"}, {OPTION_PARENT_HASHES, @@ -109,9 +140,11 @@ namespace vcpkg::Commands::CI {OPTION_SKIPPED_CASCADE_COUNT, "Asserts that the number of --exclude and supports skips exactly equal this number"}}}; - static constexpr std::array CI_SWITCHES = {{ + static constexpr std::array CI_SWITCHES = {{ {OPTION_DRY_RUN, "Print out plan without execution"}, {OPTION_RANDOMIZE, "Randomize the install order"}, + {OPTION_ALLOW_UNEXPECTED_PASSING, + "Indicates that 'Passing, remove from fail list' results should not be emitted."}, }}; const CommandStructure COMMAND_STRUCTURE = { @@ -302,28 +335,6 @@ namespace vcpkg::Commands::CI return supports_expression.evaluate(context); } - struct ExclusionPredicate - { - std::set exclusions; - std::set host_exclusions; - Triplet target_triplet; - Triplet host_triplet; - - bool operator()(const PackageSpec& spec) const - { - bool excluded = false; - if (spec.triplet() == host_triplet) - { - excluded = excluded || Util::Sets::contains(host_exclusions, spec.name()); - } - if (spec.triplet() == target_triplet) - { - excluded = excluded || Util::Sets::contains(exclusions, spec.name()); - } - return excluded; - } - }; - static Dependencies::ActionPlan compute_full_plan(const VcpkgPaths& paths, const PortFileProvider::PortFileProvider& provider, const CMakeVars::CMakeVarProvider& var_provider, @@ -354,7 +365,7 @@ namespace vcpkg::Commands::CI } static std::unique_ptr compute_action_statuses( - const ExclusionPredicate& is_excluded, + ExclusionPredicate is_excluded, const CMakeVars::CMakeVarProvider& var_provider, const std::vector& precheck_results, const Dependencies::ActionPlan& action_plan) @@ -443,19 +454,16 @@ namespace vcpkg::Commands::CI }); } - static std::set parse_exclusions(const std::unordered_map& settings, - StringLiteral opt) + static void parse_exclusions(const std::unordered_map& settings, + StringLiteral opt, + Triplet triplet, + ExclusionsMap& exclusions_map) { - std::set exclusions_set; auto it_exclusions = settings.find(opt); - if (it_exclusions != settings.end()) - { - auto exclusions = Strings::split(it_exclusions->second, ','); - exclusions_set.insert(std::make_move_iterator(exclusions.begin()), - std::make_move_iterator(exclusions.end())); - } - - return exclusions_set; + exclusions_map.insert(triplet, + it_exclusions == settings.end() + ? SortedVector{} + : SortedVector(Strings::split(it_exclusions->second, ','))); } static Optional parse_skipped_cascade_count(const std::unordered_map& settings) @@ -475,6 +483,47 @@ namespace vcpkg::Commands::CI return result; } + static void print_baseline_regressions(const TripletAndSummary& result, + const SortedVector& expected_failures, + const std::string& ci_baseline_file_name, + bool allow_unexpected_passing) + { + LocalizedString output = msg::format(msgCiBaselineRegressionHeader); + output.appendnl(); + for (auto&& port_result : result.summary.results) + { + switch (port_result.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)) + { + 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.appendnl(); + } + break; + case Build::BuildResult::SUCCEEDED: + if (!allow_unexpected_passing && expected_failures.contains(port_result.spec)) + { + output.append(msg::format(msgCiBaselineUnexpectedPass, + msg::spec = port_result.spec.to_string(), + msg::path = ci_baseline_file_name)); + output.appendnl(); + } + break; + default: break; + } + } + + fputs(output.data().c_str(), stderr); + } + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet target_triplet, @@ -487,12 +536,31 @@ namespace vcpkg::Commands::CI const auto& settings = options.settings; BinaryCache binary_cache{args, paths}; - ExclusionPredicate is_excluded{ - parse_exclusions(settings, OPTION_EXCLUDE), - parse_exclusions(settings, OPTION_HOST_EXCLUDE), - target_triplet, - host_triplet, - }; + + ExclusionsMap exclusions_map; + parse_exclusions(settings, OPTION_EXCLUDE, target_triplet, exclusions_map); + parse_exclusions(settings, OPTION_HOST_EXCLUDE, host_triplet, exclusions_map); + auto baseline_iter = settings.find(OPTION_CI_BASELINE); + const bool allow_unexpected_passing = Util::Sets::contains(options.switches, OPTION_ALLOW_UNEXPECTED_PASSING); + SortedVector expected_failures; + if (baseline_iter == settings.end()) + { + if (allow_unexpected_passing) + { + Checks::msg_exit_with_error(VCPKG_LINE_INFO, msgCiBaselineAllowUnexpectedPassingRequiresBaseline); + } + } + else + { + const auto& ci_baseline_file_name = baseline_iter->second; + const auto ci_baseline_file_contents = + paths.get_filesystem().read_contents(ci_baseline_file_name, VCPKG_LINE_INFO); + ParseMessages ci_parse_messages; + const auto lines = parse_ci_baseline(ci_baseline_file_contents, ci_baseline_file_name, ci_parse_messages); + ci_parse_messages.exit_if_errors_or_warnings(ci_baseline_file_name); + expected_failures = parse_and_apply_ci_baseline(lines, exclusions_map); + } + auto skipped_cascade_count = parse_skipped_cascade_count(settings); const auto is_dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); @@ -524,8 +592,6 @@ namespace vcpkg::Commands::CI std::vector results; auto timer = ElapsedTimer::create_started(); - Input::check_triplet(target_triplet, paths); - xunitTestResults.push_collection(target_triplet.canonical_name()); std::vector all_port_names = @@ -561,7 +627,8 @@ namespace vcpkg::Commands::CI auto action_plan = compute_full_plan(paths, provider, var_provider, all_default_full_specs, serialize_options); const auto precheck_results = binary_cache.precheck(action_plan.install_actions); - auto split_specs = compute_action_statuses(is_excluded, var_provider, precheck_results, action_plan); + auto split_specs = + compute_action_statuses(ExclusionPredicate{&exclusions_map}, var_provider, precheck_results, action_plan); { std::string msg; @@ -680,6 +747,11 @@ namespace vcpkg::Commands::CI print2("\nTriplet: ", result.triplet, "\n"); print2("Total elapsed time: ", GlobalState::timer.to_string(), "\n"); result.summary.print(); + + if (baseline_iter != settings.end()) + { + print_baseline_regressions(result, expected_failures, baseline_iter->second, allow_unexpected_passing); + } } auto it_xunit = settings.find(OPTION_XUNIT); diff --git a/src/vcpkg/packagespec.cpp b/src/vcpkg/packagespec.cpp index 09ccd2701f..12fcea5cf6 100644 --- a/src/vcpkg/packagespec.cpp +++ b/src/vcpkg/packagespec.cpp @@ -110,7 +110,7 @@ namespace vcpkg Optional parse_feature_name(ParserBase& parser) { - auto ret = parser.match_zero_or_more(ParserBase::is_package_name_char).to_string(); + auto ret = parser.match_while(ParserBase::is_package_name_char).to_string(); auto ch = parser.cur(); // ignores the feature name vwebp_sdl as a back-compat thing @@ -136,7 +136,7 @@ namespace vcpkg } Optional parse_package_name(ParserBase& parser) { - auto ret = parser.match_zero_or_more(ParserBase::is_package_name_char).to_string(); + auto ret = parser.match_while(ParserBase::is_package_name_char).to_string(); auto ch = parser.cur(); if (ParserBase::is_upper_alpha(ch) || ch == '_') { @@ -205,7 +205,7 @@ namespace vcpkg if (ch == ':') { parser.next(); - ret.triplet = parser.match_zero_or_more(ParserBase::is_package_name_char).to_string(); + ret.triplet = parser.match_while(ParserBase::is_package_name_char).to_string(); if (ret.triplet.get()->empty()) { parser.add_error("expected triplet name (must be lowercase, digits, '-')"); diff --git a/src/vcpkg/paragraphs.cpp b/src/vcpkg/paragraphs.cpp index c8af8958f8..8378de5eb8 100644 --- a/src/vcpkg/paragraphs.cpp +++ b/src/vcpkg/paragraphs.cpp @@ -163,7 +163,7 @@ namespace vcpkg::Paragraphs void get_fieldname(std::string& fieldname) { - fieldname = match_zero_or_more(is_alphanumdash).to_string(); + fieldname = match_while(is_alphanumdash).to_string(); if (fieldname.empty()) return add_error("expected fieldname"); } @@ -205,7 +205,7 @@ namespace vcpkg::Paragraphs { paragraphs.emplace_back(); get_paragraph(paragraphs.back()); - match_zero_or_more(is_lineend); + match_while(is_lineend); } if (get_error()) return get_error()->format(); diff --git a/src/vcpkg/platform-expression.cpp b/src/vcpkg/platform-expression.cpp index 69ae0e43f2..bbfbf8f117 100644 --- a/src/vcpkg/platform-expression.cpp +++ b/src/vcpkg/platform-expression.cpp @@ -222,7 +222,7 @@ namespace vcpkg::PlatformExpression // { "and", optional-whitespace, platform-expression-not } // { "or", platform-expression-binary-keyword-second-operand } } // "and" is a synonym of "&", "or" is reserved (but not yet supported) as a synonym of "|" - std::string name = match_zero_or_more(is_identifier_char).to_string(); + std::string name = match_while(is_identifier_char).to_string(); Checks::check_exit(VCPKG_LINE_INFO, !name.empty()); if (name == "and") @@ -282,7 +282,7 @@ namespace vcpkg::PlatformExpression std::unique_ptr expr_identifier() { // identifier-character, { identifier-character }, - std::string name = match_zero_or_more(is_identifier_char).to_string(); + std::string name = match_while(is_identifier_char).to_string(); if (name.empty()) { @@ -312,7 +312,7 @@ namespace vcpkg::PlatformExpression } else if (cur() == 'n') { - std::string name = match_zero_or_more(is_identifier_char).to_string(); + std::string name = match_while(is_identifier_char).to_string(); // "not" if (name == "not") diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index 2078c63ec3..3c253a8848 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -822,7 +822,7 @@ namespace vcpkg void eat_idstring(std::string& result, Expecting& expecting) { auto loc = cur_loc(); - auto token = match_zero_or_more(is_idstring_element); + auto token = match_while(is_idstring_element); if (Strings::starts_with(token, "DocumentRef-")) { diff --git a/src/vcpkg/update.cpp b/src/vcpkg/update.cpp index 0b2c179645..180abc7e9b 100644 --- a/src/vcpkg/update.cpp +++ b/src/vcpkg/update.cpp @@ -70,8 +70,8 @@ namespace vcpkg::Update PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports); - const auto outdated_packages = SortedVector(find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); + const auto outdated_packages = SortedVector( + find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); if (outdated_packages.empty()) { diff --git a/src/vcpkg/visualstudio.cpp b/src/vcpkg/visualstudio.cpp index fb6da70fd8..c9e8a70cc7 100644 --- a/src/vcpkg/visualstudio.cpp +++ b/src/vcpkg/visualstudio.cpp @@ -210,8 +210,8 @@ namespace vcpkg::VisualStudio std::vector& paths_examined = ret.paths_examined; std::vector& found_toolsets = ret.toolsets; - const SortedVector sorted{get_visual_studio_instances_internal(fs), - VisualStudioInstance::preferred_first_comparator}; + const SortedVector sorted{ + get_visual_studio_instances_internal(fs), VisualStudioInstance::preferred_first_comparator}; const bool v140_is_available = Util::find_if(sorted, [&](const VisualStudioInstance& vs_instance) { return vs_instance.major_version() == "14";