diff options
| author | Jonathan Hale <Squareys@googlemail.com> | 2018-02-15 01:18:25 +0100 |
|---|---|---|
| committer | Robert Schumacher <roschuma@microsoft.com> | 2018-02-14 16:18:25 -0800 |
| commit | 425d07ef7f82541e50cc8cb1e2716fc33bba7b12 (patch) | |
| tree | e73f3675b5fe95f0dc61521a2a7457a6e5c08d53 /toolsrc | |
| parent | 50a545fcff4b3c922aa3ef5d0646dc9850f1bb86 (diff) | |
| download | vcpkg-425d07ef7f82541e50cc8cb1e2716fc33bba7b12.tar.gz vcpkg-425d07ef7f82541e50cc8cb1e2716fc33bba7b12.zip | |
[vcpkg] Implement Default-Features (#2697)
* [vcpkg] Add Default-Feature to make_status_pgh utility function
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Parse "Default-Features" as dependencies and add test for parsing
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Document some methods and structures
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Add install_default_features_test
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Change install_default_features_test to not have preinstalled package
* [vcpkg] Test install behaviour of default features
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Implement default features
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Test default features upgrade behavior
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Implement upgrade with default features
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Test behaviour of upgrade with default features in dependencies
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Make upgrade install new default features
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Move collecting of packages for which to prevent defaults
Further down the line to create_feature_install_plan.
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Fix core missing from default features and potential inf loop
Signed-off-by: Squareys <squareys@googlemail.com>
* [vcpkg] Rename, fix and move some tests
Signed-off-by: Squareys <squareys@googlemail.com>
Diffstat (limited to 'toolsrc')
| -rw-r--r-- | toolsrc/include/tests.utils.h | 1 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/dependencies.h | 3 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/packagespec.h | 23 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/sourceparagraph.h | 9 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/statusparagraphs.h | 5 | ||||
| -rw-r--r-- | toolsrc/src/tests.paragraph.cpp | 35 | ||||
| -rw-r--r-- | toolsrc/src/tests.plan.cpp | 245 | ||||
| -rw-r--r-- | toolsrc/src/tests.utils.cpp | 6 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/dependencies.cpp | 129 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/install.cpp | 5 |
10 files changed, 434 insertions, 27 deletions
diff --git a/toolsrc/include/tests.utils.h b/toolsrc/include/tests.utils.h index b23abe4eb..970506663 100644 --- a/toolsrc/include/tests.utils.h +++ b/toolsrc/include/tests.utils.h @@ -20,6 +20,7 @@ namespace Microsoft::VisualStudio::CppUnitTestFramework std::unique_ptr<vcpkg::StatusParagraph> make_status_pgh(const char* name, const char* depends = "", + const char* default_features = "", const char* triplet = "x86-windows"); std::unique_ptr<vcpkg::StatusParagraph> make_status_feature_pgh(const char* name, const char* feature, diff --git a/toolsrc/include/vcpkg/dependencies.h b/toolsrc/include/vcpkg/dependencies.h index 014f004a2..4abeb678e 100644 --- a/toolsrc/include/vcpkg/dependencies.h +++ b/toolsrc/include/vcpkg/dependencies.h @@ -151,7 +151,8 @@ namespace vcpkg::Dependencies PackageGraph(const PortFileProvider& provider, const StatusParagraphs& status_db); ~PackageGraph(); - void install(const FeatureSpec& spec) const; + void install(const FeatureSpec& spec, + const std::unordered_set<std::string>& prevent_default_features = {}) const; void upgrade(const PackageSpec& spec) const; std::vector<AnyAction> serialize() const; diff --git a/toolsrc/include/vcpkg/packagespec.h b/toolsrc/include/vcpkg/packagespec.h index f1119e2f3..0a4347639 100644 --- a/toolsrc/include/vcpkg/packagespec.h +++ b/toolsrc/include/vcpkg/packagespec.h @@ -15,6 +15,12 @@ namespace vcpkg static ExpectedT<ParsedSpecifier, PackageSpecParseResult> from_string(const std::string& input); }; + /// + /// <summary> + /// Full specification of a package. Contains all information to reference + /// a specific package. + /// </summary> + /// struct PackageSpec { static ExpectedT<PackageSpec, PackageSpecParseResult> from_name_and_triplet(const std::string& name, @@ -46,6 +52,12 @@ namespace vcpkg Triplet m_triplet; }; + /// + /// <summary> + /// Full specification of a feature. Contains all information to reference + /// a single feature in a specific package. + /// </summary> + /// struct FeatureSpec { FeatureSpec(const PackageSpec& spec, const std::string& feature) : m_spec(spec), m_feature(feature) {} @@ -82,6 +94,12 @@ namespace vcpkg std::string m_feature; }; + /// + /// <summary> + /// Full specification of a package. Contains all information to reference + /// a collection of features in a single package. + /// </summary> + /// struct FullPackageSpec { PackageSpec package_spec; @@ -93,6 +111,11 @@ namespace vcpkg const Triplet& default_triplet); }; + /// + /// <summary> + /// Contains all information to reference a collection of features in a single package by their names. + /// </summary> + /// struct Features { std::string name; diff --git a/toolsrc/include/vcpkg/sourceparagraph.h b/toolsrc/include/vcpkg/sourceparagraph.h index dcbbc1c3b..ea8e27a94 100644 --- a/toolsrc/include/vcpkg/sourceparagraph.h +++ b/toolsrc/include/vcpkg/sourceparagraph.h @@ -29,6 +29,9 @@ namespace vcpkg std::string to_string(const Dependency& dep); + /// <summary> + /// Port metadata of additional feature in a package (part of CONTROL file) + /// </summary> struct FeatureParagraph { std::string name; @@ -37,7 +40,7 @@ namespace vcpkg }; /// <summary> - /// Port metadata (CONTROL file) + /// Port metadata of the core feature of a package (part of CONTROL file) /// </summary> struct SourceParagraph { @@ -49,6 +52,10 @@ namespace vcpkg std::vector<Dependency> depends; std::vector<std::string> default_features; }; + + /// <summary> + /// Full metadata of a package: core and other features. + /// </summary> struct SourceControlFile { static Parse::ParseExpected<SourceControlFile> parse_control_file( diff --git a/toolsrc/include/vcpkg/statusparagraphs.h b/toolsrc/include/vcpkg/statusparagraphs.h index fd33f8094..b3eec266a 100644 --- a/toolsrc/include/vcpkg/statusparagraphs.h +++ b/toolsrc/include/vcpkg/statusparagraphs.h @@ -6,6 +6,11 @@ namespace vcpkg { + /// <summary>Status paragraphs</summary> + /// + /// Collection of <see cref="vcpkg::StatusParagraph"/>, e.g. contains the information + /// about whether a package is installed or not. + /// struct StatusParagraphs { StatusParagraphs(); diff --git a/toolsrc/src/tests.paragraph.cpp b/toolsrc/src/tests.paragraph.cpp index 9cddd7bdb..dca89bc59 100644 --- a/toolsrc/src/tests.paragraph.cpp +++ b/toolsrc/src/tests.paragraph.cpp @@ -1,4 +1,4 @@ -#include "tests.pch.h"
+#include "tests.pch.h" #pragma comment(lib, "version") #pragma comment(lib, "winhttp") @@ -47,6 +47,7 @@ namespace UnitTest1 {"Maintainer", "m"}, {"Description", "d"}, {"Build-Depends", "bd"}, + {"Default-Features", "df"}, {"Supports", "x64"}, }}); Assert::IsTrue(m_pgh.has_value()); @@ -58,6 +59,8 @@ namespace UnitTest1 Assert::AreEqual("d", pgh->core_paragraph->description.c_str()); Assert::AreEqual(size_t(1), pgh->core_paragraph->depends.size()); Assert::AreEqual("bd", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual(size_t(1), pgh->core_paragraph->default_features.size()); + Assert::AreEqual("df", pgh->core_paragraph->default_features[0].c_str()); Assert::AreEqual(size_t(1), pgh->core_paragraph->supports.size()); Assert::AreEqual("x64", pgh->core_paragraph->supports[0].c_str()); } @@ -134,6 +137,21 @@ namespace UnitTest1 Assert::AreEqual("uwp", pgh->core_paragraph->depends[1].qualifier.c_str()); } + TEST_METHOD(SourceParagraph_Default_Features) + { + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "a"}, + {"Version", "1.0"}, + {"Default-Features", "a1"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual(size_t(1), pgh->core_paragraph->default_features.size()); + Assert::AreEqual("a1", pgh->core_paragraph->default_features[0].c_str()); + } + TEST_METHOD(BinaryParagraph_Construct_Minimum) { vcpkg::BinaryParagraph pgh({ @@ -200,6 +218,21 @@ namespace UnitTest1 Assert::IsTrue(pgh.abi == "abcd123"); } + TEST_METHOD(BinaryParagraph_Default_Features) + { + vcpkg::BinaryParagraph pgh({ + {"Package", "a"}, + {"Version", "1.0"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Default-Features", "a1"}, + }); + + Assert::AreEqual(size_t(0), pgh.depends.size()); + Assert::AreEqual(size_t(1), pgh.default_features.size()); + Assert::IsTrue(pgh.default_features[0] == "a1"); + } + TEST_METHOD(parse_paragraphs_empty) { const char* str = ""; diff --git a/toolsrc/src/tests.plan.cpp b/toolsrc/src/tests.plan.cpp index 86386be14..a4c628bf6 100644 --- a/toolsrc/src/tests.plan.cpp +++ b/toolsrc/src/tests.plan.cpp @@ -9,15 +9,17 @@ using namespace vcpkg; namespace UnitTest1 { static std::unique_ptr<SourceControlFile> make_control_file( - const char* name, const char* depends, const std::vector<std::pair<const char*, const char*>>& features = {}) + const char* name, + const char* depends, + const std::vector<std::pair<const char*, const char*>>& features = {}, + const std::vector<const char*>& default_features = {}) { using Pgh = std::unordered_map<std::string, std::string>; std::vector<Pgh> scf_pghs; - scf_pghs.push_back(Pgh{ - {"Source", name}, - {"Version", "0"}, - {"Build-Depends", depends}, - }); + scf_pghs.push_back(Pgh{{"Source", name}, + {"Version", "0"}, + {"Build-Depends", depends}, + {"Default-Features", Strings::join(", ", default_features)}}); for (auto&& feature : features) { scf_pghs.push_back(Pgh{ @@ -31,11 +33,15 @@ namespace UnitTest1 return std::move(*m_pgh.get()); } + /// <summary> + /// Assert that the given action an install of given features from given package. + /// </summary> static void features_check(Dependencies::AnyAction* install_action, std::string pkg_name, std::vector<std::string> vec, const Triplet& triplet = Triplet::X86_WINDOWS) { + Assert::IsTrue(install_action->install_action.has_value()); const auto& plan = install_action->install_action.value_or_exit(VCPKG_LINE_INFO); const auto& feature_list = plan.feature_list; @@ -56,6 +62,9 @@ namespace UnitTest1 } } + /// <summary> + /// Assert that the given action is a remove of given package. + /// </summary> static void remove_plan_check(Dependencies::AnyAction* remove_action, std::string pkg_name, const Triplet& triplet = Triplet::X86_WINDOWS) @@ -65,6 +74,9 @@ namespace UnitTest1 Assert::AreEqual(pkg_name.c_str(), plan.spec.name().c_str()); } + /// <summary> + /// Map of source control files by their package name. + /// </summary> struct PackageSpecMap { std::unordered_map<std::string, SourceControlFile> map; @@ -73,9 +85,10 @@ namespace UnitTest1 PackageSpec emplace(const char* name, const char* depends = "", - const std::vector<std::pair<const char*, const char*>>& features = {}) + const std::vector<std::pair<const char*, const char*>>& features = {}, + const std::vector<const char*>& default_features = {}) { - return emplace(std::move(*make_control_file(name, depends, features))); + return emplace(std::move(*make_control_file(name, depends, features, default_features))); } PackageSpec emplace(vcpkg::SourceControlFile&& scf) { @@ -434,6 +447,190 @@ namespace UnitTest1 features_check(&install_plan[0], "a", {"0", "1", "core"}, Triplet::X64_WINDOWS); } + TEST_METHOD(install_default_features_test_1) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" with default features "1" and features "0" and "1". + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect the default feature "1" to be installed, but not "0" + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"1", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_test_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // Add a port "a" of which "core" is already installed, but we will + // install the default features "explicitly" + // "a" has two features, of which "a1" is default. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get removed for rebuild and then installed with default + // features. + Assert::IsTrue(install_plan.size() == 2); + remove_plan_check(&install_plan[0], "a", Triplet::X64_WINDOWS); + features_check(&install_plan[1], "a", {"a1", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_test_3) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // "a" has two features, of which "a1" is default. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); + + // Explicitly install "a" without default features + auto install_specs = FullPackageSpec::from_string("a[core]", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect the default feature not to get installed. + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_of_dependency_test_1) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed and defaults of "b" through the dependency, + // as no explicit features of "b" are installed by the user. + Assert::IsTrue(install_plan.size() == 2); + features_check(&install_plan[1], "a", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[0], "b", {"b1", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_of_dependency_test_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed, not the defaults of "b", as the required + // dependencies are already there, installed explicitly by the user. + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(upgrade_with_default_features_1) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "", "1")); + pghs.push_back(make_status_feature_pgh("a", "0")); + pghs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X86_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + StatusParagraphs status_db(std::move(pghs)); + + // Add a port "a" of which "core" and "0" are already installed. + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should not install the default feature + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + Assert::AreEqual("a", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].install_action.has_value()); + features_check(&plan[1], "a", {"core", "0"}, Triplet::X86_WINDOWS); + } + + TEST_METHOD(upgrade_with_default_features_2) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("b")); + pghs.push_back(make_status_pgh("a", "b[core]")); + pghs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + StatusParagraphs status_db(std::move(pghs)); + + // Add a port "a" of which "core" and "0" are already installed. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a = spec_map.emplace("a", "b[core]"); + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should not install the default feature + Assert::AreEqual(size_t(3), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + Assert::AreEqual("b", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].install_action.has_value()); + features_check(&plan[1], "b", {"b0", "core"}, Triplet::X64_WINDOWS); + + Assert::AreEqual("a", plan[2].spec().name().c_str()); + Assert::IsTrue(plan[2].install_action.has_value()); + features_check(&plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + } + TEST_METHOD(transitive_features_test) { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; @@ -567,9 +764,9 @@ namespace UnitTest1 TEST_METHOD(features_depend_remove_scheme_once_removed_x64) { std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("expat", "", "x64")); - pghs.push_back(make_status_pgh("vtk", "expat", "x64")); - pghs.push_back(make_status_pgh("opencv", "", "x64")); + pghs.push_back(make_status_pgh("expat", "", "", "x64")); + pghs.push_back(make_status_pgh("vtk", "expat", "", "x64")); + pghs.push_back(make_status_pgh("opencv", "", "", "x64")); pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk", "x64")); StatusParagraphs status_db(std::move(pghs)); @@ -715,5 +912,31 @@ namespace UnitTest1 features_check(&plan[1], "a", {"core", "a1"}); } + + TEST_METHOD(basic_upgrade_scheme_with_new_default_feature) + { + // only core of package "a" is installed + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + // a1 was added as a default feature and should be installed in upgrade + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + features_check(&plan[1], "a", {"core", "a1"}); + } }; } diff --git a/toolsrc/src/tests.utils.cpp b/toolsrc/src/tests.utils.cpp index 19e877920..a3d8ffc7d 100644 --- a/toolsrc/src/tests.utils.cpp +++ b/toolsrc/src/tests.utils.cpp @@ -35,7 +35,10 @@ namespace Microsoft::VisualStudio::CppUnitTestFramework std::wstring ToString(const vcpkg::PackageSpec& t) { return ToString(t.to_string()); } } -std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, const char* depends, const char* triplet) +std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, + const char* depends, + const char* default_features, + const char* triplet) { using Pgh = std::unordered_map<std::string, std::string>; return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, @@ -43,6 +46,7 @@ std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, const char* d {"Architecture", triplet}, {"Multi-Arch", "same"}, {"Depends", depends}, + {"Default-Features", default_features}, {"Status", "install ok installed"}}); } std::unique_ptr<StatusParagraph> make_status_feature_pgh(const char* name, diff --git a/toolsrc/src/vcpkg/dependencies.cpp b/toolsrc/src/vcpkg/dependencies.cpp index 4842fde96..307c779c8 100644 --- a/toolsrc/src/vcpkg/dependencies.cpp +++ b/toolsrc/src/vcpkg/dependencies.cpp @@ -20,6 +20,9 @@ namespace vcpkg::Dependencies bool plus = false; }; + /// <summary> + /// Representation of a package and its features in a ClusterGraph. + /// </summary> struct Cluster : Util::MoveOnlyBase { InstalledPackageView installed_package; @@ -64,10 +67,18 @@ namespace vcpkg::Dependencies Graphs::Graph<ClusterPtr> install_graph; }; + /// <summary> + /// Directional graph representing a collection of packages with their features connected by their dependencies. + /// </summary> struct ClusterGraph : Util::MoveOnlyBase { explicit ClusterGraph(const PortFileProvider& provider) : m_provider(provider) {} + /// <summary> + /// Find the cluster associated with spec or if not found, create it from the PortFileProvider. + /// </summary> + /// <param name="spec">Package spec to get the cluster for.</param> + /// <returns>The cluster found or created for spec.</returns> Cluster& get(const PackageSpec& spec) { auto it = m_graph.find(spec); @@ -414,16 +425,52 @@ namespace vcpkg::Dependencies static MarkPlusResult mark_plus(const std::string& feature, Cluster& cluster, ClusterGraph& graph, - GraphPlan& graph_plan); + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features = {}); static void mark_minus(Cluster& cluster, ClusterGraph& graph, GraphPlan& graph_plan); - MarkPlusResult mark_plus(const std::string& feature, Cluster& cluster, ClusterGraph& graph, GraphPlan& graph_plan) + MarkPlusResult mark_plus(const std::string& feature, + Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features) { if (feature.empty()) { - // Indicates that core was not specified in the reference - return mark_plus("core", cluster, graph, graph_plan); + if (prevent_default_features.find(cluster.spec.name()) == prevent_default_features.end()) + { + // Indicates that core was not specified in the reference + + // Add default features for this package, if this is the "core" feature and we + // are not supposed to prevent default features for this package + if (auto scf = cluster.source_control_file.value_or(nullptr)) + { + for (auto&& default_feature : scf->core_paragraph.get()->default_features) + { + auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) + { + return res; + } + } + + // "core" is always an implicit default feature. In case we did not add it as + // a dependency above (e.g. no default features), add it here. + auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) + { + return res; + } + } + + return MarkPlusResult::SUCCESS; + } + else + { + // Skip adding the default features, as explicitly told not to. + return MarkPlusResult::SUCCESS; + } } auto it = cluster.edges.find(feature); @@ -454,16 +501,21 @@ namespace vcpkg::Dependencies if (feature != "core") { // All features implicitly depend on core - auto res = mark_plus("core", cluster, graph, graph_plan); + auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); // Should be impossible for "core" to not exist Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); } + else + { + // Add the default features of this package. + auto res = mark_plus("", cluster, graph, graph_plan, prevent_default_features); + } for (auto&& depend : cluster.edges[feature].build_edges) { auto& depend_cluster = graph.get(depend.spec()); - auto res = mark_plus(depend.feature(), depend_cluster, graph, graph_plan); + auto res = mark_plus(depend.feature(), depend_cluster, graph, graph_plan, prevent_default_features); Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, @@ -483,6 +535,18 @@ namespace vcpkg::Dependencies if (cluster.will_remove) return; cluster.will_remove = true; + std::unordered_set<std::string> prevent_default_features; + + if (cluster.request_type == RequestType::USER_REQUESTED) + { + // Do not install default features for packages which the user + // installed explicitly. New default features for dependent + // clusters should still be upgraded. + prevent_default_features.insert(cluster.spec.name()); + + // For dependent packages this is handles through the recursion + } + graph_plan.remove_graph.add_vertex({&cluster}); for (auto&& pair : cluster.edges) { @@ -498,7 +562,7 @@ namespace vcpkg::Dependencies cluster.transient_uninstalled = true; for (auto&& original_feature : cluster.original_features) { - auto res = mark_plus(original_feature, cluster, graph, graph_plan); + auto res = mark_plus(original_feature, cluster, graph, graph_plan, prevent_default_features); if (res != MarkPlusResult::SUCCESS) { System::println(System::Color::warning, @@ -506,19 +570,54 @@ namespace vcpkg::Dependencies FeatureSpec{cluster.spec, original_feature}); } } + + // Check if any default features have been added + if (auto scf = cluster.source_control_file.value_or(nullptr)) + { + auto& previous_df = cluster.installed_package.core->package.default_features; + for (auto&& default_feature : scf->core_paragraph->default_features) + { + if (std::find(previous_df.begin(), previous_df.end(), default_feature) == previous_df.end()) + { + // this is a new default feature, mark it for installation + auto res = mark_plus(default_feature, cluster, graph, graph_plan); + if (res != MarkPlusResult::SUCCESS) + { + System::println(System::Color::warning, + "Warning: could not install new default feature %s", + FeatureSpec{cluster.spec, default_feature}); + } + } + } + } } + /// <summary>Figure out which actions are required to install features specifications in `specs`.</summary> + /// <param name="provider">Contains the ports of the current environment.</param> + /// <param name="specs">Feature specifications to resolve dependencies for.</param> + /// <param name="status_db">Status of installed packages in the current environment.</param> std::vector<AnyAction> create_feature_install_plan(const PortFileProvider& provider, const std::vector<FeatureSpec>& specs, const StatusParagraphs& status_db) { + std::unordered_set<std::string> prevent_default_features; + for (auto&& spec : specs) + { + // When "core" is explicitly listed, default features should not be installed. + if (spec.feature() == "core") prevent_default_features.insert(spec.name()); + } + PackageGraph pgraph(provider, status_db); for (auto&& spec : specs) - pgraph.install(spec); + pgraph.install(spec, prevent_default_features); return pgraph.serialize(); } + /// <summary>Figure out which actions are required to install features specifications in `specs`.</summary> + /// <param name="map">Map of all source files in the current environment.</param> + /// <param name="specs">Feature specifications to resolve dependencies for.</param> + /// <param name="status_db">Status of installed packages in the current environment.</param> std::vector<AnyAction> create_feature_install_plan(const std::unordered_map<std::string, SourceControlFile>& map, const std::vector<FeatureSpec>& specs, const StatusParagraphs& status_db) @@ -527,7 +626,12 @@ namespace vcpkg::Dependencies return create_feature_install_plan(provider, specs, status_db); } - void PackageGraph::install(const FeatureSpec& spec) const + /// <param name="prevent_default_features"> + /// List of package names for which default features should not be installed instead of the core package (e.g. if + /// the user is currently installing specific features of that package). + /// </param> + void PackageGraph::install(const FeatureSpec& spec, + const std::unordered_set<std::string>& prevent_default_features) const { Cluster& spec_cluster = m_graph->get(spec.spec()); spec_cluster.request_type = RequestType::USER_REQUESTED; @@ -537,13 +641,14 @@ namespace vcpkg::Dependencies { for (auto&& feature : p_scf->feature_paragraphs) { - auto res = mark_plus(feature->name, spec_cluster, *m_graph, *m_graph_plan); + auto res = + mark_plus(feature->name, spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); Checks::check_exit( VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); } - auto res = mark_plus("core", spec_cluster, *m_graph, *m_graph_plan); + auto res = mark_plus("core", spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); Checks::check_exit( VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); @@ -556,7 +661,7 @@ namespace vcpkg::Dependencies } else { - auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan); + auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); Checks::check_exit( VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index 081063633..595945d39 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -513,6 +513,11 @@ namespace vcpkg::Install } } + /// + /// <summary> + /// Run "install" command. + /// </summary> + /// void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) { // input sanitization |
