diff --git a/include/vcpkg/base/message-args.inc.h b/include/vcpkg/base/message-args.inc.h index 4fe562ab69..317f19853b 100644 --- a/include/vcpkg/base/message-args.inc.h +++ b/include/vcpkg/base/message-args.inc.h @@ -57,3 +57,5 @@ DECLARE_MSG_ARG(version, "1.3.8") DECLARE_MSG_ARG(package_name, "zlib") DECLARE_MSG_ARG(spec, "zlib:x64-windows") DECLARE_MSG_ARG(feature_spec, "zlib[featurea,featureb]") +DECLARE_MSG_ARG(version_spec, "zlib:x64-windows@1.0.0") +DECLARE_MSG_ARG(constraint_origin, "zlib:x64-windows@1.0.0") diff --git a/include/vcpkg/base/message-data.inc.h b/include/vcpkg/base/message-data.inc.h index 0cb7f2fad6..b5774e6294 100644 --- a/include/vcpkg/base/message-data.inc.h +++ b/include/vcpkg/base/message-data.inc.h @@ -2339,8 +2339,7 @@ DECLARE_MESSAGE(UndeterminedToolChainForTriplet, (msg::triplet, msg::system_name), "", "Unable to determine toolchain use for {triplet} with with CMAKE_SYSTEM_NAME {system_name}. Did " - "you mean to use " - "VCPKG_CHAINLOAD_TOOLCHAIN_FILE?") + "you mean to use VCPKG_CHAINLOAD_TOOLCHAIN_FILE?") DECLARE_MESSAGE(UnexpectedArgument, (msg::option), "Argument is literally what the user passed on the command line.", @@ -2655,16 +2654,21 @@ DECLARE_MESSAGE(VersionGitEntryMissing, "A list of versions, 1 per line, are printed after this message.", "no version database entry for {package_name} at {version}.\nAvailable versions:") DECLARE_MESSAGE(VersionIncomparable1, - (msg::spec, msg::package_name, msg::expected, msg::actual), + (msg::spec, msg::constraint_origin, msg::expected, msg::actual), "{expected} and {actual} are versions like 1.0", - "version conflict on {spec}: {package_name} required {expected} but vcpkg could not compare it to " - "{actual}.\nThe two versions used incomparable schemes:") -DECLARE_MESSAGE(VersionIncomparable2, (msg::version, msg::new_scheme), "", "\"{version}\" was of scheme {new_scheme}") + "version conflict on {spec}: {constraint_origin} required {expected}, which cannot be compared with " + "the baseline version {actual}.") +DECLARE_MESSAGE(VersionIncomparableSchemeString, (), "", "Both versions have scheme string but different primary text.") +DECLARE_MESSAGE(VersionIncomparableSchemes, (), "", "The versions have incomparable schemes:") +DECLARE_MESSAGE(VersionIncomparable2, + (msg::version_spec, msg::new_scheme), + "", + "{version_spec} has scheme {new_scheme}") DECLARE_MESSAGE(VersionIncomparable3, (), "This precedes a JSON document describing the fix", - "This can be resolved by adding an explicit override to the preferred version, for example:") -DECLARE_MESSAGE(VersionIncomparable4, (), "", "See `vcpkg help versioning` for more information.") + "This can be resolved by adding an explicit override to the preferred version. For example:") +DECLARE_MESSAGE(VersionIncomparable4, (msg::url), "", "See `vcpkg help versioning` or {url} for more information.") DECLARE_MESSAGE(VersionInDeclarationDoesNotMatch, (msg::version), "", @@ -2689,23 +2693,17 @@ DECLARE_MESSAGE( "The names version, version-date, version-semver, and version-string are code and must not be localized", "expected a versioning field (one of version, version-date, version-semver, or version-string)") DECLARE_MESSAGE(VersionMissingRequiredFeature, - (msg::spec, msg::version, msg::feature), + (msg::version_spec, msg::feature, msg::constraint_origin), "", - "{spec}@{version} does not have required feature {feature}") + "{version_spec} does not have required feature {feature} needed by {constraint_origin}") DECLARE_MESSAGE(VersionNotFound, (msg::expected, msg::actual), "{expected} and {actual} are versions", "{expected} not available, only {actual} is available") -DECLARE_MESSAGE( - VersionNotFoundDuringDiscovery, - (msg::spec, msg::version), - "", - "version was not found during discovery: {spec}@{version}\nThis is an internal vcpkg error. Please open " - "an issue on https://github.com/Microsoft/vcpkg with detailed steps to reproduce the problem.") DECLARE_MESSAGE(VersionNotFoundInVersionsFile, (msg::version, msg::package_name), "", - "Version {version} was not found in versions file.\n" + "Version {version} was not found in versions file for {package_name}.\n" "Run:\n" "vcpkg x-add-version {package_name}\n" "to add the new port version.") @@ -2763,10 +2761,7 @@ DECLARE_MESSAGE(VersionSpecMismatch, "Failed to load port because versions are inconsistent. The file \"{path}\" contains the version " "{actual_version}, but the version database indicates that it should be {expected_version}.") DECLARE_MESSAGE(VersionTableHeader, (), "", "Version") -DECLARE_MESSAGE(VersionVerifiedOK, - (msg::package_name, msg::version, msg::commit_sha), - "", - "OK: {package_name}@{version} -> {commit_sha}") +DECLARE_MESSAGE(VersionVerifiedOK, (msg::version_spec, msg::commit_sha), "", "OK: {version_spec} -> {commit_sha}") DECLARE_MESSAGE(VSExaminedInstances, (), "", "The following Visual Studio instances were considered:") DECLARE_MESSAGE(VSExaminedPaths, (), "", "The following paths were examined for Visual Studio instances:") DECLARE_MESSAGE(VSNoInstances, (), "", "Could not locate a complete Visual Studio instance") diff --git a/include/vcpkg/base/strings.h b/include/vcpkg/base/strings.h index b513298129..495df4985e 100644 --- a/include/vcpkg/base/strings.h +++ b/include/vcpkg/base/strings.h @@ -153,6 +153,8 @@ namespace vcpkg::Strings [[nodiscard]] std::vector split(StringView s, const char delimiter); + [[nodiscard]] std::vector split_keep_empty(StringView s, const char delimiter); + [[nodiscard]] std::vector split_paths(StringView s); const char* find_first_of(StringView searched, StringView candidates); diff --git a/include/vcpkg/base/util.h b/include/vcpkg/base/util.h index 34505b0a32..e14cd32988 100644 --- a/include/vcpkg/base/util.h +++ b/include/vcpkg/base/util.h @@ -14,12 +14,11 @@ namespace vcpkg::Util { template - using ElementT = - std::remove_reference_t::iterator>())>; + using ElementT = std::decay_t().begin())>; namespace Vectors { - template> + template void append(std::vector* augend, const Container& addend) { augend->insert(augend->end(), addend.begin(), addend.end()); @@ -107,10 +106,10 @@ namespace vcpkg::Util } } - template> - std::vector filter(const Range& xs, Pred&& f) + template + std::vector> filter(const Range& xs, Pred f) { - std::vector ret; + std::vector> ret; for (auto&& x : xs) { diff --git a/include/vcpkg/cmakevars.h b/include/vcpkg/cmakevars.h index 60c42eef85..585f5aed5f 100644 --- a/include/vcpkg/cmakevars.h +++ b/include/vcpkg/cmakevars.h @@ -14,21 +14,19 @@ namespace vcpkg::CMakeVars { + using CMakeVars = std::unordered_map; + struct CMakeVarProvider { virtual ~CMakeVarProvider() = default; - virtual Optional&> get_generic_triplet_vars( - Triplet triplet) const = 0; + virtual Optional get_generic_triplet_vars(Triplet triplet) const = 0; - virtual Optional&> get_dep_info_vars( - const PackageSpec& spec) const = 0; + virtual Optional get_dep_info_vars(const PackageSpec& spec) const = 0; - const std::unordered_map& get_or_load_dep_info_vars(const PackageSpec& spec, - Triplet host_triplet) const; + const CMakeVars& get_or_load_dep_info_vars(const PackageSpec& spec, Triplet host_triplet) const; - virtual Optional&> get_tag_vars( - const PackageSpec& spec) const = 0; + virtual Optional get_tag_vars(const PackageSpec& spec) const = 0; virtual void load_generic_triplet_vars(Triplet triplet) const = 0; diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h index be88bbfcbb..99d687d9b1 100644 --- a/include/vcpkg/sourceparagraph.h +++ b/include/vcpkg/sourceparagraph.h @@ -179,6 +179,8 @@ namespace vcpkg struct SourceControlFileAndLocation { Version to_version() const { return source_control_file->to_version(); } + VersionScheme scheme() const { return source_control_file->core_paragraph->version_scheme; } + SchemedVersion schemed_version() const { return {scheme(), to_version()}; } std::unique_ptr source_control_file; Path source_location; diff --git a/include/vcpkg/versions.h b/include/vcpkg/versions.h index e5282f9a81..4c036cd7de 100644 --- a/include/vcpkg/versions.h +++ b/include/vcpkg/versions.h @@ -143,6 +143,7 @@ namespace vcpkg // Try parsing with all version schemas and return 'unk' if none match VerComp compare_any(const Version& a, const Version& b); + VerComp compare_versions(const SchemedVersion& a, const SchemedVersion& b); VerComp compare_versions(VersionScheme sa, const Version& a, VersionScheme sb, const Version& b); enum class VersionConstraintKind diff --git a/locales/messages.json b/locales/messages.json index 1d6c25312a..105ecf2544 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -1475,13 +1475,16 @@ "_VersionGitEntryMissing.comment": "A list of versions, 1 per line, are printed after this message. An example of {package_name} is zlib. An example of {version} is 1.3.8.", "VersionInDeclarationDoesNotMatch": "The version declared in file does not match checked-out version: {version}", "_VersionInDeclarationDoesNotMatch.comment": "An example of {version} is 1.3.8.", - "VersionIncomparable1": "version conflict on {spec}: {package_name} required {expected} but vcpkg could not compare it to {actual}.\nThe two versions used incomparable schemes:", - "_VersionIncomparable1.comment": "{expected} and {actual} are versions like 1.0 An example of {spec} is zlib:x64-windows. An example of {package_name} is zlib.", - "VersionIncomparable2": "\"{version}\" was of scheme {new_scheme}", - "_VersionIncomparable2.comment": "An example of {version} is 1.3.8. An example of {new_scheme} is version.", - "VersionIncomparable3": "This can be resolved by adding an explicit override to the preferred version, for example:", + "VersionIncomparable1": "version conflict on {spec}: {constraint_origin} required {expected}, which cannot be compared with the baseline version {actual}.", + "_VersionIncomparable1.comment": "{expected} and {actual} are versions like 1.0 An example of {spec} is zlib:x64-windows. An example of {constraint_origin} is zlib:x64-windows@1.0.0.", + "VersionIncomparable2": "{version_spec} has scheme {new_scheme}", + "_VersionIncomparable2.comment": "An example of {version_spec} is zlib:x64-windows@1.0.0. An example of {new_scheme} is version.", + "VersionIncomparable3": "This can be resolved by adding an explicit override to the preferred version. For example:", "_VersionIncomparable3.comment": "This precedes a JSON document describing the fix", - "VersionIncomparable4": "See `vcpkg help versioning` for more information.", + "VersionIncomparable4": "See `vcpkg help versioning` or {url} for more information.", + "_VersionIncomparable4.comment": "An example of {url} is https://github.com/microsoft/vcpkg.", + "VersionIncomparableSchemeString": "Both versions have scheme string but different primary text.", + "VersionIncomparableSchemes": "The versions have incomparable schemes:", "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": "An example of {version} is 1.3.8.", "VersionInvalidRelaxed": "`{version}` is not a valid relaxed version (semver with arbitrary numeric element count).", @@ -1490,13 +1493,11 @@ "_VersionInvalidSemver.comment": "An example of {version} is 1.3.8.", "VersionMissing": "expected a versioning field (one of version, version-date, version-semver, or version-string)", "_VersionMissing.comment": "The names version, version-date, version-semver, and version-string are code and must not be localized", - "VersionMissingRequiredFeature": "{spec}@{version} does not have required feature {feature}", - "_VersionMissingRequiredFeature.comment": "An example of {spec} is zlib:x64-windows. An example of {version} is 1.3.8. An example of {feature} is avisynthplus.", + "VersionMissingRequiredFeature": "{version_spec} does not have required feature {feature} needed by {constraint_origin}", + "_VersionMissingRequiredFeature.comment": "An example of {version_spec} is zlib:x64-windows@1.0.0. An example of {feature} is avisynthplus. An example of {constraint_origin} is zlib:x64-windows@1.0.0.", "VersionNotFound": "{expected} not available, only {actual} is available", "_VersionNotFound.comment": "{expected} and {actual} are versions", - "VersionNotFoundDuringDiscovery": "version was not found during discovery: {spec}@{version}\nThis is an internal vcpkg error. Please open an issue on https://github.com/Microsoft/vcpkg with detailed steps to reproduce the problem.", - "_VersionNotFoundDuringDiscovery.comment": "An example of {spec} is zlib:x64-windows. An example of {version} is 1.3.8.", - "VersionNotFoundInVersionsFile": "Version {version} was not found in versions file.\nRun:\nvcpkg x-add-version {package_name}\nto add the new port version.", + "VersionNotFoundInVersionsFile": "Version {version} was not found in versions file for {package_name}.\nRun:\nvcpkg x-add-version {package_name}\nto add the new port version.", "_VersionNotFoundInVersionsFile.comment": "An example of {version} is 1.3.8. An example of {package_name} is zlib.", "VersionRejectedDueToBaselineMissing": "{path} was rejected because it uses \"{json_field}\" and does not have a \"builtin-baseline\". This can be fixed by removing the uses of \"{json_field}\" or adding a \"builtin-baseline\".\nSee `vcpkg help versioning` for more information.", "_VersionRejectedDueToBaselineMissing.comment": "An example of {path} is /foo/bar. An example of {json_field} is identifer.", @@ -1513,8 +1514,8 @@ "VersionSpecMismatch": "Failed to load port because versions are inconsistent. The file \"{path}\" contains the version {actual_version}, but the version database indicates that it should be {expected_version}.", "_VersionSpecMismatch.comment": "An example of {path} is /foo/bar. An example of {expected_version} is 1.3.8. An example of {actual_version} is 1.3.8.", "VersionTableHeader": "Version", - "VersionVerifiedOK": "OK: {package_name}@{version} -> {commit_sha}", - "_VersionVerifiedOK.comment": "An example of {package_name} is zlib. An example of {version} is 1.3.8. An example of {commit_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949.", + "VersionVerifiedOK": "OK: {version_spec} -> {commit_sha}", + "_VersionVerifiedOK.comment": "An example of {version_spec} is zlib:x64-windows@1.0.0. An example of {commit_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949.", "WaitingForChildrenToExit": "Waiting for child processes to exit...", "WaitingToTakeFilesystemLock": "waiting to take filesystem lock on {path}...", "_WaitingToTakeFilesystemLock.comment": "An example of {path} is /foo/bar.", diff --git a/src/vcpkg-test/dependencies.cpp b/src/vcpkg-test/dependencies.cpp index d5a68a171f..18819d73fe 100644 --- a/src/vcpkg-test/dependencies.cpp +++ b/src/vcpkg-test/dependencies.cpp @@ -29,7 +29,8 @@ struct MockBaselineProvider : IBaselineProvider ExpectedL get_baseline_version(StringView name) const override { auto it = v.find(name); - if (it == v.end()) return LocalizedString::from_raw("error"); + if (it == v.end()) + return LocalizedString::from_raw("MockBaselineProvider::get_baseline_version(" + name.to_string() + ")"); return it->second; } }; @@ -215,6 +216,23 @@ struct MockOverlayProvider : IOverlayProvider static const MockOverlayProvider s_empty_mock_overlay; +static void CHECK_LINES(const LocalizedString& a, StringView b) +{ + auto as = Strings::split(a.data(), '\n'); + auto bs = Strings::split(b, '\n'); + for (size_t i = 0; i < as.size() && i < bs.size(); ++i) + { + INFO(i); + CHECK(as[i] == bs[i]); + } + CHECK(as.size() == bs.size()); +} + +#define WITH_EXPECTED(id, expr) \ + auto id##_storage = (expr); \ + REQUIRE(id##_storage.has_value()); \ + auto& id = *id##_storage.get() + static ExpectedL create_versioned_install_plan(const IVersionedPortfileProvider& provider, const IBaselineProvider& bprovider, const CMakeVars::CMakeVarProvider& var_provider, @@ -262,8 +280,7 @@ TEST_CASE ("basic version install single", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec())); REQUIRE(install_plan.size() == 1); REQUIRE(install_plan.install_actions.at(0).spec.name() == "a"); @@ -288,6 +305,7 @@ TEST_CASE ("basic version install detect cycle", "[versionplan]") auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec()); REQUIRE(!install_plan.has_value()); + REQUIRE(install_plan.error() == "error: cycle detected during a:x86-windows:\na:x86-windows@1\nb:x86-windows@1"); } TEST_CASE ("basic version install scheme", "[versionplan]") @@ -304,16 +322,19 @@ TEST_CASE ("basic version install scheme", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec())); - CHECK(install_plan.size() == 2); + REQUIRE(install_plan.size() == 2); + CHECK(install_plan.install_actions[0].spec.name() == "b"); + CHECK(install_plan.install_actions[1].spec.name() == "a"); - StringLiteral names[] = {"b", "a"}; - for (size_t i = 0; i < install_plan.install_actions.size() && i < 2; ++i) - { - CHECK(install_plan.install_actions[i].spec.name() == names[i]); - } + REQUIRE(install_plan.install_actions[1].package_dependencies.size() == 1); + CHECK(install_plan.install_actions[1].package_dependencies[0].name() == "b"); + + auto it = install_plan.install_actions[1].feature_dependencies.find("core"); + REQUIRE(it != install_plan.install_actions[1].feature_dependencies.end()); + REQUIRE(it->second.size() == 1); + REQUIRE(it->second[0].port() == "b"); } TEST_CASE ("basic version install scheme diamond", "[versionplan]") @@ -340,16 +361,24 @@ TEST_CASE ("basic version install scheme diamond", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec())); - CHECK(install_plan.size() == 4); + REQUIRE(install_plan.size() == 4); + CHECK(install_plan.install_actions[0].spec.name() == "d"); + CHECK(install_plan.install_actions[1].spec.name() == "c"); + CHECK(install_plan.install_actions[2].spec.name() == "b"); + CHECK(install_plan.install_actions[3].spec.name() == "a"); - StringLiteral names[] = {"d", "c", "b", "a"}; - for (size_t i = 0; i < install_plan.install_actions.size() && i < 4; ++i) - { - CHECK(install_plan.install_actions[i].spec.name() == names[i]); - } + REQUIRE(install_plan.install_actions[1].package_dependencies.size() == 1); + CHECK(install_plan.install_actions[1].package_dependencies[0].name() == "d"); + + REQUIRE(install_plan.install_actions[2].package_dependencies.size() == 2); + CHECK(install_plan.install_actions[2].package_dependencies[0].name() == "c"); + CHECK(install_plan.install_actions[2].package_dependencies[1].name() == "d"); + + REQUIRE(install_plan.install_actions[3].package_dependencies.size() == 2); + CHECK(install_plan.install_actions[3].package_dependencies[0].name() == "b"); + CHECK(install_plan.install_actions[3].package_dependencies[1].name() == "c"); } TEST_CASE ("basic version install scheme baseline missing", "[versionplan]") @@ -364,9 +393,10 @@ TEST_CASE ("basic version install scheme baseline missing", "[versionplan]") auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a"}}, {}, toplevel_spec()); REQUIRE(!install_plan.has_value()); + REQUIRE(install_plan.error() == "MockBaselineProvider::get_baseline_version(a)"); } -TEST_CASE ("basic version install scheme baseline missing success", "[versionplan]") +TEST_CASE ("basic version install scheme baseline missing 2", "[versionplan]") { MockBaselineProvider bp; @@ -385,11 +415,10 @@ TEST_CASE ("basic version install scheme baseline missing success", "[versionpla Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2"}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec()); - REQUIRE(install_plan.size() == 1); - check_name_and_version(install_plan.install_actions[0], "a", {"2", 0}); + REQUIRE(!install_plan.has_value()); + REQUIRE(install_plan.error() == "MockBaselineProvider::get_baseline_version(a)"); } TEST_CASE ("basic version install scheme baseline", "[versionplan]") @@ -452,6 +481,15 @@ TEST_CASE ("version install scheme baseline conflict", "[versionplan]") toplevel_spec()); REQUIRE(!install_plan.has_value()); + CHECK_LINES( + install_plan.error(), + R"(error: version conflict on a:x86-windows: toplevel-spec required 3, which cannot be compared with the baseline version 2. +Both versions have scheme string but different primary text. +This can be resolved by adding an explicit override to the preferred version. For example: + "overrides": [ + { "name": "a", "version": "2" } + ] +See `vcpkg help versioning` or https://learn.microsoft.com/vcpkg/users/versioning for more information.)"); } TEST_CASE ("version install string port version", "[versionplan]") @@ -513,6 +551,7 @@ TEST_CASE ("version install transitive string", "[versionplan]") { MockBaselineProvider bp; bp.v["a"] = {"2", 0}; + bp.v["b"] = {"2", 0}; MockVersionedPortfileProvider vp; vp.emplace("a", {"2", 0}).source_control_file->core_paragraph->dependencies = { @@ -526,7 +565,7 @@ TEST_CASE ("version install transitive string", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = + auto install_plan1 = create_versioned_install_plan(vp, bp, var_provider, @@ -534,9 +573,9 @@ TEST_CASE ("version install transitive string", "[versionplan]") Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2", 1}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); - + toplevel_spec()); + REQUIRE(install_plan1.has_value()); + auto& install_plan = *install_plan1.get(); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "b", {"2", 0}); CHECK(install_plan.install_actions[0].request_type == RequestType::AUTO_SELECTED); @@ -607,6 +646,7 @@ TEST_CASE ("version install diamond relaxed", "[versionplan]") MockBaselineProvider bp; bp.v["a"] = {"2", 0}; bp.v["b"] = {"3", 0}; + bp.v["c"] = {"5", 1}; MockVersionedPortfileProvider vp; vp.emplace("a", {"2", 0}, VersionScheme::Relaxed); @@ -623,17 +663,16 @@ TEST_CASE ("version install diamond relaxed", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = - create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "3", 0}}, - Dependency{"b", {}, {}, {VersionConstraintKind::Minimum, "2", 1}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "3", 0}}, + Dependency{"b", {}, {}, {VersionConstraintKind::Minimum, "2", 1}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 3); check_name_and_version(install_plan.install_actions[0], "c", {"9", 2}); @@ -917,6 +956,7 @@ TEST_CASE ("version install diamond semver", "[versionplan]") MockBaselineProvider bp; bp.v["a"] = {"2.0.0", 0}; bp.v["b"] = {"3.0.0", 0}; + bp.v["c"] = {"5.0.0", 1}; MockVersionedPortfileProvider vp; vp.emplace("a", {"2.0.0", 0}, VersionScheme::Semver); @@ -933,7 +973,8 @@ TEST_CASE ("version install diamond semver", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = + WITH_EXPECTED( + install_plan, create_versioned_install_plan(vp, bp, var_provider, @@ -942,8 +983,7 @@ TEST_CASE ("version install diamond semver", "[versionplan]") Dependency{"b", {}, {}, {VersionConstraintKind::Minimum, "2.0.0", 1}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec())); REQUIRE(install_plan.size() == 3); check_name_and_version(install_plan.install_actions[0], "c", {"9.0.0", 2}); @@ -962,7 +1002,8 @@ TEST_CASE ("version install simple date", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = + WITH_EXPECTED( + install_plan, create_versioned_install_plan(vp, bp, var_provider, @@ -970,8 +1011,7 @@ TEST_CASE ("version install simple date", "[versionplan]") Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2020-03-01", 0}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec())); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "a", {"2020-03-01", 0}); @@ -993,7 +1033,8 @@ TEST_CASE ("version install transitive date", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = + WITH_EXPECTED( + install_plan, create_versioned_install_plan(vp, bp, var_provider, @@ -1001,8 +1042,7 @@ TEST_CASE ("version install transitive date", "[versionplan]") Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2020-01-01.3", 0}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec())); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "b", {"2020-01-01.3", 0}); @@ -1014,6 +1054,7 @@ TEST_CASE ("version install diamond date", "[versionplan]") MockBaselineProvider bp; bp.v["a"] = {"2020-01-02", 0}; bp.v["b"] = {"2020-01-03", 0}; + bp.v["c"] = {"2020-01-05", 1}; MockVersionedPortfileProvider vp; vp.emplace("a", {"2020-01-02", 0}, VersionScheme::Date); @@ -1030,7 +1071,8 @@ TEST_CASE ("version install diamond date", "[versionplan]") MockCMakeVarProvider var_provider; - auto install_plan = + WITH_EXPECTED( + install_plan, create_versioned_install_plan(vp, bp, var_provider, @@ -1039,25 +1081,99 @@ TEST_CASE ("version install diamond date", "[versionplan]") Dependency{"b", {}, {}, {VersionConstraintKind::Minimum, "2020-01-02", 1}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec())); REQUIRE(install_plan.size() == 3); check_name_and_version(install_plan.install_actions[0], "c", {"2020-01-09", 2}); check_name_and_version(install_plan.install_actions[1], "b", {"2020-01-03", 0}); check_name_and_version(install_plan.install_actions[2], "a", {"2020-01-03", 0}); } +static Optional diff_lines(StringView a, StringView b) +{ + auto lines_a = Strings::split_keep_empty(a, '\n'); + auto lines_b = Strings::split_keep_empty(b, '\n'); -static void CHECK_LINES(const LocalizedString& a, const std::string& b) + std::vector> edits; + auto& first_row = edits.emplace_back(); + first_row.resize(lines_b.size() + 1); + std::iota(first_row.begin(), first_row.end(), 0); + for (size_t i = 0; i < lines_a.size(); ++i) + { + edits.emplace_back().resize(lines_b.size() + 1); + edits[i + 1][0] = edits[i][0] + 1; + for (size_t j = 0; j < lines_b.size(); ++j) + { + size_t p = edits[i + 1][j] + 1; + size_t m = edits[i][j + 1] + 1; + if (m < p) p = m; + if (lines_a[i] == lines_b[j] && edits[i][j] < p) p = edits[i][j]; + edits[i + 1][j + 1] = p; + } + } + + size_t i = lines_a.size(); + size_t j = lines_b.size(); + if (edits[i][j] == 0) return nullopt; + + std::vector lines; + + while (i > 0 && j > 0) + { + if (edits[i][j] == edits[i - 1][j - 1] && lines_a[i - 1] == lines_b[j - 1]) + { + --j; + --i; + lines.emplace_back(" " + lines_a[i]); + } + else if (edits[i][j] == edits[i - 1][j] + 1) + { + --i; + lines.emplace_back("-" + lines_a[i]); + } + else + { + --j; + lines.emplace_back("+" + lines_b[j]); + } + } + for (; i > 0; --i) + { + lines.emplace_back("-" + lines_a[i - 1]); + } + for (; j > 0; --j) + { + lines.emplace_back("+" + lines_b[j - 1]); + } + std::string ret; + for (auto it = lines.rbegin(); it != lines.rend(); ++it) + { + ret.append(*it); + ret.push_back('\n'); + } + return ret; +} + +#define REQUIRE_LINES(a, b) \ + do \ + { \ + if (auto delta = diff_lines((a), (b))) FAIL(*delta.get()); \ + } while (0) + +TEST_CASE ("diff algorithm", "[diff]") { - auto as = Strings::split(a.data(), '\n'); - auto bs = Strings::split(b, '\n'); - for (size_t i = 0; i < as.size() && i < bs.size(); ++i) + CHECK(!diff_lines("hello", "hello")); + CHECK(!diff_lines("hello\n", "hello\n")); + CHECK(!diff_lines("hello\n\nworld", "hello\n\nworld")); { - INFO(i); - CHECK(as[i] == bs[i]); + auto a = diff_lines("hello\na\nworld", "hello\nworld"); + REQUIRE(a); + CHECK(*a.get() == " hello\n-a\n world\n"); + } + { + auto a = diff_lines("hello\nworld", "hello\na\nworld"); + REQUIRE(a); + CHECK(*a.get() == " hello\n+a\n world\n"); } - CHECK(as.size() == bs.size()); } TEST_CASE ("version install scheme failure", "[versionplan]") @@ -1083,21 +1199,21 @@ TEST_CASE ("version install scheme failure", "[versionplan]") toplevel_spec()); REQUIRE(!install_plan.error().empty()); - CHECK_LINES( + REQUIRE_LINES( install_plan.error(), - R"(error: version conflict on a:x86-windows: baseline required 1.0.0 but vcpkg could not compare it to 1.0.1. + R"(error: version conflict on a:x86-windows: toplevel-spec required 1.0.1, which cannot be compared with the baseline version 1.0.0. -The two versions used incomparable schemes: - "1.0.1" was of scheme string - "1.0.0" was of scheme semver +The versions have incomparable schemes: + a@1.0.0 has scheme semver + a@1.0.1 has scheme string -This can be resolved by adding an explicit override to the preferred version, for example: +This can be resolved by adding an explicit override to the preferred version. For example: "overrides": [ - { "name": "a", "version": "1.0.1" } + { "name": "a", "version": "1.0.0" } ] -See `vcpkg help versioning` for more information.)"); +See `vcpkg help versioning` or https://learn.microsoft.com/vcpkg/users/versioning for more information.)"); } SECTION ("higher baseline") { @@ -1113,21 +1229,21 @@ See `vcpkg help versioning` for more information.)"); toplevel_spec()); REQUIRE(!install_plan.error().empty()); - CHECK_LINES( + REQUIRE_LINES( install_plan.error(), - R"(error: version conflict on a:x86-windows: baseline required 1.0.2 but vcpkg could not compare it to 1.0.1. + R"(error: version conflict on a:x86-windows: toplevel-spec required 1.0.1, which cannot be compared with the baseline version 1.0.2. -The two versions used incomparable schemes: - "1.0.1" was of scheme string - "1.0.2" was of scheme semver +The versions have incomparable schemes: + a@1.0.2 has scheme semver + a@1.0.1 has scheme string -This can be resolved by adding an explicit override to the preferred version, for example: +This can be resolved by adding an explicit override to the preferred version. For example: "overrides": [ - { "name": "a", "version": "1.0.1" } + { "name": "a", "version": "1.0.2" } ] -See `vcpkg help versioning` for more information.)"); +See `vcpkg help versioning` or https://learn.microsoft.com/vcpkg/users/versioning for more information.)"); } } @@ -1188,10 +1304,11 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") MockCMakeVarProvider var_provider; - SECTION ("lower baseline") + SECTION ("lower baseline b") { MockBaselineProvider bp; bp.v["a"] = {"2", 0}; + bp.v["b"] = {"1", 0}; auto install_plan = create_versioned_install_plan(vp, @@ -1201,8 +1318,41 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2", 1}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec()); + + REQUIRE(!install_plan.has_value()); + CHECK_LINES( + install_plan.error(), + R"(error: version conflict on b:x86-windows: a:x86-windows@2#1 required 1#1, which cannot be compared with the baseline version 1. + +The versions have incomparable schemes: + b@1 has scheme string + b@1#1 has scheme relaxed + +This can be resolved by adding an explicit override to the preferred version. For example: + + "overrides": [ + { "name": "b", "version": "1" } + ] + +See `vcpkg help versioning` or https://learn.microsoft.com/vcpkg/users/versioning for more information.)"); + } + SECTION ("lower baseline") + { + MockBaselineProvider bp; + bp.v["a"] = {"2", 0}; + bp.v["b"] = {"1", 1}; + + WITH_EXPECTED( + install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2", 1}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "b", {"1", 1}); @@ -1212,8 +1362,10 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") { MockBaselineProvider bp; bp.v["a"] = {"2", 1}; + bp.v["b"] = {"1", 1}; - auto install_plan = + WITH_EXPECTED( + install_plan, create_versioned_install_plan(vp, bp, var_provider, @@ -1221,8 +1373,7 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") Dependency{"a", {}, {}, {VersionConstraintKind::Minimum, "2", 0}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec())); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "b", {"1", 1}); @@ -1263,45 +1414,45 @@ TEST_CASE ("version install simple feature", "[versionplan]") SECTION ("relaxed") { - auto install_plan = create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"a", {"x"}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"a", {"x"}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); } SECTION ("semver") { - auto install_plan = create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"semver", {"x"}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"semver", {"x"}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "semver", {"1.0.0", 0}, {"x"}); } SECTION ("date") { - auto install_plan = create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"date", {"x"}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"date", {"x"}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "date", {"2020-01-01", 0}, {"x"}); @@ -1320,11 +1471,10 @@ TEST_CASE ("version install simple feature", "[versionplan]") Dependency{"a", {"x"}, {}, {VersionConstraintKind::Minimum, "1", 0}}, }, {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + toplevel_spec()); - REQUIRE(install_plan.size() == 1); - check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); + REQUIRE_FALSE(install_plan.has_value()); + REQUIRE(install_plan.error() == "MockBaselineProvider::get_baseline_version(a)"); } } @@ -1352,15 +1502,15 @@ TEST_CASE ("version install transitive features", "[versionplan]") bp.v["a"] = {"1", 0}; bp.v["b"] = {"1", 0}; - auto install_plan = create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"a", {"x"}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"a", {"x"}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "b", {"1", 0}, {"y"}); @@ -1393,17 +1543,18 @@ TEST_CASE ("version install transitive feature versioned", "[versionplan]") MockBaselineProvider bp; bp.v["a"] = {"1", 0}; + bp.v["b"] = {"1", 0}; bp.v["c"] = {"1", 0}; - auto install_plan = create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"a", {"x"}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"a", {"x"}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 3); check_name_and_version(install_plan.install_actions[0], "c", {"1", 0}); @@ -1472,19 +1623,18 @@ TEST_CASE ("version install constraint-reduction", "[versionplan]") bp.v["b"] = {"1", 0}; bp.v["c"] = {"1", 0}; - auto install_plan = - create_versioned_install_plan(vp, - bp, - var_provider, - { - Dependency{"b", {}, {}, {VersionConstraintKind::Minimum, "2"}}, - }, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + { + Dependency{"b", {}, {}, {VersionConstraintKind::Minimum, "2"}}, + }, + {}, + toplevel_spec())); REQUIRE(install_plan.size() == 2); - check_name_and_version(install_plan.install_actions[0], "c", {"1", 0}); + check_name_and_version(install_plan.install_actions[0], "c", {"2", 0}); check_name_and_version(install_plan.install_actions[1], "b", {"2", 0}); } } @@ -1550,13 +1700,13 @@ TEST_CASE ("version install transitive overrides", "[versionplan]") bp.v["b"] = {"2", 0}; bp.v["c"] = {"2", 1}; - auto install_plan = create_versioned_install_plan(vp, - bp, - var_provider, - {Dependency{"b"}}, - {DependencyOverride{"b", "1"}, DependencyOverride{"c", "1"}}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, + bp, + var_provider, + {Dependency{"b"}}, + {DependencyOverride{"b", "1"}, DependencyOverride{"c", "1"}}, + toplevel_spec())); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "c", {"1", 0}); @@ -1577,8 +1727,8 @@ TEST_CASE ("version install default features", "[versionplan]") MockBaselineProvider bp; bp.v["a"] = {"1", 0}; - auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {Dependency{"a"}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, + create_versioned_install_plan(vp, bp, var_provider, {Dependency{"a"}}, {}, toplevel_spec())); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); @@ -1747,8 +1897,7 @@ TEST_CASE ("version install qualified transitive", "[versionplan]") bp.v["b"] = {"1", 0}; bp.v["c"] = {"1", 0}; - auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"b"}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, var_provider, {{"b"}}, {}, toplevel_spec())); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}); @@ -1872,6 +2021,7 @@ TEST_CASE ("version install nonexisting features", "[versionplan]") auto install_plan = create_versioned_install_plan(vp, bp, {{"a", {"y"}}}); REQUIRE_FALSE(install_plan.has_value()); + REQUIRE(install_plan.error() == "error: a@1 does not have required feature y needed by toplevel-spec"); } TEST_CASE ("version install transitive missing features", "[versionplan]") @@ -1888,6 +2038,7 @@ TEST_CASE ("version install transitive missing features", "[versionplan]") auto install_plan = create_versioned_install_plan(vp, bp, {{"a", {}}}); REQUIRE_FALSE(install_plan.has_value()); + REQUIRE(install_plan.error() == "error: b@1 does not have required feature y needed by a:x86-windows@1"); } TEST_CASE ("version remove features during upgrade", "[versionplan]") @@ -1968,7 +2119,7 @@ TEST_CASE ("version install host tool", "[versionplan]") Dependency dep_a{"a"}; dep_a.host = true; - auto install_plan = create_versioned_install_plan(vp, bp, {dep_a}).value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, {dep_a})); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}); @@ -1976,7 +2127,7 @@ TEST_CASE ("version install host tool", "[versionplan]") } SECTION ("transitive 1") { - auto install_plan = create_versioned_install_plan(vp, bp, {{"b"}}).value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, {{"b"}})); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}); @@ -1991,7 +2142,7 @@ TEST_CASE ("version install host tool", "[versionplan]") Dependency dep_c{"c"}; dep_c.host = true; - auto install_plan = create_versioned_install_plan(vp, bp, {dep_c}).value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, {dep_c})); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}); @@ -2003,7 +2154,7 @@ TEST_CASE ("version install host tool", "[versionplan]") } SECTION ("self-reference") { - auto install_plan = create_versioned_install_plan(vp, bp, {{"d"}}).value_or_exit(VCPKG_LINE_INFO); + WITH_EXPECTED(install_plan, create_versioned_install_plan(vp, bp, {{"d"}})); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "d", {"1", 0}); diff --git a/src/vcpkg/base/strings.cpp b/src/vcpkg/base/strings.cpp index 191c6bdcf7..5b9e3af9c2 100644 --- a/src/vcpkg/base/strings.cpp +++ b/src/vcpkg/base/strings.cpp @@ -293,6 +293,20 @@ std::vector Strings::split(StringView s, const char delimiter) } } +std::vector Strings::split_keep_empty(StringView s, const char delimiter) +{ + std::vector output; + auto first = s.begin(); + const auto last = s.end(); + do + { + auto next = std::find_if(first, last, [=](const char c) { return c == delimiter; }); + output.emplace_back(first, next); + if (next == last) return output; + first = next + 1; + } while (1); +} + std::vector Strings::split_paths(StringView s) { #if defined(_WIN32) diff --git a/src/vcpkg/cmakevars.cpp b/src/vcpkg/cmakevars.cpp index 502091ac6f..4bd3bd5a9b 100644 --- a/src/vcpkg/cmakevars.cpp +++ b/src/vcpkg/cmakevars.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -339,9 +340,13 @@ endfunction() std::make_move_iterator(vars.front().end())); } - void TripletCMakeVarProvider::load_dep_info_vars(View specs, Triplet host_triplet) const + void TripletCMakeVarProvider::load_dep_info_vars(View original_specs, Triplet host_triplet) const { + std::vector specs = Util::filter(original_specs, [this](const PackageSpec& spec) { + return dep_resolution_vars.find(spec) == dep_resolution_vars.end(); + }); if (specs.size() == 0) return; + Debug::println("Loading dep info for: ", Strings::join(" ", specs)); std::vector>> vars(specs.size()); const auto file_path = create_dep_info_extraction_file(specs); if (specs.size() > 100) diff --git a/src/vcpkg/commands.ci-verify-versions.cpp b/src/vcpkg/commands.ci-verify-versions.cpp index f59236a217..7a2e33498c 100644 --- a/src/vcpkg/commands.ci-verify-versions.cpp +++ b/src/vcpkg/commands.ci-verify-versions.cpp @@ -227,8 +227,7 @@ namespace vcpkg::Commands::CIVerifyVersions return { msg::format(msgVersionVerifiedOK, - msg::package_name = port_name, - msg::version = entry.first.version, + msg::version_spec = Strings::concat(port_name, '@', entry.first.version), msg::commit_sha = entry.second), expected_left_tag, }; diff --git a/src/vcpkg/dependencies.cpp b/src/vcpkg/dependencies.cpp index fa249698f0..ca94575853 100644 --- a/src/vcpkg/dependencies.cpp +++ b/src/vcpkg/dependencies.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -1228,6 +1229,34 @@ namespace vcpkg namespace { + + /** + * vcpkg's Versioned Constraint Resolution Algorithm + * --- + * + * Phase 1: + * - Every spec not mentioned at top-level will have default features applied. + * - Every feature constraint from all applied versions will be applied. + * - If pinned, that version will be applied; otherwise the baseline version will be applied. + * - If a spec is not pinned, and a version constraint compares >= the baseline, that version will be applied. + * + * Phase 2: + * - Perform a postfix walk to serialize the plan. + * - Use the greatest version applied from Phase 1. + * - Use all features applied in Phase 1 that exist in the selected version. + * - Validate that every version constraint from the selected version is satisfied or pinned. + * - Validate that every feature constraint from the selected version is satisfied. + * - Validate that every spec in the plan is supported, applying the user's policy. + * - Validate that every feature in the plan is supported, applying the user's policy. + * + * (pinned means there is a matching override or overlay) + * + * Phase 1 does not depend on the order of evaluation. The implementation below exploits this to batch calls to + * CMake for calculationg dependency resolution tags. However, the results are sensitive to the definition of + * comparison. If "compares >= the baseline" changes, the set of considered constraints will change, and so will + * the results. + */ + struct VersionedPackageGraph { VersionedPackageGraph(const IVersionedPortfileProvider& ver_provider, @@ -1245,7 +1274,7 @@ namespace vcpkg void add_override(const std::string& name, const Version& v); - void add_roots(View dep, const PackageSpec& toplevel); + void solve_with_roots(View dep, const PackageSpec& toplevel); ExpectedL finalize_extract_plan(const PackageSpec& toplevel, UnsupportedPortAction unsupported_port_action); @@ -1260,419 +1289,294 @@ namespace vcpkg struct DepSpec { PackageSpec spec; - Version ver; + DependencyConstraint dc; std::vector features; }; - // This object contains the current version within a given version scheme (except for the "string" scheme, - // there we save an object for every version) - struct VersionSchemeInfo + struct PackageNodeData { - VersionScheme scheme; + // set of all scfls that have been considered + std::set considered; + + // Versions occluded by the baseline constraint are not considered. + SchemedVersion baseline; + // If overlay_or_override is true, ignore scheme and baseline_version + bool overlay_or_override = false; + // The current "best" scfl const SourceControlFileAndLocation* scfl = nullptr; - Version version; - // This tracks a list of constraint sources for debugging purposes - std::vector origins; - // mapping from feature name -> dependencies of this feature - std::map> deps; - bool is_less_than(const Version& new_ver) const; - }; + // This tracks a list of constraint sources for debugging purposes + std::set origins; - struct PackageNode - { - // Mapping from version to the newest version in the corresponding version scheme - // For example, given the versions: - // - "version-string": "1.0.0" - // - "version": "1.0.1" - // - "version": "1.0.2" - // you'd have a map: - // { - // "1.0.0": { "version-string": "1.0.0" }, - // "1.0.1": { "version": "1.0.2" }, - // "1.0.2": { "version": "1.0.2" } - // } - std::map vermap; - // We don't know how to compare "version-string" versions, so keep all the versions separately - std::map exacts; - // for each version type besides string (relaxed-semver, date), we only track the latest version - // required - Optional> relaxed_semver; - Optional> date; + // The set of features that have been requested across all constraints std::set requested_features; - bool default_features = true; - bool user_requested = false; - - VersionSchemeInfo* get_node(const Version& ver); - // Adds the version to the version resolver: - // - for string version schemes, just adds the newer version to the set - // - for non-string version schemes: - // - if the scheme doesn't exist in the set, adds the version to the set - // - if the scheme already exists in the set, and the version is newer than the existing entry, - // replaces the current entry for the scheme - VersionSchemeInfo& emplace_node(VersionScheme scheme, const Version& ver); - - PackageNode() = default; - PackageNode(const PackageNode&) = delete; - PackageNode(PackageNode&&) = default; - PackageNode& operator=(const PackageNode&) = delete; - PackageNode& operator=(PackageNode&&) = default; - - template - void foreach_vsi(F f) - { - if (auto r = this->relaxed_semver.get()) - { - f(**r); - } - if (auto d = this->date.get()) - { - f(**d); - } - for (auto&& vsi : this->exacts) - { - f(vsi.second); - } - } + bool default_features = false; }; - // the roots of the dependency graph (given in the manifest file) - std::vector m_roots; + using PackageNode = std::pair; + // mapping from portname -> version. "overrides" field in manifest file std::map m_overrides; + // direct dependencies in unevaluated form + std::vector m_roots; + // set of direct dependencies + std::set m_user_requested; // mapping from { package specifier -> node containing resolution information for that package } - std::map m_graph; + std::map m_graph; + // the set of nodes that could not be constructed in the graph due to failures + std::set m_failed_nodes; - std::pair& emplace_package(const PackageSpec& spec); + struct ConstraintFrame + { + PackageSpec spec; + View deps; + }; + std::vector m_resolve_stack; + + // Add an initial requirement for a package. + // Returns a reference to the node to place additional constraints + Optional require_package(const PackageSpec& spec, const std::string& origin); + + void require_scfl(PackageNode& ref, const SourceControlFileAndLocation* scfl, const std::string& origin); - // the following functions will add stuff recursively - void require_dependency(std::pair& ref, - const Dependency& dep, - const std::string& origin); - void require_port_version(std::pair& graph_entry, - const Version& ver, - const std::string& origin); - void require_port_feature(std::pair& ref, - const std::string& feature, - const std::string& origin); + void require_port_feature(PackageNode& ref, const std::string& feature, const std::string& origin); - void require_port_defaults(std::pair& ref, const std::string& origin); + void require_port_defaults(PackageNode& ref, const std::string& origin); - void add_feature_to(std::pair& ref, - VersionSchemeInfo& vsi, - const std::string& feature); + void resolve_stack(const ConstraintFrame& frame); + const CMakeVars::CMakeVars& batch_load_vars(const PackageSpec& spec); - ExpectedL dep_to_version(const std::string& name, const DependencyConstraint& dc); + Optional find_package(const PackageSpec& spec) const; + + // For node, for each requested feature existing in the best scfl, calculate the set of package and feature + // dependencies. + // The FeatureSpec list will contain a [core] entry for each package dependency. + // The FeatureSpec list will not contain [default]. + std::map> compute_feature_dependencies( + const PackageNode& node, std::vector& out_dep_specs) const; static LocalizedString format_incomparable_versions_message(const PackageSpec& on, StringView from, - const VersionSchemeInfo& current, - const VersionSchemeInfo& target); + const SchemedVersion& baseline, + const SchemedVersion& target); std::vector m_errors; }; - VersionedPackageGraph::VersionSchemeInfo& VersionedPackageGraph::PackageNode::emplace_node(VersionScheme scheme, - const Version& ver) + const CMakeVars::CMakeVars& VersionedPackageGraph::batch_load_vars(const PackageSpec& spec) { - auto it = vermap.find(ver); - if (it != vermap.end()) return *it->second; - - VersionSchemeInfo* vsi = nullptr; - if (scheme == VersionScheme::String) - { - vsi = &exacts[ver.text()]; - } - else if (scheme == VersionScheme::Relaxed || scheme == VersionScheme::Semver) + auto vars = m_var_provider.get_dep_info_vars(spec); + if (!vars) { - if (auto p = relaxed_semver.get()) - { - vsi = p->get(); - } - else - { - relaxed_semver = std::make_unique(); - vsi = relaxed_semver.get()->get(); - } - } - else if (scheme == VersionScheme::Date) - { - if (auto p = date.get()) - { - vsi = p->get(); - } - else + // We want to batch as many dep_infos as possible, so look ahead in the stack + std::unordered_set spec_set = {spec}; + for (auto&& s : m_resolve_stack) { - date = std::make_unique(); - vsi = date.get()->get(); + spec_set.insert(s.spec); + for (auto&& d : s.deps) + spec_set.insert({d.name, d.host ? m_host_triplet : s.spec.triplet()}); } + std::vector spec_vec(spec_set.begin(), spec_set.end()); + m_var_provider.load_dep_info_vars(spec_vec, m_host_triplet); + return m_var_provider.get_dep_info_vars(spec).value_or_exit(VCPKG_LINE_INFO); } - else - { - // not implemented - Checks::unreachable(VCPKG_LINE_INFO); - } - vsi->scheme = scheme; - vermap.emplace(ver, vsi); - return *vsi; - } - - VersionedPackageGraph::VersionSchemeInfo* VersionedPackageGraph::PackageNode::get_node(const Version& ver) - { - auto it = vermap.find(ver); - return it == vermap.end() ? nullptr : it->second; - } - - bool VersionedPackageGraph::VersionSchemeInfo::is_less_than(const Version& new_ver) const - { - Checks::check_exit(VCPKG_LINE_INFO, scfl); - ASSUME(scfl != nullptr); - auto s = scfl->source_control_file->core_paragraph->version_scheme; - auto r = compare_versions(s, version, s, new_ver); - Checks::check_exit(VCPKG_LINE_INFO, r != VerComp::unk); - return r == VerComp::lt; + return *vars.get(); } - void VersionedPackageGraph::add_feature_to(std::pair& ref, - VersionSchemeInfo& vsi, - const std::string& feature) + void VersionedPackageGraph::resolve_stack(const ConstraintFrame& frame) { - auto deps = vsi.scfl->source_control_file->find_dependencies_for_feature(feature); - if (!deps) + for (auto&& dep : frame.deps) { - // This version doesn't have this feature. This may result in an error during finalize if the - // constraint is not removed via upgrades. - return; - } - auto p = vsi.deps.emplace(feature, std::vector{}); - if (!p.second) - { - // This feature has already been handled - return; - } - - for (auto&& dep : *deps.get()) - { - PackageSpec dep_spec(dep.name, dep.host ? m_host_triplet : ref.first.triplet()); + if (!dep.platform.is_empty() && !dep.platform.evaluate(batch_load_vars(frame.spec))) continue; - if (!dep.platform.is_empty()) + PackageSpec dep_spec(dep.name, dep.host ? m_host_triplet : frame.spec.triplet()); + auto maybe_node = require_package(dep_spec, frame.spec.name()); + if (auto node = maybe_node.get()) { - auto maybe_vars = m_var_provider.get_dep_info_vars(ref.first); - if (!maybe_vars) + // If the node is overlayed or overridden, don't apply version constraints + // If the baseline is a version_string, it occludes other constraints + if (!node->second.overlay_or_override) { - m_var_provider.load_dep_info_vars({&ref.first, 1}, m_host_triplet); - maybe_vars = m_var_provider.get_dep_info_vars(ref.first); + const auto maybe_dep_ver = dep.constraint.try_get_minimum_version(); + if (auto dep_ver = maybe_dep_ver.get()) + { + auto maybe_scfl = m_ver_provider.get_control_file({dep.name, *dep_ver}); + if (auto p_scfl = maybe_scfl.get()) + { + const auto sver = p_scfl->schemed_version(); + if (compare_versions(node->second.scfl->schemed_version(), sver) == VerComp::lt) + { + // mark as current best and apply constraints + node->second.scfl = p_scfl; + require_scfl(*node, p_scfl, frame.spec.name()); + } + else if (compare_versions(node->second.baseline, sver) == VerComp::lt) + { + // apply constraints + require_scfl(*node, p_scfl, frame.spec.name()); + } + } + } } - if (!dep.platform.evaluate(maybe_vars.value_or_exit(VCPKG_LINE_INFO))) + // apply selected features + for (auto&& f : dep.features) { - continue; + if (f == "default") abort(); + require_port_feature(*node, f, frame.spec.name()); } - } - - auto& dep_node = emplace_package(dep_spec); - if (dep_spec == ref.first) - { - // this is a feature dependency for oneself - for (auto&& f : dep.features) + if (Util::find(dep.features, StringView{"core"}) == dep.features.end()) { - require_port_feature(ref, f, ref.first.name()); + require_port_defaults(*node, frame.spec.name()); } } - else - { - require_dependency(dep_node, dep, ref.first.name()); - } - - p.first->second.emplace_back(dep_spec, "core"); - for (auto&& f : dep.features) - { - p.first->second.emplace_back(dep_spec, f); - } } } - void VersionedPackageGraph::require_dependency(std::pair& ref, - const Dependency& dep, - const std::string& origin) + void VersionedPackageGraph::require_port_defaults(PackageNode& ref, const std::string& origin) { - const auto maybe_overlay = m_o_provider.get_control_file(ref.first.name()); - if (auto p_overlay = maybe_overlay.get()) - { - const auto overlay_version = p_overlay->source_control_file->to_version(); - require_port_version(ref, overlay_version, origin); - } - else if (const auto over_it = m_overrides.find(ref.first.name()); over_it != m_overrides.end()) - { - require_port_version(ref, over_it->second, origin); - } - else + ref.second.origins.insert(origin); + if (!ref.second.default_features) { - const auto base_ver = m_base_provider.get_baseline_version(dep.name); - const auto dep_ver = dep.constraint.try_get_minimum_version(); - - if (auto dv = dep_ver.get()) - { - require_port_version(ref, *dv, origin); - } + ref.second.default_features = true; - if (auto bv = base_ver.get()) + auto scfls = ref.second.considered; + for (auto scfl : scfls) { - require_port_version(ref, *bv, origin); + for (auto&& f : scfl->source_control_file->core_paragraph->default_features) + { + auto deps = scfl->source_control_file->find_dependencies_for_feature(f); + if (!deps) continue; + m_resolve_stack.push_back({ref.first, *deps.get()}); + } } } - - for (auto&& f : dep.features) - { - require_port_feature(ref, f, origin); - } - - if (Util::find(dep.features, StringView{"core"}) == dep.features.end()) - { - require_port_defaults(ref, origin); - } } - void VersionedPackageGraph::require_port_version(std::pair& graph_entry, - const Version& version, + void VersionedPackageGraph::require_port_feature(PackageNode& ref, + const std::string& feature, const std::string& origin) { - // if this port is an overlay port, ignore the given version and use the version from the overlay - auto maybe_overlay = m_o_provider.get_control_file(graph_entry.first.name()); - const vcpkg::SourceControlFileAndLocation* p_scfl = maybe_overlay.get(); - if (p_scfl) + if (feature == "default") { - const auto overlay_version = p_scfl->source_control_file->to_version(); - // If the original request did not match the overlay version, restart this function to operate on the - // overlay version - if (version != overlay_version) - { - require_port_version(graph_entry, overlay_version, origin); - return; - } + return require_port_defaults(ref, origin); } - else + ref.second.origins.insert(origin); + auto inserted = ref.second.requested_features.emplace(feature).second; + if (inserted) { - // if there is a override, ignore the given version and use the version from the override - auto over_it = m_overrides.find(graph_entry.first.name()); - if (over_it != m_overrides.end() && over_it->second != version) - { - require_port_version(graph_entry, over_it->second, origin); - return; - } - - auto maybe_scfl = m_ver_provider.get_control_file({graph_entry.first.name(), version}); - p_scfl = maybe_scfl.get(); - if (!p_scfl) + auto scfls = ref.second.considered; + for (auto scfl : scfls) { - m_errors.push_back(std::move(maybe_scfl).error()); - return; + auto deps = scfl->source_control_file->find_dependencies_for_feature(feature); + if (!deps) continue; + m_resolve_stack.push_back({ref.first, *deps.get()}); } } + } + void VersionedPackageGraph::require_scfl(PackageNode& ref, + const SourceControlFileAndLocation* scfl, + const std::string& origin) + { + ref.second.origins.insert(origin); - auto& versioned_graph_entry = - graph_entry.second.emplace_node(p_scfl->source_control_file->core_paragraph->version_scheme, version); - versioned_graph_entry.origins.push_back(origin); - // Use the new source control file if we currently don't have one or the new one is newer - bool replace; - if (versioned_graph_entry.scfl == nullptr) - { - replace = true; - } - else if (versioned_graph_entry.scfl == p_scfl) - { - replace = false; - } - else + if (ref.second.considered.find(scfl) != ref.second.considered.end()) return; + ref.second.considered.insert(scfl); + + auto features = ref.second.requested_features; + if (ref.second.default_features) { - replace = versioned_graph_entry.is_less_than(version); + const auto& defaults = ref.second.scfl->source_control_file->core_paragraph->default_features; + features.insert(defaults.begin(), defaults.end()); } - if (replace) + m_resolve_stack.push_back({ref.first, scfl->source_control_file->core_paragraph->dependencies}); + for (auto&& f : features) { - versioned_graph_entry.scfl = p_scfl; - versioned_graph_entry.version = p_scfl->source_control_file->to_version(); - versioned_graph_entry.deps.clear(); - - // add all dependencies to the graph - add_feature_to(graph_entry, versioned_graph_entry, "core"); - - for (auto&& f : graph_entry.second.requested_features) - { - add_feature_to(graph_entry, versioned_graph_entry, f); - } - - if (graph_entry.second.default_features) + auto deps = ref.second.scfl->source_control_file->find_dependencies_for_feature(f); + if (!deps) { - for (auto&& f : p_scfl->source_control_file->core_paragraph->default_features) - { - add_feature_to(graph_entry, versioned_graph_entry, f); - } + // This version doesn't have this feature. + return; } + m_resolve_stack.push_back({ref.first, *deps.get()}); } } - void VersionedPackageGraph::require_port_defaults(std::pair& ref, - const std::string& origin) + Optional VersionedPackageGraph::find_package( + const PackageSpec& spec) const { - (void)origin; - if (!ref.second.default_features) - { - ref.second.default_features = true; - ref.second.foreach_vsi([this, &ref](VersionSchemeInfo& vsi) { - if (vsi.scfl) - { - for (auto&& f : vsi.scfl->source_control_file->core_paragraph->default_features) - { - this->add_feature_to(ref, vsi, f); - } - } - }); - } + auto it = m_graph.find(spec); + if (it == m_graph.end()) return nullopt; + return *it; } - void VersionedPackageGraph::require_port_feature(std::pair& ref, - const std::string& feature, - const std::string& origin) + + Optional VersionedPackageGraph::require_package(const PackageSpec& spec, + const std::string& origin) { - if (feature == "default") + auto it = m_graph.find(spec); + if (it != m_graph.end()) { - return require_port_defaults(ref, origin); + it->second.origins.insert(origin); + return *it; } - auto inserted = ref.second.requested_features.emplace(feature).second; - if (inserted) + + if (m_failed_nodes.find(spec.name()) != m_failed_nodes.end()) { - ref.second.foreach_vsi( - [this, &ref, &feature](VersionSchemeInfo& vsi) { this->add_feature_to(ref, vsi, feature); }); + return nullopt; } - (void)origin; - } - std::pair& VersionedPackageGraph::emplace_package( - const PackageSpec& spec) - { - return *m_graph.emplace(spec, PackageNode{}).first; - } - - ExpectedL VersionedPackageGraph::dep_to_version(const std::string& name, - const DependencyConstraint& dc) - { - auto maybe_overlay = m_o_provider.get_control_file(name); + const auto maybe_overlay = m_o_provider.get_control_file(spec.name()); if (auto p_overlay = maybe_overlay.get()) { - return p_overlay->source_control_file->to_version(); + it = m_graph.emplace(spec, PackageNodeData{}).first; + it->second.overlay_or_override = true; + it->second.scfl = p_overlay; } - - auto over_it = m_overrides.find(name); - if (over_it != m_overrides.end()) + else { - return over_it->second; + Version ver; + if (const auto over_it = m_overrides.find(spec.name()); over_it != m_overrides.end()) + { + auto maybe_scfl = m_ver_provider.get_control_file({spec.name(), over_it->second}); + if (auto p_scfl = maybe_scfl.get()) + { + it = m_graph.emplace(spec, PackageNodeData{}).first; + it->second.overlay_or_override = true; + it->second.scfl = p_scfl; + } + else + { + m_errors.push_back(std::move(maybe_scfl).error()); + m_failed_nodes.insert(spec.name()); + return nullopt; + } + } + else + { + auto maybe_scfl = m_base_provider.get_baseline_version(spec.name()).then([&](const Version& ver) { + return m_ver_provider.get_control_file({spec.name(), ver}); + }); + if (auto p_scfl = maybe_scfl.get()) + { + it = m_graph.emplace(spec, PackageNodeData{}).first; + it->second.baseline = p_scfl->schemed_version(); + it->second.scfl = p_scfl; + } + else + { + m_errors.push_back(std::move(maybe_scfl).error()); + m_failed_nodes.insert(spec.name()); + return nullopt; + } + } } - auto maybe_cons = dc.try_get_minimum_version(); - if (auto p = maybe_cons.get()) - { - return std::move(*p); - } + // Implicit defaults are disabled if spec has been mentioned at top-level. + // Note that if top-level doesn't also mark that reference as `[core]`, defaults will be re-engaged. + it->second.default_features = m_user_requested.find(spec) == m_user_requested.end(); + it->second.requested_features.insert("core"); - return m_base_provider.get_baseline_version(name); + require_scfl(*it, it->second.scfl, origin); + return *it; } void VersionedPackageGraph::add_override(const std::string& name, const Version& v) @@ -1680,7 +1584,7 @@ namespace vcpkg m_overrides.emplace(name, v); } - void VersionedPackageGraph::add_roots(View deps, const PackageSpec& toplevel) + void VersionedPackageGraph::solve_with_roots(View deps, const PackageSpec& toplevel) { auto dep_to_spec = [&toplevel, this](const Dependency& d) { return PackageSpec{d.name, d.host ? m_host_triplet : toplevel.triplet()}; @@ -1689,143 +1593,114 @@ namespace vcpkg specs.push_back(toplevel); Util::sort_unique_erase(specs); - m_var_provider.load_dep_info_vars(specs, m_host_triplet); - const auto& vars = m_var_provider.get_dep_info_vars(toplevel).value_or_exit(VCPKG_LINE_INFO); - std::vector active_deps; - - // First add all top level packages to ensure the default_features is set to false before recursing into the - // individual packages. Otherwise, a case like: - // A -> B, C[core] - // B -> C - // could install the default features of C. (A is the manifest/vcpkg.json) for (auto&& dep : deps) { - if (!dep.platform.evaluate(vars)) continue; - - active_deps.push_back(&dep); - - // Disable default features for deps with [core] as a dependency - // Note: x[core], x[y] will still eventually depend on defaults due to the second x[y] - if (Util::find(dep.features, "core") != dep.features.end()) + if (!dep.platform.is_empty() && + !dep.platform.evaluate(m_var_provider.get_or_load_dep_info_vars(toplevel, m_host_triplet))) { - auto& node = emplace_package(dep_to_spec(dep)); - node.second.default_features = false; + continue; } + + auto spec = dep_to_spec(dep); + m_user_requested.insert(spec); + m_roots.push_back(DepSpec{std::move(spec), dep.constraint, dep.features}); } - for (auto pdep : active_deps) + m_resolve_stack.push_back({toplevel, deps}); + + while (!m_resolve_stack.empty()) { - const auto& dep = *pdep; - auto spec = dep_to_spec(dep); + ConstraintFrame frame = std::move(m_resolve_stack.back()); + m_resolve_stack.pop_back(); + // Frame must be passed as a local because resolve_stack() will add new elements to m_resolve_stack + resolve_stack(frame); + } + } - auto& node = emplace_package(spec); - node.second.user_requested = true; + LocalizedString VersionedPackageGraph::format_incomparable_versions_message(const PackageSpec& on, + StringView from, + const SchemedVersion& baseline, + const SchemedVersion& target) + { + LocalizedString doc = msg::format_error(msgVersionIncomparable1, + msg::spec = on, + msg::constraint_origin = from, + msg::expected = target.version, + msg::actual = baseline.version) + .append_raw("\n\n"); + if (baseline.scheme == VersionScheme::String && target.scheme == VersionScheme::String) + { + doc.append(msgVersionIncomparableSchemeString).append_raw("\n\n"); + } + else + { + doc.append(msgVersionIncomparableSchemes).append_raw('\n'); + doc.append_indent() + .append(msgVersionIncomparable2, + msg::version_spec = Strings::concat(on.name(), '@', baseline.version), + msg::new_scheme = baseline.scheme) + .append_raw('\n'); + doc.append_indent() + .append(msgVersionIncomparable2, + msg::version_spec = Strings::concat(on.name(), '@', target.version), + msg::new_scheme = target.scheme) + .append_raw("\n\n"); + } + doc.append(msgVersionIncomparable3).append_raw("\n\n"); + doc.append_indent().append_raw("\"overrides\": [\n"); + doc.append_indent(2) + .append_raw(fmt::format(R"({{ "name": "{}", "version": "{}" }})", on.name(), baseline.version)) + .append_raw('\n'); + doc.append_indent().append_raw("]\n\n"); + doc.append(msgVersionIncomparable4, msg::url = docs::versioning_url); + return doc; + } - auto maybe_overlay = m_o_provider.get_control_file(dep.name); - auto over_it = m_overrides.find(dep.name); - if (auto p_overlay = maybe_overlay.get()) - { - const auto ver = p_overlay->source_control_file->to_version(); - m_roots.push_back(DepSpec{spec, ver, dep.features}); - require_port_version(node, ver, toplevel.name()); - } - else if (over_it != m_overrides.end()) - { - m_roots.push_back(DepSpec{spec, over_it->second, dep.features}); - require_port_version(node, over_it->second, toplevel.name()); - } - else + std::map> VersionedPackageGraph::compute_feature_dependencies( + const PackageNode& node, std::vector& out_dep_specs) const + { + std::map> feature_deps; + std::set all_features = node.second.requested_features; + if (node.second.default_features) + { + const auto& f = node.second.scfl->source_control_file->core_paragraph->default_features; + all_features.insert(f.begin(), f.end()); + } + std::vector fspecs; + for (auto&& f : all_features) + { + auto maybe_fdeps = node.second.scfl->source_control_file->find_dependencies_for_feature(f); + if (auto fdeps = maybe_fdeps.get()) { - const auto dep_ver = dep.constraint.try_get_minimum_version(); - const auto base_ver = m_base_provider.get_baseline_version(dep.name); - if (auto p_dep_ver = dep_ver.get()) + fspecs.clear(); + for (auto&& fdep : *fdeps) { - m_roots.push_back(DepSpec{spec, *p_dep_ver, dep.features}); - if (auto p_base_ver = base_ver.get()) - { - // Compare version constraint with baseline to only evaluate the "tighter" constraint - auto dep_scfl = m_ver_provider.get_control_file({dep.name, *p_dep_ver}); - auto base_scfl = m_ver_provider.get_control_file({dep.name, *p_base_ver}); - if (dep_scfl && base_scfl) - { - auto r = compare_versions( - dep_scfl.get()->source_control_file->core_paragraph->version_scheme, - *p_dep_ver, - base_scfl.get()->source_control_file->core_paragraph->version_scheme, - *p_base_ver); - if (r == VerComp::lt) - { - require_port_version(node, *p_base_ver, "baseline"); - require_port_version(node, *p_dep_ver, toplevel.name()); - } - else - { - require_port_version(node, *p_dep_ver, toplevel.name()); - require_port_version(node, *p_base_ver, "baseline"); - } - } - else - { - if (!dep_scfl) m_errors.push_back(dep_scfl.error()); - if (!base_scfl) m_errors.push_back(base_scfl.error()); - } - } - else + PackageSpec fspec{fdep.name, fdep.host ? m_host_triplet : node.first.triplet()}; + + // Ignore intra-package dependencies + if (fspec == node.first) continue; + + if (!fdep.platform.is_empty() && + !fdep.platform.evaluate( + m_var_provider.get_or_load_dep_info_vars(node.first, m_host_triplet))) { - require_port_version(node, *p_dep_ver, toplevel.name()); + continue; } - } - else if (auto p_base_ver = base_ver.get()) - { - m_roots.push_back(DepSpec{spec, *p_base_ver, dep.features}); - require_port_version(node, *p_base_ver, toplevel.name()); - } - else - { - m_errors.push_back(msg::format( - msgVersionConstraintUnresolvable, msg::package_name = dep.name, msg::spec = toplevel)); - } - } - for (auto&& f : dep.features) - { - require_port_feature(node, f, toplevel.name()); + fspecs.emplace_back(fspec, "core"); + for (auto&& g : fdep.features) + fspecs.emplace_back(fspec, g); + out_dep_specs.push_back({std::move(fspec), fdep.constraint, fdep.features}); + } + Util::sort_unique_erase(fspecs); + feature_deps.emplace(f, fspecs); } } - } - - LocalizedString VersionedPackageGraph::format_incomparable_versions_message(const PackageSpec& on, - StringView from, - const VersionSchemeInfo& current, - const VersionSchemeInfo& target) - { - return msg::format_error(msgVersionIncomparable1, - msg::spec = on, - msg::package_name = from, - msg::expected = target.version, - msg::actual = current.version) - .append_raw('\n') - .append_indent() - .append(msgVersionIncomparable2, msg::version = current.version, msg::new_scheme = current.scheme) - .append_raw('\n') - .append_indent() - .append(msgVersionIncomparable2, msg::version = target.version, msg::new_scheme = target.scheme) - .append_raw('\n') - .append(msgVersionIncomparable3) - .append_raw('\n') - .append_indent() - .append_raw("\"overrides\": [\n") - .append_indent(2) - .append_raw(fmt::format(R"({{ "name": "{}", "version": "{}" }})", on.name(), current.version)) - .append_raw('\n') - .append_indent() - .append_raw("]\n") - .append(msgVersionIncomparable4); + return feature_deps; } // This function is called after all versioning constraints have been resolved. It is responsible for - // serializing out the final execution graph and performing all final validations (such as all required - // features being selected and present) + // serializing out the final execution graph and performing all final validations. ExpectedL VersionedPackageGraph::finalize_extract_plan( const PackageSpec& toplevel, UnsupportedPortAction unsupported_port_action) { @@ -1837,8 +1712,8 @@ namespace vcpkg ActionPlan ret; - // second == nullptr means "in progress" - std::map emitted; + // second == false means "in progress" + std::map emitted; struct Frame { InstallPlanAction ipa; @@ -1847,181 +1722,94 @@ namespace vcpkg std::vector stack; // Adds a new Frame to the stack if the spec was not already added - auto push = [&emitted, this, &stack, unsupported_port_action, &ret]( - const PackageSpec& spec, - const Version& new_ver, - const PackageSpec& origin, - View features) -> Optional { - auto&& node = emplace_package(spec).second; - auto overlay = m_o_provider.get_control_file(spec.name()); - auto over_it = m_overrides.find(spec.name()); - - VersionedPackageGraph::VersionSchemeInfo* p_vnode; - if (auto p_overlay = overlay.get()) - p_vnode = node.get_node(p_overlay->source_control_file->to_version()); - else if (over_it != m_overrides.end()) - p_vnode = node.get_node(over_it->second); - else - p_vnode = node.get_node(new_ver); - - if (!p_vnode) + auto push = [&emitted, this, &stack](const DepSpec& dep, StringView origin) -> ExpectedL { + auto p = emitted.emplace(dep.spec, false); + // Dependency resolution should have ensured that either every node exists OR an error should have been + // logged to m_errors + const auto& node = find_package(dep.spec).value_or_exit(VCPKG_LINE_INFO); + + // Evaluate the >=version constraint (if any) + auto maybe_min = dep.dc.try_get_minimum_version(); + if (!node.second.overlay_or_override && maybe_min) { - return msg::format_error( - msgVersionNotFoundDuringDiscovery, msg::spec = spec, msg::version = new_ver); - } - - { // use if(init;condition) if we support c++17 - const auto& supports_expr = p_vnode->scfl->source_control_file->core_paragraph->supports_expression; - if (!supports_expr.is_empty()) + // Dependency resolution should have already logged any errors retrieving the scfl + const auto& dep_scfl = m_ver_provider.get_control_file({dep.spec.name(), *maybe_min.get()}) + .value_or_exit(VCPKG_LINE_INFO); + const auto constraint_sver = dep_scfl.schemed_version(); + const auto selected_sver = node.second.scfl->schemed_version(); + auto r = compare_versions(selected_sver, constraint_sver); + if (r == VerComp::unk) { - if (!supports_expr.evaluate(m_var_provider.get_or_load_dep_info_vars(spec, m_host_triplet))) - { - FeatureSpec feature_spec(spec, "core"); - - if (unsupported_port_action == UnsupportedPortAction::Error) - { - return msg::format_error(create_unsupported_message( - msgUnsupportedFeatureSupportsExpression, feature_spec, supports_expr)); - } - - ret.unsupported_features.insert({FeatureSpec(spec, "core"), supports_expr}); - } + // In the error message, we report the baseline version instead of the "best selected" version + // to give the user simpler data to work with. + return format_incomparable_versions_message( + dep.spec, origin, node.second.baseline, constraint_sver); } + Checks::check_exit( + VCPKG_LINE_INFO, + r != VerComp::lt, + "Dependency resolution failed to consider a constraint. This is an internal error."); } - for (auto&& f : features) + // Evaluate feature constraints (if any) + for (auto&& f : dep.features) { if (f == "core") continue; if (f == "default") continue; - auto feature = p_vnode->scfl->source_control_file->find_feature(f); + auto feature = node.second.scfl->source_control_file->find_feature(f); if (!feature) { - return msg::format_error(msgVersionMissingRequiredFeature, - msg::spec = spec, - msg::version = new_ver, - msg::feature = f); - } - - const auto& supports_expr = feature.get()->supports_expression; - if (!supports_expr.is_empty()) - { - if (!supports_expr.evaluate(m_var_provider.get_or_load_dep_info_vars(spec, m_host_triplet))) - { - if (unsupported_port_action == UnsupportedPortAction::Error) - { - const auto feature_spec_text = format_name_only_feature_spec(spec.name(), f); - const auto supports_expression_text = to_string(supports_expr); - return msg::format_error(msgUnsupportedFeatureSupportsExpression, - msg::package_name = spec.name(), - msg::feature_spec = feature_spec_text, - msg::supports_expression = supports_expression_text, - msg::triplet = spec.triplet()); - } - - ret.unsupported_features.emplace(FeatureSpec{spec, f}, supports_expr); - } + return msg::format_error( + msgVersionMissingRequiredFeature, + msg::version_spec = Strings::concat(dep.spec.name(), '@', node.second.scfl->to_version()), + msg::feature = f, + msg::constraint_origin = origin); } } - auto p = emitted.emplace(spec, nullptr); if (p.second) { - // Newly inserted - if (!overlay && over_it == m_overrides.end()) - { - // Not overridden -- Compare against baseline - if (auto baseline = m_base_provider.get_baseline_version(spec.name())) - { - if (auto base_node = node.get_node(*baseline.get())) - { - if (base_node != p_vnode) - { - return format_incomparable_versions_message(spec, "baseline", *p_vnode, *base_node); - } - } - } - } - - // -> Add stack frame - auto maybe_vars = m_var_provider.get_dep_info_vars(spec); - - InstallPlanAction ipa(spec, - *p_vnode->scfl, - node.user_requested ? RequestType::USER_REQUESTED - : RequestType::AUTO_SELECTED, + // Newly inserted -> Add stack frame + std::vector deps; + RequestType request = m_user_requested.find(dep.spec) != m_user_requested.end() + ? RequestType::USER_REQUESTED + : RequestType::AUTO_SELECTED; + InstallPlanAction ipa(dep.spec, + *node.second.scfl, + request, m_host_triplet, - std::move(p_vnode->deps), + compute_feature_dependencies(node, deps), {}); - std::vector deps; - for (auto&& f : ipa.feature_list) - { - if (auto maybe_deps = - p_vnode->scfl->source_control_file->find_dependencies_for_feature(f).get()) - { - for (auto&& dep : *maybe_deps) - { - PackageSpec dep_spec(dep.name, dep.host ? m_host_triplet : spec.triplet()); - if (dep_spec == spec) continue; - - if (!dep.platform.is_empty() && - !dep.platform.evaluate(maybe_vars.value_or_exit(VCPKG_LINE_INFO))) - { - continue; - } - auto maybe_cons = dep_to_version(dep.name, dep.constraint); - - if (auto cons = maybe_cons.get()) - { - deps.emplace_back(DepSpec{std::move(dep_spec), std::move(*cons), dep.features}); - } - else - { - return msg::format_error(msgVersionConstraintUnresolvable, - msg::package_name = dep.name, - msg::spec = spec); - } - } - } - } stack.push_back(Frame{std::move(ipa), std::move(deps)}); - return nullopt; } - else + else if (p.first->second == false) { - // spec already present in map - if (p.first->second == nullptr) - { - return msg::format_error(msgCycleDetectedDuring, msg::spec = spec) - .append_raw('\n') - .append_raw(Strings::join( - "\n", stack, [](const auto& p) -> const PackageSpec& { return p.ipa.spec; })); - } - else if (p.first->second != p_vnode) - { - // comparable versions should retrieve the same info node - return format_incomparable_versions_message( - spec, origin.to_string(), *p_vnode, *p.first->second); - } - return nullopt; + return msg::format_error(msgCycleDetectedDuring, msg::spec = dep.spec) + .append_raw('\n') + .append_raw(Strings::join("\n", stack, [](const Frame& p) { + return Strings::concat( + p.ipa.spec, + '@', + p.ipa.source_control_file_and_location.value_or_exit(VCPKG_LINE_INFO).to_version()); + })); } + return Unit{}; }; for (auto&& root : m_roots) { - if (auto err = push(root.spec, root.ver, toplevel, root.features)) + auto x = push(root, toplevel.name()); + if (!x.has_value()) { - return std::move(*err.get()); + return std::move(x).error(); } - while (stack.size() > 0) + while (!stack.empty()) { auto& back = stack.back(); if (back.deps.empty()) { - emitted[back.ipa.spec] = - emplace_package(back.ipa.spec) - .second.get_node( - back.ipa.source_control_file_and_location.get()->source_control_file->to_version()); + emitted[back.ipa.spec] = true; ret.install_actions.push_back(std::move(back.ipa)); stack.pop_back(); } @@ -2029,13 +1817,69 @@ namespace vcpkg { auto dep = std::move(back.deps.back()); back.deps.pop_back(); - if (auto err = push(dep.spec, dep.ver, back.ipa.spec, dep.features)) + const auto origin = Strings::concat( + back.ipa.spec, + "@", + back.ipa.source_control_file_and_location.value_or_exit(VCPKG_LINE_INFO).to_version()); + x = push(dep, origin); + if (!x.has_value()) { - return std::move(*err.get()); + return std::move(x).error(); } } } } + + // Because supports expressions are commonplace, we assume that all dep info will be needed + m_var_provider.load_dep_info_vars( + Util::fmap(ret.install_actions, [](const InstallPlanAction& a) { return a.spec; }), m_host_triplet); + + // Evaluate supports over the produced plan + for (auto&& action : ret.install_actions) + { + const auto& scfl = action.source_control_file_and_location.value_or_exit(VCPKG_LINE_INFO); + const auto& vars = m_var_provider.get_or_load_dep_info_vars(action.spec, m_host_triplet); + // Evaluate core supports condition + const auto& supports_expr = scfl.source_control_file->core_paragraph->supports_expression; + if (!supports_expr.evaluate(vars)) + { + ret.unsupported_features.insert({FeatureSpec(action.spec, "core"), supports_expr}); + } + + // Evaluate per-feature supports conditions + for (auto&& fdeps : action.feature_dependencies) + { + if (fdeps.first == "core") continue; + + auto fpgh = scfl.source_control_file->find_feature(fdeps.first).value_or_exit(VCPKG_LINE_INFO); + if (!fpgh.supports_expression.evaluate(vars)) + { + ret.unsupported_features.insert({FeatureSpec(action.spec, fdeps.first), supports_expr}); + } + } + } + + if (unsupported_port_action == UnsupportedPortAction::Error && !ret.unsupported_features.empty()) + { + LocalizedString msg; + for (auto&& f : ret.unsupported_features) + { + if (!msg.empty()) msg.append_raw("\n"); + + const auto feature_spec = + f.first.feature() == "core" + ? f.first.spec().name() + : format_name_only_feature_spec(f.first.spec().name(), f.first.feature()); + + msg.append(msgUnsupportedFeatureSupportsExpression, + msg::package_name = f.first.spec().name(), + msg::feature_spec = feature_spec, + msg::supports_expression = to_string(f.second), + msg::triplet = f.first.spec().triplet()); + } + + return msg; + } return ret; } } @@ -2056,7 +1900,7 @@ namespace vcpkg vpg.add_override(o.name, {o.version, o.port_version}); } - vpg.add_roots(deps, toplevel); + vpg.solve_with_roots(deps, toplevel); return vpg.finalize_extract_plan(toplevel, unsupported_port_action); } } diff --git a/src/vcpkg/registries.cpp b/src/vcpkg/registries.cpp index 95c9d389aa..d84fcac4af 100644 --- a/src/vcpkg/registries.cpp +++ b/src/vcpkg/registries.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -880,7 +881,7 @@ namespace error_msg.append_indent().append_raw(version.to_string()).append_raw('\n'); } - error_msg.append(msgVersionIncomparable4); + error_msg.append(msgVersionIncomparable4, msg::url = docs::versioning_url); return error_msg; } diff --git a/src/vcpkg/versions.cpp b/src/vcpkg/versions.cpp index 91658f319d..ee079b4252 100644 --- a/src/vcpkg/versions.cpp +++ b/src/vcpkg/versions.cpp @@ -401,6 +401,11 @@ namespace vcpkg return base == VerComp::eq ? integer_vercomp(a, b) : base; } + VerComp compare_versions(const SchemedVersion& a, const SchemedVersion& b) + { + return compare_versions(a.scheme, a.version, b.scheme, b.version); + } + 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());