diff --git a/toolsrc/include/BinaryParagraph.h b/toolsrc/include/BinaryParagraph.h index 1c2edf790..f411b3c39 100644 --- a/toolsrc/include/BinaryParagraph.h +++ b/toolsrc/include/BinaryParagraph.h @@ -14,6 +14,9 @@ namespace vcpkg BinaryParagraph(); explicit BinaryParagraph(std::unordered_map fields); BinaryParagraph(const SourceParagraph& spgh, const Triplet& triplet); + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, + const FeatureParagraph& fpgh, + const Triplet& triplet); std::string displayname() const; @@ -25,8 +28,10 @@ namespace vcpkg std::string version; std::string description; std::string maintainer; + std::string feature; + std::vector default_features; std::vector depends; }; void serialize(const BinaryParagraph& pgh, std::string& out_str); -} +} \ No newline at end of file diff --git a/toolsrc/include/PackageSpec.h b/toolsrc/include/PackageSpec.h index 62b6fc9de..15b5e5b9b 100644 --- a/toolsrc/include/PackageSpec.h +++ b/toolsrc/include/PackageSpec.h @@ -1,5 +1,6 @@ #pragma once #include "PackageSpecParseResult.h" +#include "SourceParagraph.h" #include "Triplet.h" #include "vcpkg_expected.h" @@ -7,8 +8,6 @@ namespace vcpkg { struct PackageSpec { - static ExpectedT from_string(const std::string& spec_as_string, - const Triplet& default_triplet); static std::string to_string(const std::string& name, const Triplet& triplet); static ExpectedT from_name_and_triplet(const std::string& name, const Triplet& triplet); @@ -30,6 +29,9 @@ namespace vcpkg { PackageSpec package_spec; std::vector features; + + static ExpectedT from_string(const std::string& spec_as_string, + const Triplet& default_triplet); }; bool operator==(const PackageSpec& left, const PackageSpec& right); diff --git a/toolsrc/include/SourceParagraph.h b/toolsrc/include/SourceParagraph.h index e85884b51..7ddf999cc 100644 --- a/toolsrc/include/SourceParagraph.h +++ b/toolsrc/include/SourceParagraph.h @@ -60,6 +60,7 @@ namespace vcpkg std::vector filter_dependencies(const std::vector& deps, const Triplet& t); + // zlib[uwp] becomes Dependency{"zlib", "uwp"} std::vector expand_qualified_dependencies(const std::vector& depends); std::vector parse_comma_list(const std::string& str); diff --git a/toolsrc/include/vcpkg_Build.h b/toolsrc/include/vcpkg_Build.h index 9a4e2baeb..c4f3e6746 100644 --- a/toolsrc/include/vcpkg_Build.h +++ b/toolsrc/include/vcpkg_Build.h @@ -95,14 +95,35 @@ namespace vcpkg::Build const Triplet& triplet, fs::path&& port_dir, const BuildPackageOptions& build_package_options) - : src(src), triplet(triplet), port_dir(std::move(port_dir)), build_package_options(build_package_options) + : src(src) + , scf(nullptr) + , triplet(triplet) + , port_dir(std::move(port_dir)) + , build_package_options(build_package_options) + , feature_list(nullptr) + { + } + + BuildPackageConfig(const SourceControlFile& src, + const Triplet& triplet, + fs::path&& port_dir, + const BuildPackageOptions& build_package_options, + const std::unordered_set& feature_list) + : src(*src.core_paragraph) + , scf(&src) + , triplet(triplet) + , port_dir(std::move(port_dir)) + , build_package_options(build_package_options) + , feature_list(&feature_list) { } const SourceParagraph& src; + const SourceControlFile* scf; const Triplet& triplet; fs::path port_dir; const BuildPackageOptions& build_package_options; + const std::unordered_set* feature_list; }; ExtendedBuildResult build_package(const VcpkgPaths& paths, diff --git a/toolsrc/include/vcpkg_Commands.h b/toolsrc/include/vcpkg_Commands.h index 67319f240..8348a64e4 100644 --- a/toolsrc/include/vcpkg_Commands.h +++ b/toolsrc/include/vcpkg_Commands.h @@ -77,6 +77,7 @@ namespace vcpkg::Commands namespace Remove { void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet); + void remove_package(const VcpkgPaths& paths, const PackageSpec& spec, StatusParagraphs* status_db); } namespace Update diff --git a/toolsrc/include/vcpkg_Dependencies.h b/toolsrc/include/vcpkg_Dependencies.h index 018c4f5cf..e3af0fd28 100644 --- a/toolsrc/include/vcpkg_Dependencies.h +++ b/toolsrc/include/vcpkg_Dependencies.h @@ -2,6 +2,7 @@ #include "PackageSpec.h" #include "StatusParagraphs.h" #include "VcpkgPaths.h" +#include "vcpkg_Graphs.h" #include "vcpkg_optional.h" #include @@ -23,7 +24,49 @@ namespace vcpkg::Dependencies Optional status_paragraph; Optional binary_paragraph; Optional source_paragraph; + Optional source_control_file; }; +} + +namespace vcpkg::Dependencies +{ + struct FeatureSpec + { + PackageSpec spec; + std::string feature_name; + }; + + struct FeatureNodeEdges + { + std::vector remove_edges; + std::vector build_edges; + bool plus = false; + }; + std::vector to_feature_specs(const std::vector& depends, const Triplet& t); + + struct Cluster + { + std::vector status_paragraphs; + Optional source_control_file; + PackageSpec spec; + std::unordered_map edges; + std::unordered_set to_install_features; + std::unordered_set original_features; + bool will_remove = false; + bool transient_uninstalled = true; + Cluster() = default; + + private: + Cluster(const Cluster&) = delete; + Cluster& operator=(const Cluster&) = delete; + }; + + struct ClusterPtr + { + Cluster* ptr; + }; + + bool operator==(const ClusterPtr& l, const ClusterPtr& r); enum class InstallPlanType { @@ -39,6 +82,10 @@ namespace vcpkg::Dependencies InstallPlanAction(); InstallPlanAction(const PackageSpec& spec, const AnyParagraph& any_paragraph, const RequestType& request_type); + InstallPlanAction(const PackageSpec& spec, + const SourceControlFile& any_paragraph, + const std::unordered_set& features, + const RequestType& request_type); InstallPlanAction(const InstallPlanAction&) = delete; InstallPlanAction(InstallPlanAction&&) = default; InstallPlanAction& operator=(const InstallPlanAction&) = delete; @@ -48,6 +95,7 @@ namespace vcpkg::Dependencies AnyParagraph any_paragraph; InstallPlanType plan_type; RequestType request_type; + std::unordered_set feature_list; }; enum class RemovePlanType @@ -73,6 +121,12 @@ namespace vcpkg::Dependencies RequestType request_type; }; + struct AnyAction + { + Optional install_plan; + Optional remove_plan; + }; + enum class ExportPlanType { UNKNOWN, @@ -97,7 +151,28 @@ namespace vcpkg::Dependencies RequestType request_type; }; - std::vector create_install_plan(const VcpkgPaths& paths, + __interface PortFileProvider { virtual const SourceControlFile& get_control_file(const PackageSpec& spec) const; }; + + struct MapPortFile : PortFileProvider + { + const std::unordered_map& ports; + explicit MapPortFile(const std::unordered_map& map); + const SourceControlFile& get_control_file(const PackageSpec& spec) const override; + }; + + struct PathsPortFile : PortFileProvider + { + const VcpkgPaths& ports; + mutable std::unordered_map cache; + explicit PathsPortFile(const VcpkgPaths& paths); + const SourceControlFile& get_control_file(const PackageSpec& spec) const override; + + private: + PathsPortFile(const PathsPortFile&) = delete; + PathsPortFile& operator=(const PathsPortFile&) = delete; + }; + + std::vector create_install_plan(const PortFileProvider& port_file_provider, const std::vector& specs, const StatusParagraphs& status_db); @@ -108,3 +183,30 @@ namespace vcpkg::Dependencies const std::vector& specs, const StatusParagraphs& status_db); } + +template<> +struct std::hash +{ + size_t operator()(const vcpkg::Dependencies::ClusterPtr& value) const + { + return std::hash()(value.ptr->spec); + } +}; + +namespace vcpkg::Dependencies +{ + struct GraphPlan + { + Graphs::Graph remove_graph; + Graphs::Graph install_graph; + }; + bool mark_plus(const std::string& feature, + Cluster& cluster, + std::unordered_map& pkg_to_cluster, + GraphPlan& graph_plan); + void mark_minus(Cluster& cluster, std::unordered_map& pkg_to_cluster, GraphPlan& graph_plan); + + std::vector create_feature_install_plan(const std::unordered_map& map, + const std::vector& specs, + const StatusParagraphs& status_db); +} diff --git a/toolsrc/include/vcpkg_Graphs.h b/toolsrc/include/vcpkg_Graphs.h index 3c8c024c2..13c0a7136 100644 --- a/toolsrc/include/vcpkg_Graphs.h +++ b/toolsrc/include/vcpkg_Graphs.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace vcpkg::Graphs { @@ -63,4 +64,96 @@ namespace vcpkg::Graphs return sorted; } + + template + struct GraphAdjacencyProvider final : AdjacencyProvider + { + const std::unordered_map>& vertices; + + GraphAdjacencyProvider(const std::unordered_map>& vertices) : vertices(vertices) {} + + std::vector adjacency_list(const V& vertex) const override + { + const std::unordered_set& as_set = this->vertices.at(vertex); + return std::vector(as_set.cbegin(), as_set.cend()); // TODO: Avoid redundant copy + } + + V load_vertex_data(const V& vertex) const override { return vertex; } + }; + + template + struct Graph + { + public: + void add_vertex(V v) { this->vertices[v]; } + + // TODO: Change with iterators + void add_vertices(const std::vector& vs) + { + for (const V& v : vs) + { + this->vertices[v]; + } + } + + void add_edge(V u, V v) + { + this->vertices[v]; + this->vertices[u].insert(v); + } + + std::vector topological_sort() const + { + GraphAdjacencyProvider adjacency_provider{this->vertices}; + std::unordered_map indegrees = count_indegrees(); + + std::vector sorted; + sorted.reserve(indegrees.size()); + + std::unordered_map exploration_status; + exploration_status.reserve(indegrees.size()); + + for (auto& pair : indegrees) + { + if (pair.second == 0) // Starting from vertices with indegree == 0. Not required. + { + V vertex = pair.first; + topological_sort_internal(vertex, adjacency_provider, exploration_status, sorted); + } + } + + return sorted; + } + + std::unordered_map count_indegrees() const + { + std::unordered_map indegrees; + + for (auto& pair : this->vertices) + { + indegrees[pair.first]; + for (V neighbour : pair.second) + { + ++indegrees[neighbour]; + } + } + + return indegrees; + } + + const std::unordered_map>& adjacency_list() const { return this->vertices; } + std::vector vertex_list() const + { + // why no &? it returns 0 + std::vector vertex_list; + for (const auto& vertex : this->vertices) + { + vertex_list.emplace_back(vertex.first); + } + return vertex_list; + } + + private: + std::unordered_map> vertices; + }; } diff --git a/toolsrc/include/vcpkg_Util.h b/toolsrc/include/vcpkg_Util.h index 1bd1bcc4a..671997e7e 100644 --- a/toolsrc/include/vcpkg_Util.h +++ b/toolsrc/include/vcpkg_Util.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/toolsrc/src/BinaryParagraph.cpp b/toolsrc/src/BinaryParagraph.cpp index af76c6b29..b6f3e8a87 100644 --- a/toolsrc/src/BinaryParagraph.cpp +++ b/toolsrc/src/BinaryParagraph.cpp @@ -16,9 +16,11 @@ namespace vcpkg namespace Fields { + static const std::string FEATURE = "Feature"; static const std::string DESCRIPTION = "Description"; static const std::string MAINTAINER = "Maintainer"; static const std::string DEPENDS = "Depends"; + static const std::string DEFAULTFEATURES = "Default-Features"; } BinaryParagraph::BinaryParagraph() = default; @@ -38,7 +40,10 @@ namespace vcpkg .value_or_exit(VCPKG_LINE_INFO); } - parser.required_field(Fields::VERSION, this->version); + // one or the other + this->version = parser.optional_field(Fields::VERSION); + this->feature = parser.optional_field(Fields::FEATURE); + this->description = parser.optional_field(Fields::DESCRIPTION); this->maintainer = parser.optional_field(Fields::MAINTAINER); @@ -46,6 +51,10 @@ namespace vcpkg parser.required_field(Fields::MULTI_ARCH, multi_arch); this->depends = parse_comma_list(parser.optional_field(Fields::DEPENDS)); + if (this->feature.empty()) + { + this->default_features = parse_comma_list(parser.optional_field(Fields::DEFAULTFEATURES)); + } if (auto err = parser.error_info(this->spec.name())) { @@ -66,6 +75,16 @@ namespace vcpkg this->depends = filter_dependencies(spgh.depends, triplet); } + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const FeatureParagraph& fpgh, const Triplet& triplet) + { + this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); + this->version = ""; + this->feature = fpgh.name; + this->description = fpgh.description; + this->maintainer = ""; + this->depends = filter_dependencies(fpgh.depends, triplet); + } + std::string BinaryParagraph::displayname() const { return this->spec.to_string(); } std::string BinaryParagraph::dir() const { return this->spec.dir(); } @@ -78,7 +97,10 @@ namespace vcpkg void serialize(const BinaryParagraph& pgh, std::string& out_str) { out_str.append("Package: ").append(pgh.spec.name()).push_back('\n'); - out_str.append("Version: ").append(pgh.version).push_back('\n'); + if (!pgh.version.empty()) + out_str.append("Version: ").append(pgh.version).push_back('\n'); + else if (!pgh.feature.empty()) + out_str.append("Feature: ").append(pgh.feature).push_back('\n'); if (!pgh.depends.empty()) { out_str.append("Depends: "); diff --git a/toolsrc/src/PackageSpec.cpp b/toolsrc/src/PackageSpec.cpp index ab005f255..a7e5648cd 100644 --- a/toolsrc/src/PackageSpec.cpp +++ b/toolsrc/src/PackageSpec.cpp @@ -5,15 +5,52 @@ namespace vcpkg { - static bool is_valid_package_spec_char(char c) { return (c == '-') || isdigit(c) || (isalpha(c) && islower(c)); } + static bool is_valid_package_spec_char(char c) + { + return (c == '-') || isdigit(c) || (isalpha(c) && islower(c)) || (c == '[') || (c == ']'); + } - ExpectedT PackageSpec::from_string(const std::string& spec_as_string, - const Triplet& default_triplet) + ExpectedT FullPackageSpec::from_string(const std::string& spec_as_string, + const Triplet& default_triplet) { auto pos = spec_as_string.find(':'); - if (pos == std::string::npos) + auto pos_l_bracket = spec_as_string.find('['); + auto pos_r_bracket = spec_as_string.find(']'); + + FullPackageSpec f; + if (pos == std::string::npos && pos_l_bracket == std::string::npos) { - return from_name_and_triplet(spec_as_string, default_triplet); + f.package_spec = + PackageSpec::from_name_and_triplet(spec_as_string, default_triplet).value_or_exit(VCPKG_LINE_INFO); + return f; + } + else if (pos == std::string::npos) + { + if (pos_r_bracket == std::string::npos || pos_l_bracket >= pos_r_bracket) + { + return PackageSpecParseResult::INVALID_CHARACTERS; + } + const std::string name = spec_as_string.substr(0, pos_l_bracket); + f.package_spec = PackageSpec::from_name_and_triplet(name, default_triplet).value_or_exit(VCPKG_LINE_INFO); + f.features = parse_comma_list(spec_as_string.substr(pos_l_bracket + 1, pos_r_bracket - pos_l_bracket - 1)); + return f; + } + else if (pos_l_bracket == std::string::npos && pos_r_bracket == std::string::npos) + { + const std::string name = spec_as_string.substr(0, pos); + const Triplet triplet = Triplet::from_canonical_name(spec_as_string.substr(pos + 1)); + f.package_spec = PackageSpec::from_name_and_triplet(name, triplet).value_or_exit(VCPKG_LINE_INFO); + } + else + { + if (pos_r_bracket == std::string::npos || pos_l_bracket >= pos_r_bracket) + { + return PackageSpecParseResult::INVALID_CHARACTERS; + } + const std::string name = spec_as_string.substr(0, pos_l_bracket); + f.features = parse_comma_list(spec_as_string.substr(pos_l_bracket + 1, pos_r_bracket - pos_l_bracket - 1)); + const Triplet triplet = Triplet::from_canonical_name(spec_as_string.substr(pos + 1)); + f.package_spec = PackageSpec::from_name_and_triplet(name, triplet).value_or_exit(VCPKG_LINE_INFO); } auto pos2 = spec_as_string.find(':', pos + 1); @@ -21,10 +58,7 @@ namespace vcpkg { return PackageSpecParseResult::TOO_MANY_COLONS; } - - const std::string name = spec_as_string.substr(0, pos); - const Triplet triplet = Triplet::from_canonical_name(spec_as_string.substr(pos + 1)); - return from_name_and_triplet(name, triplet); + return f; } ExpectedT PackageSpec::from_name_and_triplet(const std::string& name, diff --git a/toolsrc/src/commands_ci.cpp b/toolsrc/src/commands_ci.cpp index 7ffc7577a..f781adf69 100644 --- a/toolsrc/src/commands_ci.cpp +++ b/toolsrc/src/commands_ci.cpp @@ -40,7 +40,9 @@ namespace vcpkg::Commands::CI const std::vector specs = load_all_package_specs(paths.get_filesystem(), paths.ports, triplet); StatusParagraphs status_db = database_load_check(paths); - const std::vector install_plan = Dependencies::create_install_plan(paths, specs, status_db); + const auto& paths_port_file = Dependencies::PathsPortFile(paths); + const std::vector install_plan = + Dependencies::create_install_plan(paths_port_file, specs, status_db); Checks::check_exit(VCPKG_LINE_INFO, !install_plan.empty(), "Install plan cannot be empty"); std::vector results; diff --git a/toolsrc/src/commands_install.cpp b/toolsrc/src/commands_install.cpp index 73b3e9eab..2ce5b6c62 100644 --- a/toolsrc/src/commands_install.cpp +++ b/toolsrc/src/commands_install.cpp @@ -16,6 +16,8 @@ namespace vcpkg::Commands::Install using Dependencies::InstallPlanAction; using Dependencies::RequestType; using Dependencies::InstallPlanType; + using Dependencies::RemovePlanAction; + using Dependencies::RemovePlanType; InstallDir InstallDir::from_destination_root(const fs::path& destination_root, const std::string& destination_subdirectory, @@ -290,7 +292,7 @@ namespace vcpkg::Commands::Install return BuildResult::SUCCEEDED; } - if (plan_type == InstallPlanType::BUILD_AND_INSTALL) + if (plan_type == InstallPlanType::BUILD_AND_INSTALL && !g_feature_packages) { if (use_head_version) System::println("Building package %s from HEAD... ", display_name); @@ -318,7 +320,36 @@ namespace vcpkg::Commands::Install return BuildResult::SUCCEEDED; } - if (plan_type == InstallPlanType::INSTALL) + if (plan_type == InstallPlanType::BUILD_AND_INSTALL && g_feature_packages) + { + if (use_head_version) + System::println("Building package %s from HEAD... ", display_name); + else + System::println("Building package %s... ", display_name); + + const Build::BuildPackageConfig build_config{ + *action.any_paragraph.source_control_file.value_or_exit(VCPKG_LINE_INFO), + action.spec.triplet(), + paths.port_dir(action.spec), + build_package_options, + action.feature_list}; + const auto result = Build::build_package(paths, build_config, status_db); + if (result.code != Build::BuildResult::SUCCEEDED) + { + System::println(System::Color::error, Build::create_error_message(result.code, action.spec)); + return result.code; + } + System::println("Building package %s... done", display_name); + + const BinaryParagraph bpgh = + Paragraphs::try_load_cached_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO); + System::println("Installing package %s... ", display_name); + install_package(paths, bpgh, &status_db); + System::println(System::Color::success, "Installing package %s... done", display_name); + return BuildResult::SUCCEEDED; + } + + if (plan_type == InstallPlanType::INSTALL && !g_feature_packages) { if (use_head_version && is_user_requested) { @@ -359,7 +390,10 @@ namespace vcpkg::Commands::Install // create the plan StatusParagraphs status_db = database_load_check(paths); - std::vector install_plan = Dependencies::create_install_plan(paths, specs, status_db); + + Dependencies::PathsPortFile paths_port_file(paths); + std::vector install_plan = + Dependencies::create_install_plan(paths_port_file, specs, status_db); Checks::check_exit(VCPKG_LINE_INFO, !install_plan.empty(), "Install plan cannot be empty"); // log the plan diff --git a/toolsrc/src/commands_remove.cpp b/toolsrc/src/commands_remove.cpp index e2b5d12a1..eabf2b9ae 100644 --- a/toolsrc/src/commands_remove.cpp +++ b/toolsrc/src/commands_remove.cpp @@ -14,7 +14,7 @@ namespace vcpkg::Commands::Remove using Dependencies::RequestType; using Update::OutdatedPackage; - static void remove_package(const VcpkgPaths& paths, const PackageSpec& spec, StatusParagraphs* status_db) + void remove_package(const VcpkgPaths& paths, const PackageSpec& spec, StatusParagraphs* status_db) { auto& fs = paths.get_filesystem(); StatusParagraph& pkg = **status_db->find(spec.name(), spec.triplet()); diff --git a/toolsrc/src/test_install_plan.cpp b/toolsrc/src/test_install_plan.cpp new file mode 100644 index 000000000..d02af5662 --- /dev/null +++ b/toolsrc/src/test_install_plan.cpp @@ -0,0 +1,527 @@ +#include "CppUnitTest.h" +#include "vcpkg_Dependencies.h" +#include "vcpkg_Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +using namespace vcpkg; + +namespace UnitTest1 +{ + class InstallPlanTests : public TestClass + { + struct PackageSpecMap + { + std::unordered_map map; + Triplet triplet; + PackageSpecMap(const Triplet& t) { triplet = t; } + + PackageSpec get_package_spec(std::vector>&& fields) + { + auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(fields)); + Assert::IsTrue(m_pgh.has_value()); + auto& scf = *m_pgh.get(); + + auto spec = PackageSpec::from_name_and_triplet(scf->core_paragraph->name, triplet); + Assert::IsTrue(spec.has_value()); + map.emplace(*spec.get(), std::move(*scf.get())); + return PackageSpec{*spec.get()}; + } + PackageSpec set_package_map(std::string source, std::string version, std::string build_depends) + { + return get_package_spec({{{"Source", source}, {"Version", version}, {"Build-Depends", build_depends}}}); + } + }; + + static void features_check(Dependencies::AnyAction* install_action, + std::string pkg_name, + std::vector vec, + const Triplet& triplet = Triplet::X86_WINDOWS) + { + const auto& plan = install_action->install_plan.value_or_exit(VCPKG_LINE_INFO); + const auto& feature_list = plan.feature_list; + + Assert::AreEqual(plan.spec.triplet().to_string().c_str(), triplet.to_string().c_str()); + + Assert::AreEqual(pkg_name.c_str(), + (*plan.any_paragraph.source_control_file.get())->core_paragraph->name.c_str()); + Assert::AreEqual(size_t(vec.size()), feature_list.size()); + + for (auto&& feature_name : vec) + { + if (feature_name == "core" || feature_name == "") + { + Assert::IsTrue(Util::find(feature_list, "core") != feature_list.end() || + Util::find(feature_list, "") != feature_list.end()); + continue; + } + Assert::IsTrue(Util::find(feature_list, feature_name) != feature_list.end()); + } + } + + static void remove_plan_check(Dependencies::AnyAction* remove_action, + std::string pkg_name, + const Triplet& triplet = Triplet::X86_WINDOWS) + { + const auto& plan = remove_action->remove_plan.value_or_exit(VCPKG_LINE_INFO); + Assert::AreEqual(plan.spec.triplet().to_string().c_str(), triplet.to_string().c_str()); + Assert::AreEqual(pkg_name.c_str(), plan.spec.name().c_str()); + } + + TEST_METHOD(basic_install_scheme) + { + std::vector> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = spec_map.set_package_map("a", "1.2.8", "b"); + auto spec_b = spec_map.set_package_map("b", "1.3", "c"); + auto spec_c = spec_map.set_package_map("c", "2.5.3", ""); + + auto map_port = Dependencies::MapPortFile(spec_map.map); + auto install_plan = + Dependencies::create_install_plan(map_port, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(3), install_plan.size()); + Assert::AreEqual("c", install_plan[0].spec.name().c_str()); + Assert::AreEqual("b", install_plan[1].spec.name().c_str()); + Assert::AreEqual("a", install_plan[2].spec.name().c_str()); + } + + TEST_METHOD(multiple_install_scheme) + { + std::vector> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = spec_map.set_package_map("a", "1.2.8", "d"); + auto spec_b = spec_map.set_package_map("b", "1.3", "d, e"); + auto spec_c = spec_map.set_package_map("c", "2.5.3", "e, h"); + auto spec_d = spec_map.set_package_map("d", "4.0", "f, g, h"); + auto spec_e = spec_map.set_package_map("e", "1.0", "g"); + auto spec_f = spec_map.set_package_map("f", "1.0", ""); + auto spec_g = spec_map.set_package_map("g", "1.0", ""); + auto spec_h = spec_map.set_package_map("h", "1.0", ""); + + auto map_port = Dependencies::MapPortFile(spec_map.map); + auto install_plan = Dependencies::create_install_plan( + map_port, {spec_a, spec_b, spec_c}, StatusParagraphs(std::move(status_paragraphs))); + + auto iterator_pos = [&](const PackageSpec& spec) -> int { + auto it = std::find_if( + install_plan.begin(), install_plan.end(), [&](auto& action) { return action.spec == spec; }); + Assert::IsTrue(it != install_plan.end()); + return (int)(it - install_plan.begin()); + }; + + int a_pos = iterator_pos(spec_a), b_pos = iterator_pos(spec_b), c_pos = iterator_pos(spec_c), + d_pos = iterator_pos(spec_d), e_pos = iterator_pos(spec_e), f_pos = iterator_pos(spec_f), + g_pos = iterator_pos(spec_g), h_pos = iterator_pos(spec_h); + + Assert::IsTrue(a_pos > d_pos); + Assert::IsTrue(b_pos > e_pos); + Assert::IsTrue(b_pos > d_pos); + Assert::IsTrue(c_pos > e_pos); + Assert::IsTrue(c_pos > h_pos); + Assert::IsTrue(d_pos > f_pos); + Assert::IsTrue(d_pos > g_pos); + Assert::IsTrue(d_pos > h_pos); + Assert::IsTrue(e_pos > g_pos); + } + + TEST_METHOD(long_install_scheme) + { + using Pgh = std::unordered_map; + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "j"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "k"}, + {"Status", "install ok installed"}})); + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "k"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = spec_map.set_package_map("a", "1.2.8", "b, c, d, e, f, g, h, j, k"); + auto spec_b = spec_map.set_package_map("b", "1.2.8", "c, d, e, f, g, h, j, k"); + auto spec_c = spec_map.set_package_map("c", "1.2.8", "d, e, f, g, h, j, k"); + auto spec_d = spec_map.set_package_map("d", "1.2.8", "e, f, g, h, j, k"); + auto spec_e = spec_map.set_package_map("e", "1.2.8", "f, g, h, j, k"); + auto spec_f = spec_map.set_package_map("f", "1.2.8", "g, h, j, k"); + auto spec_g = spec_map.set_package_map("g", "1.2.8", "h, j, k"); + auto spec_h = spec_map.set_package_map("h", "1.2.8", "j, k"); + auto spec_j = spec_map.set_package_map("j", "1.2.8", "k"); + auto spec_k = spec_map.set_package_map("k", "1.2.8", ""); + + auto map_port = Dependencies::MapPortFile(spec_map.map); + auto install_plan = + Dependencies::create_install_plan(map_port, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(8), install_plan.size()); + Assert::AreEqual("h", install_plan[0].spec.name().c_str()); + Assert::AreEqual("g", install_plan[1].spec.name().c_str()); + Assert::AreEqual("f", install_plan[2].spec.name().c_str()); + Assert::AreEqual("e", install_plan[3].spec.name().c_str()); + Assert::AreEqual("d", install_plan[4].spec.name().c_str()); + Assert::AreEqual("c", install_plan[5].spec.name().c_str()); + Assert::AreEqual("b", install_plan[6].spec.name().c_str()); + Assert::AreEqual("a", install_plan[7].spec.name().c_str()); + } + + TEST_METHOD(basic_feature_test_1) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "a"}, + {"Default-Features", ""}, + {"Version", "1.3.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "b, b[beefeatureone]"}, + {"Status", "install ok installed"}})); + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "b"}, + {"Feature", "beefeatureone"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "b"}, + {"Default-Features", "beefeatureone"}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = + FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "a"}, {"Version", "1.3.8"}, {"Build-Depends", "b, b[beefeatureone]"}}, + {{"Feature", "featureone"}, + {"Description", "the first feature for a"}, + {"Build-Depends", "b[beefeaturetwo]"}}, + }), + {"featureone"}}; + auto spec_b = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + {{"Feature", "beefeatureone"}, {"Description", "the first feature for b"}, {"Build-Depends", ""}}, + {{"Feature", "beefeaturetwo"}, {"Description", "the second feature for b"}, {"Build-Depends", ""}}, + {{"Feature", "beefeaturethree"}, {"Description", "the third feature for b"}, {"Build-Depends", ""}}, + })}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(4), install_plan.size()); + remove_plan_check(&install_plan[0], "a"); + remove_plan_check(&install_plan[1], "b"); + features_check(&install_plan[2], "b", {"beefeatureone", "core", "beefeatureone"}); + features_check(&install_plan[3], "a", {"featureone", "core"}); + } + + TEST_METHOD(basic_feature_test_2) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = + FullPackageSpec{spec_map.get_package_spec( + {{{"Source", "a"}, {"Version", "1.3.8"}, {"Build-Depends", "b[beefeatureone]"}}, + {{"Feature", "featureone"}, + {"Description", "the first feature for a"}, + {"Build-Depends", "b[beefeaturetwo]"}} + + }), + {"featureone"}}; + auto spec_b = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + {{"Feature", "beefeatureone"}, {"Description", "the first feature for b"}, {"Build-Depends", ""}}, + {{"Feature", "beefeaturetwo"}, {"Description", "the second feature for b"}, {"Build-Depends", ""}}, + {{"Feature", "beefeaturethree"}, {"Description", "the third feature for b"}, {"Build-Depends", ""}}, + })}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(2), install_plan.size()); + features_check(&install_plan[0], "b", {"beefeatureone", "beefeaturetwo", "core"}); + features_check(&install_plan[1], "a", {"featureone", "core"}); + } + + TEST_METHOD(basic_feature_test_3) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "a"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = FullPackageSpec{ + spec_map.get_package_spec( + {{{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", "b"}}, + {{"Feature", "one"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}}), + {"core"}}; + auto spec_b = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + })}; + auto spec_c = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "c"}, {"Version", "1.3"}, {"Build-Depends", "a[one]"}}, + }), + {"core"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_c, spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(4), install_plan.size()); + remove_plan_check(&install_plan[0], "a"); + features_check(&install_plan[1], "b", {"core"}); + features_check(&install_plan[2], "a", {"one", "core"}); + features_check(&install_plan[3], "c", {"core"}); + } + + TEST_METHOD(basic_feature_test_4) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "a"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "a"}, + {"Feature", "one"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = FullPackageSpec{ + spec_map.get_package_spec( + {{{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", "b"}}, + {{"Feature", "one"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}}), + }; + auto spec_b = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + })}; + auto spec_c = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "c"}, {"Version", "1.3"}, {"Build-Depends", "a[one]"}}, + }), + {"core"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_c}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(1), install_plan.size()); + features_check(&install_plan[0], "c", {"core"}); + } + + TEST_METHOD(basic_feature_test_5) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = FullPackageSpec{ + spec_map.get_package_spec( + {{{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + {{"Feature", "1"}, {"Description", "the first feature for a"}, {"Build-Depends", "b[1]"}}, + {{"Feature", "2"}, {"Description", "the first feature for a"}, {"Build-Depends", "b[2]"}}, + {{"Feature", "3"}, {"Description", "the first feature for a"}, {"Build-Depends", "a[2]"}}}), + {"3"}}; + auto spec_b = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + {{"Feature", "1"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}, + {{"Feature", "2"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}, + })}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(2), install_plan.size()); + features_check(&install_plan[0], "b", {"core", "2"}); + features_check(&install_plan[1], "a", {"core", "3", "2"}); + } + + TEST_METHOD(basic_feature_test_6) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "b"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", "b[core]"}}, + }), + {"core"}}; + auto spec_b = FullPackageSpec{ + spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + {{"Feature", "1"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}, + }), + {"1"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_a, spec_b}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(3), install_plan.size()); + remove_plan_check(&install_plan[0], "b"); + features_check(&install_plan[1], "b", {"core", "1"}); + features_check(&install_plan[2], "a", {"core"}); + } + + TEST_METHOD(basic_feature_test_7) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "x"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "b"}, + {"Status", "install ok installed"}})); + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "b"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + + auto spec_a = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + })}; + auto spec_x = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "x"}, {"Version", "1.3"}, {"Build-Depends", "a"}}, + }), + {"core"}}; + auto spec_b = FullPackageSpec{ + spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}, {"Default-Features", ""}}, + {{"Feature", "1"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}, + }), + {"1"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {spec_b, spec_x}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(5), install_plan.size()); + remove_plan_check(&install_plan[0], "x"); + remove_plan_check(&install_plan[1], "b"); + + // TODO: order here may change but A < X, and B anywhere + features_check(&install_plan[2], "a", {"core"}); + features_check(&install_plan[3], "x", {"core"}); + features_check(&install_plan[4], "b", {"core", "1"}); + } + + TEST_METHOD(basic_feature_test_8) + { + using Pgh = std::unordered_map; + + std::vector> status_paragraphs; + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "a"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x64-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + status_paragraphs.push_back(std::make_unique(Pgh{{"Package", "a"}, + {"Default-Features", ""}, + {"Version", "1.3"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", ""}, + {"Status", "install ok installed"}})); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + + auto spec_a_64 = FullPackageSpec{ + spec_map.get_package_spec( + {{{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", "b"}}, + {{"Feature", "one"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}}), + {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + })}; + auto spec_c_64 = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "c"}, {"Version", "1.3"}, {"Build-Depends", "a[one]"}}, + }), + {"core"}}; + + spec_map.triplet = Triplet::X86_WINDOWS; + auto spec_a_86 = FullPackageSpec{ + spec_map.get_package_spec( + {{{"Source", "a"}, {"Version", "1.3"}, {"Build-Depends", "b"}}, + {{"Feature", "one"}, {"Description", "the first feature for a"}, {"Build-Depends", ""}}}), + {"core"}}; + auto spec_b_86 = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "b"}, {"Version", "1.3"}, {"Build-Depends", ""}}, + })}; + auto spec_c_86 = FullPackageSpec{spec_map.get_package_spec({ + {{"Source", "c"}, {"Version", "1.3"}, {"Build-Depends", "a[one]"}}, + }), + {"core"}}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + {spec_c_64, spec_a_86, spec_a_64, spec_c_86}, + StatusParagraphs(std::move(status_paragraphs))); + + /*Assert::AreEqual(size_t(8), install_plan.size()); + auto iterator_pos = [&](const PackageSpec& spec, size_t start) -> int { + auto it = std::find_if(install_plan.begin() + start, install_plan.end(), [&](auto& action) { + return action.spec == spec; + }); + Assert::IsTrue(it != install_plan.end()); + return (int)(it - install_plan.begin()); + }; + int a_64_1 = iterator_pos(spec_a_64.package_spec, 0), a_86_1 = iterator_pos(spec_a_86.package_spec, 0), + b_64 = iterator_pos(spec_b_64.package_spec, 0), b_86 = iterator_pos(spec_b_86.package_spec, 0), + c_64 = iterator_pos(spec_c_64.package_spec, 0), c_86 = iterator_pos(spec_c_86.package_spec, 0), + a_64_2 = iterator_pos(spec_a_64.package_spec, a_64_1 + 1), + a_86_2 = iterator_pos(spec_a_86.package_spec, a_86_1 + 1);*/ + + remove_plan_check(&install_plan[0], "a", Triplet::X64_WINDOWS); + remove_plan_check(&install_plan[1], "a"); + features_check(&install_plan[2], "b", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[3], "a", {"one", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[4], "c", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[5], "b", {"core"}); + features_check(&install_plan[6], "a", {"one", "core"}); + features_check(&install_plan[7], "c", {"core"}); + } + }; +} \ No newline at end of file diff --git a/toolsrc/src/tests_paragraph.cpp b/toolsrc/src/tests_paragraph.cpp index 2a53cc8b4..dd9a40160 100644 --- a/toolsrc/src/tests_paragraph.cpp +++ b/toolsrc/src/tests_paragraph.cpp @@ -372,25 +372,27 @@ namespace UnitTest1 TEST_METHOD(package_spec_parse) { - vcpkg::ExpectedT spec = - vcpkg::PackageSpec::from_string("zlib", vcpkg::Triplet::X86_WINDOWS); + vcpkg::ExpectedT spec = + vcpkg::FullPackageSpec::from_string("zlib", vcpkg::Triplet::X86_WINDOWS); Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, spec.error()); - Assert::AreEqual("zlib", spec.get()->name().c_str()); - Assert::AreEqual(vcpkg::Triplet::X86_WINDOWS.canonical_name(), spec.get()->triplet().canonical_name()); + Assert::AreEqual("zlib", spec.get()->package_spec.name().c_str()); + Assert::AreEqual(vcpkg::Triplet::X86_WINDOWS.canonical_name(), + spec.get()->package_spec.triplet().canonical_name()); } TEST_METHOD(package_spec_parse_with_arch) { - vcpkg::ExpectedT spec = - vcpkg::PackageSpec::from_string("zlib:x64-uwp", vcpkg::Triplet::X86_WINDOWS); + vcpkg::ExpectedT spec = + vcpkg::FullPackageSpec::from_string("zlib:x64-uwp", vcpkg::Triplet::X86_WINDOWS); Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, spec.error()); - Assert::AreEqual("zlib", spec.get()->name().c_str()); - Assert::AreEqual(vcpkg::Triplet::X64_UWP.canonical_name(), spec.get()->triplet().canonical_name()); + Assert::AreEqual("zlib", spec.get()->package_spec.name().c_str()); + Assert::AreEqual(vcpkg::Triplet::X64_UWP.canonical_name(), + spec.get()->package_spec.triplet().canonical_name()); } TEST_METHOD(package_spec_parse_with_multiple_colon) { - auto ec = vcpkg::PackageSpec::from_string("zlib:x86-uwp:", vcpkg::Triplet::X86_WINDOWS).error(); + auto ec = vcpkg::FullPackageSpec::from_string("zlib:x86-uwp:", vcpkg::Triplet::X86_WINDOWS).error(); Assert::AreEqual(vcpkg::PackageSpecParseResult::TOO_MANY_COLONS, ec); } diff --git a/toolsrc/src/vcpkg_Build.cpp b/toolsrc/src/vcpkg_Build.cpp index c794b5ede..a0d690f37 100644 --- a/toolsrc/src/vcpkg_Build.cpp +++ b/toolsrc/src/vcpkg_Build.cpp @@ -102,6 +102,17 @@ namespace vcpkg::Build paths.get_filesystem().write_contents(binary_control_file, Strings::serialize(bpgh)); } + static void create_binary_feature_control_file(const VcpkgPaths& paths, + const SourceParagraph& source_paragraph, + const FeatureParagraph& feature_paragraph, + const Triplet& triplet, + const BuildInfo& build_info) + { + BinaryParagraph bpgh = BinaryParagraph(source_paragraph, feature_paragraph, triplet); + const fs::path binary_control_file = paths.packages / bpgh.dir() / "CONTROL"; + paths.get_filesystem().write_contents(binary_control_file, Strings::serialize(bpgh)); + } + ExtendedBuildResult build_package(const VcpkgPaths& paths, const BuildPackageConfig& config, const StatusParagraphs& status_db) @@ -135,17 +146,36 @@ namespace vcpkg::Build const Toolset& toolset = paths.get_toolset(pre_build_info.platform_toolset); const auto cmd_set_environment = make_build_env_cmd(pre_build_info, toolset); + std::string features; + if (g_feature_packages) + { + if (config.feature_list) + { + for (auto&& feature : *config.feature_list) + { + features.append(feature + ";"); + } + if (features.size() > 0) + { + features.pop_back(); + } + } + } + const std::wstring cmd_launch_cmake = make_cmake_cmd( cmake_exe_path, ports_cmake_script_path, - {{L"CMD", L"BUILD"}, - {L"PORT", config.src.name}, - {L"CURRENT_PORT_DIR", config.port_dir / "/."}, - {L"TARGET_TRIPLET", triplet.canonical_name()}, - {L"VCPKG_PLATFORM_TOOLSET", toolset.version}, - {L"VCPKG_USE_HEAD_VERSION", to_bool(config.build_package_options.use_head_version) ? L"1" : L"0"}, - {L"_VCPKG_NO_DOWNLOADS", !to_bool(config.build_package_options.allow_downloads) ? L"1" : L"0"}, - {L"GIT", git_exe_path}}); + { + {L"CMD", L"BUILD"}, + {L"PORT", config.src.name}, + {L"CURRENT_PORT_DIR", config.port_dir / "/."}, + {L"TARGET_TRIPLET", triplet.canonical_name()}, + {L"VCPKG_PLATFORM_TOOLSET", toolset.version}, + {L"VCPKG_USE_HEAD_VERSION", to_bool(config.build_package_options.use_head_version) ? L"1" : L"0"}, + {L"_VCPKG_NO_DOWNLOADS", !to_bool(config.build_package_options.allow_downloads) ? L"1" : L"0"}, + {L"GIT", git_exe_path}, + {L"FEATURES", features}, + }); const std::wstring command = Strings::wformat(LR"(%s && %s)", cmd_set_environment, cmd_launch_cmake); @@ -170,7 +200,21 @@ namespace vcpkg::Build { return {BuildResult::POST_BUILD_CHECKS_FAILED, {}}; } - + if (g_feature_packages) + { + if (config.feature_list) + { + for (auto&& feature : *config.feature_list) + { + for (auto&& f_pgh : config.scf->feature_paragraphs) + { + if (f_pgh->name == feature) + create_binary_feature_control_file( + paths, *config.scf->core_paragraph, *f_pgh, triplet, build_info); + } + } + } + } create_binary_control_file(paths, config.src, triplet, build_info); // const fs::path port_buildtrees_dir = paths.buildtrees / spec.name; diff --git a/toolsrc/src/vcpkg_Dependencies.cpp b/toolsrc/src/vcpkg_Dependencies.cpp index bbf807c95..820e51b33 100644 --- a/toolsrc/src/vcpkg_Dependencies.cpp +++ b/toolsrc/src/vcpkg_Dependencies.cpp @@ -12,6 +12,8 @@ namespace vcpkg::Dependencies { + bool operator==(const ClusterPtr& l, const ClusterPtr& r) { return l.ptr == r.ptr; } + std::vector AnyParagraph::dependencies(const Triplet& triplet) const { auto to_package_specs = [&](const std::vector& dependencies_as_string) { @@ -54,6 +56,20 @@ namespace vcpkg::Dependencies { } + InstallPlanAction::InstallPlanAction(const PackageSpec& spec, + const SourceControlFile& any_paragraph, + const std::unordered_set& features, + const RequestType& request_type) + : InstallPlanAction() + { + this->spec = spec; + this->request_type = request_type; + + this->plan_type = InstallPlanType::BUILD_AND_INSTALL; + this->any_paragraph.source_control_file = &any_paragraph; + this->feature_list = features; + } + InstallPlanAction::InstallPlanAction(const PackageSpec& spec, const AnyParagraph& any_paragraph, const RequestType& request_type) @@ -139,20 +155,53 @@ namespace vcpkg::Dependencies return left->spec.name() < right->spec.name(); } - std::vector create_install_plan(const VcpkgPaths& paths, + MapPortFile::MapPortFile(const std::unordered_map& map) : ports(map) {} + + const SourceControlFile& MapPortFile::get_control_file(const PackageSpec& spec) const + { + auto scf = ports.find(spec); + if (scf == ports.end()) + { + Checks::exit_fail(VCPKG_LINE_INFO); + } + return scf->second; + } + + PathsPortFile::PathsPortFile(const VcpkgPaths& paths) : ports(paths) {} + + const SourceControlFile& PathsPortFile::get_control_file(const PackageSpec& spec) const + { + std::unordered_map::iterator cache_it = cache.find(spec); + if (cache_it != cache.end()) + { + return cache_it->second; + } + Parse::ParseExpected source_control_file = + Paragraphs::try_load_port(ports.get_filesystem(), ports.port_dir(spec)); + + if (auto scf = source_control_file.get()) + { + auto it = cache.emplace(spec, std::move(*scf->get())); + return it.first->second; + } + print_error_message(source_control_file.error()); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + std::vector create_install_plan(const PortFileProvider& port_file_provider, const std::vector& specs, const StatusParagraphs& status_db) { struct InstallAdjacencyProvider final : Graphs::AdjacencyProvider { - const VcpkgPaths& paths; + const PortFileProvider& port_file_provider; const StatusParagraphs& status_db; const std::unordered_set& specs_as_set; - InstallAdjacencyProvider(const VcpkgPaths& p, + InstallAdjacencyProvider(const PortFileProvider& port_file_provider, const StatusParagraphs& s, const std::unordered_set& specs_as_set) - : paths(p), status_db(s), specs_as_set(specs_as_set) + : port_file_provider(port_file_provider), status_db(s), specs_as_set(specs_as_set) { } @@ -169,23 +218,14 @@ namespace vcpkg::Dependencies : RequestType::AUTO_SELECTED; auto it = status_db.find_installed(spec); if (it != status_db.end()) return InstallPlanAction{spec, {*it->get(), nullopt, nullopt}, request_type}; - - Expected maybe_bpgh = Paragraphs::try_load_cached_package(paths, spec); - if (auto bpgh = maybe_bpgh.get()) - return InstallPlanAction{spec, {nullopt, *bpgh, nullopt}, request_type}; - - auto maybe_scf = Paragraphs::try_load_port(paths.get_filesystem(), paths.port_dir(spec)); - if (auto scf = maybe_scf.get()) - return InstallPlanAction{spec, {nullopt, nullopt, *scf->get()->core_paragraph}, request_type}; - - print_error_message(maybe_scf.error()); - Checks::exit_fail(VCPKG_LINE_INFO); + return InstallPlanAction{ + spec, {nullopt, nullopt, *port_file_provider.get_control_file(spec).core_paragraph}, request_type}; } }; const std::unordered_set specs_as_set(specs.cbegin(), specs.cend()); std::vector toposort = - Graphs::topological_sort(specs, InstallAdjacencyProvider{paths, status_db, specs_as_set}); + Graphs::topological_sort(specs, InstallAdjacencyProvider{port_file_provider, status_db, specs_as_set}); Util::erase_remove_if(toposort, [](const InstallPlanAction& plan) { return plan.request_type == RequestType::AUTO_SELECTED && plan.plan_type == InstallPlanType::ALREADY_INSTALLED; @@ -298,4 +338,229 @@ namespace vcpkg::Dependencies Graphs::topological_sort(specs, ExportAdjacencyProvider{paths, status_db, specs_as_set}); return toposort; } + + std::vector to_feature_specs(const std::vector& depends, const Triplet& triplet) + { + std::vector f_specs; + for (auto&& depend : depends) + { + int end = (int)depend.find(']'); + if (end != std::string::npos) + { + int start = (int)depend.find('['); + + auto feature_name = depend.substr(start + 1, end - start - 1); + auto package_name = depend.substr(0, start); + auto p_spec = PackageSpec::from_name_and_triplet(package_name, triplet).value_or_exit(VCPKG_LINE_INFO); + auto feature_spec = FeatureSpec{p_spec, feature_name}; + f_specs.emplace_back(std::move(feature_spec)); + } + else + { + auto p_spec = PackageSpec::from_name_and_triplet(depend, triplet).value_or_exit(VCPKG_LINE_INFO); + + auto feature_spec = FeatureSpec{p_spec, ""}; + f_specs.emplace_back(std::move(feature_spec)); + } + } + return f_specs; + } + + bool mark_plus(const std::string& feature, + Cluster& cluster, + std::unordered_map& pkg_to_cluster, + GraphPlan& graph_plan) + { + auto it = cluster.edges.find(feature); + std::string updated_feature = feature; + if (updated_feature == "") + { + updated_feature = "core"; + it = cluster.edges.find("core"); + } + if (it == cluster.edges.end()) + { + Checks::unreachable(VCPKG_LINE_INFO); + } + + if (cluster.edges[updated_feature].plus) return true; + + if (cluster.original_features.find(updated_feature) == cluster.original_features.end()) + { + cluster.transient_uninstalled = true; + } + + if (!cluster.transient_uninstalled) + { + return false; + } + cluster.edges[updated_feature].plus = true; + + if (!cluster.original_features.empty()) + { + mark_minus(cluster, pkg_to_cluster, graph_plan); + } + + graph_plan.install_graph.add_vertex({&cluster}); + auto& tracked = cluster.to_install_features; + tracked.insert(updated_feature); + if (tracked.find("core") == tracked.end() && tracked.find("") == tracked.end()) + { + cluster.to_install_features.insert("core"); + for (auto&& depend : cluster.edges["core"].build_edges) + { + auto& depend_cluster = pkg_to_cluster[depend.spec]; + mark_plus(depend.feature_name, depend_cluster, pkg_to_cluster, graph_plan); + graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); + } + } + + for (auto&& depend : cluster.edges[updated_feature].build_edges) + { + auto& depend_cluster = pkg_to_cluster[depend.spec]; + mark_plus(depend.feature_name, depend_cluster, pkg_to_cluster, graph_plan); + if (&depend_cluster == &cluster) continue; + graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); + } + return true; + } + + void mark_minus(Cluster& cluster, std::unordered_map& pkg_to_cluster, GraphPlan& graph_plan) + { + if (cluster.will_remove) return; + cluster.will_remove = true; + + graph_plan.remove_graph.add_vertex({&cluster}); + for (auto&& pair : cluster.edges) + { + auto& remove_edges_edges = pair.second.remove_edges; + for (auto&& depend : remove_edges_edges) + { + auto& depend_cluster = pkg_to_cluster[depend.spec]; + graph_plan.remove_graph.add_edge({&cluster}, {&depend_cluster}); + depend_cluster.transient_uninstalled = true; + mark_minus(depend_cluster, pkg_to_cluster, graph_plan); + } + } + for (auto&& original_feature : cluster.original_features) + { + cluster.transient_uninstalled = true; + mark_plus(original_feature, cluster, pkg_to_cluster, graph_plan); + } + } + std::vector create_feature_install_plan(const std::unordered_map& map, + const std::vector& specs, + const StatusParagraphs& status_db) + { + std::unordered_map pkg_spec_to_package_node; + + for (const auto& it : map) + { + Cluster& node = pkg_spec_to_package_node[it.first]; + + node.spec = it.first; + FeatureNodeEdges core_dependencies; + auto core_depends = filter_dependencies(it.second.core_paragraph->depends, node.spec.triplet()); + core_dependencies.build_edges = to_feature_specs(core_depends, node.spec.triplet()); + node.edges["core"] = std::move(core_dependencies); + + for (const auto& feature : it.second.feature_paragraphs) + { + FeatureNodeEdges added_edges; + auto depends = filter_dependencies(feature->depends, node.spec.triplet()); + added_edges.build_edges = to_feature_specs(depends, node.spec.triplet()); + node.edges.emplace(feature->name, std::move(added_edges)); + } + node.source_control_file = &it.second; + } + + for (auto&& status_paragraph : get_installed_ports(status_db)) + { + auto& spec = status_paragraph->package.spec; + auto& status_paragraph_feature = status_paragraph->package.feature; + Cluster& cluster = pkg_spec_to_package_node[spec]; + + cluster.transient_uninstalled = false; + auto reverse_edges = + to_feature_specs(status_paragraph->package.depends, status_paragraph->package.spec.triplet()); + + for (auto&& dependency : reverse_edges) + { + auto pkg_node = pkg_spec_to_package_node.find(dependency.spec); + auto depends_name = dependency.feature_name; + if (depends_name == "") + { + for (auto&& default_feature : status_paragraph->package.default_features) + { + auto& target_node = pkg_node->second.edges[default_feature]; + target_node.remove_edges.emplace_back(FeatureSpec{spec, status_paragraph_feature}); + } + depends_name = "core"; + } + auto& target_node = pkg_node->second.edges[depends_name]; + target_node.remove_edges.emplace_back(FeatureSpec{spec, status_paragraph_feature}); + } + cluster.status_paragraphs.emplace_back(*status_paragraph); + if (status_paragraph_feature == "") + { + cluster.original_features.insert("core"); + } + else + { + cluster.original_features.insert(status_paragraph_feature); + } + } + + GraphPlan graph_plan; + for (auto&& spec : specs) + { + Cluster& spec_cluster = pkg_spec_to_package_node[spec.package_spec]; + for (auto&& feature : spec.features) + { + mark_plus(feature, spec_cluster, pkg_spec_to_package_node, graph_plan); + } + } + + Graphs::GraphAdjacencyProvider adjacency_remove_graph(graph_plan.remove_graph.adjacency_list()); + auto remove_vertex_list = graph_plan.remove_graph.vertex_list(); + auto remove_toposort = Graphs::topological_sort(remove_vertex_list, adjacency_remove_graph); + + Graphs::GraphAdjacencyProvider adjacency_install_graph(graph_plan.install_graph.adjacency_list()); + auto insert_vertex_list = graph_plan.install_graph.vertex_list(); + auto insert_toposort = Graphs::topological_sort(insert_vertex_list, adjacency_install_graph); + + std::vector install_plan; + + for (auto&& like_cluster : remove_toposort) + { + auto scf = *like_cluster.ptr->source_control_file.get(); + + AnyAction any_plan; + any_plan.remove_plan = RemovePlanAction{ + PackageSpec::from_name_and_triplet(scf->core_paragraph->name, like_cluster.ptr->spec.triplet()) + .value_or_exit(VCPKG_LINE_INFO), + RemovePlanType::REMOVE, + RequestType::AUTO_SELECTED}; + + install_plan.emplace_back(std::move(any_plan)); + } + + for (auto&& like_cluster : insert_toposort) + { + if (!like_cluster.ptr->transient_uninstalled) continue; + + auto scf = *like_cluster.ptr->source_control_file.get(); + auto pkg_spec = + PackageSpec::from_name_and_triplet(scf->core_paragraph->name, like_cluster.ptr->spec.triplet()) + .value_or_exit(VCPKG_LINE_INFO); + auto action = + InstallPlanAction{pkg_spec, *scf, like_cluster.ptr->to_install_features, RequestType::AUTO_SELECTED}; + + AnyAction any_plan; + any_plan.install_plan = std::move(action); + install_plan.emplace_back(std::move(any_plan)); + } + + return install_plan; + } } diff --git a/toolsrc/src/vcpkg_Input.cpp b/toolsrc/src/vcpkg_Input.cpp index fdedd5507..f4e9a07c2 100644 --- a/toolsrc/src/vcpkg_Input.cpp +++ b/toolsrc/src/vcpkg_Input.cpp @@ -12,10 +12,10 @@ namespace vcpkg::Input CStringView example_text) { const std::string as_lowercase = Strings::ascii_to_lowercase(package_spec_as_string); - auto expected_spec = PackageSpec::from_string(as_lowercase, default_triplet); + auto expected_spec = FullPackageSpec::from_string(as_lowercase, default_triplet); if (auto spec = expected_spec.get()) { - return PackageSpec{*spec}; + return PackageSpec{spec->package_spec}; } // Intentionally show the lowercased string @@ -39,26 +39,11 @@ namespace vcpkg::Input const Triplet& default_triplet, CStringView example_text) { - int left_pos = (int)full_package_spec_as_string.find('['); - if (left_pos == std::string::npos) + const std::string as_lowercase = Strings::ascii_to_lowercase(full_package_spec_as_string); + auto expected_spec = FullPackageSpec::from_string(as_lowercase, default_triplet); + if (auto spec = expected_spec.get()) { - return FullPackageSpec{ - check_and_get_package_spec(full_package_spec_as_string, default_triplet, example_text)}; - } - int right_pos = (int)full_package_spec_as_string.find(']'); - if (left_pos >= right_pos) - { - System::println(System::Color::error, "Error: Argument is not formatted correctly \"%s\""); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - std::string package_spec_as_string = full_package_spec_as_string.substr(0, left_pos); - const std::string as_lowercase = Strings::ascii_to_lowercase(package_spec_as_string); - auto expected_spec = PackageSpec::from_string(as_lowercase, default_triplet); - if (auto&& spec = expected_spec.get()) - { - return {*spec, - parse_comma_list(full_package_spec_as_string.substr(left_pos + 1, right_pos - left_pos - 1))}; + return *spec; } // Intentionally show the lowercased string diff --git a/toolsrc/src/vcpkg_Parse.cpp b/toolsrc/src/vcpkg_Parse.cpp index b63ce41a9..659af2939 100644 --- a/toolsrc/src/vcpkg_Parse.cpp +++ b/toolsrc/src/vcpkg_Parse.cpp @@ -6,18 +6,6 @@ namespace vcpkg::Parse { - static Optional get_field(const std::unordered_map& fields, - const std::string& fieldname) - { - auto it = fields.find(fieldname); - if (it == fields.end()) - { - return nullopt; - } - - return it->second; - } - static Optional remove_field(std::unordered_map* fields, const std::string& fieldname) { diff --git a/toolsrc/vcpkgtest/vcpkgtest.vcxproj b/toolsrc/vcpkgtest/vcpkgtest.vcxproj index 26d5c98eb..ca66260d4 100644 --- a/toolsrc/vcpkgtest/vcpkgtest.vcxproj +++ b/toolsrc/vcpkgtest/vcpkgtest.vcxproj @@ -22,6 +22,7 @@ + diff --git a/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters b/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters index f1230cfce..e376e59f4 100644 --- a/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters +++ b/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters @@ -24,5 +24,8 @@ Source Files + + Source Files + \ No newline at end of file