diff --git a/include/vcpkg/base/util.h b/include/vcpkg/base/util.h index 371a122755..f6b26c16ec 100644 --- a/include/vcpkg/base/util.h +++ b/include/vcpkg/base/util.h @@ -142,6 +142,13 @@ namespace vcpkg::Util return std::find(begin(cont), end(cont), v); } + template + auto contains(Container&& cont, V&& v) + { + using std::end; + return find(cont, v) != end(cont); + } + template auto find_if(Container&& cont, Pred pred) { diff --git a/include/vcpkg/packagespec.h b/include/vcpkg/packagespec.h index 444051005a..533bda0785 100644 --- a/include/vcpkg/packagespec.h +++ b/include/vcpkg/packagespec.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -149,13 +150,17 @@ namespace vcpkg struct Dependency { - // Remove when support for MSVC v140 is dropped. Dependency(std::string n = {}, std::vector f = {}, PlatformExpression::Expr expr = {}, DependencyConstraint dc = {}, bool h = false) - : name(std::move(n)), features(std::move(f)), platform(std::move(expr)), constraint(std::move(dc)), host(h) + : name(std::move(n)) + , features(std::move(f)) + , platform(std::move(expr)) + , constraint(std::move(dc)) + , host(h) + , default_features(Util::contains(features, "core") ? DefaultFeatures::No : DefaultFeatures::DontCare) { } std::string name; @@ -163,7 +168,12 @@ namespace vcpkg PlatformExpression::Expr platform; DependencyConstraint constraint; bool host = false; - + enum class DefaultFeatures + { + Yes, + No, + DontCare + } default_features = DefaultFeatures::DontCare; Json::Object extra_info; friend bool operator==(const Dependency& lhs, const Dependency& rhs); diff --git a/src/vcpkg-test/dependencies.cpp b/src/vcpkg-test/dependencies.cpp index ce5db90eac..a36e5eb186 100644 --- a/src/vcpkg-test/dependencies.cpp +++ b/src/vcpkg-test/dependencies.cpp @@ -101,7 +101,7 @@ T unwrap(ExpectedS e) static void check_name_and_version(const Dependencies::InstallPlanAction& ipa, StringLiteral name, - Versions::Version v, + const Versions::Version& v, std::initializer_list features = {}) { CHECK(ipa.spec.name() == name); @@ -218,10 +218,11 @@ static ExpectedS create_versioned_install_plan( const CMakeVars::CMakeVarProvider& var_provider, const std::vector& deps, const std::vector& overrides, - const PackageSpec& toplevel) + const PackageSpec& toplevel, + const Triplet host_triplet = Test::ARM_UWP) { return Dependencies::create_versioned_install_plan( - provider, bprovider, s_empty_mock_overlay, var_provider, deps, overrides, toplevel, Test::ARM_UWP); + provider, bprovider, s_empty_mock_overlay, var_provider, deps, overrides, toplevel, host_triplet); } namespace vcpkg::Dependencies @@ -1521,6 +1522,139 @@ TEST_CASE ("version install transitive default features", "[versionplan]") check_name_and_version(install_plan.install_actions[1], "b", {"1", 0}); } +TEST_CASE ("version dont install default features when host", "[versionplan]") +{ + MockVersionedPortfileProvider vp; + + auto a_x = make_fpgh("x"); + auto& a_scf = vp.emplace("a", {"1", 0}, Scheme::Relaxed).source_control_file; + a_scf->core_paragraph->default_features.emplace_back("x"); + a_scf->feature_paragraphs.push_back(std::move(a_x)); + + MockCMakeVarProvider var_provider; + + MockBaselineProvider bp; + bp.v["a"] = {"1", 0}; + + SECTION ("only one host dependency ") + { + auto install_plan = unwrap(create_versioned_install_plan( + vp, bp, var_provider, {Dependency{"a", {}, {}, {}, true}}, {}, toplevel_spec())); + REQUIRE(install_plan.size() == 1); + check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {}); + } + SECTION ("only one host dependency, default-features = true") + { + Dependency dep{"a", {}, {}, {}, true}; + dep.default_features = Dependency::DefaultFeatures::Yes; + auto install_plan = unwrap(create_versioned_install_plan(vp, bp, var_provider, {dep}, {}, toplevel_spec())); + REQUIRE(install_plan.size() == 1); + check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); + } + SECTION ("triplet = host triplet") + { + auto install_plan = unwrap( + create_versioned_install_plan(vp, + bp, + var_provider, + {Dependency{"a", {}, {}, {}, true}, Dependency{"a", {}, {}, {}, false}}, + {}, + toplevel_spec(), + toplevel_spec().triplet())); + REQUIRE(install_plan.size() == 1); + check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); + } + SECTION ("triplet = host triplet, default-features = false") + { + auto install_plan = unwrap( + create_versioned_install_plan(vp, + bp, + var_provider, + {Dependency{"a", {}, {}, {}, true}, Dependency{"a", {"core"}, {}, {}, false}}, + {}, + toplevel_spec(), + toplevel_spec().triplet())); + REQUIRE(install_plan.size() == 1); + check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {}); + } +} + +TEST_CASE ("'default-features': true vs 'default-features': true", "[versionplan]") +{ + MockVersionedPortfileProvider vp; + + auto a_x = make_fpgh("x"); + auto& a_scf = vp.emplace("a", {"1", 0}, Scheme::Relaxed).source_control_file; + a_scf->core_paragraph->default_features.emplace_back("x"); + a_scf->feature_paragraphs.push_back(std::move(a_x)); + + MockCMakeVarProvider var_provider; + + MockBaselineProvider bp; + bp.v["a"] = {"1", 0}; + + SECTION ("default-features = true, host = true") + { + Dependency dep_host{"a", {}, {}, {}, true}; + dep_host.default_features = Dependency::DefaultFeatures::Yes; + Dependency dep_normal{"a", {}, {}, {}, false}; + dep_normal.default_features = Dependency::DefaultFeatures::No; + auto install_plan = unwrap(create_versioned_install_plan( + vp, bp, var_provider, {dep_host, dep_normal}, {}, toplevel_spec(), toplevel_spec().triplet())); + REQUIRE(install_plan.size() == 1); + check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); + } + SECTION ("default-features = true, host = false") + { + Dependency dep_host{"a", {}, {}, {}, true}; + dep_host.default_features = Dependency::DefaultFeatures::No; + Dependency dep_normal{"a", {}, {}, {}, false}; + dep_normal.default_features = Dependency::DefaultFeatures::Yes; + auto install_plan = unwrap(create_versioned_install_plan( + vp, bp, var_provider, {dep_host, dep_normal}, {}, toplevel_spec(), toplevel_spec().triplet())); + REQUIRE(install_plan.size() == 1); + check_name_and_version(install_plan.install_actions[0], "a", {"1", 0}, {"x"}); + } +} + +TEST_CASE ("version dont install default features when host (transitive)", "[versionplan]") +{ + MockVersionedPortfileProvider vp; + MockBaselineProvider bp; + const auto create_port = [&](auto name) -> auto& + { + auto a_x = make_fpgh("x"); + auto& a_scf = vp.emplace(name, {"1", 0}, Scheme::Relaxed).source_control_file; + a_scf->core_paragraph->default_features.emplace_back("x"); + a_scf->feature_paragraphs.push_back(std::move(a_x)); + bp.v[name] = {"1", 0}; + return a_scf; + }; + // every port has a default feature x; Dependencies a -> b, b -> c, c -> d + // we request a host dependency to "a" and a normal one to "c" while triplet = host-triplet + // => a and b should have no default features, c and d should have a default feature + create_port("a")->core_paragraph->dependencies.emplace_back("b"); + create_port("b")->core_paragraph->dependencies.emplace_back("c"); + create_port("c")->core_paragraph->dependencies.emplace_back("d"); + create_port("d"); + + MockCMakeVarProvider var_provider; + + auto install_plan = + unwrap(create_versioned_install_plan(vp, + bp, + var_provider, + {Dependency{"a", {}, {}, {}, true}, Dependency{"c", {}, {}, {}, false}}, + {}, + toplevel_spec(), + toplevel_spec().triplet())); + REQUIRE(install_plan.size() == 4); + check_name_and_version(install_plan.install_actions[0], "d", {"1", 0}, {"x"}); + check_name_and_version(install_plan.install_actions[1], "c", {"1", 0}, {"x"}); + check_name_and_version(install_plan.install_actions[2], "b", {"1", 0}, {}); + check_name_and_version(install_plan.install_actions[3], "a", {"1", 0}, {}); +} + static PlatformExpression::Expr parse_platform(StringView l) { return unwrap(PlatformExpression::parse_platform_expression(l, PlatformExpression::MultipleBinaryOperators::Deny)); diff --git a/src/vcpkg/dependencies.cpp b/src/vcpkg/dependencies.cpp index b6ac5475a9..d1b29d0c51 100644 --- a/src/vcpkg/dependencies.cpp +++ b/src/vcpkg/dependencies.cpp @@ -1250,6 +1250,7 @@ namespace vcpkg::Dependencies std::vector features; }; + struct PackageNode; // This object contains the current version within a given version sheme (except for the "string" scheme, // there we save an object for every version) struct VersionSchemeInfo @@ -1261,6 +1262,7 @@ namespace vcpkg::Dependencies std::vector origins; // mapping from feature name -> dependencies of this feature std::map> deps; + std::set dependencies; // track to which PackageNodes this vsi has dependencies bool is_less_than(const Versions::Version& new_ver) const; }; @@ -1276,15 +1278,15 @@ namespace vcpkg::Dependencies Optional> semver; Optional> date; std::set requested_features; - bool default_features = true; + // state to track if default features must be enables at next add_port_at_version call + bool update_default_features = false; bool user_requested = false; VersionSchemeInfo* get_node(const Versions::Version& ver); // Created/Updates(is newer than existing) the version entry for the given sheme VersionSchemeInfo& emplace_node(Versions::Scheme scheme, const Versions::Version& ver); - PackageNode() = default; - PackageNode(bool default_features) : default_features(default_features){}; + PackageNode(bool only_host_dependency) : only_host_dependency(only_host_dependency){}; PackageNode(const PackageNode&) = delete; PackageNode(PackageNode&&) = default; PackageNode& operator=(const PackageNode&) = delete; @@ -1310,6 +1312,47 @@ namespace vcpkg::Dependencies f(vsi.second); } } + + bool default_features_enabled() const + { + using DF = Dependency::DefaultFeatures; + return user_requested_default_features == DF::Yes || + (user_requested_default_features == DF::DontCare && !only_host_dependency); + } + void update_default_features_state(bool host_dependency, Dependency::DefaultFeatures user_requested) + { + const bool old = default_features_enabled(); + const bool old_only_host = only_host_dependency; + only_host_dependency = only_host_dependency && host_dependency; + if (user_requested_default_features == Dependency::DefaultFeatures::DontCare || + user_requested == Dependency::DefaultFeatures::Yes) + { + user_requested_default_features = user_requested; + } + if (old != default_features_enabled()) + { + update_default_features = true; + } + if (old_only_host != only_host_dependency) + { + // we are now a normal dependency, all dependencies of this port must be now also normal + // dependencies + foreach_vsi([](VersionSchemeInfo& vsi) { + for (PackageNode* dep : vsi.dependencies) + { + if (dep->is_only_host_dependency()) + { + dep->update_default_features_state(false, Dependency::DefaultFeatures::DontCare); + } + } + }); + } + } + bool is_only_host_dependency() const { return only_host_dependency; } + + private: + Dependency::DefaultFeatures user_requested_default_features = Dependency::DefaultFeatures::DontCare; + bool only_host_dependency = false; // if the package was only requested as host dependency }; std::vector m_roots; // the roots of the dependency graph (given in the manifest file) @@ -1317,7 +1360,10 @@ namespace vcpkg::Dependencies std::map m_overrides; std::map m_graph; // mapping from (portname, triplet) -> what to install - std::pair& emplace_package(const PackageSpec& spec); + std::pair& emplace_package( + const PackageSpec& spec, + bool host_dependency, + Dependency::DefaultFeatures user_requested = Dependency::DefaultFeatures::DontCare); void add_dependency_from(std::pair& ref, const Dependency& dep, @@ -1496,7 +1542,7 @@ namespace vcpkg::Dependencies } } - auto& dep_node = emplace_package(dep_spec); + auto& dep_node = emplace_package(dep_spec, dep.host || ref.second.is_only_host_dependency()); if (dep_spec == ref.first) { // this is a feature dependency for oneself @@ -1508,6 +1554,7 @@ namespace vcpkg::Dependencies else { add_dependency_from(dep_node, dep, ref.first.name()); + vsi.dependencies.insert(&dep_node.second); } p.first->second.emplace_back(dep_spec, "core"); @@ -1602,11 +1649,12 @@ namespace vcpkg::Dependencies replace = versioned_graph_entry.is_less_than(version); } - if (replace) + if (replace || graph_entry.second.update_default_features) { versioned_graph_entry.scfl = p_scfl; versioned_graph_entry.version = to_version(*p_scfl->source_control_file); versioned_graph_entry.deps.clear(); + versioned_graph_entry.dependencies.clear(); // add all dependencies to the graph add_feature_to(graph_entry, versioned_graph_entry, "core"); @@ -1615,14 +1663,14 @@ namespace vcpkg::Dependencies { add_feature_to(graph_entry, versioned_graph_entry, f); } - - if (graph_entry.second.default_features) + if (graph_entry.second.default_features_enabled()) { for (auto&& f : p_scfl->source_control_file->core_paragraph->default_features) { add_feature_to(graph_entry, versioned_graph_entry, f); } } + graph_entry.second.update_default_features = false; } } else @@ -1645,9 +1693,11 @@ namespace vcpkg::Dependencies } std::pair& VersionedPackageGraph::emplace_package( - const PackageSpec& spec) + const PackageSpec& spec, bool host_dependency, Dependency::DefaultFeatures user_requested) { - return *m_graph.emplace(spec, PackageNode{}).first; + auto& node = *m_graph.emplace(spec, PackageNode(host_dependency)).first; + node.second.update_default_features_state(host_dependency, user_requested); + return node; } Optional VersionedPackageGraph::dep_to_version(const std::string& name, @@ -1712,13 +1762,8 @@ namespace vcpkg::Dependencies auto spec = dep_to_spec(dep); - auto& node = emplace_package(spec); + auto& node = emplace_package(spec, dep.host, dep.default_features); node.second.user_requested = true; - // Disable default features for deps with [core] as a dependency - if (Util::find(dep.features, "core") != dep.features.end()) - { - node.second.default_features = false; - } auto maybe_overlay = m_o_provider.get_control_file(dep.name); auto over_it = m_overrides.find(dep.name); @@ -1848,7 +1893,9 @@ namespace vcpkg::Dependencies const Versions::Version& new_ver, const PackageSpec& origin, View features) -> Optional { - auto&& node = m_graph[spec]; + auto iter = m_graph.find(spec); + Checks::check_exit(VCPKG_LINE_INFO, iter != m_graph.end()); + auto&& node = iter->second; auto overlay = m_o_provider.get_control_file(spec.name()); auto over_it = m_overrides.find(spec.name()); @@ -1973,7 +2020,9 @@ namespace vcpkg::Dependencies auto& back = stack.back(); if (back.deps.empty()) { - emitted[back.ipa.spec] = m_graph[back.ipa.spec].get_node( + auto iter = m_graph.find(back.ipa.spec); + Checks::check_exit(VCPKG_LINE_INFO, iter != m_graph.end()); + emitted[back.ipa.spec] = iter->second.get_node( to_version(*back.ipa.source_control_file_location.get()->source_control_file)); ret.install_actions.push_back(std::move(back.ipa)); stack.pop_back(); diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index d81893aa7c..4772036d2d 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -480,7 +480,11 @@ namespace vcpkg r.optional_object_field(obj, FEATURES, dep.features, arr_id_d); bool default_features = true; - r.optional_object_field(obj, DEFAULT_FEATURES, default_features, Json::BooleanDeserializer::instance); + if (r.optional_object_field(obj, DEFAULT_FEATURES, default_features, Json::BooleanDeserializer::instance)) + { + using DF = Dependency::DefaultFeatures; + dep.default_features = default_features ? DF::Yes : DF::No; + } if (!default_features) { dep.features.push_back("core");