diff --git a/azure-pipelines/e2e_ports/overlays/classic-versions-a/portfile.cmake b/azure-pipelines/e2e_ports/overlays/classic-versions-a/portfile.cmake new file mode 100644 index 0000000000..065116c276 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/classic-versions-a/portfile.cmake @@ -0,0 +1 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e_ports/overlays/classic-versions-a/vcpkg.json b/azure-pipelines/e2e_ports/overlays/classic-versions-a/vcpkg.json new file mode 100644 index 0000000000..28bb53b4e0 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/classic-versions-a/vcpkg.json @@ -0,0 +1,4 @@ +{ + "name": "classic-versions-a", + "version": "1" +} \ No newline at end of file diff --git a/azure-pipelines/e2e_ports/overlays/classic-versions-b/portfile.cmake b/azure-pipelines/e2e_ports/overlays/classic-versions-b/portfile.cmake new file mode 100644 index 0000000000..71cc270565 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/classic-versions-b/portfile.cmake @@ -0,0 +1 @@ +message(FATAL_ERROR "FATAL_ERROR") diff --git a/azure-pipelines/e2e_ports/overlays/classic-versions-b/vcpkg.json b/azure-pipelines/e2e_ports/overlays/classic-versions-b/vcpkg.json new file mode 100644 index 0000000000..0e51ca4212 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/classic-versions-b/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "classic-versions-b", + "version": "1", + "dependencies": [ + { + "name": "classic-versions-a", + "version>=": "2" + } + ] +} \ No newline at end of file diff --git a/azure-pipelines/end-to-end-tests-dir/classic-versions.ps1 b/azure-pipelines/end-to-end-tests-dir/classic-versions.ps1 new file mode 100644 index 0000000000..c0db693ecf --- /dev/null +++ b/azure-pipelines/end-to-end-tests-dir/classic-versions.ps1 @@ -0,0 +1,10 @@ +. $PSScriptRoot/../end-to-end-tests-prelude.ps1 + +# Not a number +$out = Run-Vcpkg @commonArgs install classic-versions-b | Out-String +Throw-IfNotFailed +if ($out -notmatch ".*warning:.*dependency classic-versions-a.*at least version 2.*is currently 1.*") +{ + $out + throw "Expected to fail and print warning about mismatched versions" +} diff --git a/include/vcpkg/binaryparagraph.h b/include/vcpkg/binaryparagraph.h index 476bc5198c..030425ff74 100644 --- a/include/vcpkg/binaryparagraph.h +++ b/include/vcpkg/binaryparagraph.h @@ -32,6 +32,8 @@ namespace vcpkg bool is_feature() const { return !feature.empty(); } + Version get_version() const { return {version, port_version}; } + PackageSpec spec; std::string version; int port_version = 0; diff --git a/include/vcpkg/dependencies.h b/include/vcpkg/dependencies.h index 274b166430..aefdbbd68f 100644 --- a/include/vcpkg/dependencies.h +++ b/include/vcpkg/dependencies.h @@ -67,7 +67,8 @@ namespace vcpkg::Dependencies const SourceControlFileAndLocation& scfl, const RequestType& request_type, Triplet host_triplet, - std::map>&& dependencies); + std::map>&& dependencies, + std::vector&& build_failure_messages); std::string displayname() const; const std::string& public_abi() const; @@ -86,6 +87,7 @@ namespace vcpkg::Dependencies std::map> feature_dependencies; std::vector package_dependencies; + std::vector build_failure_messages; InternalFeatureSet feature_list; Triplet host_triplet; diff --git a/include/vcpkg/versions.h b/include/vcpkg/versions.h index f635277aee..a589ae64e6 100644 --- a/include/vcpkg/versions.h +++ b/include/vcpkg/versions.h @@ -133,6 +133,11 @@ namespace vcpkg VerComp compare(const DateVersion& a, const DateVersion& b); + // Try parsing with all version schemas and return 'unk' if none match + VerComp compare_any(const Version& a, const Version& b); + + VerComp compare_versions(VersionScheme sa, const Version& a, VersionScheme sb, const Version& b); + enum class VersionConstraintKind { None, @@ -158,3 +163,4 @@ namespace vcpkg } VCPKG_FORMAT_WITH_TO_STRING(vcpkg::VersionSpec); +VCPKG_FORMAT_WITH_TO_STRING(vcpkg::Version); diff --git a/locales/messages.en.json b/locales/messages.en.json index 86b4f91cee..6a83aaff31 100644 --- a/locales/messages.en.json +++ b/locales/messages.en.json @@ -121,6 +121,7 @@ "VcpkgInvalidCommand": "invalid command: {command_name}", "VcpkgSendMetricsButDisabled": "Warning: passed --sendmetrics, but metrics are disabled.", "VersionCommandHeader": "vcpkg package management program version {version}\n\nSee LICENSE.txt for license information.", + "VersionConstraintViolated": "dependency {spec} was expected to be at least version {expected_version}, but is currently {actual_version}.", "VersionInvalidDate": "`{version}` is not a valid date version. Dates must follow the format YYYY-MM-DD and disambiguators must be dot-separated positive integer values without leading zeroes.", "VersionInvalidRelaxed": "`{version}` is not a valid relaxed version (semver with arbitrary numeric element count).", "VersionInvalidSemver": "`{version}` is not a valid semantic version, consult .", diff --git a/locales/messages.json b/locales/messages.json index 619ed3fbe7..dbae709942 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -202,6 +202,8 @@ "VcpkgSendMetricsButDisabled": "Warning: passed --sendmetrics, but metrics are disabled.", "VersionCommandHeader": "vcpkg package management program version {version}\n\nSee LICENSE.txt for license information.", "_VersionCommandHeader.comment": "example of {version} is '1.3.8'.\n", + "VersionConstraintViolated": "dependency {spec} was expected to be at least version {expected_version}, but is currently {actual_version}.", + "_VersionConstraintViolated.comment": "example of {spec} is 'zlib:x64-windows'.\nexample of {expected_version} is '1.3.8'.\nexample of {actual_version} is '1.3.8'.\n", "VersionInvalidDate": "`{version}` is not a valid date version. Dates must follow the format YYYY-MM-DD and disambiguators must be dot-separated positive integer values without leading zeroes.", "_VersionInvalidDate.comment": "example of {version} is '1.3.8'.\n", "VersionInvalidRelaxed": "`{version}` is not a valid relaxed version (semver with arbitrary numeric element count).", diff --git a/src/vcpkg-test/binarycaching.cpp b/src/vcpkg-test/binarycaching.cpp index 9bf828d6f4..664eec6bea 100644 --- a/src/vcpkg-test/binarycaching.cpp +++ b/src/vcpkg-test/binarycaching.cpp @@ -260,7 +260,8 @@ Build-Depends: bzip scfl, Dependencies::RequestType::USER_REQUESTED, Test::ARM_UWP, - {{"a", {}}, {"b", {}}}); + {{"a", {}}, {"b", {}}}, + {}); ipa.abi_info = Build::AbiInfo{}; ipa.abi_info.get()->package_abi = "packageabi"; @@ -390,7 +391,8 @@ Version: 1.5 scfl, Dependencies::RequestType::USER_REQUESTED, Test::ARM_UWP, - std::map>{}); + std::map>{}, + std::vector{}); Dependencies::InstallPlanAction& ipa_without_abi = install_plan.back(); // test that the binary cache does the right thing. See also CHECKs etc. in KnowNothingBinaryProvider diff --git a/src/vcpkg-test/dependencies.cpp b/src/vcpkg-test/dependencies.cpp index c7dac0ef08..657da380e5 100644 --- a/src/vcpkg-test/dependencies.cpp +++ b/src/vcpkg-test/dependencies.cpp @@ -817,6 +817,45 @@ TEST_CASE ("version sort date", "[versionplan]") CHECK(versions[8].original_string == "2021-01-01.10"); } +TEST_CASE ("version compare string", "[versionplan]") +{ + const Version a_0("a", 0); + const Version a_1("a", 1); + const Version b_1("b", 1); + CHECK(VerComp::lt == compare_versions(VersionScheme::String, a_0, VersionScheme::String, a_1)); + CHECK(VerComp::eq == compare_versions(VersionScheme::String, a_0, VersionScheme::String, a_0)); + CHECK(VerComp::gt == compare_versions(VersionScheme::String, a_1, VersionScheme::String, a_0)); + CHECK(VerComp::unk == compare_versions(VersionScheme::String, a_1, VersionScheme::String, b_1)); +} + +TEST_CASE ("version compare_any", "[versionplan]") +{ + const Version a_0("a", 0); + const Version a_1("a", 1); + const Version b_1("b", 1); + CHECK(VerComp::lt == compare_any(a_0, a_1)); + CHECK(VerComp::gt == compare_any(a_1, a_0)); + CHECK(VerComp::eq == compare_any(a_0, a_0)); + CHECK(VerComp::unk == compare_any(a_1, b_1)); + + const Version v_0_0("0", 0); + const Version v_1_0("1", 0); + const Version v_1_1_1("1.1", 1); + CHECK(VerComp::lt == compare_any(v_0_0, v_1_0)); + CHECK(VerComp::gt == compare_any(v_1_1_1, v_1_0)); + CHECK(VerComp::eq == compare_any(v_0_0, v_0_0)); + + const Version date_0("2021-04-05", 0); + const Version date_1("2022-02-01", 0); + CHECK(VerComp::eq == compare_any(date_0, date_0)); + CHECK(VerComp::lt == compare_any(date_0, date_1)); + + CHECK(VerComp::unk == compare_any(date_0, a_0)); + // Note: dates are valid relaxed dotversions, so these are valid comparisons + CHECK(VerComp::gt == compare_any(date_0, v_0_0)); + CHECK(VerComp::gt == compare_any(date_0, v_1_1_1)); +} + TEST_CASE ("version install simple semver", "[versionplan]") { MockBaselineProvider bp; diff --git a/src/vcpkg-test/spdx.cpp b/src/vcpkg-test/spdx.cpp index a475f038c2..1b8cac7e5b 100644 --- a/src/vcpkg-test/spdx.cpp +++ b/src/vcpkg-test/spdx.cpp @@ -23,7 +23,7 @@ TEST_CASE ("spdx maximum serialization", "[spdx]") cpgh.raw_version = "1.0"; cpgh.version_scheme = VersionScheme::Relaxed; - InstallPlanAction ipa(spec, scfl, RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}); + InstallPlanAction ipa(spec, scfl, RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}); auto& abi = *(ipa.abi_info = Build::AbiInfo{}).get(); abi.package_abi = "ABIHASH"; @@ -178,7 +178,7 @@ TEST_CASE ("spdx minimum serialization", "[spdx]") cpgh.raw_version = "1.0"; cpgh.version_scheme = VersionScheme::String; - InstallPlanAction ipa(spec, scfl, RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}); + InstallPlanAction ipa(spec, scfl, RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}); auto& abi = *(ipa.abi_info = Build::AbiInfo{}).get(); abi.package_abi = "deadbeef"; @@ -307,7 +307,7 @@ TEST_CASE ("spdx concat resources", "[spdx]") cpgh.raw_version = "1.0"; cpgh.version_scheme = VersionScheme::String; - InstallPlanAction ipa(spec, scfl, RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}); + InstallPlanAction ipa(spec, scfl, RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}); auto& abi = *(ipa.abi_info = Build::AbiInfo{}).get(); abi.package_abi = "deadbeef"; diff --git a/src/vcpkg/build.cpp b/src/vcpkg/build.cpp index 065b81f95e..3b00b5575f 100644 --- a/src/vcpkg/build.cpp +++ b/src/vcpkg/build.cpp @@ -232,7 +232,16 @@ namespace vcpkg::Build if (result.code != BuildResult::SUCCEEDED) { - print2(Color::error, Build::create_error_message(result, spec), '\n'); + LocalizedString warnings; + for (auto&& msg : action->build_failure_messages) + { + warnings.append(msg).appendnl(); + } + if (!warnings.data().empty()) + { + msg::print(Color::warning, warnings); + } + msg::println(Color::error, Build::create_error_message(result, spec)); print2(Build::create_user_troubleshooting_message(*action, paths), '\n'); return 1; } diff --git a/src/vcpkg/dependencies.cpp b/src/vcpkg/dependencies.cpp index 9512ea1574..d1e0c2c0cd 100644 --- a/src/vcpkg/dependencies.cpp +++ b/src/vcpkg/dependencies.cpp @@ -20,6 +20,12 @@ namespace vcpkg::Dependencies { namespace { + DECLARE_AND_REGISTER_MESSAGE(VersionConstraintViolated, + (msg::spec, msg::expected_version, msg::actual_version), + "", + "dependency {spec} was expected to be at least version " + "{expected_version}, but is currently {actual_version}."); + struct ClusterGraph; struct ClusterInstalled @@ -44,6 +50,7 @@ namespace vcpkg::Dependencies struct ClusterInstallInfo { std::map> build_edges; + std::map> version_constraints; bool defaults_requested = false; }; @@ -127,12 +134,18 @@ namespace vcpkg::Dependencies if (auto vars = maybe_vars.get()) { // Qualified dependency resolution is available - auto fullspec_list = filter_dependencies( - *qualified_deps, m_spec.triplet(), host_triplet, *vars, ImplicitDefault::YES); - - for (auto&& fspec : fullspec_list) + for (auto&& dep : *qualified_deps) { - fspec.expand_fspecs_to(dep_list); + if (dep.platform.evaluate(*vars)) + { + auto fullspec = dep.to_full_spec(m_spec.triplet(), host_triplet, ImplicitDefault::YES); + fullspec.expand_fspecs_to(dep_list); + if (auto opt = dep.constraint.try_get_minimum_version()) + { + info.version_constraints[fullspec.package_spec].insert( + std::move(opt).value_or_exit(VCPKG_LINE_INFO)); + } + } } Util::sort_unique_erase(dep_list); @@ -145,8 +158,13 @@ namespace vcpkg::Dependencies { if (dep.platform.is_empty()) { - dep.to_full_spec(m_spec.triplet(), host_triplet, ImplicitDefault::YES) - .expand_fspecs_to(dep_list); + auto fullspec = dep.to_full_spec(m_spec.triplet(), host_triplet, ImplicitDefault::YES); + fullspec.expand_fspecs_to(dep_list); + if (auto opt = dep.constraint.try_get_minimum_version()) + { + info.version_constraints[fullspec.package_spec].insert( + std::move(opt).value_or_exit(VCPKG_LINE_INFO)); + } } else { @@ -253,6 +271,20 @@ namespace vcpkg::Dependencies return nullopt; } + Optional get_version() const + { + if (auto p_installed = m_installed.get()) + { + return p_installed->ipv.core->package.get_version(); + } + else if (auto p_scfl = m_scfl.get()) + { + return p_scfl->to_version(); + } + else + return nullopt; + } + PackageSpec m_spec; ExpectedS m_scfl; @@ -425,13 +457,15 @@ namespace vcpkg::Dependencies const SourceControlFileAndLocation& scfl, const RequestType& request_type, Triplet host_triplet, - std::map>&& dependencies) + std::map>&& dependencies, + std::vector&& build_failure_messages) : spec(spec) , source_control_file_and_location(scfl) , plan_type(InstallPlanType::BUILD_AND_INSTALL) , request_type(request_type) , build_options{} , feature_dependencies(std::move(dependencies)) + , build_failure_messages(std::move(build_failure_messages)) , host_triplet(host_triplet) { for (const auto& kv : feature_dependencies) @@ -979,6 +1013,27 @@ namespace vcpkg::Dependencies // If a cluster only has an installed object and is marked as user requested we should still report it. if (auto info_ptr = p_cluster->m_install_info.get()) { + std::vector constraint_violations; + for (auto&& constraints : info_ptr->version_constraints) + { + for (auto&& constraint : constraints.second) + { + auto&& dep_clust = m_graph->get(constraints.first); + auto maybe_v = dep_clust.get_version(); + if (auto v = maybe_v.get()) + { + if (compare_any(*v, constraint) == VerComp::lt) + { + constraint_violations.push_back(msg::format(msg::msgWarningMessage) + .append(msgVersionConstraintViolated, + msg::spec = constraints.first, + msg::expected_version = constraint, + msg::actual_version = *v)); + print2("found constraint violation: ", constraint_violations.back().data(), "\n"); + } + } + } + } std::map> computed_edges; for (auto&& kv : info_ptr->build_edges) { @@ -1007,7 +1062,8 @@ namespace vcpkg::Dependencies p_cluster->get_scfl_or_exit(), p_cluster->request_type, m_graph->m_host_triplet, - std::move(computed_edges)); + std::move(computed_edges), + std::move(constraint_violations)); } else if (p_cluster->request_type == RequestType::USER_REQUESTED && p_cluster->m_installed.has_value()) { @@ -1382,41 +1438,6 @@ namespace vcpkg::Dependencies return it == vermap.end() ? nullptr : it->second; } - static VerComp compare_version_texts(VersionScheme sa, const Version& a, VersionScheme sb, const Version& b) - { - if (sa == VersionScheme::String && sb == VersionScheme::String) - { - return int_to_vercomp(a.text().compare(b.text())); - } - - if (sa == VersionScheme::Date && sb == VersionScheme::Date) - { - return compare(DateVersion::try_parse(a.text()).value_or_exit(VCPKG_LINE_INFO), - DateVersion::try_parse(b.text()).value_or_exit(VCPKG_LINE_INFO)); - } - - if ((sa == VersionScheme::Semver || sa == VersionScheme::Relaxed) && - (sb == VersionScheme::Semver || sb == VersionScheme::Relaxed)) - { - return compare(DotVersion::try_parse(a.text(), sa).value_or_exit(VCPKG_LINE_INFO), - DotVersion::try_parse(b.text(), sb).value_or_exit(VCPKG_LINE_INFO)); - } - - return VerComp::unk; - } - - static VerComp compare_versions(VersionScheme sa, const Version& a, VersionScheme sb, const Version& b) - { - const auto inner_compare = compare_version_texts(sa, a, sb, b); - if (inner_compare == VerComp::eq) - { - if (a.port_version() < b.port_version()) return VerComp::lt; - if (a.port_version() > b.port_version()) return VerComp::gt; - } - - return inner_compare; - } - bool VersionedPackageGraph::VersionSchemeInfo::is_less_than(const Version& new_ver) const { Checks::check_exit(VCPKG_LINE_INFO, scfl); @@ -1959,7 +1980,8 @@ namespace vcpkg::Dependencies node.user_requested ? RequestType::USER_REQUESTED : RequestType::AUTO_SELECTED, m_host_triplet, - std::move(p_vnode->deps)); + std::move(p_vnode->deps), + {}); std::vector deps; for (auto&& f : ipa.feature_list) { diff --git a/src/vcpkg/install.cpp b/src/vcpkg/install.cpp index 5dd9f90fb4..0f5ea43aeb 100644 --- a/src/vcpkg/install.cpp +++ b/src/vcpkg/install.cpp @@ -392,6 +392,15 @@ namespace vcpkg::Install if (result.code != Build::BuildResult::SUCCEEDED) { + LocalizedString warnings; + for (auto&& msg : action.build_failure_messages) + { + warnings.append(msg).appendnl(); + } + if (!warnings.data().empty()) + { + msg::print(Color::warning, warnings); + } msg::println(Color::error, Build::create_error_message(result, action.spec)); return result; } diff --git a/src/vcpkg/versions.cpp b/src/vcpkg/versions.cpp index ed46f9e34f..260c119e3a 100644 --- a/src/vcpkg/versions.cpp +++ b/src/vcpkg/versions.cpp @@ -392,6 +392,44 @@ namespace vcpkg return VerComp::eq; } + static VerComp compare_version_texts(VersionScheme sa, const Version& a, VersionScheme sb, const Version& b) + { + if (sa == VersionScheme::String && sb == VersionScheme::String) + { + return a.text() == b.text() ? VerComp::eq : VerComp::unk; + } + + if (sa == VersionScheme::Date && sb == VersionScheme::Date) + { + return compare(DateVersion::try_parse(a.text()).value_or_exit(VCPKG_LINE_INFO), + DateVersion::try_parse(b.text()).value_or_exit(VCPKG_LINE_INFO)); + } + + if ((sa == VersionScheme::Semver || sa == VersionScheme::Relaxed) && + (sb == VersionScheme::Semver || sb == VersionScheme::Relaxed)) + { + return compare(DotVersion::try_parse(a.text(), sa).value_or_exit(VCPKG_LINE_INFO), + DotVersion::try_parse(b.text(), sb).value_or_exit(VCPKG_LINE_INFO)); + } + + return VerComp::unk; + } + + static VerComp integer_vercomp(int a, int b) + { + if (a == b) return VerComp::eq; + return a < b ? VerComp::lt : VerComp::gt; + } + static inline VerComp portversion_vercomp(VerComp base, int a, int b) + { + return base == VerComp::eq ? integer_vercomp(a, b) : base; + } + + VerComp compare_versions(VersionScheme sa, const Version& a, VersionScheme sb, const Version& b) + { + return portversion_vercomp(compare_version_texts(sa, a, sb, b), a.port_version(), b.port_version()); + } + VerComp compare(const DateVersion& a, const DateVersion& b) { if (auto x = strcmp(a.version_string.c_str(), b.version_string.c_str())) @@ -402,6 +440,34 @@ namespace vcpkg return static_cast(Util::range_lexcomp(a.identifiers, b.identifiers, uint64_comp)); } + VerComp compare_any(const Version& a, const Version& b) + { + if (a.text() == b.text()) + { + return integer_vercomp(a.port_version(), b.port_version()); + } + auto date_a = DateVersion::try_parse(a.text()); + if (auto p_date_a = date_a.get()) + { + auto date_b = DateVersion::try_parse(b.text()); + if (auto p_date_b = date_b.get()) + { + return portversion_vercomp(compare(*p_date_a, *p_date_b), a.port_version(), b.port_version()); + } + } + + auto dot_a = DotVersion::try_parse_relaxed(a.text()); + if (auto p_dot_a = dot_a.get()) + { + auto dot_b = DotVersion::try_parse_relaxed(b.text()); + if (auto p_dot_b = dot_b.get()) + { + return portversion_vercomp(compare(*p_dot_a, *p_dot_b), a.port_version(), b.port_version()); + } + } + return VerComp::unk; + } + StringView normalize_external_version_zeros(StringView sv) { if (sv.empty()) return "0";