diff --git a/docs/vcpkg-schema-definitions.schema.json b/docs/vcpkg-schema-definitions.schema.json index 399d7c1e4d..a098e2713a 100644 --- a/docs/vcpkg-schema-definitions.schema.json +++ b/docs/vcpkg-schema-definitions.schema.json @@ -211,7 +211,7 @@ "features": { "type": "array", "items": { - "$ref": "#/definitions/identifier" + "$ref": "#/definitions/dependency-feature" } }, "host": { @@ -239,6 +239,33 @@ }, "additionalProperties": false }, + "dependency-feature-object": { + "description": "Expanded form of a dependency feature with platform expression.", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/identifier" + }, + "platform": { + "$ref": "#/definitions/platform-expression" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "dependency-feature": { + "description": "A feature", + "oneOf": [ + { + "$ref": "#/definitions/identifier" + }, + { + "$ref": "#/definitions/dependency-feature-object" + } + ] + }, "description-field": { "description": "A string or array of strings containing the description of a package or feature.", "$ref": "#/definitions/string-or-strings" diff --git a/docs/vcpkg.schema.json b/docs/vcpkg.schema.json index 9d774ef9a3..0d32d81484 100644 --- a/docs/vcpkg.schema.json +++ b/docs/vcpkg.schema.json @@ -84,7 +84,7 @@ "description": "Features enabled by default with the package.", "type": "array", "items": { - "$ref": "vcpkg-schema-definitions.schema.json#/definitions/identifier" + "$ref": "vcpkg-schema-definitions.schema.json#/definitions/dependency-feature" } }, "supports": { diff --git a/include/vcpkg/base/message-data.inc.h b/include/vcpkg/base/message-data.inc.h index 3057caf3ab..e3bcf071e2 100644 --- a/include/vcpkg/base/message-data.inc.h +++ b/include/vcpkg/base/message-data.inc.h @@ -4,6 +4,7 @@ DECLARE_MESSAGE(ABoolean, (), "", "a boolean") DECLARE_MESSAGE(ABuiltinRegistry, (), "", "a builtin registry") DECLARE_MESSAGE(AConfigurationObject, (), "", "a configuration object") DECLARE_MESSAGE(ADependency, (), "", "a dependency") +DECLARE_MESSAGE(ADependencyFeature, (), "", "a feature in a dependency") DECLARE_MESSAGE(ADemandObject, (), "'demands' are a concept in the schema of a JSON file the user can edit", @@ -124,6 +125,7 @@ DECLARE_MESSAGE(AnArtifactsGitRegistryUrl, (), "", "an artifacts git registry UR DECLARE_MESSAGE(AnArtifactsRegistry, (), "", "an artifacts registry") DECLARE_MESSAGE(AnArrayOfDependencies, (), "", "an array of dependencies") DECLARE_MESSAGE(AnArrayOfDependencyOverrides, (), "", "an array of dependency overrides") +DECLARE_MESSAGE(AnArrayOfFeatures, (), "", "an array of features") DECLARE_MESSAGE(AnArrayOfIdentifers, (), "", "an array of identifiers") DECLARE_MESSAGE(AnArrayOfOverlayPaths, (), "", "an array of overlay paths") DECLARE_MESSAGE(AnArrayOfOverlayTripletsPaths, (), "", "an array of overlay triplets paths") diff --git a/include/vcpkg/binaryparagraph.h b/include/vcpkg/binaryparagraph.h index 87ffceef57..ca7840e94f 100644 --- a/include/vcpkg/binaryparagraph.h +++ b/include/vcpkg/binaryparagraph.h @@ -14,6 +14,7 @@ namespace vcpkg BinaryParagraph(); explicit BinaryParagraph(Paragraph fields); BinaryParagraph(const SourceParagraph& spgh, + const std::vector& default_features, Triplet triplet, const std::string& abi_tag, std::vector deps); diff --git a/include/vcpkg/dependencies.h b/include/vcpkg/dependencies.h index 7682a42de1..303d5c493c 100644 --- a/include/vcpkg/dependencies.h +++ b/include/vcpkg/dependencies.h @@ -70,7 +70,8 @@ namespace vcpkg const RequestType& request_type, Triplet host_triplet, std::map>&& dependencies, - std::vector&& build_failure_messages); + std::vector&& build_failure_messages, + std::vector default_features); const std::string& public_abi() const; bool has_package_abi() const; @@ -80,6 +81,7 @@ namespace vcpkg Optional source_control_file_and_location; Optional installed_package; + Optional> default_features; InstallPlanType plan_type; RequestType request_type; diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h index 157fa7827f..fd2d0c3eae 100644 --- a/include/vcpkg/sourceparagraph.h +++ b/include/vcpkg/sourceparagraph.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -36,18 +37,39 @@ namespace vcpkg Optional try_get_minimum_version() const; }; + struct DependencyRequestedFeature + { + std::string name; + PlatformExpression::Expr platform; + DependencyRequestedFeature(std::string name) + : DependencyRequestedFeature(std::move(name), PlatformExpression::Expr::Empty()) + { + } + DependencyRequestedFeature(std::string name, PlatformExpression::Expr platform) + : name(std::move(name)), platform(std::move(platform)) + { + Checks::check_exit(VCPKG_LINE_INFO, !this->name.empty() && this->name != "core" && this->name != "default"); + } + friend bool operator==(const DependencyRequestedFeature& lhs, const DependencyRequestedFeature& rhs); + friend bool operator!=(const DependencyRequestedFeature& lhs, const DependencyRequestedFeature& rhs); + }; + struct Dependency { std::string name; - std::vector features; + // a list of "real" features without "core" or "default". Use member default_features instead. + std::vector features; PlatformExpression::Expr platform; DependencyConstraint constraint; bool host = false; + bool default_features = true; + bool has_platform_expressions() const; + Json::Object extra_info; - /// @param id adds "default" if "core" not present. - FullPackageSpec to_full_spec(Triplet target, Triplet host, ImplicitDefault id) const; + /// @param id adds "default" if `default_features` is false. + FullPackageSpec to_full_spec(View features, Triplet target, Triplet host) const; friend bool operator==(const Dependency& lhs, const Dependency& rhs); friend bool operator!=(const Dependency& lhs, const Dependency& rhs) { return !(lhs == rhs); } @@ -69,8 +91,7 @@ namespace vcpkg std::vector filter_dependencies(const std::vector& deps, Triplet t, Triplet host, - const std::unordered_map& cmake_vars, - ImplicitDefault id); + const std::unordered_map& cmake_vars); /// /// Port metadata of additional feature in a package (part of CONTROL file) @@ -108,7 +129,7 @@ namespace vcpkg std::string documentation; std::vector dependencies; std::vector overrides; - std::vector default_features; + std::vector default_features; // there are two distinct "empty" states here // "user did not provide a license" -> nullopt diff --git a/locales/messages.json b/locales/messages.json index 7d09d1546e..b1c1927ad2 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -8,6 +8,7 @@ "ADemandObject": "a demand object", "_ADemandObject.comment": "'demands' are a concept in the schema of a JSON file the user can edit", "ADependency": "a dependency", + "ADependencyFeature": "a feature in a dependency", "ADictionaryOfContacts": "a dictionary of contacts", "AFeature": "a feature", "AFilesystemRegistry": "a filesystem registry", @@ -115,6 +116,7 @@ "_AmbiguousConfigDeleteConfigFile.comment": "An example of {path} is /foo/bar.", "AnArrayOfDependencies": "an array of dependencies", "AnArrayOfDependencyOverrides": "an array of dependency overrides", + "AnArrayOfFeatures": "an array of features", "AnArrayOfIdentifers": "an array of identifiers", "AnArrayOfOverlayPaths": "an array of overlay paths", "AnArrayOfOverlayTripletsPaths": "an array of overlay triplets paths", diff --git a/src/vcpkg-test/binarycaching.cpp b/src/vcpkg-test/binarycaching.cpp index db6483dee6..e49924d4a2 100644 --- a/src/vcpkg-test/binarycaching.cpp +++ b/src/vcpkg-test/binarycaching.cpp @@ -227,6 +227,7 @@ Build-Depends: bzip RequestType::USER_REQUESTED, Test::ARM_UWP, {{"a", {}}, {"b", {}}}, + {}, {}); ipa.abi_info = AbiInfo{}; @@ -352,7 +353,8 @@ Version: 1.5 RequestType::USER_REQUESTED, Test::ARM_UWP, std::map>{}, - std::vector{}); + std::vector{}, + std::vector{}); InstallPlanAction& ipa_without_abi = install_plan.back(); ipa_without_abi.package_dir = "pkgs/someheadpackage"; @@ -428,7 +430,8 @@ Description: a spiffy compression library wrapper RequestType::USER_REQUESTED, Test::ARM64_WINDOWS, std::map>{}, - std::vector{}); + std::vector{}, + std::vector{}); plan.install_actions[0].abi_info = AbiInfo{}; plan.install_actions[0].abi_info.get()->package_abi = "packageabi"; @@ -455,7 +458,8 @@ Description: a spiffy compression library wrapper RequestType::USER_REQUESTED, Test::ARM64_WINDOWS, std::map>{}, - std::vector{}); + std::vector{}, + std::vector{}); plan.install_actions[1].abi_info = AbiInfo{}; plan.install_actions[1].abi_info.get()->package_abi = "packageabi2"; diff --git a/src/vcpkg-test/dependencies.cpp b/src/vcpkg-test/dependencies.cpp index 4e490f3881..d5fb975aef 100644 --- a/src/vcpkg-test/dependencies.cpp +++ b/src/vcpkg-test/dependencies.cpp @@ -89,6 +89,17 @@ struct MockVersionedPortfileProvider : IVersionedPortfileProvider } }; +struct CoreDependency : Dependency +{ + CoreDependency(std::string name, + std::vector features = {}, + PlatformExpression::Expr platform = {}) + : Dependency{std::move(name), std::move(features), std::move(platform)} + { + default_features = false; + } +}; + static void check_name_and_features(const InstallPlanAction& ipa, StringLiteral name, std::initializer_list features) @@ -1318,7 +1329,7 @@ TEST_CASE ("version install simple feature", "[versionplan]") bp, var_provider, { - Dependency{"a", {"x"}}, + Dependency{"a", {{"x"}}}, }, {}, toplevel_spec())); @@ -1333,13 +1344,13 @@ TEST_CASE ("version install simple feature", "[versionplan]") bp, var_provider, { - Dependency{"semver", {"x"}}, + 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"}); + check_name_and_version(install_plan.install_actions[0], "semver", {"1.0.0", 0}, {{"x"}}); } SECTION ("date") { @@ -1348,13 +1359,13 @@ TEST_CASE ("version install simple feature", "[versionplan]") bp, var_provider, { - Dependency{"date", {"x"}}, + 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"}); + check_name_and_version(install_plan.install_actions[0], "date", {"2020-01-01", 0}, {{"x"}}); } } @@ -1367,7 +1378,7 @@ TEST_CASE ("version install simple feature", "[versionplan]") bp, var_provider, { - Dependency{"a", {"x"}, {}, {VersionConstraintKind::Minimum, "1", 0}}, + Dependency{"a", {{"x"}}, {}, {VersionConstraintKind::Minimum, "1", 0}}, }, {}, toplevel_spec()); @@ -1389,7 +1400,7 @@ TEST_CASE ("version install transitive features", "[versionplan]") MockVersionedPortfileProvider vp; auto a_x = make_fpgh("x"); - a_x->dependencies.push_back(Dependency{"b", {"y"}}); + a_x->dependencies.push_back(Dependency{"b", {{"y"}}}); vp.emplace("a", {"1", 0}, VersionScheme::Relaxed).source_control_file->feature_paragraphs.push_back(std::move(a_x)); auto b_y = make_fpgh("y"); @@ -1406,7 +1417,7 @@ TEST_CASE ("version install transitive features", "[versionplan]") bp, var_provider, { - Dependency{"a", {"x"}}, + Dependency{"a", {{"x"}}}, }, {}, toplevel_spec())); @@ -1421,7 +1432,7 @@ TEST_CASE ("version install transitive feature versioned", "[versionplan]") MockVersionedPortfileProvider vp; auto a_x = make_fpgh("x"); - a_x->dependencies.push_back(Dependency{"b", {"y"}, {}, {VersionConstraintKind::Minimum, "2", 0}}); + a_x->dependencies.push_back(Dependency{"b", {{"y"}}, {}, {VersionConstraintKind::Minimum, "2", 0}}); vp.emplace("a", {"1", 0}, VersionScheme::Relaxed).source_control_file->feature_paragraphs.push_back(std::move(a_x)); { @@ -1450,7 +1461,7 @@ TEST_CASE ("version install transitive feature versioned", "[versionplan]") bp, var_provider, { - Dependency{"a", {"x"}}, + Dependency{"a", {{"x"}}}, }, {}, toplevel_spec())); @@ -1647,9 +1658,8 @@ TEST_CASE ("version dont install default features", "[versionplan]") MockBaselineProvider bp; bp.v["a"] = {"1", 0}; - auto install_plan = - create_versioned_install_plan(vp, bp, var_provider, {Dependency{"a", {"core"}}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {CoreDependency{"a"}}, {}, toplevel_spec()) + .value_or_exit(VCPKG_LINE_INFO); REQUIRE(install_plan.size() == 1); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}); @@ -1665,7 +1675,7 @@ TEST_CASE ("version install transitive default features", "[versionplan]") a_scf->feature_paragraphs.push_back(std::move(a_x)); auto& b_scf = vp.emplace("b", {"1", 0}, VersionScheme::Relaxed).source_control_file; - b_scf->core_paragraph->dependencies.push_back({"a", {"core"}}); + b_scf->core_paragraph->dependencies.push_back(CoreDependency{"a"}); auto& c_scf = vp.emplace("c", {"1", 0}, VersionScheme::Relaxed).source_control_file; c_scf->core_paragraph->dependencies.push_back({"a"}); @@ -1684,9 +1694,9 @@ TEST_CASE ("version install transitive default features", "[versionplan]") check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); check_name_and_version(install_plan.install_actions[1], "b", {"1", 0}); - install_plan = create_versioned_install_plan( - vp, bp, var_provider, {Dependency{"a", {"core"}}, Dependency{"c"}}, {}, toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + install_plan = + create_versioned_install_plan(vp, bp, var_provider, {CoreDependency{"a"}, Dependency{"c"}}, {}, toplevel_spec()) + .value_or_exit(VCPKG_LINE_INFO); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); @@ -1756,7 +1766,7 @@ TEST_CASE ("version install qualified default suppression", "[versionplan]") a_scf->feature_paragraphs.push_back(make_fpgh("x")); vp.emplace("b", {"1", 0}, VersionScheme::Relaxed) - .source_control_file->core_paragraph->dependencies.push_back({"a", {"core"}}); + .source_control_file->core_paragraph->dependencies.push_back(CoreDependency{"a"}); MockCMakeVarProvider var_provider; @@ -1764,14 +1774,14 @@ TEST_CASE ("version install qualified default suppression", "[versionplan]") bp.v["a"] = {"1", 0}; bp.v["b"] = {"1", 0}; - auto install_plan = - create_versioned_install_plan(vp, - bp, - var_provider, - {{"b", {}, parse_platform("!linux")}, {"a", {"core"}, parse_platform("linux")}}, - {}, - toplevel_spec()) - .value_or_exit(VCPKG_LINE_INFO); + auto install_plan = create_versioned_install_plan( + vp, + bp, + var_provider, + {{"b", {}, parse_platform("!linux")}, CoreDependency{"a", {}, parse_platform("linux")}}, + {}, + toplevel_spec()) + .value_or_exit(VCPKG_LINE_INFO); REQUIRE(install_plan.size() == 2); check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); @@ -1879,13 +1889,13 @@ TEST_CASE ("version install self features", "[versionplan]") MockVersionedPortfileProvider vp; auto& a_scf = vp.emplace("a", {"1", 0}).source_control_file; a_scf->feature_paragraphs.push_back(make_fpgh("x")); - a_scf->feature_paragraphs.back()->dependencies.push_back({"a", {"core", "y"}}); + a_scf->feature_paragraphs.back()->dependencies.push_back(CoreDependency{"a", {{"y"}}}); a_scf->feature_paragraphs.push_back(make_fpgh("y")); a_scf->feature_paragraphs.push_back(make_fpgh("z")); MockCMakeVarProvider var_provider; - auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a", {"x"}}}, {}, toplevel_spec()) + auto install_plan = create_versioned_install_plan(vp, bp, var_provider, {{"a", {{"x"}}}}, {}, toplevel_spec()) .value_or_exit(VCPKG_LINE_INFO); REQUIRE(install_plan.size() == 1); @@ -1917,7 +1927,7 @@ TEST_CASE ("version install nonexisting features", "[versionplan]") auto& a_scf = vp.emplace("a", {"1", 0}).source_control_file; a_scf->feature_paragraphs.push_back(make_fpgh("x")); - auto install_plan = create_versioned_install_plan(vp, bp, {{"a", {"y"}}}); + 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"); @@ -1931,7 +1941,7 @@ TEST_CASE ("version install transitive missing features", "[versionplan]") MockVersionedPortfileProvider vp; auto& a_scf = vp.emplace("a", {"1", 0}).source_control_file; - a_scf->core_paragraph->dependencies.push_back({"b", {"y"}}); + a_scf->core_paragraph->dependencies.push_back({"b", {{"y"}}}); vp.emplace("b", {"1", 0}); auto install_plan = create_versioned_install_plan(vp, bp, {{"a", {}}}); @@ -1953,7 +1963,7 @@ TEST_CASE ("version remove features during upgrade", "[versionplan]") MockVersionedPortfileProvider vp; // a@0 -> b[x], c>=1 auto& a_scf = vp.emplace("a", {"1", 0}).source_control_file; - a_scf->core_paragraph->dependencies.push_back({"b", {"x"}}); + a_scf->core_paragraph->dependencies.push_back({"b", {{"x"}}}); a_scf->core_paragraph->dependencies.push_back({"c", {}, {}, {VersionConstraintKind::Minimum, "1", 1}}); // a@1 -> b auto& a1_scf = vp.emplace("a", {"1", 1}).source_control_file; @@ -2232,10 +2242,10 @@ TEST_CASE ("respect supports expressions of features", "[versionplan]") MockCMakeVarProvider var_provider; var_provider.dep_info_vars[{"a", toplevel_spec().triplet()}]["VCPKG_CMAKE_SYSTEM_NAME"] = ""; - auto install_plan = create_versioned_install_plan(vp, bp, {{"a", {"x"}}}, var_provider); + auto install_plan = create_versioned_install_plan(vp, bp, {{"a", {{"x"}}}}, var_provider); CHECK(install_plan.has_value()); var_provider.dep_info_vars[{"a", toplevel_spec().triplet()}]["VCPKG_CMAKE_SYSTEM_NAME"] = "Linux"; - install_plan = create_versioned_install_plan(vp, bp, {{"a", {"x"}}}, var_provider); + install_plan = create_versioned_install_plan(vp, bp, {{"a", {{"x"}}}}, var_provider); CHECK_FALSE(install_plan.has_value()); SECTION ("override") { @@ -2245,7 +2255,7 @@ TEST_CASE ("respect supports expressions of features", "[versionplan]") bp, oprovider, var_provider, - {Dependency{"a", {"x"}}}, + {Dependency{"a", {{"x"}}}}, {DependencyOverride{"a", "1", 1}}, toplevel_spec()); CHECK(install_plan.has_value()); @@ -2255,13 +2265,92 @@ TEST_CASE ("respect supports expressions of features", "[versionplan]") bp, oprovider, var_provider, - {Dependency{"a", {"x"}}}, + {Dependency{"a", {{"x"}}}}, {DependencyOverride{"a", "1", 0}}, toplevel_spec()); CHECK_FALSE(install_plan.has_value()); } } +TEST_CASE ("respect platform expressions in DependencyRequestedFeature", "[versionplan]") +{ + using namespace PlatformExpression; + MockBaselineProvider bp; + bp.v["a"] = {"1", 0}; + + MockVersionedPortfileProvider vp; + { + auto a_x = std::make_unique(); + a_x->name = "x"; + vp.emplace("a", {"1", 0}).source_control_file->feature_paragraphs.push_back(std::move(a_x)); + } + + MockCMakeVarProvider var_provider; + Dependency dep{ + "a", {{"x", parse_platform_expression("linux", MultipleBinaryOperators::Deny).value_or_exit(VCPKG_LINE_INFO)}}}; + + SECTION ("on windows") + { + var_provider.dep_info_vars[toplevel_spec()]["VCPKG_CMAKE_SYSTEM_NAME"] = ""; + auto maybe_install_plan = create_versioned_install_plan(vp, bp, {dep}, var_provider); + CHECK(maybe_install_plan.has_value()); + auto& install_plan = maybe_install_plan.value_or_exit(VCPKG_LINE_INFO); + CHECK(install_plan.install_actions.size() == 1); + CHECK(install_plan.install_actions[0].feature_list.size() == 1); + } + + SECTION ("on linux") + { + var_provider.dep_info_vars[toplevel_spec()]["VCPKG_CMAKE_SYSTEM_NAME"] = "Linux"; + auto maybe_install_plan = create_versioned_install_plan(vp, bp, {dep}, var_provider); + CHECK(maybe_install_plan.has_value()); + auto& install_plan = maybe_install_plan.value_or_exit(VCPKG_LINE_INFO); + CHECK(install_plan.install_actions.size() == 1); + CHECK(install_plan.install_actions[0].feature_list.size() == 2); + } +} + +TEST_CASE ("respect platform expressions in default features", "[versionplan]") +{ + using namespace PlatformExpression; + MockBaselineProvider bp; + bp.v["a"] = {"1", 0}; + + MockVersionedPortfileProvider vp; + { + // port with a feature x that is default on "linux" + auto a_x = std::make_unique(); + a_x->name = "x"; + auto& scf = vp.emplace("a", {"1", 0}).source_control_file; + scf->feature_paragraphs.push_back(std::move(a_x)); + scf->core_paragraph->default_features.emplace_back( + "x", parse_platform_expression("linux", MultipleBinaryOperators::Deny).value_or_exit(VCPKG_LINE_INFO)); + } + + MockCMakeVarProvider var_provider; + Dependency dep{"a"}; + PackageSpec spec{"a", toplevel_spec().triplet()}; + SECTION ("on windows") + { + var_provider.dep_info_vars[spec]["VCPKG_CMAKE_SYSTEM_NAME"] = ""; + auto maybe_install_plan = create_versioned_install_plan(vp, bp, {dep}, var_provider); + CHECK(maybe_install_plan.has_value()); + auto& install_plan = maybe_install_plan.value_or_exit(VCPKG_LINE_INFO); + CHECK(install_plan.install_actions.size() == 1); + CHECK(install_plan.install_actions[0].feature_list.size() == 1); + } + + SECTION ("on linux") + { + var_provider.dep_info_vars[spec]["VCPKG_CMAKE_SYSTEM_NAME"] = "Linux"; + auto maybe_install_plan = create_versioned_install_plan(vp, bp, {dep}, var_provider); + CHECK(maybe_install_plan.has_value()); + auto& install_plan = maybe_install_plan.value_or_exit(VCPKG_LINE_INFO); + CHECK(install_plan.install_actions.size() == 1); + CHECK(install_plan.install_actions[0].feature_list.size() == 2); + } +} + TEST_CASE ("formatting plan 1", "[dependencies]") { std::vector> status_paragraphs; @@ -2281,13 +2370,13 @@ TEST_CASE ("formatting plan 1", "[dependencies]") const Path pr = "packages_root"; InstallPlanAction install_a( - {"a", Test::X64_OSX}, scfl_a, pr, RequestType::AUTO_SELECTED, Test::X64_ANDROID, {}, {}); + {"a", Test::X64_OSX}, scfl_a, pr, RequestType::AUTO_SELECTED, Test::X64_ANDROID, {}, {}, {}); InstallPlanAction install_b( - {"b", Test::X64_OSX}, scfl_b, pr, RequestType::AUTO_SELECTED, Test::X64_ANDROID, {{"1", {}}}, {}); + {"b", Test::X64_OSX}, scfl_b, pr, RequestType::AUTO_SELECTED, Test::X64_ANDROID, {{"1", {}}}, {}, {}); InstallPlanAction install_c( - {"c", Test::X64_OSX}, scfl_c, pr, RequestType::USER_REQUESTED, Test::X64_ANDROID, {}, {}); + {"c", Test::X64_OSX}, scfl_c, pr, RequestType::USER_REQUESTED, Test::X64_ANDROID, {}, {}, {}); InstallPlanAction install_f( - {"f", Test::X64_OSX}, scfl_f, pr, RequestType::USER_REQUESTED, Test::X64_ANDROID, {}, {}); + {"f", Test::X64_OSX}, scfl_f, pr, RequestType::USER_REQUESTED, Test::X64_ANDROID, {}, {}, {}); install_f.plan_type = InstallPlanType::EXCLUDED; InstallPlanAction already_installed_d( @@ -2379,9 +2468,9 @@ TEST_CASE ("dependency graph API snapshot: host and target") MockVersionedPortfileProvider vp; auto& scfl_a = vp.emplace("a", {"1", 0}); InstallPlanAction install_a( - {"a", Test::X86_WINDOWS}, scfl_a, "packages_root", RequestType::AUTO_SELECTED, Test::X64_WINDOWS, {}, {}); + {"a", Test::X86_WINDOWS}, scfl_a, "packages_root", RequestType::AUTO_SELECTED, Test::X64_WINDOWS, {}, {}, {}); InstallPlanAction install_a_host( - {"a", Test::X64_WINDOWS}, scfl_a, "packages_root", RequestType::AUTO_SELECTED, Test::X64_WINDOWS, {}, {}); + {"a", Test::X64_WINDOWS}, scfl_a, "packages_root", RequestType::AUTO_SELECTED, Test::X64_WINDOWS, {}, {}, {}); ActionPlan plan; plan.install_actions.push_back(std::move(install_a)); plan.install_actions.push_back(std::move(install_a_host)); @@ -2438,4 +2527,4 @@ TEST_CASE ("dependency graph API snapshot: host and target") CHECK(package_url_a == "pkg:github/vcpkg/a:x86-windows@1"); CHECK(relationship_a == "direct"); CHECK(dependencies_a.size() == 0); -} \ No newline at end of file +} diff --git a/src/vcpkg-test/manifests.cpp b/src/vcpkg-test/manifests.cpp index 17163b50b4..bc7b93fee8 100644 --- a/src/vcpkg-test/manifests.cpp +++ b/src/vcpkg-test/manifests.cpp @@ -838,7 +838,13 @@ TEST_CASE ("manifest construct maximum", "[manifests]") "description": "d", "builtin-baseline": "123", "dependencies": ["bd"], - "default-features": ["df"], + "default-features": [ + "df", + { + "name": "zuko", + "platform": "windows & arm" + } + ], "features": { "$feature-level-comment": "hi", "$feature-level-comment2": "123456", @@ -849,13 +855,23 @@ TEST_CASE ("manifest construct maximum", "[manifests]") "firebending", { "name": "order-white-lotus", - "features": [ "the-ancient-ways" ], + "features": [ + "the-ancient-ways", + { + "name": "windows-tests", + "platform": "windows" + } + ], "platform": "!(windows & arm)" - }, - { - "$extra": [], - "$my": [], - "name": "tea" + }, + { + "$extra": [], + "$my": [], + "name": "tea" + }, + { + "name": "z-no-defaults", + "default-features": false } ] }, @@ -894,8 +910,11 @@ TEST_CASE ("manifest construct maximum", "[manifests]") REQUIRE(pgh.core_paragraph->description[0] == "d"); REQUIRE(pgh.core_paragraph->dependencies.size() == 1); REQUIRE(pgh.core_paragraph->dependencies[0].name == "bd"); - REQUIRE(pgh.core_paragraph->default_features.size() == 1); - REQUIRE(pgh.core_paragraph->default_features[0] == "df"); + REQUIRE(pgh.core_paragraph->default_features.size() == 2); + REQUIRE(pgh.core_paragraph->default_features[0].name == "df"); + REQUIRE(pgh.core_paragraph->default_features[0].platform.is_empty()); + REQUIRE(pgh.core_paragraph->default_features[1].name == "zuko"); + REQUIRE(to_string(pgh.core_paragraph->default_features[1].platform) == "windows & arm"); REQUIRE(pgh.core_paragraph->supports_expression.is_empty()); REQUIRE(pgh.core_paragraph->builtin_baseline == "123"); @@ -904,12 +923,21 @@ TEST_CASE ("manifest construct maximum", "[manifests]") REQUIRE(pgh.feature_paragraphs[0]->name == "iroh"); REQUIRE(pgh.feature_paragraphs[0]->description.size() == 1); REQUIRE(pgh.feature_paragraphs[0]->description[0] == "zuko's uncle"); - REQUIRE(pgh.feature_paragraphs[0]->dependencies.size() == 3); + REQUIRE(pgh.feature_paragraphs[0]->dependencies.size() == 4); REQUIRE(pgh.feature_paragraphs[0]->dependencies[0].name == "firebending"); REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].name == "order-white-lotus"); - REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features.size() == 1); - REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features[0] == "the-ancient-ways"); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].default_features == true); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features.size() == 2); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features[0].name == "the-ancient-ways"); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features[0].platform.is_empty()); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features[1].name == "windows-tests"); + REQUIRE_FALSE(pgh.feature_paragraphs[0]->dependencies[1].features[1].platform.is_empty()); + REQUIRE( + pgh.feature_paragraphs[0]->dependencies[1].features[1].platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + REQUIRE_FALSE(pgh.feature_paragraphs[0]->dependencies[1].features[1].platform.evaluate( + {{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + REQUIRE_FALSE(pgh.feature_paragraphs[0]->dependencies[1].platform.evaluate( {{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].platform.evaluate( @@ -918,6 +946,8 @@ TEST_CASE ("manifest construct maximum", "[manifests]") {{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); REQUIRE(pgh.feature_paragraphs[0]->dependencies[2].name == "tea"); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[3].name == "z-no-defaults"); + REQUIRE(pgh.feature_paragraphs[0]->dependencies[3].default_features == false); REQUIRE(pgh.feature_paragraphs[1]->name == "zuko"); REQUIRE(pgh.feature_paragraphs[1]->description.size() == 2); @@ -1038,7 +1068,27 @@ TEST_CASE ("SourceParagraph manifest default features", "[manifests]") auto& pgh = **m_pgh.get(); REQUIRE(pgh.core_paragraph->default_features.size() == 1); - REQUIRE(pgh.core_paragraph->default_features[0] == "a1"); + REQUIRE(pgh.core_paragraph->default_features[0].name == "a1"); + REQUIRE(pgh.core_paragraph->default_features[0].platform.is_empty()); +} + +TEST_CASE ("SourceParagraph manifest default feature missing name", "[manifests]") +{ + auto m_pgh = test_parse_port_manifest(R"json({ + "name": "a", + "version-string": "1.0", + "default-features": [{"platform": "!windows"}] + })json", + PrintErrors::No); + REQUIRE(!m_pgh.has_value()); + + m_pgh = test_parse_port_manifest(R"json({ + "name": "a", + "version-string": "1.0", + "default-features": [{"name": "", "platform": "!windows"}] + })json", + PrintErrors::No); + REQUIRE(!m_pgh.has_value()); } TEST_CASE ("SourceParagraph manifest description paragraph", "[manifests]") diff --git a/src/vcpkg-test/paragraph.cpp b/src/vcpkg-test/paragraph.cpp index 9e70579182..90084cc95f 100644 --- a/src/vcpkg-test/paragraph.cpp +++ b/src/vcpkg-test/paragraph.cpp @@ -117,7 +117,8 @@ TEST_CASE ("SourceParagraph construct maximum", "[paragraph]") REQUIRE(pgh.core_paragraph->dependencies.size() == 1); REQUIRE(pgh.core_paragraph->dependencies[0].name == "bd"); REQUIRE(pgh.core_paragraph->default_features.size() == 1); - REQUIRE(pgh.core_paragraph->default_features[0] == "df"); + REQUIRE(pgh.core_paragraph->default_features[0].name == "df"); + REQUIRE(pgh.core_paragraph->default_features[0].platform.is_empty()); } TEST_CASE ("SourceParagraph construct feature", "[paragraph]") @@ -203,7 +204,8 @@ TEST_CASE ("SourceParagraph default features", "[paragraph]") auto& pgh = **m_pgh.get(); REQUIRE(pgh.core_paragraph->default_features.size() == 1); - REQUIRE(pgh.core_paragraph->default_features[0] == "a1"); + REQUIRE(pgh.core_paragraph->default_features[0].name == "a1"); + REQUIRE(pgh.core_paragraph->default_features[0].platform.is_empty()); } TEST_CASE ("BinaryParagraph construct minimum", "[paragraph]") diff --git a/src/vcpkg-test/plan.cpp b/src/vcpkg-test/plan.cpp index 35ae4c036b..2666931345 100644 --- a/src/vcpkg-test/plan.cpp +++ b/src/vcpkg-test/plan.cpp @@ -448,6 +448,45 @@ TEST_CASE ("install all features test", "[plan]") features_check(install_plan.install_actions.at(0), "a", {"0", "1", "core"}, Test::X64_WINDOWS); } +TEST_CASE ("install platform dependent default features", "[plan]") +{ + std::vector> status_paragraphs; + + // Add a port "a" with default features "1" and features "0" and "1". + PackageSpecMap spec_map(Test::X64_WINDOWS); + auto iter = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}); + // feature "1" is a default feature on "linux" + using MBO = PlatformExpression::MultipleBinaryOperators; + auto linux_expr = PlatformExpression::parse_platform_expression("linux", MBO::Deny).value_or_exit(VCPKG_LINE_INFO); + spec_map.map["a"].source_control_file->core_paragraph->default_features = { + DependencyRequestedFeature{"1", linux_expr}}; + + MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + SECTION ("on !linux") + { + // Install "a" (without explicit feature specification) + auto install_plan = create_feature_install_plan(map_port, + var_provider, + Test::parse_test_fspecs("a:x64-windows"), + StatusParagraphs(std::move(status_paragraphs))); + // Expect the default feature "1" to be installed, but not "0" + REQUIRE(install_plan.size() == 1); + features_check(install_plan.install_actions.at(0), "a", {"core"}, Test::X64_WINDOWS); + } + SECTION ("on linux") + { + var_provider.dep_info_vars[iter] = {{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}}; + auto install_plan = create_feature_install_plan(map_port, + var_provider, + Test::parse_test_fspecs("a:x64-windows"), + StatusParagraphs(std::move(status_paragraphs))); + REQUIRE(install_plan.size() == 1); + features_check(install_plan.install_actions.at(0), "a", {"core", "1"}, Test::X64_WINDOWS); + } +} + TEST_CASE ("install default features test 1", "[plan]") { std::vector> status_paragraphs; diff --git a/src/vcpkg-test/spdx.cpp b/src/vcpkg-test/spdx.cpp index 6f790f8453..937dcda4a8 100644 --- a/src/vcpkg-test/spdx.cpp +++ b/src/vcpkg-test/spdx.cpp @@ -21,7 +21,7 @@ TEST_CASE ("spdx maximum serialization", "[spdx]") cpgh.raw_version = "1.0"; cpgh.version_scheme = VersionScheme::Relaxed; - InstallPlanAction ipa(spec, scfl, "test_packages_root", RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}); + InstallPlanAction ipa(spec, scfl, "test_packages_root", RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}, {}); auto& abi = *(ipa.abi_info = AbiInfo{}).get(); abi.package_abi = "ABIHASH"; @@ -175,7 +175,7 @@ TEST_CASE ("spdx minimum serialization", "[spdx]") cpgh.raw_version = "1.0"; cpgh.version_scheme = VersionScheme::String; - InstallPlanAction ipa(spec, scfl, "test_packages_root", RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}); + InstallPlanAction ipa(spec, scfl, "test_packages_root", RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}, {}); auto& abi = *(ipa.abi_info = AbiInfo{}).get(); abi.package_abi = "deadbeef"; @@ -303,7 +303,7 @@ TEST_CASE ("spdx concat resources", "[spdx]") cpgh.raw_version = "1.0"; cpgh.version_scheme = VersionScheme::String; - InstallPlanAction ipa(spec, scfl, "test_packages_root", RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}); + InstallPlanAction ipa(spec, scfl, "test_packages_root", RequestType::USER_REQUESTED, Test::X86_WINDOWS, {}, {}, {}); auto& abi = *(ipa.abi_info = AbiInfo{}).get(); abi.package_abi = "deadbeef"; diff --git a/src/vcpkg-test/versionplan.cpp b/src/vcpkg-test/versionplan.cpp index 561b06ffbe..a7d2d5fcaf 100644 --- a/src/vcpkg-test/versionplan.cpp +++ b/src/vcpkg-test/versionplan.cpp @@ -25,21 +25,18 @@ TEST_CASE ("parse depends", "[dependencies]") TEST_CASE ("filter depends", "[dependencies]") { const std::vector defaults{"core", "default"}; - const std::vector core{"core"}; const std::unordered_map x64_win_cmake_vars{{"VCPKG_TARGET_ARCHITECTURE", "x64"}, {"VCPKG_CMAKE_SYSTEM_NAME", ""}}; const std::unordered_map arm_uwp_cmake_vars{{"VCPKG_TARGET_ARCHITECTURE", "arm"}, {"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}}; - auto deps_ = parse_dependencies_list("liba (!uwp), libb, libc (uwp)"); REQUIRE(deps_); auto& deps = *deps_.get(); SECTION ("x64-windows") { - auto v = - filter_dependencies(deps, Test::X64_WINDOWS, Test::X86_WINDOWS, x64_win_cmake_vars, ImplicitDefault::YES); + auto v = filter_dependencies(deps, Test::X64_WINDOWS, Test::X86_WINDOWS, x64_win_cmake_vars); REQUIRE(v.size() == 2); REQUIRE(v.at(0).package_spec.name() == "liba"); REQUIRE(v.at(0).features == defaults); @@ -49,12 +46,12 @@ TEST_CASE ("filter depends", "[dependencies]") SECTION ("arm-uwp") { - auto v2 = filter_dependencies(deps, Test::ARM_UWP, Test::X86_WINDOWS, arm_uwp_cmake_vars, ImplicitDefault::NO); + auto v2 = filter_dependencies(deps, Test::ARM_UWP, Test::X86_WINDOWS, arm_uwp_cmake_vars); REQUIRE(v2.size() == 2); REQUIRE(v2.at(0).package_spec.name() == "libb"); - REQUIRE(v2.at(0).features == core); + REQUIRE(v2.at(0).features == defaults); REQUIRE(v2.at(1).package_spec.name() == "libc"); - REQUIRE(v2.at(1).features == core); + REQUIRE(v2.at(1).features == defaults); } } diff --git a/src/vcpkg/binaryparagraph.cpp b/src/vcpkg/binaryparagraph.cpp index 3a30458a61..950e2e2edc 100644 --- a/src/vcpkg/binaryparagraph.cpp +++ b/src/vcpkg/binaryparagraph.cpp @@ -101,6 +101,7 @@ namespace vcpkg } BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, + const std::vector& default_features, Triplet triplet, const std::string& abi_tag, std::vector deps) @@ -110,7 +111,7 @@ namespace vcpkg , description(spgh.description) , maintainers(spgh.maintainers) , feature() - , default_features(spgh.default_features) + , default_features(default_features) , dependencies(std::move(deps)) , abi(abi_tag) { diff --git a/src/vcpkg/commands.add.cpp b/src/vcpkg/commands.add.cpp index 4d399a5873..efad647de1 100644 --- a/src/vcpkg/commands.add.cpp +++ b/src/vcpkg/commands.add.cpp @@ -101,14 +101,17 @@ namespace vcpkg::Commands return dep.name == spec.name && !dep.host && structurally_equal(spec.platform.value_or(PlatformExpression::Expr()), dep.platform); }); + const auto features = Util::fmap(spec.features.value_or({}), [](auto& feature) { + return DependencyRequestedFeature{feature, PlatformExpression::Expr::Empty()}; + }); if (dep == manifest_scf.core_paragraph->dependencies.end()) { manifest_scf.core_paragraph->dependencies.push_back( - Dependency{spec.name, spec.features.value_or({}), spec.platform.value_or({})}); + Dependency{spec.name, features, spec.platform.value_or({})}); } else if (spec.features) { - for (const auto& feature : spec.features.value_or_exit(VCPKG_LINE_INFO)) + for (const auto& feature : features) { if (!Util::Vectors::contains(dep->features, feature)) { diff --git a/src/vcpkg/commands.build.cpp b/src/vcpkg/commands.build.cpp index 2c23d5719c..4f5f3aee04 100644 --- a/src/vcpkg/commands.build.cpp +++ b/src/vcpkg/commands.build.cpp @@ -600,6 +600,7 @@ namespace vcpkg auto find_itr = action.feature_dependencies.find("core"); Checks::check_exit(VCPKG_LINE_INFO, find_itr != action.feature_dependencies.end()); BinaryParagraph bpgh(*scfl.source_control_file->core_paragraph, + action.default_features.value_or_exit(VCPKG_LINE_INFO), action.spec.triplet(), action.public_abi(), fspecs_to_pspecs(find_itr->second)); diff --git a/src/vcpkg/commands.install.cpp b/src/vcpkg/commands.install.cpp index 3a286f0208..0d945c31c9 100644 --- a/src/vcpkg/commands.install.cpp +++ b/src/vcpkg/commands.install.cpp @@ -1117,12 +1117,23 @@ namespace vcpkg { features.emplace_back("core"); } - + PackageSpec toplevel{manifest_core.name, default_triplet}; auto core_it = std::remove(features.begin(), features.end(), "core"); if (core_it == features.end()) { - const auto& default_features = manifest_core.default_features; - features.insert(features.end(), default_features.begin(), default_features.end()); + if (Util::any_of(manifest_core.default_features, [](const auto& f) { return !f.platform.is_empty(); })) + { + const auto& vars = var_provider.get_or_load_dep_info_vars(toplevel, host_triplet); + for (const auto& f : manifest_core.default_features) + { + if (f.platform.evaluate(vars)) features.push_back(f.name); + } + } + else + { + for (const auto& f : manifest_core.default_features) + features.push_back(f.name); + } } else { @@ -1176,7 +1187,6 @@ namespace vcpkg auto oprovider = make_manifest_provider( fs, paths.original_cwd, extended_overlay_ports, manifest->path, std::move(manifest_scf)); - PackageSpec toplevel{manifest_core.name, default_triplet}; auto install_plan = create_versioned_install_plan(*verprovider, *baseprovider, *oprovider, diff --git a/src/vcpkg/dependencies.cpp b/src/vcpkg/dependencies.cpp index e759bb5390..97b3dfd116 100644 --- a/src/vcpkg/dependencies.cpp +++ b/src/vcpkg/dependencies.cpp @@ -48,6 +48,8 @@ namespace vcpkg std::map> build_edges; std::map> version_constraints; bool defaults_requested = false; + std::vector default_features; + bool reduced_defaults = false; }; /// @@ -103,9 +105,59 @@ namespace vcpkg { if (!info.defaults_requested) { - info.defaults_requested = true; - for (auto&& f : scfl.source_control_file->core_paragraph->default_features) - out_new_dependencies.emplace_back(m_spec, f); + if (Util::any_of(scfl.source_control_file->core_paragraph->default_features, + [](const auto& feature) { return !feature.platform.is_empty(); })) + { + if (auto maybe_vars = var_provider.get_dep_info_vars(m_spec)) + { + info.defaults_requested = true; + for (auto&& f : scfl.source_control_file->core_paragraph->default_features) + { + if (f.platform.evaluate(maybe_vars.value_or_exit(VCPKG_LINE_INFO))) + { + info.default_features.push_back(f.name); + } + } + } + } + else + { + info.defaults_requested = true; + for (auto&& f : scfl.source_control_file->core_paragraph->default_features) + info.default_features.push_back(f.name); + } + + if (info.reduced_defaults) + { + info.reduced_defaults = false; + // If the user did not explicitly request this installation, we need to add all new default + // features + std::set defaults_set{info.default_features.begin(), + info.default_features.end()}; + + // Install only features that were not previously available + if (auto p_inst = m_installed.get()) + { + for (auto&& prev_default : p_inst->ipv.core->package.default_features) + { + defaults_set.erase(prev_default); + } + } + + for (const std::string& default_feature : defaults_set) + { + // Instead of dealing with adding default features to each of our dependencies right + // away we just defer to the next pass of the loop. + out_new_dependencies.emplace_back(m_spec, default_feature); + } + } + else + { + for (auto&& default_feature : std::move(info.default_features)) + { + out_new_dependencies.emplace_back(m_spec, std::move(default_feature)); + } + } } return; } @@ -135,7 +187,16 @@ namespace vcpkg { if (dep.platform.evaluate(*vars)) { - auto fullspec = dep.to_full_spec(m_spec.triplet(), host_triplet, ImplicitDefault::YES); + std::vector features; + features.reserve(dep.features.size()); + for (const auto& f : dep.features) + { + if (f.platform.evaluate(*vars)) + { + features.push_back(f.name); + } + } + auto fullspec = dep.to_full_spec(features, m_spec.triplet(), host_triplet); fullspec.expand_fspecs_to(dep_list); if (auto opt = dep.constraint.try_get_minimum_version()) { @@ -153,9 +214,12 @@ namespace vcpkg bool requires_qualified_resolution = false; for (const Dependency& dep : *qualified_deps) { - if (dep.platform.is_empty()) + if (!dep.has_platform_expressions()) { - auto fullspec = dep.to_full_spec(m_spec.triplet(), host_triplet, ImplicitDefault::YES); + auto fullspec = + dep.to_full_spec(Util::fmap(dep.features, [](const auto& f) { return f.name; }), + m_spec.triplet(), + host_triplet); fullspec.expand_fspecs_to(dep_list); if (auto opt = dep.constraint.try_get_minimum_version()) { @@ -203,30 +267,12 @@ namespace vcpkg if (defaults_requested) { - for (auto&& def_feature : get_scfl_or_exit().source_control_file->core_paragraph->default_features) - out_reinstall_requirements.emplace_back(m_spec, def_feature); + out_reinstall_requirements.emplace_back(m_spec, "default"); } else if (request_type != RequestType::USER_REQUESTED) { - // If the user did not explicitly request this installation, we need to add all new default features - auto&& new_defaults = get_scfl_or_exit().source_control_file->core_paragraph->default_features; - std::set defaults_set{new_defaults.begin(), new_defaults.end()}; - - // Install only features that were not previously available - if (auto p_inst = m_installed.get()) - { - for (auto&& prev_default : p_inst->ipv.core->package.default_features) - { - defaults_set.erase(prev_default); - } - } - - for (const std::string& feature : defaults_set) - { - // Instead of dealing with adding default features to each of our dependencies right - // away we just defer to the next pass of the loop. - out_reinstall_requirements.emplace_back(m_spec, feature); - } + out_reinstall_requirements.emplace_back(m_spec, "default"); + m_install_info.get()->reduced_defaults = true; } } @@ -463,9 +509,11 @@ namespace vcpkg const RequestType& request_type, Triplet host_triplet, std::map>&& dependencies, - std::vector&& build_failure_messages) + std::vector&& build_failure_messages, + std::vector default_features) : PackageAction{{spec}, fdeps_to_pdeps(spec, dependencies), fdeps_to_feature_list(dependencies)} , source_control_file_and_location(scfl) + , default_features(std::move(default_features)) , plan_type(InstallPlanType::BUILD_AND_INSTALL) , request_type(request_type) , build_options{} @@ -797,6 +845,9 @@ namespace vcpkg } else if (spec.feature() == "default") { + has_supports = Util::any_of( + clust.get_scfl_or_exit().source_control_file->core_paragraph->default_features, + [](const DependencyRequestedFeature& feature) { return !feature.platform.is_empty(); }); } else { @@ -812,9 +863,10 @@ namespace vcpkg } // And it has at least one qualified dependency - if (has_supports || (paragraph_depends && Util::any_of(*paragraph_depends, [](auto&& dep) { - return !dep.platform.is_empty(); - }))) + if (has_supports || + (paragraph_depends && Util::any_of(*paragraph_depends, [](const Dependency& dep) { + return dep.has_platform_expressions(); + }))) { // Add it to the next batch run qualified_dependencies.emplace_back(spec); @@ -1043,8 +1095,7 @@ namespace vcpkg const auto& default_features = [&] { if (dep_clust.m_install_info.has_value()) { - return dep_clust.get_scfl_or_exit() - .source_control_file->core_paragraph->default_features; + return dep_clust.m_install_info.get()->default_features; } if (auto p = dep_clust.m_installed.get()) @@ -1068,7 +1119,8 @@ namespace vcpkg p_cluster->request_type, m_graph->m_host_triplet, std::move(computed_edges), - std::move(constraint_violations)); + std::move(constraint_violations), + std::move(info_ptr->default_features)); } else if (p_cluster->request_type == RequestType::USER_REQUESTED && p_cluster->m_installed.has_value()) { @@ -1311,7 +1363,7 @@ namespace vcpkg { PackageSpec spec; DependencyConstraint dc; - std::vector features; + std::vector features; }; struct PackageNodeData @@ -1376,6 +1428,8 @@ namespace vcpkg std::map> compute_feature_dependencies( const PackageNode& node, std::vector& out_dep_specs) const; + bool evaluate(const PackageSpec& spec, const PlatformExpression::Expr& platform_expr) const; + static LocalizedString format_incomparable_versions_message(const PackageSpec& on, StringView from, const SchemedVersion& baseline, @@ -1442,10 +1496,13 @@ namespace vcpkg // apply selected features for (auto&& f : dep.features) { - if (f == "default") abort(); - require_port_feature(*node, f, frame.spec.name()); + if (f.name == "default") abort(); + if (evaluate(frame.spec, f.platform)) + { + require_port_feature(*node, f.name, frame.spec.name()); + } } - if (Util::find(dep.features, StringView{"core"}) == dep.features.end()) + if (dep.default_features) { require_port_defaults(*node, frame.spec.name()); } @@ -1465,9 +1522,12 @@ namespace vcpkg { 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()}); + if (evaluate(ref.first, f.platform)) + { + auto deps = scfl->source_control_file->find_dependencies_for_feature(f.name); + if (!deps) continue; + m_resolve_stack.push_back({ref.first, *deps.get()}); + } } } } @@ -1476,10 +1536,6 @@ namespace vcpkg const std::string& feature, const std::string& origin) { - if (feature == "default") - { - return require_port_defaults(ref, origin); - } ref.second.origins.insert(origin); auto inserted = ref.second.requested_features.emplace(feature).second; if (inserted) @@ -1505,8 +1561,13 @@ namespace vcpkg auto features = ref.second.requested_features; if (ref.second.default_features) { - const auto& defaults = ref.second.scfl->source_control_file->core_paragraph->default_features; - features.insert(defaults.begin(), defaults.end()); + for (auto&& f : ref.second.scfl->source_control_file->core_paragraph->default_features) + { + if (evaluate(ref.first, f.platform)) + { + features.insert(f.name); + } + } } m_resolve_stack.push_back({ref.first, scfl->source_control_file->core_paragraph->dependencies}); @@ -1600,6 +1661,12 @@ namespace vcpkg return *it; } + bool VersionedPackageGraph::evaluate(const PackageSpec& spec, + const PlatformExpression::Expr& platform_expr) const + { + return platform_expr.evaluate(m_var_provider.get_or_load_dep_info_vars(spec, m_host_triplet)); + } + void VersionedPackageGraph::add_override(const std::string& name, const Version& v) { m_overrides.emplace(name, v); @@ -1684,8 +1751,13 @@ namespace vcpkg 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()); + for (auto&& f : node.second.scfl->source_control_file->core_paragraph->default_features) + { + if (evaluate(node.first, f.platform)) + { + all_features.insert(f.name); + } + } } std::vector fspecs; for (auto&& f : all_features) @@ -1710,7 +1782,12 @@ namespace vcpkg fspecs.emplace_back(fspec, "core"); for (auto&& g : fdep.features) - fspecs.emplace_back(fspec, g); + { + if (evaluate(fspec, g.platform)) + { + fspecs.emplace_back(fspec, g.name); + } + } out_dep_specs.push_back({std::move(fspec), fdep.constraint, fdep.features}); } Util::sort_unique_erase(fspecs); @@ -1775,15 +1852,15 @@ namespace vcpkg // Evaluate feature constraints (if any) for (auto&& f : dep.features) { - if (f == "core") continue; - if (f == "default") continue; - auto feature = node.second.scfl->source_control_file->find_feature(f); + if (f.name == "core") continue; + if (f.name == "default") continue; + auto feature = node.second.scfl->source_control_file->find_feature(f.name); if (!feature) { return msg::format_error( msgVersionMissingRequiredFeature, msg::version_spec = Strings::concat(dep.spec.name(), '@', node.second.scfl->to_version()), - msg::feature = f, + msg::feature = f.name, msg::constraint_origin = origin); } } @@ -1791,6 +1868,16 @@ namespace vcpkg if (p.second) { // Newly inserted -> Add stack frame + auto maybe_vars = m_var_provider.get_or_load_dep_info_vars(p.first->first, m_host_triplet); + + std::vector default_features; + for (const auto& feature : node.second.scfl->source_control_file->core_paragraph->default_features) + { + if (feature.platform.evaluate(maybe_vars)) + { + default_features.push_back(feature.name); + } + } std::vector deps; RequestType request = Util::Sets::contains(m_user_requested, dep.spec) ? RequestType::USER_REQUESTED : RequestType::AUTO_SELECTED; @@ -1800,7 +1887,8 @@ namespace vcpkg request, m_host_triplet, compute_feature_dependencies(node, deps), - {}); + {}, + std::move(default_features)); stack.push_back(Frame{std::move(ipa), std::move(deps)}); } else if (p.first->second == false) diff --git a/src/vcpkg/paragraphs.cpp b/src/vcpkg/paragraphs.cpp index ef6c18eae9..13f452aeed 100644 --- a/src/vcpkg/paragraphs.cpp +++ b/src/vcpkg/paragraphs.cpp @@ -198,7 +198,19 @@ namespace vcpkg loc); return nullopt; } - return Dependency{pqs.name, pqs.features.value_or({}), pqs.platform.value_or({})}; + Dependency dependency{pqs.name, {}, pqs.platform.value_or({})}; + for (const auto& feature : pqs.features.value_or({})) + { + if (feature == "core") + { + dependency.default_features = false; + } + else + { + dependency.features.emplace_back(feature); + } + } + return dependency; }); }); if (!opt) return {LocalizedString::from_raw(parser.get_error()->to_string()), expected_right_tag}; diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index 980f723394..802a67dfc4 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -40,9 +40,20 @@ namespace vcpkg }; } - FullPackageSpec Dependency::to_full_spec(Triplet target, Triplet host_triplet, ImplicitDefault id) const + bool Dependency::has_platform_expressions() const { - return FullPackageSpec{{name, host ? host_triplet : target}, internalize_feature_list(features, id)}; + return !platform.is_empty() || Util::any_of(features, [](const auto f) { return !f.platform.is_empty(); }); + } + + FullPackageSpec Dependency::to_full_spec(View feature_list, Triplet target, Triplet host_triplet) const + { + InternalFeatureSet internal_feature_list(feature_list.begin(), feature_list.end()); + internal_feature_list.push_back("core"); + if (default_features) + { + internal_feature_list.push_back("default"); + } + return FullPackageSpec{{name, host ? host_triplet : target}, std::move(internal_feature_list)}; } bool operator==(const Dependency& lhs, const Dependency& rhs) @@ -68,6 +79,19 @@ namespace vcpkg } bool operator!=(const DependencyOverride& lhs, const DependencyOverride& rhs); + bool operator==(const DependencyRequestedFeature& lhs, const DependencyRequestedFeature& rhs) + { + if (lhs.name != rhs.name) return false; + if (!structurally_equal(lhs.platform, rhs.platform)) return false; + + return true; + } + + bool operator!=(const DependencyRequestedFeature& lhs, const DependencyRequestedFeature& rhs) + { + return !(lhs == rhs); + } + struct UrlDeserializer : Json::StringDeserializer { LocalizedString type_name() const override { return msg::format(msgAUrl); } @@ -200,6 +224,19 @@ namespace vcpkg } }; + struct DependencyFeatureLess + { + bool operator()(const DependencyRequestedFeature& lhs, const DependencyRequestedFeature& rhs) const + { + if (lhs.name == rhs.name) + { + auto platform_cmp = compare(lhs.platform, rhs.platform); + return platform_cmp < 0; + } + return lhs.name < rhs.name; + } + }; + // assume canonicalized feature list struct DependencyLess { @@ -226,8 +263,11 @@ namespace vcpkg if (rhs.features.size() < lhs.features.size()) return false; // then finally order by feature list - if (std::lexicographical_compare( - lhs.features.begin(), lhs.features.end(), rhs.features.begin(), rhs.features.end())) + if (std::lexicographical_compare(lhs.features.begin(), + lhs.features.end(), + rhs.features.begin(), + rhs.features.end(), + DependencyFeatureLess{})) { return true; } @@ -243,7 +283,7 @@ namespace vcpkg void operator()(Dependency& dep) const { - std::sort(dep.features.begin(), dep.features.end()); + std::sort(dep.features.begin(), dep.features.end(), DependencyFeatureLess{}); dep.extra_info.sort_keys(); } void operator()(SourceParagraph& spgh) const @@ -251,7 +291,7 @@ namespace vcpkg std::for_each(spgh.dependencies.begin(), spgh.dependencies.end(), *this); std::sort(spgh.dependencies.begin(), spgh.dependencies.end(), DependencyLess{}); - std::sort(spgh.default_features.begin(), spgh.default_features.end()); + std::sort(spgh.default_features.begin(), spgh.default_features.end(), DependencyFeatureLess{}); spgh.extra_info.sort_keys(); } @@ -337,7 +377,8 @@ namespace vcpkg auto maybe_default_features = parse_default_features_list(buf, origin, textrowcol); if (const auto default_features = maybe_default_features.get()) { - spgh->default_features = *default_features; + spgh->default_features = + Util::fmap(*default_features, [](const auto& name) { return DependencyRequestedFeature{name}; }); } else { @@ -460,9 +501,52 @@ namespace vcpkg static const PlatformExprDeserializer instance; }; - const PlatformExprDeserializer PlatformExprDeserializer::instance; + struct DependencyFeatureDeserializer : Json::IDeserializer + { + LocalizedString type_name() const override { return msg::format(msgADependencyFeature); } + + constexpr static StringLiteral NAME = "name"; + constexpr static StringLiteral PLATFORM = "platform"; + + Span valid_fields() const override + { + static const StringView t[] = { + NAME, + PLATFORM, + }; + return t; + } + + Optional visit_string(Json::Reader& r, StringView sv) const override + { + return Json::IdentifierDeserializer::instance.visit_string(r, sv).map( + [](std::string&& name) { return std::move(name); }); + } + + Optional visit_object(Json::Reader& r, const Json::Object& obj) const override + { + std::string name; + PlatformExpression::Expr platform; + r.required_object_field(type_name(), obj, NAME, name, Json::IdentifierDeserializer::instance); + r.optional_object_field(obj, PLATFORM, platform, PlatformExprDeserializer::instance); + if (name.empty()) return nullopt; + return DependencyRequestedFeature{std::move(name), std::move(platform)}; + } + + const static DependencyFeatureDeserializer instance; + }; + const DependencyFeatureDeserializer DependencyFeatureDeserializer::instance; + + struct DependencyFeatureArrayDeserializer : Json::ArrayDeserializer + { + LocalizedString type_name() const override { return msg::format(msgAnArrayOfFeatures); } + + static const DependencyFeatureArrayDeserializer instance; + }; + const DependencyFeatureArrayDeserializer DependencyFeatureArrayDeserializer::instance; + struct DependencyDeserializer final : Json::IDeserializer { virtual LocalizedString type_name() const override { return msg::format(msgADependency); } @@ -513,15 +597,9 @@ namespace vcpkg } r.required_object_field(type_name(), obj, NAME, dep.name, Json::PackageNameDeserializer::instance); - r.optional_object_field(obj, FEATURES, dep.features, Json::IdentifierArrayDeserializer::instance); - - bool default_features = true; - r.optional_object_field(obj, DEFAULT_FEATURES, default_features, Json::BooleanDeserializer::instance); - if (!default_features) - { - dep.features.emplace_back("core"); - } + r.optional_object_field(obj, FEATURES, dep.features, DependencyFeatureArrayDeserializer::instance); + r.optional_object_field(obj, DEFAULT_FEATURES, dep.default_features, Json::BooleanDeserializer::instance); r.optional_object_field(obj, HOST, dep.host, Json::BooleanDeserializer::instance); r.optional_object_field(obj, PLATFORM, dep.platform, PlatformExprDeserializer::instance); auto has_ge_constraint = r.optional_object_field( @@ -1105,9 +1183,8 @@ namespace vcpkg } r.optional_object_field(obj, SUPPORTS, spgh.supports_expression, PlatformExprDeserializer::instance); - r.optional_object_field( - obj, DEFAULT_FEATURES, spgh.default_features, Json::IdentifierArrayDeserializer::instance); + obj, DEFAULT_FEATURES, spgh.default_features, DependencyFeatureArrayDeserializer::instance); FeaturesObject features_tmp; r.optional_object_field(obj, FEATURES, features_tmp, FeaturesFieldDeserializer::instance); @@ -1531,15 +1608,20 @@ namespace vcpkg std::vector filter_dependencies(const std::vector& deps, Triplet target, Triplet host, - const std::unordered_map& cmake_vars, - ImplicitDefault id) + const std::unordered_map& cmake_vars) { std::vector ret; for (auto&& dep : deps) { if (dep.platform.evaluate(cmake_vars)) { - ret.emplace_back(dep.to_full_spec(target, host, id)); + std::vector features; + features.reserve(dep.features.size()); + for (const auto& f : dep.features) + { + if (f.platform.evaluate(cmake_vars)) features.push_back(f.name); + } + ret.emplace_back(dep.to_full_spec(features, target, host)); } } return ret; @@ -1547,7 +1629,7 @@ namespace vcpkg static bool is_dependency_trivial(const Dependency& dep) { - return dep.features.empty() && dep.platform.is_empty() && dep.extra_info.is_empty() && + return dep.features.empty() && dep.default_features && dep.platform.is_empty() && dep.extra_info.is_empty() && dep.constraint.type == VersionConstraintKind::None && !dep.host; } @@ -1569,16 +1651,6 @@ namespace vcpkg return; } - auto& arr = obj.insert(name, Json::Array()); - for (const auto& s : pgh) - { - arr.push_back(Json::Value::string(s)); - } - }; - auto serialize_optional_array = - [&](Json::Object& obj, StringLiteral name, const std::vector& pgh) { - if (pgh.empty()) return; - auto& arr = obj.insert(name, Json::Array()); for (const auto& s : pgh) { @@ -1591,6 +1663,26 @@ namespace vcpkg obj.insert(name, s); } }; + auto serialize_dependency_features = [&](Json::Object& obj, StringLiteral name, const auto& features) { + if (!features.empty()) + { + auto& features_array = obj.insert(name, Json::Array()); + for (const auto& f : features) + { + if (f.platform.is_empty()) + { + features_array.push_back(Json::Value::string(f.name)); + } + else + { + Json::Object entry; + entry.insert(DependencyFeatureDeserializer::NAME, f.name); + entry.insert(DependencyFeatureDeserializer::PLATFORM, to_string(f.platform)); + features_array.push_back(std::move(entry)); + } + } + } + }; auto serialize_dependency = [&](Json::Array& arr, const Dependency& dep) { if (is_dependency_trivial(dep)) { @@ -1607,15 +1699,11 @@ namespace vcpkg dep_obj.insert(DependencyDeserializer::NAME, dep.name); if (dep.host) dep_obj.insert(DependencyDeserializer::HOST, Json::Value::boolean(true)); - auto features_copy = dep.features; - auto core_it = std::find(features_copy.begin(), features_copy.end(), "core"); - if (core_it != features_copy.end()) + if (!dep.default_features) { dep_obj.insert(DependencyDeserializer::DEFAULT_FEATURES, Json::Value::boolean(false)); - features_copy.erase(core_it); } - - serialize_optional_array(dep_obj, DependencyDeserializer::FEATURES, features_copy); + serialize_dependency_features(dep_obj, DependencyDeserializer::FEATURES, dep.features); serialize_optional_string(dep_obj, DependencyDeserializer::PLATFORM, to_string(dep.platform)); if (dep.constraint.type == VersionConstraintKind::Minimum) { @@ -1711,7 +1799,8 @@ namespace vcpkg } } - serialize_optional_array(obj, ManifestDeserializer::DEFAULT_FEATURES, scf.core_paragraph->default_features); + serialize_dependency_features( + obj, ManifestDeserializer::DEFAULT_FEATURES, scf.core_paragraph->default_features); if (!scf.feature_paragraphs.empty() || !scf.extra_features_info.is_empty()) {